2、原始数据采集实战:使用Python读取BMP280原始气压值、温度补偿计算、高度转换公式推导、数据可视化
好,咱们正式开始动手了。
上一章聊了气压测高的基本原理,你可能觉得「嗯,懂了」。但懂原理和能跑出数据,中间还隔着一条河。我当年第一次调BMP280的时候,就卡在「读出来的数怎么跟气压对不上」这个问题上,折腾了一下午。后来才发现,是忘了做温度补偿。
这一章,咱们就把这条河趟过去。我会带着你,从I2C读取原始寄存器值开始,一步步算出温度、气压,最后转成海拔高度,再画成图看看效果。
2.1 硬件连接与I2C地址确认
先把BMP280连到你的开发板或树莓派上。接线很简单:
- VCC → 3.3V(别接5V,会烧)
- GND → GND
- SCL → 主控的SCL引脚
- SDA → 主控的SDA引脚
BMP280的I2C地址有两种可能:0x76 或 0x77。具体看你的模块上SDO引脚的电平。接地就是0x76,接VDDIO就是0x77。我习惯先扫描一下总线确认:
import smbus2
bus = smbus2.SMBus(1)
for addr in [0x76, 0x77]:
try:
bus.read_byte(addr)
print(f"找到设备: 0x{addr:02X}")
except:
pass
sudo raspi-config)。我第一次用的时候忘了开,还以为是硬件坏了。
2.2 读取校准参数
BMP280出厂时,每个芯片都内置了一套校准系数。这些系数存储在寄存器 0x88 到 0xA1 之间,一共24个字节。说白了,就是芯片的「个性数据」——没有它们,你算出来的气压就是错的。
读取方法很简单:
def read_calibration(bus, addr=0x76):
calib = bus.read_i2c_block_data(addr, 0x88, 24)
# 解析为有符号/无符号16位整数
dig_T1 = calib[0] | (calib[1] << 8) # 无符号
dig_T2 = calib[2] | (calib[3] << 8) # 有符号
if dig_T2 > 32767: dig_T2 -= 65536
dig_T3 = calib[4] | (calib[5] << 8)
if dig_T3 > 32767: dig_T3 -= 65536
# ... 气压校准系数类似
return dig_T1, dig_T2, dig_T3, ...
嗯,这里要注意:温度校准系数是3个(T1无符号,T2/T3有符号),气压校准系数是9个(P1无符号,P2~P9有符号)。别搞混了,我一开始就栽在这个坑里。
2.3 读取原始温度与气压值
校准参数拿到手,接下来读原始数据。温度寄存器在 0xFA(MSB)、0xFB(LSB)、0xFC(XLSB),一共20位。气压寄存器在 0xF7、0xF8、0xF9,也是20位。
def read_raw_temp(bus, addr=0x76):
data = bus.read_i2c_block_data(addr, 0xFA, 3)
raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
return raw
def read_raw_pressure(bus, addr=0x76):
data = bus.read_i2c_block_data(addr, 0xF7, 3)
raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
return raw
2.4 温度补偿计算
温度补偿的公式,BMP280数据手册里写得挺清楚。我直接给你代码,咱们一步步来:
def compensate_temperature(raw_temp, dig_T1, dig_T2, dig_T3):
var1 = (raw_temp / 16384.0 - dig_T1 / 1024.0) * dig_T2
var2 = ((raw_temp / 131072.0 - dig_T1 / 8192.0) ** 2) * dig_T3
t_fine = var1 + var2
temp = t_fine / 5120.0
return temp, t_fine
这里有个关键变量 t_fine,它是个中间结果。气压补偿也要用到它。所以我把温度补偿和气压补偿分开写,但共享 t_fine。
2.5 气压补偿计算
有了 t_fine,气压补偿就顺理成章了:
def compensate_pressure(raw_pressure, dig_P1, dig_P2, ..., t_fine):
var1 = (t_fine / 2.0) - 64000.0
var2 = var1 * var1 * dig_P6 / 32768.0
var2 = var2 + var1 * dig_P5 * 2.0
var2 = (var2 / 4.0) + (dig_P4 * 65536.0)
var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * dig_P1
if var1 == 0:
return 0
p = 1048576.0 - raw_pressure
p = (p - var2 / 4096.0) * 6250.0 / var1
var1 = dig_P9 * p * p / 2147483648.0
var2 = p * dig_P8 / 32768.0
p = p + (var1 + var2 + dig_P7) / 16.0
return p / 100.0 # 单位:hPa
公式看着长,其实逻辑很清晰。说白了就是先用温度修正气压的零点,再用非线性项修正曲线。我建议你把这个函数封装好,以后每次调用就行。
2.6 高度转换公式推导
气压转高度,用的是国际气压公式。咱们用简化版就够了:
标准气压公式:
h = 44330 * (1 - (P / P0) ** (1/5.255))
其中 P0 是海平面标准气压(1013.25 hPa),P 是当前实测气压。这个公式假设大气是理想气体,温度递减率固定。实际中会有误差,但做一般的高度测量够用了。
我习惯再加一个修正:如果知道当地当天的海平面气压(比如从气象局查),把 P0 替换掉,精度能提升不少。
def pressure_to_altitude(pressure, sea_level_pressure=1013.25):
return 44330 * (1 - (pressure / sea_level_pressure) ** (1/5.255))
2.7 数据可视化
数据算出来了,光看数字没意思。咱们画个图,看看气压和高度随时间的变化。我用 matplotlib 和 datetime 来做:
import matplotlib.pyplot as plt
import datetime
times = []
pressures = []
altitudes = []
# 假设你已经采集了100个数据点
for i in range(100):
raw_t = read_raw_temp(bus)
raw_p = read_raw_pressure(bus)
temp, t_fine = compensate_temperature(raw_t, dig_T1, dig_T2, dig_T3)
press = compensate_pressure(raw_p, dig_P1, dig_P2, ..., t_fine)
alt = pressure_to_altitude(press)
times.append(datetime.datetime.now())
pressures.append(press)
altitudes.append(alt)
time.sleep(1) # 每秒采一次
# 画图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
ax1.plot(times, pressures, 'b-')
ax1.set_ylabel('气压 (hPa)')
ax2.plot(times, altitudes, 'r-')
ax2.set_ylabel('高度 (m)')
plt.show()
2.8 本章知识体系
我把这一章的核心逻辑画成了流程图,方便你对照理解:
你看,整个流程就是一条直线:从硬件到原始数据,再到补偿计算,最后出结果。每一步都有坑,但咱们都填上了。
2.9 避坑指南
- 我曾经 忘了设置BMP280的工作模式。默认是休眠模式,读出来的数据全是0。记得写寄存器
0xF4设置0x2F(正常模式,过采样16倍)。 - 我曾经 在气压补偿时用了错误的符号位处理。有符号数转Python整数时,一定要手动处理补码。不然算出来的气压忽高忽低。
- 数据抖动 是正常的。别慌,下一章咱们就讲滤波。
好了,这一章的内容就到这儿。代码都给你了,建议你亲手跑一遍。看着气压曲线在屏幕上跳动,那种感觉,比光看公式爽多了。
公众号:蓝海资料掘金营,微信deep3321