一起玩转树莓派(17)——BMP180数字压力传感器应用

网友投稿 1641 2022-10-12

一起玩转树莓派(17)——BMP180数字压力传感器应用

一起玩转树莓派(17)——BMP180数字压力传感器应用

一起玩转树莓派(17)——BMP180数字压力传感器应用

一.BMP180使用说明

BMP180是一款高级的温度气压传感器,通过测量的气压值也可以计算出当前海拔高度。其压力测量范围为300-1100hPa,对应的海拔高度为正9000m-负500m。工作电压在1.8V到3.6V之间。体积小,精度高,采用I2C接口,使用非常方便。BMP180传感器在GPS导航,天气检测,海拔测量和垂直方向速度检测等方面有广泛的应用。本实验,我们尝试使用树莓派的I2C接口来读取BMP180的温度和气压值,并进行海拔高度的计算。

相对于本系列博客前面传感器实验案例,BMP180的使用略微复杂。在编写一款传感器的驱动代码之前,首先需要阅读对应的芯片手册。BMP180数据手册可以在如下地址找到,此手册有详细的BMP180的使用方法及温度气压数值的计算公式。

​​使用校准数据对温度和气压进行补偿校准

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-8import 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-8import smbusimport 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-8import smbusimport timeimport 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.0while 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)

运行上面代码,测量结果如下图所示:

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:开发应用小程序(小程序开发app)
下一篇:基于colorUI的微信小程序上传图片视频于阿里OSS
相关文章

 发表评论

暂时没有评论,来抢沙发吧~