MCU | STM32F103C8T6 |
传感器 | BMP280 |
软件 | keil MDK |
库 | 标准库 |
BMP280是一款专为移动应用设计的绝对气压传感器。传感器模块封装在一个非常紧凑的8引脚金属盖LGA封装中,占地面积仅为2.0×2.5 mm2,封装高度为0.95 mm。它的小尺寸和2.7µA@1Hz的低功耗允许在手机、GPS模块或手表等电池驱动设备中实现。
根据数据手册,获取到我们需要的基本参数如下:
Pressure range(压力范围) | 300-1100Pa |
Package | 8-pin LGA metal-lid |
Relative accuracy (相对精度) (950 … 1050hPa @25°C) |
±0.12 hPa, equiv. to ±1 m |
Absolute accuracy(绝对精度) (950 ...1050 hPa, 0 ...+40 °C) |
typ. ±1 hPa |
Digital interfaces(接口) | I²C (up to 3.4 MHz) SPI (3 and 4 wire, up to 10 MHz) |
Temperature range (温度范围) | -40 … +85 °C |
我们在上面已经得知,该传感器接口有IIC和SPI,那么,选择其中一种即可,而本文选择的IIC。 现在需要根据芯片手册目录,找到对应的文章内容。
与设备的所有通信都是通过从寄存器中读取和写入寄存器来执行的。
寄存器的宽度为8位。有几个寄存器是保留的;它们不应该被写入,并且在读取它们时不保证特定的值。
1.Register 0xD0 “id”
“id”寄存器包含芯片标识号chip_id[7:0],它是0x58。一旦设备完成通电重置,就可以读取此数字。
2.Register 0xE0 “reset”
“重置”寄存器包含软重置字reset[7:0]。如果值0xB6被写入寄存器,则使用完整的通电重置程序重置设备。写入0xB6以外的其他值没有任何效果。读出的值总是0x00。
3. Register 0xF3 “status”
“状态”寄存器包含两位,表示设备的状态。
这里只给出三个描述,其他的请参考手册阅读。程序全部定义成宏
#define BMP280_CHIPID_REG 0xD0 /*Chip ID Register */
#define BMP280_RESET_REG 0xE0 /*Softreset Register */
#define BMP280_STATUS_REG 0xF3 /*Status Register */
#define BMP280_CTRLMEAS_REG 0xF4 /*Ctrl Measure Register */
#define BMP280_CONFIG_REG 0xF5 /*Configuration Register */
#define BMP280_PRESSURE_MSB_REG 0xF7 /*Pressure MSB Register */
#define BMP280_PRESSURE_LSB_REG 0xF8 /*Pressure LSB Register */
#define BMP280_PRESSURE_XLSB_REG 0xF9 /*Pressure XLSB Register */
#define BMP280_TEMPERATURE_MSB_REG 0xFA /*Temperature MSB Reg */
#define BMP280_TEMPERATURE_LSB_REG 0xFB /*Temperature LSB Reg */
#define BMP280_TEMPERATURE_XLSB_REG 0xFC /*Temperature XLSB Reg */
手册说明:The 7-bit device address is 111011x. The 6 MSB bits are fixed. The last bit is changeable by SDO value and can be changed during operation. Connecting SDO to GND results in slave address 1110110 (0x76); connection it to VDDIO results in slave address 1110111 (0x77), which is the same as BMP180’s I²C address. The SDO pin cannot be left floating; if left floating, the I²C address will be undefined.
提示:大概意思就是说,地址7位,6位为固定,最后一位可以通过SDO修改。SDO连接GND,IIC从机地址为0x76,连接到VDD,从机地址为0x77。
直接看数据
上图表示IIC多字节写入,整理步骤如下:
注意:从机地址一般带有读写位,这里的从机地址就不能只是发送0x76
这样,整个写数据通信发送完成。
读数据与写数据类似:
读数据步骤:
多字节多数据就完成了。
BMP280输出由ADC输出值组成。然而,每个传感元件的行为不同,并且必须使用一组校准参数来计算实际压力和温度。第3.11.3章中的推荐计算使用不动点算法。在高级语言中,如Matlab 或LabVIEW, 定点代码可能得不到很好的支持。在这种情况下,可以使用附录8.1中的浮点代码作为替代。对于8位微控制器,可变大小可能受到限制。在这种情况下,附录8.2中给出了精度降低的简化32位整数代码
下表显示了在GCC优化级别为O2的32位Cortex-M3微控制器上进行补偿计算所需的时钟周期数。此控制器不包含浮点单元,因此模拟所有浮点计算。浮点仅推荐用于存在FPU的PC应用程序。
微调参数在生产过程中编程到设备的非易失性存储器(NVM)中,客户不能更改。每个补偿字是存储在2的补码中的16位有符号或无符号整数值。由于存储器被组织成8位字,所以必须始终组合两个字才能表示补偿字。8位寄存器被命名为calib00…calib25,并存储在存储器地址0x88…0xA1。
对应的补偿字对于温度补偿相关值被命名为dig_T#,而对于压力补偿相关值则被命名为dig_P#。
我们也将所有数据定义成宏
#define BMP280_DIG_T1_LSB_REG 0x88
#define BMP280_DIG_T1_MSB_REG 0x89
#define BMP280_DIG_T2_LSB_REG 0x8A
#define BMP280_DIG_T2_MSB_REG 0x8B
#define BMP280_DIG_T3_LSB_REG 0x8C
#define BMP280_DIG_T3_MSB_REG 0x8D
#define BMP280_DIG_P1_LSB_REG 0x8E
#define BMP280_DIG_P1_MSB_REG 0x8F
#define BMP280_DIG_P2_LSB_REG 0x90
#define BMP280_DIG_P2_MSB_REG 0x91
#define BMP280_DIG_P3_LSB_REG 0x92
#define BMP280_DIG_P3_MSB_REG 0x93
#define BMP280_DIG_P4_LSB_REG 0x94
#define BMP280_DIG_P4_MSB_REG 0x95
#define BMP280_DIG_P5_LSB_REG 0x96
#define BMP280_DIG_P5_MSB_REG 0x97
#define BMP280_DIG_P6_LSB_REG 0x98
#define BMP280_DIG_P6_MSB_REG 0x99
#define BMP280_DIG_P7_LSB_REG 0x9A
#define BMP280_DIG_P7_MSB_REG 0x9B
#define BMP280_DIG_P8_LSB_REG 0x9C
#define BMP280_DIG_P8_MSB_REG 0x9D
#define BMP280_DIG_P9_LSB_REG 0x9E
#define BMP280_DIG_P9_MSB_REG 0x9F
手册强烈建议使用Bosch Sensortec提供的API进行读数和补偿。如果不需要这样做,也提供了其他补偿公式:
static uint32_t BMP280CompensateP(s32 adcP)
{
int64_t var1, var2, p;
var1 = ((int64_t)bmp280Cal.t_fine) - 128000;
var2 = var1 * var1 * (int64_t)bmp280Cal.dig_P6;
var2 = var2 + ((var1*(int64_t)bmp280Cal.dig_P5) << 17);
var2 = var2 + (((int64_t)bmp280Cal.dig_P4) << 35);
var1 = ((var1 * var1 * (int64_t)bmp280Cal.dig_P3) >> 8) + ((var1 * (int64_t)bmp280Cal.dig_P2) << 12);
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)bmp280Cal.dig_P1) >> 33;
if (var1 == 0)
return 0;
p = 1048576 - adcP;
p = (((p << 31) - var2) * 3125) / var1;
var1 = (((int64_t)bmp280Cal.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
var2 = (((int64_t)bmp280Cal.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)bmp280Cal.dig_P7) << 4);
return (uint32_t)p;
}
下图显示了压力和温度测量的详细算法。
关于IIC协议这里不再叙述,就正常的通信,主要讲述传感器部分
uint8_t BMP280_Init(void)
{
uint8_t bmp280_id;
uint8_t tmp[10];
Sensors_I2C_ReadRegister(BMP280_SLAVE_ADDRESS, BMP280_CHIPID_REG, 1, &bmp280_id);
/* 读取校准数据 */
Sensors_I2C_ReadRegister(BMP280_SLAVE_ADDRESS, BMP280_DIG_T1_LSB_REG,24,(u8 *)&bmp280Cal);
tmp[0] = BMP280_MODE;
Sensors_I2C_WriteRegister(BMP280_SLAVE_ADDRESS, BMP280_CTRLMEAS_REG, 1, tmp);
tmp[0] = (5<<2);
Sensors_I2C_WriteRegister(BMP280_SLAVE_ADDRESS, BMP280_CONFIG_REG, 1, tmp);
return bmp280_id;
}
手册对寄存器中有详细的描述,具体什么意思可以参考用户手册
void BMP280GetData(float* pressure, float* temperature, float* asl)
{
static float t;
static float p;
BMP280GetPressure();
t = BMP280CompensateT(bmp280RawTemperature) / 100.0;
p = BMP280CompensateP(bmp280RawPressure) / 25600.0;
presssureFilter(&p, pressure);
*temperature = (float)t;/*单位度*/
*asl = BMP280PressureToAltitude(pressure); /*转换成海拔*/
}
static void BMP280GetPressure(void)
{
u8 data[BMP280_DATA_FRAME_SIZE];
// read data from sensor
Sensors_I2C_ReadRegister(BMP280_SLAVE_ADDRESS, BMP280_PRESSURE_MSB_REG, BMP280_DATA_FRAME_SIZE, (u8 *)&data);
bmp280RawPressure = (s32)((((uint32_t)(data[0])) << 12) | (((uint32_t)(data[1])) << 4) | ((uint32_t)data[2] >> 4));
bmp280RawTemperature = (s32)((((uint32_t)(data[3])) << 12) | (((uint32_t)(data[4])) << 4) | ((uint32_t)data[5] >> 4));
}
获取数据的简单来说就是IIC通信,手册中提到寄存器0xF7-0xF9为大气压, 0xFA…0xFC为温度。通过读取寄存器的只可以获取原始温度,大气压。
大气压寄存器如下:
大概就是一共20位的数据,分为MSB,LSB,XLSB,XLSB就取决于精度了。获取数据后可以做一些滤波处理,比如均值滤波等,例如:
static void presssureFilter(float* in, float* out)
{
static u8 i = 0;
static float filter_buf[FILTER_NUM] = {0.0};
double filter_sum = 0.0;
u8 cnt = 0;
float deta;
if(filter_buf[i] == 0.0f)
{
filter_buf[i] = *in;
*out = *in;
if(++i >= FILTER_NUM)
i=0;
}
else
{
if(i)
deta = *in-filter_buf[i - 1];
else
deta = *in-filter_buf[FILTER_NUM - 1];
if(fabs(deta) < FILTER_A)
{
filter_buf[i] = *in;
if(++i >= FILTER_NUM)
i = 0;
}
for(cnt = 0; cnt < FILTER_NUM; cnt++)
{
filter_sum += filter_buf[cnt];
}
*out = filter_sum / FILTER_NUM;
}
}
经过一些滤波后的数据,看起来比较稳定。另外也可以将温度输出出来,也可以将大气压转换成海拔等。这里就不讲述了,大致一样的操作。
大气压传感器的通信是比较简单的,就是基于IIC,但是主要的还是传感器的校准以及补偿算法等。并且传感器的手册是英文的,对英语不是特别感冒的朋友(说的就是我),可能费劲。需要花费更多的时间去理解。本文就是简单的驱动,如果有错误的地方,及时提出更正。