一起玩转树莓派(17)——BMP180数字压力传感器应用
一.BMP180使用说明
BMP180是一款高级的温度气压传感器,通过测量的气压值也可以计算出当前海拔高度。其压力测量范围为300-1100hPa,对应的海拔高度为正9000m-负500m。工作电压在1.8V到3.6V之间。体积小,精度高,采用I2C接口,使用非常方便。BMP180传感器在GPS导航,天气检测,海拔测量和垂直方向速度检测等方面有广泛的应用。本实验,我们尝试使用树莓派的I2C接口来读取BMP180的温度和气压值,并进行海拔高度的计算。
相对于本系列博客前面传感器实验案例,BMP180的使用略微复杂。在编写一款传感器的驱动代码之前,首先需要阅读对应的芯片手册。BMP180数据手册可以在如下地址找到,此手册有详细的BMP180的使用方法及温度气压数值的计算公式。
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/RaspberryPi_I2C/files/BST-BMP180-DS000-09.pdf
本次实验使用的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个是无符号整数,其他的都是有符号的整数,对于有符号的整数,寄存器中本身存储的是补码,我们需要将其转换成原码数据。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import smbus
deviceAddress = 0x77
def getInt(data): r = data if (data & 0x8000) != 0: d = data ^ 0xffff d = d + 1 r = -d return r
def readCalibrate(adrList, index, sign): value = (adrList[index] << 8) + adrList[index + 1] if sign: return getInt(value) else: return value
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
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import smbus import time
deviceAddress = 0x77
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 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)8>
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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)
示例代码如下:
1 2 3 4 5 6 7 8
| 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
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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) return P /100.0
|
二. 实验-使用BMP180测量温度、气压和海拔高度
通过前面的介绍,一些核心的功能代码我们其实都已经实现,首先先将BMP180与树莓派相连:
BMP180 |
树莓派 |
3.3 |
3.3V |
GND |
GND |
SCL |
SCL |
SDA |
SDA |
要计算海拔高度,可以使用如下公式:
其中p0可以取海平面的标准大气压1013.25hPa。
连好线后,首先确认树莓派开启了I2C总线,如下:
在树莓派的终端使用如下指令可以查看外接的元件地址:
可以看到如下图所示的输出:
其中0x77即为BMP180设备地址。
下面给出完整的实验代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| import smbus import time import math
bus = smbus.SMBus(1)
deviceAddress = 0x77
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 if (data & 0x8000) != 0: d = data ^ 0xffff d = d + 1 r = -d return r
def readCalibrate(adrList, index, sign): value = (adrList[index] << 8) + adrList[index + 1] if sign: return getInt(value) else: return value
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) 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)
|
运行上面代码,测量结果如下图所示:
专注技术,懂的热爱,愿意分享,做个朋友
QQ:316045346