[APM32F4] 【APM32F402R Micro-EVB开发板测评】6、FreeRTOS任务定时读取BMP280气压和温度

[复制链接]
slytherinsun 发表于 2025-8-28 21:51 | 显示全部楼层 |阅读模式
本帖最后由 slytherinsun 于 2025-8-28 21:51 编辑

1.BMP280气压计简介
BMP280是一款数字输出的绝对气压传感器,测量范围为300-1100hPa,数据手册中描述相当于海平面以下500米到海平面以上9000米的范围。相对精度±0.12hPa,绝对精度±1hPa,分辨率0.16Pa,同时内置高精度温度传感器,范围为-40°C - 85°C,分辨率0.01°C,支持I²C(3.4MHz)和SPI(3/4线10MHz)通讯接口。
本次测评使用集成了BMP280芯片的GY-BM E/P 280模块,使用I²C接口进行通讯,BMP280通过SDO引脚的高低电平来设置其I²C地址的最后一位,模块中此引脚接地,因此模块的I²C地址为0x76(不包含读写状态位)
2.工作模式
BMP280有三种工作模式,ctrl_meas寄存器(地址0xF4)的Mode[1:0]标志三种不同模式,其中上电复位后进入睡眠模式(sleep mode),也可以对Mode[1:0]写入b'00进入睡眠模式,睡眠模式不进行测量,可以访问所有寄存器,能够读取芯片ID和修正系数。
对Mode[1:0]写入b'01或b'10时进入被动模式(forced mode),进入此模式后会根据osrs_t和osrs_p的过采样配置进行一次温度和气压的测量,测量完成后自动进入睡眠模式,同时用户可以读取测量到的温度和气压值。
对Mode[1:0]写入b'11进入正常模式(Normal Mode),进入此模式后会根据t_sb[2:0]设置的间隔时间周期性的测量,测量过程同样受osrs_t和osrs_p的过采样配置影响。
模式状态转换图如下:

bmp01

bmp01

本次测试使用被动模式来触发测量。

3.相关寄存器
修正系数(0x88 - 0xA1),BMP280的NVM中出厂内置了13个16Bit的修正系数,其中0xA0 - 0xA1是保留字段,不使用,其余12个用于进行温度和气压的计算,存储在地址为0x88 - 0x9F的寄存器中,如下图所示。

bmp02

bmp02

芯片ID寄存器(0xD0),存储了芯片的ID值0x58,芯片上电复位完成后即可访问。
复位寄存器(0xE0),用于对芯片进行软件复位,写入值0xB6到此寄存器即可复位芯片,写入其他值无效。
状态寄存器(0xF3),有效位为Bit0和Bit3,每次测量转换时Bit3自动置1,测量转换完成后归0。上电复位和每次测量转换前拷贝NVM中的数据到寄存器时自动置1,拷贝完成后归0,本次测评不使用。
控制测量寄存器(0xF4),Bit7-5对应温度过采样osrs_t[2:0]的设置,Bit4-3对应气压过采样osrs_p[2:0]的设置,Bit1-0对应工作模式Mode[1:0]的设置。具体含义如下图

bmp03

bmp03

bmp04

bmp04

bmp05

bmp05

配置寄存器(0xF5),用于配置正常模式连测量转换的间隔周期,IIR滤波器的配置和3线SPI模式的开启关闭,本次测评不使用。

压力值寄存器(0xF7 - 0xF9),存储了20Bit的气压测量转换后的数据,具体含义如下图:

bmp06

bmp06

温度值寄存器(0xFA - 0xFC),存储了20Bit的温度测量转换后的数据,具体含义如下图:

bmp07

bmp07

