TOPに戻る

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

 前回、下記の環境で、I2Cのアクセスまで実験しました。それまでのOSと変わらない使い勝手でした。

環境

  • ハードウェア Raspberry Pi 4(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ボード解説のページ

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

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

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

 LPS22HBのデータシートはこのWebページから

  • 電源電圧  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で割れば、摂氏の値が得られます。

 

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() 連続してデータが読み出せています。

-