一起玩转树莓派(17)——BMP180数字压力传感器应用
一.BMP180使用说明
BMP180是一款高级的温度气压传感器,通过测量的气压值也可以计算出当前海拔高度。其压力测量范围为300-1100hPa,对应的海拔高度为正9000m-负500m。工作电压在1.8V到3.6V之间。体积小,精度高,采用I2C接口,使用非常方便。BMP180传感器在GPS导航,天气检测,海拔测量和垂直方向速度检测等方面有广泛的应用。本实验,我们尝试使用树莓派的I2C接口来读取BMP180的温度和气压值,并进行海拔高度的计算。
相对于本系列博客前面传感器实验案例,BMP180的使用略微复杂。在编写一款传感器的驱动代码之前,首先需要阅读对应的芯片手册。BMP180数据手册可以在如下地址找到,此手册有详细的BMP180的使用方法及温度气压数值的计算公式。
本次实验使用的BMP180传感器元件如下图所示:
可以看到此元件有5个引脚,其中VCC引脚可以不做使用,SCL和SDA引脚是I2C总线通信引脚,3.3和GND用来接电源正负极。
1.1 使用校准数据对温度和气压进行补偿校准
BMP180的压力和温度数据,必须通过传感器的校准数据进行补偿计算,校准数据可以通过读取其内部EEPROM存储器来获取,EEPROM (Electrically Erasable Programmable read only memory)又称为E2PROM,即带电可擦可编程只读存储器,等下我们会介绍校准数据的读取方法。
BMP180除了拥有压力温度物理传感器外,还包含E2PROM存储模块,ADC模数转换模块和控制单元等,核心电路图如下:
E2PROM存储器*存储176位数据,这176位数据被分为11个字,即11个校准系数,每个校准系数为2个字节16位数据。在计算温度和气压之前,需要先将这11个校准系数获取到,下图列出了使用I2C访问的寄存器地址:
需要注意,在这11个校准系数中,AC4,AC5和AC6这3个是无符号整数,其他的都是有符号的整数,对于有符号的整数,寄存器中本身存储的是补码,我们需要将其转换成原码数据。示例代码如下:
#coding:utf-8
import smbus
# 定义BMP180设备地址
deviceAddress = 0x77
# 有符号数的补码转换
def getInt(data):
r = data
# 第1位为1表示是负数
# 对负数的补码再取补码即可得到源码
if (data & 0x8000) != 0:
d = data ^ 0xffff
d = d + 1
r = -d
return r
# 封装的获取E2PROM存储器中数据的方法
# sign参数表示是否是有符号数
def readCalibrate(adrList, index, sign):
value = (adrList[index] << 8) + adrList[index + 1]
if sign:
return getInt(value)
else:
return value
# 获取E2PROM存储其的数据
# read_i2c_block_data为i2C读取一块数据的方法,第2个参数为寄存器地址,第3个参数为读取的字节数
calibrationList = bus.read_i2c_block_data(deviceAddress, 0xAA, 22)
# 获取需要的校准系数
AC1 = readCalibrate(calibrationList, 0, True)
AC2 = readCalibrate(calibrationList, 2, True)
AC3 = readCalibrate(calibrationList, 4, True)
AC4 = readCalibrate(calibrationList, 6, False)
AC5 = readCalibrate(calibrationList, 8, False)
AC6 = readCalibrate(calibrationList, 10, False)
B1 = readCalibrate(calibrationList, 12, True)
B2 = readCalibrate(calibrationList, 14, True)
MB = readCalibrate(calibrationList, 16, True)
MC = readCalibrate(calibrationList, 18, True)
MD = readCalibrate(calibrationList, 20, True)
上面代码中deviceAddress为BMP180连接上I2C总线后的设备地址,后面在连线时,我们再介绍如何得到。read\_i2c\_block_data函数用来读取一块数据,从芯片手册可知,E2PROM存储器数据地址的起始是0xAA,且是连续的,我们直接将22个字节全部读出即可。
1.2 测量温度与压力数值
BMP180对温度和压力的测量逻辑非常简单,先发出测量指令,之后等待一定的数据转换时间,之后即可直接通过I2C总线来读取测量数据。测量流程如下:
在上面的流程图中可以看到,发送测量温度指令后,等待4.5ms后即可读取温度数据,之后发送测量压力指令,等待一段时间后,即可获取压力数据,拿到原始的数据后通过一定的计算方式,即可算出最终的物理量。需要注意,测量压力所需要等待的转换时间与采样精度有关。
通过选择不同的采样精度,我们可以让BMP180工作在最适合的场景中,即实现功耗,性能和测量速度的按需调整。采样精度模式可选的有如下几种:
可以看到,功耗越低的模式(Mode),采样数越少(Internal number of samples),转换速度越快(Conversion time),噪声越大(RMS noise),精度越差。功耗的配置参数由oversampling_setting决定,后面我们会用到它。
1.3 计算温度和压力数据的全过程
现在,我们已经了解了BMP180的一些使用细节,只需要按照芯片手册的步骤来测量和计算即可得到物理数据。完整的过程下图所示:
可以看到,上面流程图从Start开始后,一共有6个需要做的步骤,其中最后一步是展示数据,我们可以省略掉,还剩下5个核心步骤。
第一步:读取校准系数数据
这一步前面我们已经完成。
第二步:读取尚未进行补偿运算的温度数值
首先通过I2C总线向地位为0xF4的寄存器写入0x2E的指令数据,等待4.5ms后,0xF6(MSB)和0xF7(LSB)寄存器的值,最终计算出未补偿的温度数值为:
UT = MSB << 8 + LSB
示例代码如下:
#coding:utf-8
import smbus
import time
# 定义BMP180设备地址
deviceAddress = 0x77
# 通过I2C读取设备某个寄存器的数据
# regAdr 寄存器地址
def readByte(device, regAdr):
return bus.read_byte_data(device, regAdr)
# 读取一个字的数据
def readWord(device, regAdr):
high = bus.read_byte_data(device, regAdr)
low = bus.read_byte_data(device, regAdr + 1)
res = (high << 8) + low
return res
# 写入一个字节的数据
def writeByte(device, regAdr, data):
bus.write_byte_data(device, regAdr, data)
# 有符号数的补码转换
def getInt(data):
r = data
# 第1位为1表示是负数
# 对负数的补码再取补码即可得到源码
if (data & 0x8000) != 0:
d = data ^ 0xffff
d = d + 1
r = -d
return r
# 获取原始的温度数据(未补偿)
def getTemperature():
# 写入指令
writeByte(deviceAddress, 0xF4, 0x2E)
# 等待温度数据转换
time.sleep(0.005)
# 读取的数据为有符号数
value = readWord(deviceAddress, 0xF6)
# 符号处理
return getInt(value)
第三步:获取未补偿的压力数据
与获取温度的原始数据类似,首先向0xF4寄存器写入数据0x34+(oss<<6),其中参数oss即为software\_oversampling\_setting,即我们前面提到的采样精度参数,根据需要选择即可。之后等待一定的转换时间,此时间与oss参数的选择有关,上面表中已经列出。再读取0xF6(MSB),0xF7(LSB)和0xF8(XLSB)的数据,最后使用如下计算方式得到原始压力数据:
UP = (MSB<<16 + LSB<<8 + XLSB) >> (8 - oss)
示例代码如下:
# 定义采样精度,这里取超高分辨率
OSS = 3
# 获取原始的压力数据 (未补偿)
def getPressure():
# 写入指令
writeByte(deviceAddress, 0xF4, 0x34 + (OSS << 6))
time.sleep(0.026)
MSB = readByte(deviceAddress, 0xF6)
LSB = readByte(deviceAddress, 0xF7)
XLSB = readByte(deviceAddress, 0xF8)
value = ((MSB << 16) + (LSB << 8) + XLSB) >> (8 - OSS)
return value
第四步:计算真实的物理温度值
使用如下公式计算真正的物理温度值:
X1 = (UT - AC6) * AC5 / (2^15)
X2 = MC * (2^11) / (X1 + MD)
B5 = X1 + X2
T = (B5 + 8) / (2^4)
示例代码如下:
import math
# 计算真实的温度
def calculateTemperature(data):
X1 = ((data - AC6) * AC5) / math.pow(2, 15)
X2 = (MC * math.pow(2, 11)) / (X1 + MD)
B5 = X1 + X2
T = (B5 + 8) / math.pow(2, 4)
return T / 10.0
需要注意,最终计算的到的温度数值为0.1摄氏度单位,转换成摄氏度直接除以10即可。
第5步:计算真实的物理气压值
其他数据的计算相对复杂,公式如下:
B6 = B5 - 4000
X1 = (B2 (B6 B6 / 2^12)) / 2^11
X2 = AC2 * B6 / 2^11
X3 = X1 + X2
B3 = (((AC1 * 4 + X3) << OSS) + 2) / 4
X1 = AC3 * B6 / 2^13
X2 = (B1 (B6 B6 / 2^12)) / 2^16
X3 = ((X1 + X2) + 2) /2 ^2
B4 = AC4 *(UNSIGNED LONG)(X3 + 32768) / 2^15
B7 = ((UNSIGNED LONG)UP - B3) * (50000 >> OSS)
如果B7小于0x80000000,则 P = (B7 2) / B4 否则 P = (B7 / B4) 2
X1 = (P / 2^8) * (P / 2^8)
X2 = (-7357 * P) / 2^16
P = P + (X1 + X2 + 3791) / 2^4
示例代码如下:
# 计算真实的气压
def calculatePressure(temp, data):
X1 = ((temp - AC6) * AC5) / math.pow(2, 15)
X2 = (MC * math.pow(2, 11)) / (X1 + MD)
B5 = X1 + X2
B6 = B5 - 4000
X1 = (B2 * (B6 * B6 >> 12)) >> 11
X2 = AC2 * B6 >> 11
X3 = X1 + X2
B3 = (((AC1 * 4 + X3) << OSS) + 2) >> 2
X1 = (AC3 * B6) >> 13
X2 = (B1 * (B6 * B6 >> 12)) >> 16
X3 = ((X1 + X2) + 2) >> 2
B4 = AC4 * (X3 + 32768) >> 15
B7 = (data - B3) * (50000 >> OSS)
if (B7 < 0x80000000):
P = (B7 * 2) / B4
else:
P = (B7 / B4) * 2
X1 = (P >> 8) * (P >> 8)
X1 = (X1 * 3038) >> 16
X2 = (-7357 * P) >> 16
P = P + ((X1 + X2 + 3791) >> 4)
# 单位为Pa 转换为hPa除以100即可
return P /100.0
二. 实验-使用BMP180测量温度、气压和海拔高度
通过前面的介绍,一些核心的功能代码我们其实都已经实现,首先先将BMP180与树莓派相连:
BMP180 | 树莓派 |
---|---|
3.3 | 3.3V |
GND | GND |
SCL | SCL |
SDA | SDA |
要计算海拔高度,可以使用如下公式:
其中p0可以取海平面的标准大气压1013.25hPa。
连好线后,首先确认树莓派开启了I2C总线,如下:
在树莓派的终端使用如下指令可以查看外接的元件地址:
sudo i2cdetect -y 1
可以看到如下图所示的输出:
其中0x77即为BMP180设备地址。
下面给出完整的实验代码:
#coding:utf-8
import smbus
import time
import math
# 初始化
bus = smbus.SMBus(1)
# 定义BMP180设备地址
deviceAddress = 0x77
# 通过I2C读取设备某个寄存器的数据
# regAdr 寄存器地址
def readByte(device, regAdr):
return bus.read_byte_data(device, regAdr)
# 读取一个字的数据
def readWord(device, regAdr):
high = bus.read_byte_data(device, regAdr)
low = bus.read_byte_data(device, regAdr + 1)
res = (high << 8) + low
return res
# 写入一个字节的数据
def writeByte(device, regAdr, data):
bus.write_byte_data(device, regAdr, data)
# 有符号数的补码转换
def getInt(data):
r = data
# 第1位为1表示是负数
# 对负数的补码再取补码即可得到源码
if (data & 0x8000) != 0:
d = data ^ 0xffff
d = d + 1
r = -d
return r
# 封装的获取E2PROM存储器中数据的方法
# sign参数表示是否是有符号数
def readCalibrate(adrList, index, sign):
value = (adrList[index] << 8) + adrList[index + 1]
if sign:
return getInt(value)
else:
return value
# 获取E2PROM存储其的数据
# read_i2c_block_data为i2C读取一块数据的方法,第2个参数为寄存器地址,第3个参数为读取的字节数
calibrationList = bus.read_i2c_block_data(deviceAddress, 0xAA, 22)
# 获取需要的校准系数
AC1 = readCalibrate(calibrationList, 0, True)
AC2 = readCalibrate(calibrationList, 2, True)
AC3 = readCalibrate(calibrationList, 4, True)
AC4 = readCalibrate(calibrationList, 6, False)
AC5 = readCalibrate(calibrationList, 8, False)
AC6 = readCalibrate(calibrationList, 10, False)
B1 = readCalibrate(calibrationList, 12, True)
B2 = readCalibrate(calibrationList, 14, True)
MB = readCalibrate(calibrationList, 16, True)
MC = readCalibrate(calibrationList, 18, True)
MD = readCalibrate(calibrationList, 20, True)
# 获取原始的温度数据(未补偿)
def getTemperature():
# 写入指令
writeByte(deviceAddress, 0xF4, 0x2E)
# 等待温度数据转换
time.sleep(0.005)
# 读取的数据为有符号数
value = readWord(deviceAddress, 0xF6)
# 符号处理
return getInt(value)
# 定义采样精度,这里取超高分辨率
OSS = 3
# 获取原始的压力数据 (未补偿)
def getPressure():
# 写入指令
writeByte(deviceAddress, 0xF4, 0x34 + (OSS << 6))
time.sleep(0.026)
MSB = readByte(deviceAddress, 0xF6)
LSB = readByte(deviceAddress, 0xF7)
XLSB = readByte(deviceAddress, 0xF8)
value = ((MSB << 16) + (LSB << 8) + XLSB) >> (8 - OSS)
return value
# 计算真实的温度
def calculateTemperature(data):
X1 = ((data - AC6) * AC5) / math.pow(2, 15)
X2 = (MC * math.pow(2, 11)) / (X1 + MD)
B5 = X1 + X2
T = (B5 + 8) / math.pow(2, 4)
return T / 10.0
# 计算真实的气压
def calculatePressure(temp, data):
X1 = ((temp - AC6) * AC5) / math.pow(2, 15)
X2 = (MC * math.pow(2, 11)) / (X1 + MD)
B5 = int(X1 + X2)
B6 = B5 - 4000
X1 = (B2 * (B6 * B6 >> 12)) >> 11
X2 = AC2 * B6 >> 11
X3 = X1 + X2
B3 = (((AC1 * 4 + X3) << OSS) + 2) >> 2
X1 = (AC3 * B6) >> 13
X2 = (B1 * (B6 * B6 >> 12)) >> 16
X3 = ((X1 + X2) + 2) >> 2
B4 = AC4 * (X3 + 32768) >> 15
B7 = (data - B3) * (50000 >> OSS)
if (B7 < 0x80000000):
P = int((B7 * 2) / B4)
else:
P = int((B7 / B4) * 2)
X1 = (P >> 8) * (P >> 8)
X1 = (X1 * 3038) >> 16
X2 = (-7357 * P) >> 16
P = P + ((X1 + X2 + 3791) >> 4)
# 单位为Pa 转换为hPa 除以100即可
return P / 100.0
while True:
t = getTemperature()
temp = calculateTemperature(t)
press = calculatePressure(t, getPressure())
high = 44330 * (1 - math.pow((press / 1013.25), 1.0) / 5.255)
print('温度:%f, 气压:%f, 海拔:%f'%(temp, press, high))
time.sleep(1)
运行上面代码,测量结果如下图所示:
专注技术,懂的热爱,愿意分享,做个朋友