4.测量转换流程
BMP280支持标准的I²C读写协议,对其的读取和写入接口参考APM32F402_403_SDK_V1.0.2中的I²C的DMA例程进行修改。
4.1复位初始化
先对BMP280进行复位和初始化,即向复位寄存器(0xE0)写0xB6对芯片进行复位,等待复位完成后读取芯片ID,之后读取12个16Bit的修正系数。
4.2配置过采样并触发测量
此时BMP280处于睡眠模式,后续在线程中定时配置过采样(气压x16,温度x2)并触发被动模式进行温度和气压的测量转换。
4.3数据读取
根据芯片手册查看对应过采样配置下数据的转换时间(Max43.2ms)进行延时,等待转换完成后读取压力值寄存器和温度值寄存器获取20Bit的气压和温度数据。
4.4数据计算
获取的20Bit的气压和温度数据需要进行一定的计算才能转换为可识别的气压和温度数值,在BMP280的数据手册中分别给出了使用浮点数和整型数的两份C语言算法,算法中的"BMP280_S32_t"和"BMP280_U32_t"可分别用int32_t和uint_t代替。
由于浮点数版本的算法在MCU上过于消耗其性能,所以本次测评使用的是整型数的算法。
  1. // Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
  2. // t_fine carries fine temperature as global value
  3. BMP280_S32_t t_fine;
  4. BMP280_S32_t bmp280_compensate_T_int32(BMP280_S32_t adc_T)
  5. {
  6. BMP280_S32_t var1, var2, T;
  7. var1 = ((((adc_T>>3) – ((BMP280_S32_t)dig_T1<<1))) * ((BMP280_S32_t)dig_T2)) >> 11;
  8. var2 = (((((adc_T>>4) – ((BMP280_S32_t)dig_T1)) * ((adc_T>>4) – ((BMP280_S32_t)dig_T1))) >> 12) *
  9. ((BMP280_S32_t)dig_T3)) >> 14;
  10. t_fine = var1 + var2;
  11. T = (t_fine * 5 + 128) >> 8;
  12. return T;
  13. }
  14. // Returns pressure in Pa as unsigned 32 bit integer. Output value of “96386” equals 96386 Pa = 963.86 hPa
  15. BMP280_U32_t bmp280_compensate_P_int32(BMP280_S32_t adc_P)
  16. {
  17. BMP280_S32_t var1, var2;
  18. BMP280_U32_t p;
  19. var1 = (((BMP280_S32_t)t_fine)>>1) – (BMP280_S32_t)64000;
  20. var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((BMP280_S32_t)dig_P6);
  21. var2 = var2 + ((var1*((BMP280_S32_t)dig_P5))<<1);
  22. var2 = (var2>>2)+(((BMP280_S32_t)dig_P4)<<16);
  23. var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((BMP280_S32_t)dig_P2) * var1)>>1))>>18;
  24. var1 =((((32768+var1))*((BMP280_S32_t)dig_P1))>>15);
  25. if (var1 == 0)
  26. {
  27. return 0; // avoid exception caused by division by zero
  28. }
  29. p = (((BMP280_U32_t)(((BMP280_S32_t)1048576)-adc_P)-(var2>>12)))*3125;
  30. if (p < 0x80000000)
  31. {
  32. p = (p << 1) / ((BMP280_U32_t)var1);
  33. }
  34. else
  35. {
  36. p = (p / (BMP280_U32_t)var1) * 2;
  37. }
  38. var1 = (((BMP280_S32_t)dig_P9) * ((BMP280_S32_t)(((p>>3) * (p>>3))>>13)))>>12;
  39. var2 = (((BMP280_S32_t)(p>>2)) * ((BMP280_S32_t)dig_P8))>>13;
  40. p = (BMP280_U32_t)((BMP280_S32_t)p + ((var1 + var2 + dig_P7) >> 4));
  41. return p;
  42. }
5.代码编写
5.1创建定时器及读取线程
在FreeRTOS例程中增加软件定时器和读取线程,实现如下:
  1. envirThreadID = osThreadNew(Environ_Thread, NULL, &envirThreadattr);
  2.     if (envirThreadID == NULL)
  3.     {
  4.         printf("Create environment thread failed!\r\n");
  5.     }


  6.     timerid_2 = osTimerNew(Timer2_CallBack, osTimerPeriodic, NULL, NULL);
  7.     if(NULL != timerid_2) {
  8.         ret = osTimerStart(timerid_2, 2000);
  9.         if(osOK != ret) {
  10.             printf("Create timer2 failed!\r\n");
  11.         }
  12.     }
  1. void Timer2_CallBack(void *arg)
  2. {
  3.     UNUSED(arg);
  4.     static uint8_t toggle = 0;
  5.     toggle = !toggle;
  6.     osThreadFlagsSet(envirThreadID, 0x00000001);
  7. }
  1. void Environ_Thread(void *argument)
  2. {
  3.     UNUSED(argument);

  4.     volatile uint32_t env_flags = 0;
  5.     volatile uint32_t env_times = 0;
  6.     int32_t adc_temp, adc_pres, temp, temp_fine;
  7.     uint32_t barometer;
  8.     BMP280_trimming_param bmp280_trim = {0};
  9.     bmp280_reset(BMP280_I2C_ADDR);
  10.     osDelay(5);
  11.     bmp280_init(BMP280_I2C_ADDR, &bmp280_trim);
  12.     while (1)
  13.     {
  14.         env_times++;
  15.         env_flags = osThreadFlagsWait(0x00000001, osFlagsWaitAny, osWaitForever);
  16.         osThreadFlagsClear(env_flags);
  17.         bmp280_force_convert(BMP280_I2C_ADDR, BMP280_OSRS_X2, BMP280_OSRS_X16);
  18.         osDelay(50);
  19.         bmp280_read_adc(BMP280_I2C_ADDR, &adc_pres, &adc_temp);
  20.         temp = bmp280_calc_temperature(&temp_fine, adc_temp, &bmp280_trim);
  21.         barometer = bmp280_calc_barometer(&temp_fine, adc_pres, &bmp280_trim);
  22.         printf("Envir->baro(hPa)temp(C):%.2f,%.1f\n", (double)barometer / 100.0, (double)temp / 100.0);
  23.     }
  24. }
5.2复位初始化
对BMP280的复位操作只需要在线程开始入口处执行一次,实现如下:
  1. int8_t bmp280_reset(uint8_t addr)
  2. {
  3.     uint8_t rx_data;
  4.     I2C_DMA_Write_Byte_Data(addr, BMP280_REG_RESET, 0xB6);
  5.     osDelay(10);
  6.     I2C_DMA_Read_Byte(addr, BMP280_REG_ID, &rx_data);
  7.     printf("BMP280 ID:0x%02X\r\n", rx_data);
  8.     return 0;
  9. }
