3、气压传感器(BMP280):工作原理、SPI与I2C模式选择、BMP280校准参数、驱动代码实现与数据补偿
各位同学,咱们今天聊聊BMP280这颗气压传感器。说实话,在嵌入式气象站里,气压传感器是个容易被忽略但极其重要的角色。你想想看,天气预报里那个「1013.25hPa」的标准大气压,就是靠这类传感器测出来的。BMP280是博世出品,精度不错,价格也亲民,我手头好几个项目都在用。
3.1 工作原理:MEMS压阻式,没那么神秘
BMP280内部是一颗MEMS芯片,说白了就是一块微小的硅膜片。气压变化时,膜片会微微变形,贴在膜片上的压敏电阻阻值跟着变。通过惠斯通电桥把电阻变化转成电压信号,再经过ADC采样,就得到了原始的气压读数。
嗯,这里要注意:BMP280内部其实有两个传感器——一个测气压,一个测温度。为什么要有温度?因为硅材料的压阻系数对温度敏感,必须用温度值来做补偿。我在项目里见过有人只读气压不读温度,结果数据飘得没法看。
核心参数速览:
- 测量范围:300~1100 hPa(覆盖海平面到万米高空)
- 绝对精度:±1 hPa(典型值)
- 相对精度:±0.12 hPa(这个很关键,做高度计够用)
- 工作电压:1.71V~3.6V
- 功耗:2.7μA @ 1Hz采样(超低功耗)
3.2 SPI与I2C模式选择:别纠结,看场景
BMP280同时支持SPI和I2C。我个人习惯是:如果板子上I2C总线不忙,优先用I2C,因为连线少(只要SDA和SCL两根线)。但如果你需要高速采样,或者总线上挂了很多I2C设备,那就切到SPI。
怎么切换?看CSB引脚(片选)的电平:
| CSB引脚电平 | 通信模式 | 备注 |
|---|---|---|
| 高电平(VDDIO) | I2C | SDO引脚决定I2C地址(0x76或0x77) |
| 低电平(GND) | SPI | SPI模式0(CPOL=0, CPHA=0) |
我曾经踩过一个坑:把CSB直接接地想用SPI,结果忘了拉高SDO引脚的电平,导致I2C地址冲突。嗯,硬件设计时一定要仔细看数据手册的Table 2。
我的建议:新手做气象站,直接用I2C模式。把CSB接VDDIO,SDO接GND(地址0x76),省心。SPI留给那些需要高速读取的场景,比如无人机飞控。
3.3 BMP280校准参数:藏在芯片里的「出厂密码」
BMP280出厂时,每个芯片都烧录了一组校准参数。这些参数存在寄存器0x88~0xA1(共24个字节)。你必须先读出这些参数,才能把原始ADC值换算成真实的气压和温度。
校准参数一共有12个,都是无符号或有符号整型:
| 参数名 | 字节数 | 类型 | 说明 |
|---|---|---|---|
| dig_T1 | 2 | uint16 | 温度校准系数1 |
| dig_T2 | 2 | int16 | 温度校准系数2 |
| dig_T3 | 2 | int16 | 温度校准系数3 |
| dig_P1 | 2 | uint16 | 气压校准系数1 |
| dig_P2~P9 | 各2 | int16 | 气压校准系数2~9 |
为什么需要这么多参数?因为BMP280的ADC输出是非线性的,必须用二阶甚至三阶多项式来拟合。说白了,就是芯片厂家帮你测好了每个芯片的「个性」,你照着公式算就行。
3.4 驱动代码实现:从读寄存器到出数据
好,咱们直接上代码。我习惯把BMP280的驱动封装成三个函数:初始化、读取原始值、补偿计算。
// bmp280.h
#ifndef BMP280_H
#define BMP280_H
#include <stdint.h>
typedef struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
int32_t t_fine; // 温度补偿中间值
} bmp280_calib_t;
// 初始化:读取校准参数,配置工作模式
int bmp280_init(bmp280_calib_t *calib);
// 读取原始温度(20位ADC值)
int32_t bmp280_read_raw_temp(void);
// 读取原始气压(20位ADC值)
int32_t bmp280_read_raw_pressure(void);
// 补偿计算:返回摄氏度(放大100倍,如25.12℃返回2512)
int32_t bmp280_compensate_temp(bmp280_calib_t *calib, int32_t adc_T);
// 补偿计算:返回帕斯卡(放大256倍,如101325Pa返回25939200)
uint32_t bmp280_compensate_pressure(bmp280_calib_t *calib, int32_t adc_P);
#endif
补偿计算的公式,我直接贴博世官方的算法。说实话,这公式看着长,但你别怕,照着抄就行:
// bmp280.c 关键部分
int32_t bmp280_compensate_temp(bmp280_calib_t *calib, int32_t adc_T) {
int32_t var1, var2, T;
var1 = ((((adc_T >> 3) - ((int32_t)calib->dig_T1 << 1))) *
((int32_t)calib->dig_T2)) >> 11;
var2 = (((((adc_T >> 4) - ((int32_t)calib->dig_T1)) *
((adc_T >> 4) - ((int32_t)calib->dig_T1))) >> 12) *
((int32_t)calib->dig_T3)) >> 14;
calib->t_fine = var1 + var2;
T = (calib->t_fine * 5 + 128) >> 8;
return T; // 单位:0.01℃
}
uint32_t bmp280_compensate_pressure(bmp280_calib_t *calib, int32_t adc_P) {
int64_t var1, var2, p;
var1 = ((int64_t)calib->t_fine) - 128000;
var2 = var1 * var1 * (int64_t)calib->dig_P6;
var2 = var2 + ((var1 * (int64_t)calib->dig_P5) << 17);
var2 = var2 + (((int64_t)calib->dig_P4) << 35);
var1 = ((var1 * var1 * (int64_t)calib->dig_P3) >> 8) +
((var1 * (int64_t)calib->dig_P2) << 12);
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)calib->dig_P1) >> 33;
if (var1 == 0) return 0;
p = 1048576 - adc_P;
p = (((p << 31) - var2) * 3125) / var1;
var1 = (((int64_t)calib->dig_P9) * (p >> 13) * (p >> 13)) >> 25;
var2 = (((int64_t)calib->dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)calib->dig_P7) << 4);
return (uint32_t)p; // 单位:Pa/256
}
注意:补偿计算中用了大量移位操作,这是为了在定点MCU上避免浮点运算。如果你用STM32F4以上带FPU的芯片,可以直接用浮点,但要注意精度损失。我曾在Cortex-M0上跑过,移位运算比浮点快10倍以上。
3.5 数据补偿:别让温度毁了你的气压值
BMP280的补偿算法已经做得很好了,但实际应用中还有几个坑:
- 自热效应:传感器工作时会轻微发热,导致测到的温度比环境高0.5~1℃。我建议连续采样时,前几次数据丢弃,等热平衡了再读。
- 气压波动:开门、空调、甚至你呼吸的气流都会影响读数。做气象站时,建议取10次采样的平均值,或者用滑动滤波。
- 高度换算:气压转海拔高度用国际气压公式。但要注意,海平面气压是变化的,你得先校准当地的海平面气压值。
避坑指南:我曾经在户外测试时发现气压数据跳变严重,排查了半天,结果是传感器通风口被标签纸挡住了。BMP280的封装顶部有个小孔,千万别用胶带封死!
好了,关于BMP280的核心内容就这些。下一章咱们讲温湿度传感器SHT30,那个更简单,但也有不少细节。记住,传感器驱动开发,七分看数据手册,三分靠调试经验。多动手,多踩坑,自然就熟了。