ラズパイ5 2023年10月更新 bookworm ② pythonでI2Cバスをアクセス

[＜ラズパイ 2023年10月更新 bookworm　連載記事＞](https://www.denshi.club/parts/2024/01/202310-bookworm-2-python.html)をラズパイ5で再検証した記事です。

　[前回](https://qiita.com/jamjam/items/1f1ed357e2e1f8847605)、下記の環境で、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ボード

　 [LPS22ボード解説のページ](https://learn.adafruit.com/adafruit-lps25-pressure-sensor)

  ![LPS22a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/757d06e9-7428-9774-e532-65b45a596f78.png)


　Stemma QT/Qwiic（JST SH 4ピン）コネクタは2か所に装着されていて、どちらにつないでもかまいません。このコネクタを使ってI2Cで制御する場合、特に、ジャンパ線をつなぐなどは不要です。

　コネクタは、表と裏のどちらも差し込めそうですが、ピンが内部の上部に並んでいるので、差し込める方向は一意です。ロック機構はないですが、すぐに抜けるということはありません。

### 気圧センサLPS22のおもなスペック

　[LPS22HBのデータシートはこのWebページから](https://www.st.com/ja/mems-and-sensors/lps22hb.html#documentation)

- 電源電圧　 1.7～3.6V
- 気圧測定範囲　260～1260hPa、確度；±1hPa、分解能；±0.1hPa
- 温度測定範囲　-40～+85℃、確度；±1.5℃
- インターフェース　I2C（0～400kHz）、SPI（10MHz）
- スレーブ・アドレス　0x5d、裏面のAddrをショートすると0x5c

### 接続

![lps22hb-rsp-100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/37937baf-3488-b4e7-b45c-72fab0bb3e43.png)


### プログラムではI2Cのアクセスにsmbusをimportするーその1

　I2Cバスで利用するライブラリ（モジュール）は、smbusです。

　Python3にインストールされているライブラリを表示するpip listからはsmbus2が表示されますが、smbusは出てきません。

　LinuxでネイティブにサポートされているI2Cアクセス用デバイス・ドライバにi2c-devがあります。

　apt-getコマンドは、パッケージの操作・管理を行うコマンドです。`$ apt-cache search smbus` を実行します。

:::note info
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ライブラリを使ったプログラムです。

```python
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のソースは、こちら](https://github.com/Gadgetoid/py-smbus)にありますが、ドキュメントにアクセスできません。

　smbus2にライブラリを変更しました。


 ```python
 import smbus2
import time

i2c =  smbus2.SMBus(1)

以下同じ
```

　問題なく動きます。[smbus2のドキュメントはこちら](https://pypi.org/project/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（論理和）すると、２の補数形式の24ビット・データが得られます。
　データシートに書かれた計算式により4096で割れば、hPaの値が得られます。

`data = i2c.read_i2c_block_data(addr, 0x2b, 2)`

　同様に、温度データをオフセット0x2bから読み出します。2バイトです。

`temp = (data[1] << 8 | data[0] ) / 100.0`

　データシートに書かれた計算式により16ビット・データを100で割れば、摂氏の値が得られます。

 :::note alert
 得られる温度は、このセンサ自体の温度です。周辺温度より3度ほど高めになります。
 
 :::

　
### 1バイトごとにデータを読むプログラムーその2

　＜その１＞で用いたread_i2c_block_data()関数は、指定したバイト数分データを読み込みます。読み込んだデータはリストになっているので、処理しやすいです。

　1バイトごとに読むには、read_byte_data(addr, オフセット)を使います。気圧、温度ともに書き換えました。


```python
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＞のままです。

 ```python
 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()　気圧は３バイト、温度は２バイトと連続してデータが読み出せています。

![lps22hb-rsp-103a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/973212fa-f623-5b43-e588-923800452d3e.png)


＜**その2**＞read_byte_data()　1バイトごとデータが読み出されています。


![lps22hb-rsp-102a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/f99ac23f-57f6-60f0-2c10-af51f1fc0ff6.png)



＜**その3**＞温度データをread_word_data()　連続してデータが読み出せています。

![lps22hb-rsp-101a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/e9cd45e6-63b0-11dc-e6fa-43a23fee612e.png)