<ラズパイ 2023年10月更新 bookworm 連載記事>をラズパイ5で再検証した記事です。
前回、下記の環境で、I2Cのアクセスまで実験しました。それまでのOSと変わらない使い勝手でした。
環境
- ハードウェア Raspberry Pi 5(4GBモデル)
- OS Raspberry Pi OS (64ビット)、リリースDecember 5th 2023
- Windows10 22H2にてssh(OpenSSH_9.2p1 Debian-2+deb12u2, OpenSSL 3.0.11 19 Sep 2023)を動作させている
Pythonで気圧センサLPS22HBをアクセスする
ここでは、Pythonを使って、I2Cバスにつながっているセンサのデータ(気圧と温度)を読み取ります。
AdafruitのStemma QT/Qwiicボード
Stemma QT/Qwiic(JST SH 4ピン)コネクタは2か所に装着されていて、どちらにつないでもかまいません。このコネクタを使ってI2Cで制御する場合、特に、ジャンパ線をつなぐなどは不要です。
コネクタは、表と裏のどちらも差し込めそうですが、ピンが内部の上部に並んでいるので、差し込める方向は一意です。ロック機構はないですが、すぐに抜けるということはありません。
気圧センサLPS22のおもなスペック
- 電源電圧 1.7~3.6V
- 気圧測定範囲 260~1260hPa、確度;±1hPa、分解能;±0.1hPa
- 温度測定範囲 -40~+85℃、確度;±1.5℃
- インターフェース I2C(0~400kHz)、SPI(10MHz)
- スレーブ・アドレス 0x5d、裏面のAddrをショートすると0x5c
接続
プログラムではI2Cのアクセスにsmbusをimportするーその1
I2Cバスで利用するライブラリ(モジュール)は、smbusです。
Python3にインストールされているライブラリを表示するpip listからはsmbus2が表示されますが、smbusは出てきません。
LinuxでネイティブにサポートされているI2Cアクセス用デバイス・ドライバにi2c-devがあります。
apt-getコマンドは、パッケージの操作・管理を行うコマンドです。$ apt-cache search smbus
を実行します。
python3-crccheck - implementation of 170+ CRC algorithms for Python 3
python3-smbus - Python 3 bindings for Linux SMBus access through i2c-dev
libdevice-i2c-perl - module to control and read hardware devices with i2c(SMBus)
python3-smbus2 - another pure Python implementation of the python-smbus package
python3-smbus2-doc - doc for another pure Python implementation of the python-smbus
2行目python3-smbusは、「i2c-dev を介した Linux SMBus アクセス用の Python 3 バインディング」と書かれています。Python3バインディングとは、C/C++で書かれたコードをpythonで使えるようにすることを言っているようです。Python3では、smbusライブラリを利用してI2Cデバイスをアクセスすればいいようです。
python3-smbus2の説明は、別の実装と書かれていますが、pureとわざわざ書かれているのが気になります。smbusとsmbus2の使い分けは不明です。
smbusライブラリを使ったプログラムです。
import smbus
import time
i2c = smbus.SMBus(1)
addr = 0x5d
i2c.write_byte_data(addr, 0x10, 0x10)
while 1:
i2c.write_byte_data(addr, 0x11, 0x11)
data = i2c.read_i2c_block_data(addr, 0x28, 3)
press = (data[2]<<16 | data[1] << 8 | data[0] ) / 4096.0
print("\nPress = " + str(int(press)) + "hPa")
data = i2c.read_i2c_block_data(addr, 0x2b, 2)
temp = (data[1] << 8 | data[0] ) / 100.0
print("Temp = " + str(round(temp,1)) + "`C")
time.sleep(2)
python3-smbusのソースは、こちらにありますが、ドキュメントにアクセスできません。
smbus2にライブラリを変更しました。
import smbus2
import time
i2c = smbus2.SMBus(1)
以下同じ
問題なく動きます。smbus2のドキュメントはこちらにあります。
以下、smbus2を利用し、そのドキュメントを参照しながらプログラムを説明します。
プログラムで使われているsmbus2ライブラリの関数
i2c.write_byte_data(addr, 0x10, 0x10)
書き込み関数の引数は左から、スレーブ・アドレスaddrに対し、オフセット0x10に0x10のデータを1バイト書き込みます。
オフセット0x10は、データシートにあるCTRL_REG1 (10h) =Control register 1です。1バイト長あり、bit6、bit5、bit4はODR[2:0]で、デフォルトが0になっており、スリープ・モードです。bit4に'1'(0x10)を書き込むことで、ODR(Output data rate)を1Hzを設定し、スリープから抜け出ます。
ループ(while 1:)の中に入ります。
i2c.write_byte_data(addr, 0x11, 0x11)
書き込み関数の引数は左から、スレーブ・アドレスaddrに対し、オフセット0x11に0x11のデータを1バイト書き込みます。
オフセット0x11は、データシートにあるCTRL_REG2 (11h) =Control register 2です。1バイト長あり、bit4は、IF_ADD_INCでデフォルトが'1'になっており、Register address automatically incremented during a multiple byte access with a serial interfaceです。そのままでいいはずですが、過去のプログラムではbit4に'1'を書き込んでいたので、同じようにしました。bit0はONE_SHOTで、デフォルトは'0'なので、'1'を書き込んで有効にします。結果、データは0x11です。
data = i2c.read_i2c_block_data(addr, 0x28, 3)
read_i2c_block_data()関数を用いて24ビットある気圧データを読み込みます。read_byte_data()関数で、1バイトずつ、三つのデータを読むこともできますが、read_i2c_block_data()は、指定した複数バイトを一度に読み込めます。
関数の引数は左から、スレーブ・アドレスaddrに対し、オフセット0x28から3バイト読み込み、変数dataに入れます。データシートによれば、0x28はPRESS_OUT_XLレジスタで、気圧データの最下位バイトです。ちなみに、0x29はPRESS_OUT_Lの真ん中のバイト、0x2aはPRESS_OUT_Hの最上位バイトです。
data[2]にPRESS_OUT_Hが、data[1]にPRESS_OUT_Lが、data[0]にPRESS_OUT_XLが収納されます。
press = (data[2]<<16 | data[1] << 8 | data[0] ) / 4096.0
最下位バイトPRESS_OUT_Hを左に16ビット・シフトし、真ん中のバイトPRESS_OUT_Lを左に8ビット・シフトし、および最下位バイトPRESS_OUT_XLの三つのデータをor(論理和)すると、2の補数形式の24ビット・データが得られます。
データシートに書かれた計算式により4096で割れば、hPaの値が得られます。
data = i2c.read_i2c_block_data(addr, 0x2b, 2)
同様に、温度データをオフセット0x2bから読み出します。2バイトです。
temp = (data[1] << 8 | data[0] ) / 100.0
データシートに書かれた計算式により16ビット・データを100で割れば、摂氏の値が得られます。
得られる温度は、このセンサ自体の温度です。周辺温度より3度ほど高めになります。
:
1バイトごとにデータを読むプログラムーその2
<その1>で用いたread_i2c_block_data()関数は、指定したバイト数分データを読み込みます。読み込んだデータはリストになっているので、処理しやすいです。
1バイトごとに読むには、read_byte_data(addr, オフセット)を使います。気圧、温度ともに書き換えました。
import smbus2
import time
i2c = smbus2.SMBus(1)
addr = 0x5d
i2c.write_byte_data(addr, 0x10, 0x10)
while 1:
i2c.write_byte_data(addr, 0x11, 0x11)
pdata_XL = i2c.read_byte_data(addr, 0x28)
pdata_L = i2c.read_byte_data(addr, 0x29)
pdata_H = i2c.read_byte_data(addr, 0x2a)
press = (pdata_H<<16 | pdata_L << 8 | pdata_XL ) / 4096.0
print("\nPress = " + str(int(press)) + "hPa")
tdata_L = i2c.read_byte_data(addr, 0x2b)
tdata_H = i2c.read_byte_data(addr, 0x2c)
temp = (tdata_H << 8 | tdata_L ) / 100.0
print("Temp = " + str(round(temp,1)) + "`C")
time.sleep(2)
実行結果は変わりません。
2バイトごとにデータを読むプログラムーその3
温度データは2バイトです。read_word_data()を使うと一度で2バイトのデータを読み込めます。気圧は、<その2>のままです。
import smbus2
import time
i2c = smbus2.SMBus(1)
addr = 0x5d
i2c.write_byte_data(addr, 0x10, 0x10)
while 1:
i2c.write_byte_data(addr, 0x11, 0x11)
pdata_XL = i2c.read_byte_data(addr, 0x28)
pdata_L = i2c.read_byte_data(addr, 0x29)
pdata_H = i2c.read_byte_data(addr, 0x2a)
press = (pdata_H<<16 | pdata_L << 8 | pdata_XL ) / 4096.0
print("\nPress = " + str(int(press)) + "hPa")
tdata = i2c.read_word_data(addr, 0x2b)
temp = tdata / 100.0
print("Temp = " + str(round(temp,1)) + "`C")
time.sleep(2)
それぞれの波形を見る
<その1>read_i2c_block_data() 気圧は3バイト、温度は2バイトと連続してデータが読み出せています。
<その2>read_byte_data() 1バイトごとデータが読み出されています。
<その3>温度データをread_word_data() 連続してデータが読み出せています。