5.3开启测量转换
在线程循环中周期触发转换,实现如下:
  1. int8_t bmp280_force_convert(uint8_t addr, uint8_t osrs_t, uint8_t osrs_p)
  2. {
  3.     uint8_t ctrl_meas = ((osrs_t & 0x07) << 5) | ((osrs_p & 0x07) << 2) | BMP280_MODE_FORCE;
  4.     uint8_t rx_data = 0;
  5.     I2C_DMA_Write_Byte_Data(addr, BMP280_REG_CTRL_MEAS, ctrl_meas);
  6.     I2C_DMA_Read_Byte(addr, BMP280_REG_ID, &rx_data);
  7.     if((rx_data & 0xFC) == (ctrl_meas & 0xFC)) {
  8.         return 0;
  9.     }
  10.     else {
  11.         return -1;
  12.     }
  13. }

5.4读取转换值并计算
在触发转换后等待转换完成,然后读取转换结果并计算温度和气压,具体实现如下:

  1. int8_t bmp280_read_adc(uint8_t addr, int32_t* adc_pres, int32_t* adc_temp)
  2. {
  3.     uint8_t rx_data[6] = {0};
  4.     I2C_DMA_Read_Bytes(addr, BMP280_REG_PRESS_VAL, rx_data, 6);
  5.     *adc_pres = (int32_t)(rx_data[0] << 12) | (rx_data[1] << 4) | (rx_data[2] >> 4);
  6.     *adc_temp = (int32_t)(rx_data[3] << 12) | (rx_data[4] << 4) | (rx_data[5] >> 4);
  7.     if((0x80000 == *adc_pres) || (0x80000 == *adc_temp)) {
  8.         printf("bmp280 read invalid value\r\n");
  9.         return -1;
  10.     }
  11.     else {
  12.         return 0;
  13.     }
  14. }

  15. int32_t bmp280_calc_temperature(int32_t *temp_fine, int32_t adc_temp, BMP280_trimming_param *ptr_trim)
  16. {
  17.     int32_t var1, var2, temperature;
  18.     var1 = ((((adc_temp >> 3) - ((int32_t)ptr_trim->dig_t1 << 1))) * ((int32_t)ptr_trim->dig_t2)) >> 11;
  19.     var2 = (((((adc_temp >> 4) - ((int32_t)ptr_trim->dig_t1)) * ((adc_temp >> 4) - ((int32_t)ptr_trim->dig_t1))) >> 12) * ((int32_t)ptr_trim->dig_t3)) >> 14;
  20.     *temp_fine = var1 + var2;
  21.     temperature = (*temp_fine * 5 + 128) >> 8;
  22.     return temperature;
  23. }

  24. uint32_t bmp280_calc_barometer(const int32_t *temp_fine, int32_t adc_pres, BMP280_trimming_param *ptr_trim)
  25. {
  26.     int32_t pvar1, pvar2;
  27.     uint32_t baro;
  28.     pvar1 = (*temp_fine >> 1) - (int32_t)64000;
  29.     pvar2 = (((pvar1 >> 2) * (pvar1 >> 2)) >> 11) * ((int32_t)ptr_trim->dig_p6);
  30.     pvar2 = pvar2 + ((pvar1 * ((int32_t)ptr_trim->dig_p5)) << 1);
  31.     pvar2 = (pvar2 >> 2) + (((int32_t)ptr_trim->dig_p4) << 16);
  32.     pvar1 = (((ptr_trim->dig_p3 * (((pvar1 >> 2) * (pvar1 >> 2)) >> 13)) >> 3) + ((((int32_t)ptr_trim->dig_p2) * pvar1) >> 1)) >> 18;
  33.     pvar1 = (((32768 + pvar1) * (int32_t)ptr_trim->dig_p1) >> 15);
  34.     if(0 != pvar1) {
  35.         baro = ((int32_t)(((int32_t)1048576) - adc_pres) - (pvar2 >> 12)) * 3125;
  36.         if(baro <= 0x80000000) {
  37.             baro = (baro << 1) / (uint32_t)pvar1;
  38.         }
  39.         else {
  40.             baro = (baro / (uint32_t)pvar1) * 2;
  41.         }
  42.         pvar1 = (((int32_t)ptr_trim->dig_p9) * ((int32_t)(((baro >> 3) * (baro >>3)) >> 13))) >> 12;
  43.         pvar2 = (((int32_t)(baro >> 2)) * (int32_t)ptr_trim->dig_p8) >> 13;
  44.         baro = (uint32_t)((int32_t)baro + ((pvar1 + pvar2 + ptr_trim->dig_p7) >> 4));
  45.     }
  46.     else{
  47.         baro = 0;
  48.     }
  49.     return baro;
  50. }


6.结果展示
在串口日志中可以看到在周期性的打印温度和气压值。

bmp08

bmp08


您需要登录后才可以回帖 登录 | 注册

本版积分规则

7

主题

29

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部