Install BOSCH digital pressure sensor BMP280 on SP7021 demo board V3

 

The goal of this document is to illustrate how to install BOSCH digital pressure sensor BMP280 on SP7021 demo board V3.

BMP280 supports both I2C and SPI interfaces. This document is going to illustrate how to install BMP280 using I2C interface. Refer to BMP280 pressure sensor module below:

It consists of a BMP280, four 10k resistors and two capacitors. The real size of BMP280 is only 2.5 x 2.0 mm2 and its height is only 0.95 mm.

Please follow the following steps:

1. Install hardware

Refer to schematics of BMP280 pressure sensor module:

BMP280 supports both I2C and SPI interfaces. Interface of BMP280 is selected by state of pin CSB. If CSB is connected to VDD, I2C interface is active. If CSB is connected to GND, SPI interface is active. In I2C mode, The last bit of I2C slave address is decided by state of pin SDO. Connecting SDO to GND results in I2C slave address to be 0x76 while connecting SDO to VDD results in I2C slave address to be 0x77.

R1, R2, R3 and R4 are 10k pull-up/down resistors which give a fixed level to signals CSB, SDI, SCK and SDO, respectively when they are not connected. C1 and C2 are bypass capacitors which stabilize the 3.3V power. All interface signals are connected to CON6 directly. No extra components are needed.

Refer to pin-assignment of Raspberry Pi compatible pin-header of SP7021 demo board V3:

Connect BMP280 pressure sensor module to SP7021 demo board V3 as shown in following table:

Pin # of CON6 of BMP280 module

Pin name of CON6 of BMP280 module

Pin # of 40-pin pin-header

Pin name of 40-pin pin-header

1

VCC

1

VCCIO33 (3.3V)

2

GND

9

GND

3

SCK (SCL)

5

GPIO_P1_5/G_MX13 (SCL1)

4

SDI (SDA)

3

GPIO_P1_4/G_MX12 (SDA1)

5

CSB

17

VDD (3.3V)

6

SDO

6

GND

Note that SDO is connected to GND so I2C address of BMP280 is 0x76. CSB is connected to VDD, I2C interface is active. Refer to photograph which shows BMP280 pressure module is connected to 40-pin pin-header of SP7021 demo board V3.

2. Modify device-tree source file

Modify device-tree node i2cm0 in device-tree source file linux/kernel/arch/arm/boot/dts/sp7021-demov3.dts to setup channel 0 of I2C master for BMP280 as shown below:

