一、前言
上一讲介绍了串口的使用,那这一讲就介绍一下IIC,IIC由时钟线(SCL)和数据线(SDA)组成,时钟线提供了通讯的时序,只能由主机发给从机,而数据线和单总线类似,数据传输是双向的。具体的原理就不多说了,感兴趣的同学可以自行查阅相关资料,也可以看我之前写的那两篇光照传感器的博客。
BH1750光照传感器超详细攻略(从原理到代码讲解)
OPT3001光照传感器驱动教学
这一讲主要以BMP280为例,其实在传感器的选择上面我纠结了一会。因为我现在手上有的传感器都在一块扩展板上面,而用IIC的传感器就两个,另外一个比较特殊,相比正常IIC缺少了一些东西,不适合做例子,就只能选BMP280,但是BMP280的寄存器多了点,数据处理也较复杂,其实也不适合做零基础入门教程。但手上暂时也没用其他传感器了,就先将就用吧,我尽量把编程部分讲详细些。
二、硬件连接
BMP280是一款气压检测芯片,同时也可以测量温度,通讯方式有两种:IIC和SPI,选择不同的方式接线不一样,我这里用的是扩展板,硬件已经连接好了,连接的硬件接口是IIC1。具体硬件的介绍就不多说了,感兴趣的同学可以去看下数据手册。
注:IIC接口一定要接对,并且记住IIC的编号,树莓派3B+的IIC接口有两组,IIC0和IIC1,我这里用的是IIC1。
BMP280引脚定义如下:
引脚我就不一一介绍了,我只讲一下SDO这个引脚,用IIC方式通讯时,该引脚决定了IIC的器件地址(直接影响代码),原文如下:
翻译一下,意思就是SDO拉低的话器件地址是1110110(0x76),拉高的话器件地址是1110111(0x77)。
三、寄存器介绍
BMP280寄存器其实不多,不过对于初学者来说可能会有点懵,不要慌,且听我慢慢道来。
BMP280内存分布如下:
注:因为上图的地址是反过来写的,那我的顺序也反一下。
寄存器地址 | 说明 |
---|---|
0xA1-0x88 | 校准数据 |
0xE0 | 用来重启传感器的 |
0xF3 | 指示传感器当前状态 |
0xF4 | 设置数据采集选项 |
0xF5 | 设置基本配置参数 |
0xF7-0xF9 | 气压原始数据 |
0xFA-0xFC | 温度原始数据 |
每个寄存器每个位代表的意义我就不具体解释,自行查阅数据手册。
四、编程原理介绍
编程的步骤大致如下:
1)读取ID寄存器,判断读取到的值和传感器实际的值是否一致(这一步可以确认传感器和树莓派通讯是否正常)
2)读取校准数据
3)设置数据采集选项
4)设置基本配置参数
5)分别读取气压和温度的原始数据(三个寄存器的值合并拼接到一起才是完整的数据)
6)把原始数据和校准数据代入公式计算出最终的气压和温度
五、使能树莓派IIC接口
方法1:通过配置命令
sudo raspi-config // 打开配置
sudo reboot // 重启,如果之前没有使能,配置完之后要重启才能生效
方法2:在可视化界面的设置里面修改
如果之前没有使能,配置完之后要重启才能生效
六、IIC通讯测试
我们可以使用i2c-tool工具来查询IIC设备是否正常连接,我这个系统镜像是自带i2c-tool,如果没有下载,可以通过下面的命令下载。
sudo apt-get install i2c-tools
用i2c-tool查询i2c设备
i2cdetect -y 1 // -y:取消用户交互过程,直接执行指令; 1:I2C的总线编号为1(根据自己的接口选择)
注:我这里有多个IIC设备,器件地址为76的这个才是BMP280(BMP280的地址只可能是76或者77,具体取决于SD0引脚的电平,我前面有讲过)。
七、编写代码并运行
IIC的通信部分主要是调用了smbus这个库,关于这个库的相关函数和解释,可以参考下面这个链接。
https://pypi.org/project/smbus2/
示例代码如下:
import time
import smbus
# BMP280 iic address.
BMP280_I2C_ADDRESS = 0x76 # SDO = 0
# Registers value
BMP280_ID_Value = 0x58 # BMP280 ID
BMP280_RESET_VALUE = 0xB6
# BMP280 Registers definition
BMP280_TEMP_XLSB_REG = 0xFC # Temperature XLSB Register
BMP280_TEMP_LSB_REG = 0xFB # Temperature LSB Register
BMP280_TEMP_MSB_REG = 0xFA # Temperature LSB Register
BMP280_PRESS_XLSB_REG = 0xF9 # Pressure XLSB Register
BMP280_PRESS_LSB_REG = 0xF8 # Pressure LSB Register
BMP280_PRESS_MSB_REG = 0xF7 # Pressure MSB Register
BMP280_CONFIG_REG = 0xF5 # Configuration Register
BMP280_CTRL_MEAS_REG = 0xF4 # Ctrl Measure Register
BMP280_STATUS_REG = 0xF3 # Status Register
BMP280_RESET_REG = 0xE0 # Softreset Register
BMP280_ID_REG = 0xD0 # Chip ID Register
# calibration parameters
BMP280_DIG_T1_LSB_REG = 0x88
BMP280_DIG_T1_MSB_REG = 0x89
BMP280_DIG_T2_LSB_REG = 0x8A
BMP280_DIG_T2_MSB_REG = 0x8B
BMP280_DIG_T3_LSB_REG = 0x8C
BMP280_DIG_T3_MSB_REG = 0x8D
BMP280_DIG_P1_LSB_REG = 0x8E
BMP280_DIG_P1_MSB_REG = 0x8F
BMP280_DIG_P2_LSB_REG = 0x90
BMP280_DIG_P2_MSB_REG = 0x91
BMP280_DIG_P3_LSB_REG = 0x92
BMP280_DIG_P3_MSB_REG = 0x93
BMP280_DIG_P4_LSB_REG = 0x94
BMP280_DIG_P4_MSB_REG = 0x95
BMP280_DIG_P5_LSB_REG = 0x96
BMP280_DIG_P5_MSB_REG = 0x97
BMP280_DIG_P6_LSB_REG = 0x98
BMP280_DIG_P6_MSB_REG = 0x99
BMP280_DIG_P7_LSB_REG = 0x9A
BMP280_DIG_P7_MSB_REG = 0x9B
BMP280_DIG_P8_LSB_REG = 0x9C
BMP280_DIG_P8_MSB_REG = 0x9D
BMP280_DIG_P9_LSB_REG = 0x9E
BMP280_DIG_P9_MSB_REG = 0x9F
class BMP180(object):
def __init__(self, address=BMP280_I2C_ADDRESS):
self._address = address
self._bus = smbus.SMBus(1) # 1: iic编号为1(根据自己的硬件接口选择对应的编号)
# Load calibration values.
if self._read_byte(BMP280_ID_REG) == BMP280_ID_Value: # read bmp280 id
self._load_calibration() # load calibration data
# BMP280_T_MODE_1 << 5 | BMP280_P_MODE_1 << 2 | BMP280_SLEEP_MODE;
ctrlmeas = 0xFF
# BMP280_T_SB1 << 5 | BMP280_FILTER_MODE_1 << 2;
config = 0x14
self._write_byte(BMP280_CTRL_MEAS_REG, ctrlmeas) # write bmp280 config
# sets the data acquisition options
self._write_byte(BMP280_CONFIG_REG, config)
else:
print("Read BMP280 id error!rn")
def _read_byte(self, cmd):
return self._bus.read_byte_data(self._address, cmd)
def _read_u16(self, cmd):
LSB = self._bus.read_byte_data(self._address, cmd)
MSB = self._bus.read_byte_data(self._address, cmd+1)
return (MSB << 8) + LSB
def _read_s16(self, cmd):
result = self._read_u16(cmd)
if result > 32767:
result -= 65536
return result
def _write_byte(self, cmd, val):
self._bus.write_byte_data(self._address, cmd, val)
def _load_calibration(self): # load calibration data
"load calibration"
""" read the temperature calibration parameters """
self.dig_T1 = self._read_u16(BMP280_DIG_T1_LSB_REG)
self.dig_T2 = self._read_s16(BMP280_DIG_T2_LSB_REG)
self.dig_T3 = self._read_s16(BMP280_DIG_T3_LSB_REG)
""" read the pressure calibration parameters """
self.dig_P1 = self._read_u16(BMP280_DIG_P1_LSB_REG)
self.dig_P2 = self._read_s16(BMP280_DIG_P2_LSB_REG)
self.dig_P3 = self._read_s16(BMP280_DIG_P3_LSB_REG)
self.dig_P4 = self._read_s16(BMP280_DIG_P4_LSB_REG)
self.dig_P5 = self._read_s16(BMP280_DIG_P5_LSB_REG)
self.dig_P6 = self._read_s16(BMP280_DIG_P6_LSB_REG)
self.dig_P7 = self._read_s16(BMP280_DIG_P7_LSB_REG)
self.dig_P8 = self._read_s16(BMP280_DIG_P8_LSB_REG)
self.dig_P9 = self._read_s16(BMP280_DIG_P9_LSB_REG)
# print(self.dig_T1)
# print(self.dig_T2)
# print(self.dig_T3)
# print(self.dig_P1)
# print(self.dig_P2)
# print(self.dig_P3)
# print(self.dig_P4)
# print(self.dig_P5)
# print(self.dig_P6)
# print(self.dig_P7)
# print(self.dig_P8)
# print(self.dig_P9)
def compensate_temperature(self, adc_T):
"""Returns temperature in DegC, double precision. Output value of "1.23"equals 51.23 DegC."""
var1 = ((adc_T) / 16384.0 - (self.dig_T1) / 1024.0) * (self.dig_T2)
var2 = (((adc_T) / 131072.0 - (self.dig_T1) / 8192.0) *
((adc_T) / 131072.0 - (self.dig_T1) / 8192.0)) * (self.dig_T3)
self.t_fine = var1 + var2
temperature = (var1 + var2) / 5120.0
return temperature
def compensate_pressure(self, adc_P):
"""Returns pressure in Pa as double. Output value of "6386.2"equals 96386.2 Pa = 963.862 hPa."""
var1 = (self.t_fine / 2.0) - 64000.0
var2 = var1 * var1 * (self.dig_P6) / 32768.0
var2 = var2 + var1 * (self.dig_P5) * 2.0
var2 = (var2 / 4.0) + ((self.dig_P4) * 65536.0)
var1 = ((self.dig_P3) * var1 * var1 / 524288.0 +
(self.dig_P2) * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * (self.dig_P1)
if var1 == 0.0:
return 0 # avoid exception caused by division by zero
pressure = 1048576.0 - adc_P
pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1
var1 = (self.dig_P9) * pressure * pressure / 2147483648.0
var2 = pressure * (self.dig_P8) / 32768.0
pressure = pressure + (var1 + var2 + (self.dig_P7)) / 16.0
return pressure
def get_temperature_and_pressure(self):
"""Returns pressure in Pa as double. Output value of "6386.2"equals 96386.2 Pa = 963.862 hPa."""
xlsb = self._read_byte(BMP280_TEMP_XLSB_REG)
lsb = self._read_byte(BMP280_TEMP_LSB_REG)
msb = self._read_byte(BMP280_TEMP_MSB_REG)
adc_T = (msb << 12) | (lsb << 4) | (
xlsb >> 4) # temperature registers data
temperature = self.compensate_temperature(
adc_T) # temperature compensate
xlsb = self._read_byte(BMP280_PRESS_XLSB_REG)
lsb = self._read_byte(BMP280_PRESS_LSB_REG)
msb = self._read_byte(BMP280_PRESS_MSB_REG)
adc_P = (msb << 12) | (lsb << 4) | (
xlsb >> 4) # pressure registers data
pressure = self.compensate_pressure(
adc_P) # pressure compensate
return temperature, pressure
if __name__ == '__main__':
import time
print("BMP280 Test Program ...n")
bmp280 = BMP180()
while True:
time.sleep(1)
temperature, pressure = bmp280.get_temperature_and_pressure()
print(' Temperature = %.2f C Pressure = %.3f kPa' %
(temperature, pressure/1000))
示例代码运行结果:
八、结束语
IIC的学习和使用归结起来其实就三点,1:IIC通讯原理(硬件原理和底层编程),2:寄存器(不同的芯片寄存器也有区别,需要了解每个寄存器的作用),3:应用(配置寄存器、读取数据、数据处理)
其中,第一点其实可以不学。为什么这么说呢?我在上一讲有讲过了树莓派和单片机的区别,树莓派想快速入门,可以先忽略底层,从应用层开始。上面的三点内容的第一点的底层编程其实在树莓派的库里面已经做好了,我们只要调用iic的读取和写入的函数,做我们自己应用层的编程即可。
好了,这一讲的内容就这么多了,如果对你有帮助,可以给个收藏,如果想了解更多树莓派的知识可以关注我,后续我会继续更新更多的教程。
教程相关的软件和源码:https://pan.baidu.com/s/1-lVAZyH2s-VTn5qeSnEPhA ,提取码:qwer