news 2026/9/15 16:02:35

51单片机读取BMP280气压计:从I2C驱动到校准公式完整解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机读取BMP280气压计:从I2C驱动到校准公式完整解析

简介:面向51单片机学习者和嵌入式开发者的BMP085/BMP280气压计读取工程资源,围绕I2C通信协议,完整实现传感器初始化、读取温度与气压原始数据、换算为实际物理量、结合标准大气模型估算海拔高度,并利用传感器内部温度测量进行补偿。压缩包为rar格式,共15个文件,主要包含C语言源码、Keil工程配置文件、汇编启动文件以及编译生成的hex、obj、list等输出文件,整体仅44KB,结构轻量,便于直接对照学习或二次修改。目前已有813人学习。读者通过该工程可快速掌握I2C时序操作与传感器数据处理流程,获得可直接烧录的hex文件;参照源码可以系统梳理寄存器配置、数据转换、海拔计算等关键步骤,同时对比BMP085与BMP280在寄存器和控制逻辑上的差异,为后续传感器选型与电路调试提供实用参考。

1. 51 读气压计不是换颗 BMP280 那么简单

实际项目里,用一个 51 单片机读 BMP085 气压计一直是低成本气象站和高度计的标准做法:I2C 读取校准系数、触发一次转换、再用官方整数补偿公式,就能得到温度和气压。但 BMP085 已经停产多年,很多人最近都在把老代码往 BMP280 上平移,却发现这颗“继承者”并不是地址兼容就能跑。BMP280 的控制寄存器、校准系数存放位置、数据字节长度和最终计算公式全都不一样,直接替换连读数都会是零或者乱码。这篇从 51 单片机读取 BMP085/BMP280 气压计的完整链路讲起,把 I2C 底层驱动、两个型号的读取代码、整数校准公式和换型时的验证技巧一次说明白。不管你是 Proteus 仿真还是实物调试,顺着这条路都能走通。

2. 读取 BMP085 / BMP280 前先分清 I2C 地址和寄存器差异

2.1 SDO 引脚决定 BMP280 是 0x76 还是 0x77

BMP085 和 BMP280 都是 7 位 I2C 地址。BMP085 的地址固定在 0x77,硬件上没有更改手段。BMP280 多了一个 SDO 引脚,这颗引脚的电平决定从机地址:SDO 接 VCC 时地址是 0x77,SDO 接地时地址是 0x76。板子上看到的 BMP280 模块默认地址往往已经由 SDO 的上拉或者下拉电阻固定,多数做成了 0x76,但并不能保证。所以代码里不要写死,优先用宏定义,再在初始化后读一下状态寄存器 0xF3,能从机正常回 ACK,说明地址找对了。

I2C 总线上发送地址时要左移一位,最低位是读写方向。读取 BMP085 温度原始值时,命令序列是 Start 0xEE(0x77 写)寄存器地址 Repeat Start 0xEF(0x77 读)数据 Stop。如果 BMP280 地址是 0x76,对应写地址就是 0xEC,读地址 0xED。这个细节在 Proteus 仿真时特别容易忽略,因为仿真里不接 SDO,默认地址是固定的,真实板子却可能完全不同。

2.2 寄存器地图对照:BMP085 和 BMP280 不是一个套路

功能BMP085BMP280
设备地址0x77 固定0x76 或 0x77
校准系数0xAA-0xBF,共 22 字节0x88-0x9F,共 24 字节
状态寄存器无,靠延时等待0xF3,含 measuring 位
测量控制0xF40xF4、0xF5
温度原始值0xF6-0xF7,16 位0xFA-0xFC,20 位
气压原始值0xF6-0xF8,19 位0xF7-0xF9,20 位

BMP085 的 0xF6 地址既用来读温度也用来读压力,读压力时还要根据 OSS 过采样设置去读 2 到 3 个字节。BMP280 则是连续寄存器 0xF7 到 0xFC,把压力 MSB、LSB、XLSB 和温度 MSB、LSB、XLSB 排在一起,一次连续读 6 个字节就能同时拿到两组原始值。这个差异直接决定了读取函数不能复用。

BMP085 的校准系数里,AC1、AC2、AC3 是有符号数,AC4、AC5、AC6 是无符号数,后面 B1、B2、MB、MC、MD 又全是有符号数。BMP280 的校准是 12 个 16 位值,从 0x88 开始的顺序依次是 dig_T1、dig_T2、dig_T3,然后 dig_P1 到 dig_P9。结构体看着差不多,但排列顺序和有无符号不一样,迁移 C 代码时不能照搬结构体。

2.3 51 模拟 I2C:底层四个函数加一个阻塞读块

51 单片常见型号像 STC89C52、AT89S52 没有硬件 I2C,主流做法是用 GPIO 模拟。底层需要实现 Start、Stop、SendByte、RecvByte 和两个应答函数。下面这一份最小实现,在 12MHz 晶振、6 时钟模式下_nop_()延时约 1us,I2C 时钟约 100kHz,满足 BMP280 标准模式要求。

#include <reg51.h> #include <intrins.h> sbit SCL = P1^0; sbit SDA = P1^1; void I2C_Delay(void) { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start(void) { SDA = 1; SCL = 1; I2C_Delay(); SDA = 0; I2C_Delay(); SCL = 0; } void I2C_Stop(void) { SDA = 0; SCL = 1; I2C_Delay(); SDA = 1; } void I2C_SendByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SDA = dat & 0x80; dat <<= 1; SCL = 1; I2C_Delay(); SCL = 0; } } unsigned char I2C_RecvByte(void) { unsigned char i, dat = 0; SDA = 1; for (i = 0; i < 8; i++) { SCL = 1; I2C_Delay(); dat = (dat << 1) | SDA; SCL = 0; I2C_Delay(); } return dat; } bit I2C_GetAck(void) { bit ack; SDA = 1; SCL = 1; I2C_Delay(); ack = SDA; SCL = 0; return ack; } void I2C_SendAck(bit ack) { SDA = ack; SCL = 1; I2C_Delay(); SCL = 0; SDA = 1; }

这段代码有四个点需要额外说明。第一,I2C_GetAck()读到的 0 表示从机应答,1 表示非应答,判断时不要写反。第二,I2C_SendAck(0)表示主设备继续读下一字节,I2C_SendAck(1)表示读最后一个字节后回非应答。第三,SDA 释放时必须先SDA = 1,否则从机拉不动总线。第四,如果 Keil 报SDA = dat & 0x80的类型警告,可以改成SDA = dat & 0x80 ? 1 : 0,不影响逻辑。

通用读寄存器函数建议封装成“Start 写地址和寄存器,再 Repeat Start 读地址,读一字节后回 NACK”。这里必须用 Repeat Start,不要让 Stop 再 Start,否则一些 I2C 从设备会直接丢掉刚才选中的寄存器指针。

unsigned char I2C_ReadReg(unsigned char dev_addr, unsigned char reg) { unsigned char dat; I2C_Start(); I2C_SendByte(dev_addr << 1); I2C_GetAck(); I2C_SendByte(reg); I2C_GetAck(); I2C_Start(); I2C_SendByte((dev_addr << 1) | 0x01); I2C_GetAck(); dat = I2C_RecvByte(); I2C_SendAck(1); I2C_Stop(); return dat; }

这里的dev_addr传入 7 位地址,例如 0x77。函数内部左移一位后变成 0xEE,读地址变成 0xEF。如果调用时直接传 0xEE,最终发送地址会被左移成 0xDC,整个 I2C 通讯全错。这是移植别人代码时最容易犯的错,51、STM32、Arduino 的驱动文件里都见过。

3. 51 单片机读取 BMP085:校准系数、寄存器与一次完整换算

3.1 读取 22 字节校准系数并正确解释符号

BMP085 出厂时已经在内部 OTP 里写好了 11 个 16 位校准值,每次上电必须重新读取。读取利用 I2C 连续读 22 字节,然后按顺序拆到结构体。AC1 到 AC3 用signed short,AC4 到 AC6 用unsigned short,B1、B2、MB、MC、MD 都是signed short。这里最坑的是 AC4 到 AC6,如果用int定义,后面温度公式会把无符号数当成有符号数,整个计算结果直接发散。

typedef struct { short ac1, ac2, ac3; unsigned short ac4, ac5, ac6; short b1, b2, mb, mc, md; } BMP085_CAL_t; BMP085_CAL_t bmp085_cal; void BMP085_ReadCalibration(void) { unsigned char buf[22]; unsigned char i; BMP085_ReadBlock(0xAA, buf, 22); bmp085_cal.ac1 = (short)((buf[0] << 8) | buf[1]); bmp085_cal.ac2 = (short)((buf[2] << 8) | buf[3]); bmp085_cal.ac3 = (short)((buf[4] << 8) | buf[5]); bmp085_cal.ac4 = (unsigned short)((buf[6] << 8) | buf[7]); bmp085_cal.ac5 = (unsigned short)((buf[8] << 8) | buf[9]); bmp085_cal.ac6 = (unsigned short)((buf[10] << 8) | buf[11]); bmp085_cal.b1 = (short)((buf[12] << 8) | buf[13]); bmp085_cal.b2 = (short)((buf[14] << 8) | buf[15]); bmp085_cal.mb = (short)((buf[16] << 8) | buf[17]); bmp085_cal.mc = (short)((buf[18] << 8) | buf[19]); bmp085_cal.md = (short)((buf[20] << 8) | buf[21]); }

buf必须是无符号数组。如果定义了char buf[22],当 buf[0] 最高位是 1 时,左移 8 位会先发生符号扩展,后面的或运算拿到的字节序就是错的。Keil C51 下这种错不报编译警告,查起来很痛苦。BMP085_ReadBlock 是 2.3 节 I2C 底层之上的批量读函数,通用实现是发设备地址、寄存器地址,再从 0xAA 开始连续读 22 字节,最后一字节回 NACK。

3.2 写 0xF4 控制寄存器:温度 0x2E,气压 0x34 到 0xF4

BMP085 的测量控制寄存器是 0xF4。写 0x2E 触发温度测量,写 0x34、0x74、0xB4、0xF4 触发气压测量,对应 OSS 过采样精度 0 到 3。触发后不能立刻读结果,必须等待转换完成。BMP085 虽然有 EOC 引脚,但很多模块没有引出,51 端最常用的方式就是延时。

void BMP085_StartTempMeasurement(void) { BMP085_WriteReg(0xF4, 0x2E); } void BMP085_StartPressureMeasurement(unsigned char oss) { BMP085_WriteReg(0xF4, 0x34 + (oss << 6)); }

0x34 + (oss << 6)这个表达式分别得到 0x34、0x74、0xB4、0xF4,和手册一致。注意 oss 必须限制在 0 到 3,如果传入 4 以上,控制字会变成 0x34 0x40 的组合,触发指令无效。温度测量不受 OSS 影响,所以每次都固定写 0x2E。

3.3 温度压力整数补偿:b5 是温度到气压的桥梁

BMP085 的温度补偿会产生一个中间量 b5,气压补偿也要复用 b5,所以需要把 b5 保存到全局变量。下面这段代码是官方补偿公式的 51 整数实现,中间变量全部用 long,Keil C51 编译后运行时间会长一点,但 1Hz 采样频率下完全够用。

long BMP085_b5; unsigned short BMP085_GetTemperature(unsigned short ut) { long x1, x2; x1 = ((long)ut - bmp085_cal.ac6) * bmp085_cal.ac5 >> 15; x2 = ((long)bmp085_cal.mc << 11) / (x1 + bmp085_cal.md); BMP085_b5 = x1 + x2; return (unsigned short)((BMP085_b5 + 8) >> 4); } unsigned long BMP085_GetPressure(unsigned long up, unsigned char oss) { long x1, x2, x3, b3, b6, p; unsigned long b4, b7; b6 = BMP085_b5 - 4000; x1 = (long)bmp085_cal.b2 * (b6 * b6 >> 12) >> 11; x2 = (long)bmp085_cal.ac2 * b6 >> 11; x3 = x1 + x2; b3 = ((((long)bmp085_cal.ac1 * 4 + x3) << oss) + 2) >> 2; x1 = (long)bmp085_cal.ac3 * b6 >> 13; x2 = (long)bmp085_cal.b1 * (b6 * b6 >> 12) >> 16; x3 = (x1 + x2 + 2) >> 2; b4 = bmp085_cal.ac4 * (unsigned long)(x3 + 32768) >> 15; b7 = ((unsigned long)(up - b3)) * (50000 >> oss); if (b7 < 0x80000000) p = (b7 << 1) / b4; else p = (b7 / b4) << 1; x1 = (p >> 8) * (p >> 8); x1 = x1 * 3038 >> 16; x2 = -7357 * p >> 16; p = p + ((x1 + x2 + 3791) >> 4); return (unsigned long)p; }

这段代码有三个需要说明的参数。温度函数返回的是 0.1 摄氏度,比如数值 247 表示 24.7°C,显示时用整数除法分离整数和小数,不要在 51 上直接除以 10.0,能省点内存是一点。气压函数的返回值单位是 Pa,不是 hPa。101325 Pa 等于 1013.25 hPa,如果驱动里直接除 1000,会把标准大气压显示成 101.325,单位就错了。最后一个细节是b7 < 0x80000000并不是比较负数,而是判断无符号数的高位,用于区分官方公式里正负分支,不要“优化”成有符号比较。

3.4 OSS 等待时间与精度选择

BMP085 的 OSS 值直接决定 ADC 转换时间,数据手册给出的典型转换时间如下。代码里的延时要比手册多留一点,尤其在 Keil 优化等级较高时,空循环可能被优化掉。

OSS控制字采样次数转换时间
00x341 次4.5ms
10x742 次7.5ms
20xB44 次13.5ms
30xF48 次25.5ms

实际项目里,51 单片机做室内气压采集用 OSS=1 就够了,功耗和等待时间比较均衡。如果想做高度计,建议 OSS=2。海拔每升高 8 米气压大约变化 100Pa,OSS=0 时读数抖动可能到十几 Pa,高度显示会跳得很厉害。OSS=3 的采集频率大概 30Hz,在 51 上收益不大,除非电池供电才需要压噪声。

4. 51 单片机读取 BMP280:强制模式、数据寄存器和精确补偿

4.1 校准数据:0x88 开始连续 24 字节

BMP280 的校准系数和 BMP085 完全不是一个布局。0x88 地址开始依次是 dig_T1、dig_T2、dig_T3、dig_P1 到 dig_P9,共 12 个 16 位值,合计 24 字节。BMP280 没有湿度传感器,不需要读 BME280 才有的 0xE1 补校数据,读到 0x9F 就可以。

typedef struct { unsigned short dig_T1; signed short dig_T2; signed short dig_T3; unsigned short dig_P1; signed short dig_P2, dig_P3, dig_P4; signed short dig_P5, dig_P6, dig_P7; signed short dig_P8, dig_P9; } BMP280_CAL_t; BMP280_CAL_t bmp280_cal; void BMP280_ReadCalibration(void) { unsigned char buf[24]; BMP280_ReadBlock(0x88, buf, 24); bmp280_cal.dig_T1 = (unsigned short)((buf[0] << 8) | buf[1]); bmp280_cal.dig_T2 = (signed short)((buf[2] << 8) | buf[3]); bmp280_cal.dig_T3 = (signed short)((buf[4] << 8) | buf[5]); bmp280_cal.dig_P1 = (unsigned short)((buf[6] << 8) | buf[7]); bmp280_cal.dig_P2 = (signed short)((buf[8] << 8) | buf[9]); bmp280_cal.dig_P3 = (signed short)((buf[10] << 8) | buf[11]); bmp280_cal.dig_P4 = (signed short)((buf[12] << 8) | buf[13]); bmp280_cal.dig_P5 = (signed short)((buf[14] << 8) | buf[15]); bmp280_cal.dig_P6 = (signed short)((buf[16] << 8) | buf[17]); bmp280_cal.dig_P7 = (signed short)((buf[18] << 8) | buf[19]); bmp280_cal.dig_P8 = (signed short)((buf[20] << 8) | buf[21]); bmp280_cal.dig_P9 = (signed short)((buf[22] << 8) | buf[23]); }

注意 BMP280 的温度校准只有 dig_T1 是无符号,其他全部是有符号;压力校准只有 dig_P1 无符号,dig_P2 到 dig_P9 有符号。这和 BMP085 的结构体顺序不同,直接粘贴旧代码后结果会离谱。读取 BMP280 校准系数时,设备地址宏要写成#define BMP280_ADDR 0x77,如果模块上 SDO 接地就改成 0x76。

4.2 控制寄存器 0xF4:forced 模式更适合 51 的单次采集

BMP280 的 0xF4 寄存器可以同时设置气压过采样 osrs_p、温度过采样 osrs_t 和模式。因为 51 单片机没有硬件 I2C,也不像 STM32 那样适合跑复杂状态机,所以不建议用 normal 模式,最好用 forced 模式:写一次 0xF4 触发一次测量,读完成后再触发下一次。这样采样频率可以由程序控制,调试时也能清楚看到每个测量周期。

#define BMP280_ADDR 0x77 #define BMP280_REG_CTRL_MEAS 0xF4 #define BMP280_REG_STATUS 0xF3 #define BMP280_REG_DATA 0xF7 void BMP280_Trigger(void) { // osrs_p=010, osrs_t=001, mode=01 forced BMP280_WriteReg(BMP280_REG_CTRL_MEAS, 0x45); } bit BMP280_IsMeasuring(void) { unsigned char status = I2C_ReadReg(BMP280_ADDR, BMP280_REG_STATUS); return (status & 0x09) ? 1 : 0; }

0x45 的二进制是 01000101,拆开看就是气压过采样 x2,温度过采样 x1,模式为 forced。如果你的应用需要更高精度,把 osrs_p 改成 101,再给温度也设成 oversampling x2,控制字会变成 0x55;如果不想纠结每一位,直接使用(5 << 5) | (1 << 2) | 1这种写法,含义比魔数清楚。读状态寄存器时,0xF3 的 bit3 是 measuring,bit0 是 im_update,定时器里有一直为 1 的现象,所以上面用 0x09 同时等两个状态位落下。

4.3 20 位原始数据拼接与整数补偿

BMP280 的数据寄存器从 0xF7 开始,0xF7、0xF8、0xF9 是压力,0xFA、0xFB、0xFC 是温度。每个量都是 20 位,用 6 字节一次连续读取。

void BMP280_ReadRaw(unsigned long *pressure, unsigned long *temperature) { unsigned char d[6]; BMP280_ReadBlock(BMP280_REG_DATA, d, 6); *pressure = ((unsigned long)d[0] << 12) | ((unsigned long)d[1] << 4) | ((unsigned long)d[2] >> 4); *temperature = ((unsigned long)d[3] << 12) | ((unsigned long)d[4] << 4) | ((unsigned long)d[5] >> 4); }

注意拼接顺序。20 位 ADC 值实际上是把 18 位结果左对齐到 20 位,所以 MSB 左移 12 位,中间那个字节左移 4 位,最后 XLSB 只取高 4 位,丢掉低 4 位噪声。有些移植程序直接左移 8 位加两个字节,最后算出来的气压低 256 倍,正是漏看了>> 4

温度补偿和气压补偿的官方整数公式比较复杂。在 51 上建议分两个函数实现,中间量 t_fine 用全局变量,因为计算气压必须用到温度补偿后的 t_fine,不能先算气压再算温度。

signed long bmp280_t_fine; signed long BMP280_CompensateTemp(signed long adc_T) { signed long var1, var2, T; var1 = ((((adc_T >> 3) - ((signed long)bmp280_cal.dig_T1 << 1)) * (signed long)bmp280_cal.dig_T2) >> 11); var2 = (((((adc_T >> 4) - (signed long)bmp280_cal.dig_T1)) * ((adc_T >> 4) - (signed long)bmp280_cal.dig_T1)) >> 12); var2 = (var2 * (signed long)bmp280_cal.dig_T3) >> 14; bmp280_t_fine = var1 + var2; T = (bmp280_t_fine * 5 + 128) >> 8; return T; } unsigned long BMP280_CompensatePressure(signed long adc_P) { signed long var1, var2; unsigned long p; var1 = (bmp280_t_fine >> 1) - 64000; var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * (signed long)bmp280_cal.dig_P6; var2 = var2 + ((var1 * (signed long)bmp280_cal.dig_P5) << 1); var2 = (var2 >> 2) + ((signed long)bmp280_cal.dig_P4 << 16); var1 = ((((signed long)bmp280_cal.dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + (((signed long)bmp280_cal.dig_P2 * var1) >> 1)) >> 18; var1 = ((((signed long)32768 + var1) * (signed long)bmp280_cal.dig_P1) >> 15); if (var1 == 0) { return 0; } p = (((unsigned long)((((signed long)1048576) - adc_P) - (var2 >> 12)))) * 3125; if (p < 0x80000000) { p = (p << 1) / (unsigned long)var1; } else { p = (p / (unsigned long)var1) * 2; } var1 = ((signed long)bmp280_cal.dig_P9 * (signed long)(((p >> 3) * (p >> 3)) >> 13)) >> 12; var2 = ((signed long)(p >> 2) * (signed long)bmp280_cal.dig_P8) >> 13; p = (unsigned long)((signed long)p + ((var1 + var2 + (signed long)bmp280_cal.dig_P7) >> 4)); return p; }

这段代码里,温度函数返回值是 0.01°C,比如 2536 就是 25.36°C。气压函数返回值是 Pa,单位换算和 BMP085 一样。移植时最容易写错的是 var2 的第二个乘法,少打一个括号都会让中间量溢出;51 的 long 是 32 位,中间计算尽量避免先乘后移位,否则 Keil 可能插入临时量导致代码变大。

4.4 一个完整采样循环的调用顺序

BMP280 用 forced 模式时,正确的调用顺序是:上电等待 10ms,读校准系数,然后每采集一次都执行“触发 等待完成 读原始值 补偿”,不能只触发一次然后在循环里反复读,否则读到的永远是上一次的转换结果。

void BMP280_ReadAll(unsigned long *p, unsigned long *t) { unsigned long raw_p, raw_t; BMP280_Trigger(); while (BMP280_IsMeasuring()); BMP280_ReadRaw(&raw_p, &raw_t); *t = (unsigned long)BMP280_CompensateTemp((signed long)raw_t); *p = BMP280_CompensatePressure((signed long)raw_p); }

BMP280_IsMeasuring()里加了0x09的状态判断。有些 BMP280 模块上电后第一次读校准时会因为 NVM 忙而返回状态异常,所以调用BMP280_ReadCalibration之前最好先延时 100ms,不要一复位就去读。真实板子上如果卡在while (BMP280_IsMeasuring()),优先检查地址、SDA/SCL 上拉电阻,再看 0xF3 寄存器是否一直被拉高。

5. 从 BMP085 换到 BMP280 的 4 个踩坑点与验证方法

5.1 电平范围:51 的 5V 和 BMP280 的 3.3V 不能直连

BMP280 是 3.3V 器件,SDA 和 SCL 耐受一般不超过 VDD+0.3V。很多 51 开发板是 5V 供电,P1 和 P3 口上拉后输出高电平接近 5V,直接接 BMP280 可能在长时间运行后损坏传感器。常见做法是给 BMP280 单独接 3.3V LDO,SDA 和 SCL 用 I2C 电平转换板,或者用两个 MOS 管做双向转换。如果只是调试,可以用 1k 电阻串联到 51 的 P1 口,再把 SDA/SCL 上拉到 3.3V,但这样不能保证所有器件都稳定,正式项目还是建议加电平转换。

5.2 换型后先抓 ACK,再看数据

从 BMP085 换到 BMP280,不要直接改完代码就读数。先用逻辑分析仪抓 SDA 和 SCL 两个通道,采样率设 1MHz 以上,检查从机地址字节之后的 ACK 位是不是低电平。如果抓到的地址是 0xDC 而不是 0xEE,说明调用时把 7 位地址和 8 位写地址混用了。如果地址正确但没有 ACK,先查 SDO 引脚和电源。Proteus 仿真里换 BMP280 后还要重新设置器件地址,仿真默认有时是 0x76,和代码不一致时一样没有 ACK。

5.3 气压读数滤波:一阶滞后加 5 次滑动平均

51 单片机读 BMP280 的原始计算值本身已经很稳定,但 I2C 读取过程中偶尔会出现一个跳变值。现象是气压值偶尔从 101325 跳到 101390,持续一个采样周期后又恢复。最简单的处理是在采样循环里做一个 5 次滑动平均,同时限制单次变化量超过 30Pa 的样本直接丢弃。

#define BMP_FILTER_N 5 unsigned long bmp_filter_buf[BMP_FILTER_N]; unsigned char bmp_filter_index; unsigned long BMP_Filter(unsigned long new_value) { unsigned char i; unsigned long sum = 0; bmp_filter_buf[bmp_filter_index] = new_value; bmp_filter_index = (bmp_filter_index + 1) % BMP_FILTER_N; for (i = 0; i < BMP_FILTER_N; i++) { sum += bmp_filter_buf[i]; } return sum / BMP_FILTER_N; }

这个滤波算法在 51 上占用的 RAM 很小,5 个 unsigned long 一共 20 字节。滤波后如果还要做高度显示,用简化大气方程alt = (1 - pow(p / 101325.0, 0.1903)) * 44330,但 51 上不要直接调用 pow,可以查表或者用定点近似。做高度计的时候,BMP280 的温度对气压板有热影响,尽量避免把传感器放在 MCU 正上方,给传感器留一点透气空间,精度比换更高端的传感器更明显。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 16:02:26

CHB-MIT脑电数据集详解:从数据读取到癫痫发作检测预处理

1. 为什么CHB-MIT至今仍是发作检测入门的首选数据集做脑电数据处理的人&#xff0c;绕不开CHB-MIT这个经典数据集。它由波士顿儿童医院发布&#xff0c;收录了多位癫痫患儿的头皮EEG记录。我们课题组刚开始接触癫痫自动检测时&#xff0c;翻遍公开数据&#xff0c;最后也是从CH…

作者头像 李华
网站建设 2026/9/15 16:02:17

ECharts模板实战:拆解option、地图与伪3D饼图,迁移Vue3

简介&#xff1a;39套超炫ECharts大数据可视化模板源码&#xff0c;定位为数据大屏与可视化项目设计参考。面向Web前端开发者、数据分析师&#xff0c;以及需要搭建运营监控、销售分析、地理分布等驾驶舱界面的工程师。整套源码包共含2000个文件&#xff0c;压缩包约248.5MB&am…

作者头像 李华
网站建设 2026/9/15 16:00:18

Flink CDC同步性能瓶颈:Sink并行度未生效的排查与调优

先说结论&#xff1a;这个坑我踩了整整一天&#xff0c;如果你也遇到FlinkCDC同步任务吞吐上不去、延迟持续增长、并行度怎么调都没用的情况&#xff0c;大概率和我遇到的是同一个原因——写入端的并行度并没有真正生效。先说下我当时的场景&#xff1a;源端是MySQL&#xff0c…

作者头像 李华
网站建设 2026/9/15 15:57:59

语音预处理关键:分帧加窗原理、参数与Python实战

语音处理里干了大半年&#xff0c;最容易被忽略、却又最影响后续效果的一步&#xff0c;就是分帧和加窗。很多初学者把网络上下好的音频直接丢给模型&#xff0c;出来的结果乱七八糟&#xff0c;回头怀疑是模型不行&#xff0c;实际上一大半问题出在预处理没做好。语音信号的分…

作者头像 李华
网站建设 2026/9/15 15:55:31

ORB-SLAM3 Euroc数据集测试实战:编译、运行与精度评估

第一次把ORB-SLAM3跑在Euroc的MH01序列上&#xff0c;我看着屏幕上不断刷新的地图点和轨迹线&#xff0c;第一反应是“终于跑通了”&#xff0c;第二反应是“这轨迹怎么和真值差了这么多”。后来仔细一查&#xff0c;问题居然出在一个非常不起眼的地方——我给单目模式传的yaml…

作者头像 李华
网站建设 2026/9/15 15:54:46

VGG19实战:从结构解析到PyTorch实现与调参全指南

前一段时间整理自己深度学习系列的学习笔记&#xff0c;正好写到“深度学习12—VGG19实现”这一篇。VGG19这个模型&#xff0c;现在看起来已经不算最前沿&#xff0c;但它在视觉模型演进里的位置非常特殊&#xff1a;它是“深度”这个概念真正被做到极致的代表作&#xff0c;也…

作者头像 李华