&i2cm0 { clock-frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&i2cm0_pins>; #address-cells = <1>; #size-cells = <0>; bmp280@76 { compatible = "bosch,bmp280"; reg = <0x76>; status = "okay"; }; };

Note that i2cm0 is a label to node i2c@9c004600. I2C address of BMP280 is at 0x76 (SDO is connected to GND).

Also, modify dts node pinmux_i2cm0-pins to setup pins for channel 0 of I2C master as shown below:

i2cm0_pins: pinmux_i2cm0-pins { sunplus,pins = < SPPCTL_IOPAD(13, SPPCTL_PCTL_G_PMUX, MUXF_I2CM0_CLK, 0) SPPCTL_IOPAD(12, SPPCTL_PCTL_G_PMUX, MUXF_I2CM0_DAT, 0) >; };

Note that node pinmux_i2cm0-pins is sub-node of node pctl. G_MX13 and G_MX12 are set as CLK and DAT signals of channel 0 of I2C master, respectively.

Please note that to comply with Linux rules, after version 5.10.59, 4 property-names of pin node of SP7021 are changed as shown in table below:

5.4.35

5.10.59

5.4.35

5.10.59

sppctl,function

function

sppctl,groups

groups

sppctl,pins

sunplus,pins

sppctl,zero_func

sunplus,zerofunc

3. Enable Linux device drivers

Run make kconfig in project top directory. When “Linux/arm Kernel Configuration” menu pops up, move cursor to go to “Device Drivers” → “I2C support” → “I2C Hardware Bus support” sub-menu. Move cursor to “SP I2C support” and enable it. Refer to screenshot below:

Move cursor to go to “Device Drivers” → “Industrial I/O support” and press <Y> to enable it. Refer to screenshot below:

 

Press <Enter> key to enter “Industrial I/O support” sub-menu. Move cursor to go to “Pressure sensors” → “Bosch Sensortec BMP180/BMP280 pressure sensor I2C driver” and enable it. Refer to screenshot below:

Finally, save configuration.

4. Build Linux image

Go to top folder. Run make all to build Linux image.

5. Boot Linux

Boot Linux with the built image.

6. Read pressure or temperature

After Linux boots up successfully, run ls /sys/bus/iio/devices/iio:device0 to list sysfs of device iio:device0 (bmp280). Refer to screenshot below:

Run cat command to show information of device iio:device0. Refer to screenshot below:

Device name is bmp280. Device number is 252:0. Available oversampling ratio for pressure are 1, 2, 4, 8 and 16. Current oversampling ratio 16. Available oversampling ratio for temperature are 1, 2, 4, 8 and 16. Current oversampling ratio 2.

Run cat /sys/bus/iio/devices/iio:device0/in_pressure_input to read pressure and run cat /sys/bus/iio/devices/iio:device0/in_temp_input to read temperature. Unit of read-back pressure is kilo Pa (kPa). Unit of read-back temperature is milli degrees Celsius (oC). Refer to screenshot below:

The current pressure and temperature are 101.149 kPa and 26.210 oC.

7. User-space device

If you want to use user-space applications to access I2C device file, instead of IIO interface via sysfs, please run make menuconfig and move cursor to go to “Device Drivers” → “I2C support” → “I2C device interface” and enable it. Refer to screenshot below:

When I2C interface driver probes successfully, it creates character device, /dev/i2c-X, where X is the channel number of I2C bus. Refer to screenshot, I2C device, /dev/i2c-0, is shown:

8. Example C code of using user-space device

Refer to the following C code for reading temperature and pressure from BMP280 via Linux device file, /dev/i2c-0.

#include <stdio.h> #include <linux/types.h> #include <ctype.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <sys/ioctl.h> #include <errno.h> #include <string.h> #define I2C_RETRIES 0x0701 #define I2C_TIMEOUT 0x0702 #define I2C_RDWR 0x0707 #define I2C_DEV "/dev/i2c-0" /* i2c device */ #define I2C_ADDR 0x76 /* slave device address */ typedef short s16_t; typedef unsigned short u16_t; typedef long s32_t; typedef unsigned long u32_t; typedef long long s64_t; typedef unsigned long long u64_t; struct i2c_msg { unsigned short addr; unsigned short flags; unsigned short len; unsigned char *buf; }; struct i2c_rdwr_ioctl_data { struct i2c_msg *msgs; int nmsgs; }; struct i2c_rdwr_ioctl_data storage_data; struct i2c_msg storage_msg[2]; s32_t bmp280_compensate_T_int32(s32_t adc_T); u32_t bmp280_compensate_P_int64(s32_t adc_P); int i2c_write(int fd, unsigned char slvAddr, unsigned short index, unsigned char * const data, unsigned char len) { int ret; unsigned char *tmp = (unsigned char*)malloc(1024); if (tmp == NULL) { perror("Allocate memory error (i2c_write)!"); return -1; } tmp[0] = ((unsigned char *)&index)[0]; memcpy(tmp+1, data, len); len += 1; /***write data to storage**/ storage_data.nmsgs = 1; storage_data.msgs[0].len = len; // Data length storage_data.msgs[0].addr = slvAddr; // Device Addr storage_data.msgs[0].flags = 0; // write storage_data.msgs[0].buf = tmp; ret = ioctl(fd, I2C_RDWR, &storage_data); if (tmp) free(tmp); return (ret < 0)? -1: 0; } int i2c_read(int fd, unsigned char slvAddr, unsigned short index, unsigned char *data, int len) { char tmp[4]; tmp[0] = ((unsigned char *)&index)[0]; storage_data.nmsgs = 2; storage_data.msgs[0].len = 1; storage_data.msgs[0].addr = slvAddr; storage_data.msgs[0].flags = 0; //Dummy write storage_data.msgs[0].buf = (unsigned char *)&tmp; storage_data.msgs[1].len = len; storage_data.msgs[1].addr = slvAddr; storage_data.msgs[1].flags = 1; storage_data.msgs[1].buf = data; if (ioctl(fd,I2C_RDWR, &storage_data) < 0) { perror("ioctl error"); return -1; } return 0; } // Returns temperature in degree Celsius, resolution is 0.01 degree Celsius. // Output value of “5123” equals 51.23 degrees Celsius. // t_fine carries fine temperature as global value. u16_t dig_T1; s16_t dig_T2, dig_T3; s32_t t_fine; s32_t bmp280_compensate_T_int32(s32_t adc_T) { s32_t var1, var2, T; var1 = ((((adc_T>>3) - ((s32_t)dig_T1<<1))) * ((s32_t)dig_T2)) >> 11; var2 = (((((adc_T>>4) - ((s32_t)dig_T1)) * ((adc_T>>4) - ((s32_t)dig_T1)))>> 12) * ((s32_t)dig_T3)) >> 14; t_fine = var1 + var2; T = (t_fine * 5 + 128) >> 8; return T; } // Returns pressure in Pa as unsigned 32 bits integer in Q24.8 format (24 integer bits and 8 fractional bits). // Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa u16_t dig_P1; s16_t dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9; u32_t bmp280_compensate_P_int64(s32_t adc_P) { s64_t var1, var2, p; var1 = ((s64_t)t_fine) - 128000; var2 = var1 * var1 * (s64_t)dig_P6; var2 = var2 + ((var1*(s64_t)dig_P5)<<17); var2 = var2 + (((s64_t)dig_P4)<<35); var1 = ((var1 * var1 * (s64_t)dig_P3)>>8) + ((var1 * (s64_t)dig_P2)<<12); var1 = (((((s64_t)1)<<47)+var1))*((s64_t)dig_P1) >> 33; if (var1 == 0) { return 0; // avoid exception caused by division by zero } p = 1048576 - adc_P; p = (((p<<31)-var2)*3125) / var1; var1 = (((s64_t)dig_P9) * (p>>13) * (p>>13)) >> 25; var2 = (((s64_t)dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((s64_t)dig_P7)<<4); return (u32_t)p; } int main(int argc, char **argv) { int fd = 0, i; unsigned char read_buf[64]; unsigned char write_buf[16]; unsigned char dig_buf[20]; unsigned char temp_buf[4], press_buf[4]; s32_t adc_T, adc_P; storage_data.nmsgs = 2; storage_data.msgs = storage_msg; fd = open(I2C_DEV,O_RDWR); if (fd < 0) { printf("Cannot open \'%s\'!\n", I2C_DEV); exit(1); } printf("Opened \'%s\' successfully!\n", I2C_DEV); if (i2c_read(fd, I2C_ADDR, 0XD0, read_buf, 1) != 0) goto error; printf("I2C addr = 0x%02X, Chip id = 0x%02X\n", I2C_ADDR, read_buf[0]); ioctl(fd, I2C_TIMEOUT, 1); /*set timeout value*/ ioctl(fd, I2C_RETRIES, 2); /*set retry times*/ // osrs_t = 001 (oversmapling=1x) // osrs_p = 011 (oversmapling=4x) // mode = 11 (normal) write_buf[0] = 0x2F; if (i2c_write(fd, I2C_ADDR, 0xF4, write_buf, 1) != 0) goto error; // t_sb = 000 (0.5mS) // filter = 100 (coef.= 16) // rsv = 0 // spi_en = 0 (i2c mode) write_buf[0] = 0x10; if (i2c_write(fd, I2C_ADDR, 0xF5, write_buf, 1) != 0) goto error; // Read calibration data: dig_T1, dig_T2, dig_T3. if (i2c_read(fd, I2C_ADDR, 0X88, dig_buf, 6) != 0) goto error; dig_T1 = (dig_buf[1]<<8) | dig_buf[0]; dig_T2 = (dig_buf[3]<<8) | dig_buf[2]; dig_T3 = (dig_buf[5]<<8) | dig_buf[4]; printf("dig_T1 = %u, dig_T2 = %d, dig_T3 = %d\n", dig_T1, dig_T2, dig_T3); // Read calibration data: dig_P1, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9. if (i2c_read(fd, I2C_ADDR, 0x8E, dig_buf, 18) != 0) goto error; dig_P1 = (dig_buf[1]<<8) | dig_buf[0]; dig_P2 = (dig_buf[3]<<8) | dig_buf[2]; dig_P3 = (dig_buf[5]<<8) | dig_buf[4]; dig_P4 = (dig_buf[7]<<8) | dig_buf[6]; dig_P5 = (dig_buf[9]<<8) | dig_buf[8]; dig_P6 = (dig_buf[11]<<8) | dig_buf[10]; dig_P7 = (dig_buf[13]<<8) | dig_buf[12]; dig_P8 = (dig_buf[15]<<8) | dig_buf[14]; dig_P9 = (dig_buf[17]<<8) | dig_buf[16]; printf("dig_P1 = %u, dig_P2 = %d, dig_P3 = %d, dig_P4 = %d, dig_P5 = %d\n", dig_P1, dig_P2, dig_P3, dig_P4, dig_P5); printf("dig_P6 = %d, dig_P7 = %d, dig_P8 = %d, dig_P9 = %d\n", dig_P6, dig_P7, dig_P8, dig_P9); // Read temperature data: MSB, LSB, XLSB if (i2c_read(fd, I2C_ADDR, 0XFA, temp_buf, 3) != 0) goto error; adc_T = ((temp_buf[0]<<16)|(temp_buf[1]<<8)|temp_buf[2]) >> 4; printf("adc_T = %d\n", adc_T); printf("Temperature = %.2f (degree Celsius)\n", bmp280_compensate_T_int32(adc_T) / 100.0f); // Read pressure data: MSB, LSB, XLSB if (i2c_read(fd, I2C_ADDR, 0xF7, press_buf, 3) != 0) goto error; adc_P = ((press_buf[0]<<16)|(press_buf[1]<<8)|press_buf[2]) >> 4; printf("adc_P = %d\n", adc_P); printf("Pressure = %.2f (Pa)\n", bmp280_compensate_P_int64(adc_P) / 256.0f); if (fd) { close(fd); } return 0; error: fprintf(stderr, "Failed to access i2c device \'%s\'!\n", I2C_DEV); exit(1); }

Refer to screenshot captured when executable bmp280 was running: