一起玩转树莓派(17)——BMP180数字压力传感器应用
一起玩转树莓派(17)——BMP180数字压力传感器应用
一.BMP180使用说明
BMP180是一款高级的温度气压传感器,通过测量的气压值也可以计算出当前海拔高度。其压力测量范围为300-1100hPa,对应的海拔高度为正9000m-负500m。工作电压在1.8V到3.6V之间。体积小,精度高,采用I2C接口,使用非常方便。BMP180传感器在GPS导航,天气检测,海拔测量和垂直方向速度检测等方面有广泛的应用。本实验,我们尝试使用树莓派的I2C接口来读取BMP180的温度和气压值,并进行海拔高度的计算。
相对于本系列博客前面传感器实验案例,BMP180的使用略微复杂。在编写一款传感器的驱动代码之前,首先需要阅读对应的芯片手册。BMP180数据手册可以在如下地址找到,此手册有详细的BMP180的使用方法及温度气压数值的计算公式。
(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>)
本次实验使用的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)
运行上面代码,测量结果如下图所示:
专注技术,懂的热爱,愿意分享,做个朋友
你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:
相关文章
- 怎样查看运行中的 Spring 应用配置?
- GNOME 43 发布,标志性的版本
- OpenHarmony应用Hap包签名
- Windows 11 2022首次大更新解读:四大创新、安卓App终于来
- 微软 Windows 11 安卓子系统 WSA 应用登陆 31 个国家和地区
- JDBC的典型应用—桥接模式
- 可以跑安卓App了!Windows 11 2022首次大更新发布 一键下载
- 设计模式之工厂模式—要的是工厂而不是作坊
- 如何在 Android 上优雅的进行 HTTPS 明文抓包
- 如何在 Android 设备上运行 Linux
- 如何在 Ubuntu 桌面中应用强调色
- 如何在 Linux Mint 中创建和切换工作区
- 微软 Windows 11 安卓子系统 WSA 八月更新内容公布:修复 App 重启问题,禁用烦人通知
- 回调函数在命令解析中的应用
- Android内卡挂载之FUSE文件系统
- ObjectMapper,别再像个傻子一样一直New了!
- 微软 Windows 11 Dev 预览版 25182 发布:相机适配 Arm64 支持全新隐私,商店 App 直接安装游戏
- 如何在 Windows 11 上将 Android 手机用作网络摄像头
- 别着急升!劝大家等等iOS 16正式版:原因有四点
- 兼顾敏捷交付和系统稳定运行,大型银行平台化落地实践