一、前言
上一講介紹了串口的使用,那這一講就介紹一下IIC,IIC由時鐘線(SCL)和數(shù)據(jù)線(SDA)組成,時鐘線提供了通訊的時序,只能由主機發(fā)給從機,而數(shù)據(jù)線和單總線類似,數(shù)據(jù)傳輸是雙向的。具體的原理就不多說了,感興趣的同學可以自行查閱相關(guān)資料,也可以看我之前寫的那兩篇光照傳感器的博客。
BH1750光照傳感器超詳細攻略(從原理到代碼講解)
OPT3001光照傳感器驅(qū)動教學
這一講主要以BMP280為例,其實在傳感器的選擇上面我糾結(jié)了一會。因為我現(xiàn)在手上有的傳感器都在一塊擴展板上面,而用IIC的傳感器就兩個,另外一個比較特殊,相比正常IIC缺少了一些東西,不適合做例子,就只能選BMP280,但是BMP280的寄存器多了點,數(shù)據(jù)處理也較復(fù)雜,其實也不適合做零基礎(chǔ)入門教程。但手上暫時也沒用其他傳感器了,就先將就用吧,我盡量把編程部分講詳細些。
二、硬件連接
BMP280是一款氣壓檢測芯片,同時也可以測量溫度,通訊方式有兩種:IIC和SPI,選擇不同的方式接線不一樣,我這里用的是擴展板,硬件已經(jīng)連接好了,連接的硬件接口是IIC1。具體硬件的介紹就不多說了,感興趣的同學可以去看下數(shù)據(jù)手冊。
注:IIC接口一定要接對,并且記住IIC的編號,樹莓派3B+的IIC接口有兩組,IIC0和IIC1,我這里用的是IIC1。
BMP280引腳定義如下:
引腳我就不一一介紹了,我只講一下SDO這個引腳,用IIC方式通訊時,該引腳決定了IIC的器件地址(直接影響代碼),原文如下:
翻譯一下,意思就是SDO拉低的話器件地址是1110110(0x76),拉高的話器件地址是1110111(0x77)。
三、寄存器介紹
BMP280寄存器其實不多,不過對于初學者來說可能會有點懵,不要慌,且聽我慢慢道來。
BMP280內(nèi)存分布如下:
注:因為上圖的地址是反過來寫的,那我的順序也反一下。
寄存器地址 | 說明 |
---|---|
0xA1-0x88 | 校準數(shù)據(jù) |
0xE0 | 用來重啟傳感器的 |
0xF3 | 指示傳感器當前狀態(tài) |
0xF4 | 設(shè)置數(shù)據(jù)采集選項 |
0xF5 | 設(shè)置基本配置參數(shù) |
0xF7-0xF9 | 氣壓原始數(shù)據(jù) |
0xFA-0xFC | 溫度原始數(shù)據(jù) |
每個寄存器每個位代表的意義我就不具體解釋,自行查閱數(shù)據(jù)手冊。
四、編程原理介紹
編程的步驟大致如下:
1)讀取ID寄存器,判斷讀取到的值和傳感器實際的值是否一致(這一步可以確認傳感器和樹莓派通訊是否正常)
2)讀取校準數(shù)據(jù)
3)設(shè)置數(shù)據(jù)采集選項
4)設(shè)置基本配置參數(shù)
5)分別讀取氣壓和溫度的原始數(shù)據(jù)(三個寄存器的值合并拼接到一起才是完整的數(shù)據(jù))
6)把原始數(shù)據(jù)和校準數(shù)據(jù)代入公式計算出最終的氣壓和溫度
五、使能樹莓派IIC接口
方法1:通過配置命令
sudo raspi-config // 打開配置
sudo reboot // 重啟,如果之前沒有使能,配置完之后要重啟才能生效
方法2:在可視化界面的設(shè)置里面修改
如果之前沒有使能,配置完之后要重啟才能生效
六、IIC通訊測試
我們可以使用i2c-tool工具來查詢IIC設(shè)備是否正常連接,我這個系統(tǒng)鏡像是自帶i2c-tool,如果沒有下載,可以通過下面的命令下載。
sudo apt-get install i2c-tools
用i2c-tool查詢i2c設(shè)備
i2cdetect -y 1 // -y:取消用戶交互過程,直接執(zhí)行指令; 1:I2C的總線編號為1(根據(jù)自己的接口選擇)
注:我這里有多個IIC設(shè)備,器件地址為76的這個才是BMP280(BMP280的地址只可能是76或者77,具體取決于SD0引腳的電平,我前面有講過)。
七、編寫代碼并運行
IIC的通信部分主要是調(diào)用了smbus這個庫,關(guān)于這個庫的相關(guān)函數(shù)和解釋,可以參考下面這個鏈接。
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(根據(jù)自己的硬件接口選擇對應(yīng)的編號)
# 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))
示例代碼運行結(jié)果:
八、結(jié)束語
IIC的學習和使用歸結(jié)起來其實就三點,1:IIC通訊原理(硬件原理和底層編程),2:寄存器(不同的芯片寄存器也有區(qū)別,需要了解每個寄存器的作用),3:應(yīng)用(配置寄存器、讀取數(shù)據(jù)、數(shù)據(jù)處理)
其中,第一點其實可以不學。為什么這么說呢?我在上一講有講過了樹莓派和單片機的區(qū)別,樹莓派想快速入門,可以先忽略底層,從應(yīng)用層開始。上面的三點內(nèi)容的第一點的底層編程其實在樹莓派的庫里面已經(jīng)做好了,我們只要調(diào)用iic的讀取和寫入的函數(shù),做我們自己應(yīng)用層的編程即可。
好了,這一講的內(nèi)容就這么多了,如果對你有幫助,可以給個收藏,如果想了解更多樹莓派的知識可以關(guān)注我,后續(xù)我會繼續(xù)更新更多的教程。
教程相關(guān)的軟件和源碼:https://pan.baidu.com/s/1-lVAZyH2s-VTn5qeSnEPhA ,提取碼:qwer