みかんのゆるふわ技術ブログ

Raspberry PiやIoT関係のことを書き残していきます

Raspberry PiでI2C bitbang!!~好きなピンでI2Cを使おう

過去の記事でも活用している通り、Raspberry PiはI2Cの機能があります。

f:id:kimura_khs:20200922113215p:plain
画像出典: raspberrypi.org

Raspberry Piの3番ピン GPIO2 (SDA)と、5番ピン GPIO3 (SCL) の2本の線を使ってI2Cセンサーなど外部のデバイスと通信します。

Raspberry PiのI2C

便利なRapsberry Piですが、I2C機能を使っていると、様々な問題に直面することがあります😥

プルアップ抵抗

この2つのピンはRaspberry Pi基板上で1.8kΩ抵抗プルアップされています。 そのため、外部にプルアップ抵抗は不要です。

しかし、例えば以前紹介したBME280センサーのブレイクアウト基板には10kΩのプルアップ抵抗が搭載されています。

f:id:kimura_khs:20200922113125j:plain:w320

↑わかりにくいですが、ピンヘッダの横にある、103と書かれた灰色の長方形の部品が10kΩプルアップ抵抗です。

これをRaspberry PiのI2Cピンに接続すると、1.8kΩと10kΩのプルアップ抵抗が並列に入ります。 さて、合成抵抗はいくらになるでしょう📝

並列の合成抵抗の求め方はみなさん覚えているでしょうか?忘れた方は理科の教科書📚やネットの記事で思い出しましょう😅

chuugakurika.com

計算するとわかりますが、合成抵抗は約1.5kΩとなり、さらにパワーアップ(?)します。元々抵抗が小さいのでそんなに変わりませんが…。

過去記事にある通りこの基板はその状態でも動いたので、趣味の電子工作の範囲では問題ありません。 しかし、プルアップ抵抗の推奨値はデバイスによって違うので、物によっては動かない可能性もあります。

内部のプルアップ抵抗が邪魔になる場合もないとは言えません。

クロックストレッチ機能が使えない

I2Cはマスタークロックを出して、スレーブはそれに合わせて動きます。Raspberry Piがマスターになります。

しかし、時間のかかる処理をするデバイスや、処理能力の低いデバイスなど、スレーブデバイス側の事情でマスターのクロックのタイミングに間に合わないこともあります。

そういう時スレーブはクロック線をLOWに引っ張ります。 マスターはクロックをLOW→HIGHと交互に動かしますが、スレーブがLOWに引っ張っていると、HIGHにしたのにクロック線がLOWのままになることがあります。

マスターはこういう状態を検出し、スレーブがクロック線をLOWに引っ張るのをやめる(HIGHになる)のを待ちます。 これを、クロックストレッチングといいます。

スレーブ側にも都合がありますので、マスターでクロック線の状態を監視して、LOWに引っ張っているスレーブがいたら待ってあげないといけませんね。

しかし、Raspberry PiのI2Cにはこの機能がありません😱

スレーブが待ってほしくてクロックをLOWに引っ張っても、マスターであるRaspberry Piは知らん顔で処理を続けるようです。 ひどいご主人様ですね😢

この動作は以下の記事で詳しく解析されています。

www.advamation.com

(この記事ではバグと表現されていますが、仕様かバグか私はよくわかりません)

先に出たBME280など多くのデバイスは特に問題なく動作しますが、 この機能が必要なデバイスは動かない可能性があります。

こんなマスター(ご主人様)の下で働くのは嫌ですもんね😅

I2C bitbangで悩みを解決!

このような特徴を持つRaspberry PiのI2Cですが、それはRaspberry PiのハードウェアI2C機能を使った場合の話。

I2Cは100kHzや400kHzなどそれほど早くないし、自分でクロックを出せてタイミングにシビアでもないので、 実はCPUのパワーでソフトウェアI2Cを実現することもできます! Linuxのような非リアルタイムOSでも十分実用的です。

このように、ソフトウェアでピンを直接操作して通信を実現するやり方を、昔からBitbang(ビットバング)といいます。

ハードウェアI2C機能を使うと、CPUがI2C専用機能回路ブロックに「このデータをI2Cで送っておいて」とか「I2Cで送られてきたデータを教えて」みたいなことができて楽です。

ソフトウェアI2C(ビットバング)では、CPUがクロックを上げ下げして作り、データを送信したり受信したりということもソフトウェアでやります。

専用の回路を使わないので大変ですね😅

でも素晴らしいことに、LinuxにはI2C bitbangのドライバが付属しています! また、PythonやC言語でI2C bitbangドライバを使わずに直接GPIO操作して実現するライブラリもあります。すでにあるものが使えるのがありがたいですね。

ソフトウェアで操作できるGPIOポートならどれでも使えるので便利です。 好きなだけI2Cバスの数を増やすこともできます。 クロックストレッチ機能もソフトウェアで実装すれば問題ありません。 CPUの負荷は上がりますが…。

前置きが長くなりましたが、このように便利なI2C bitbang機能を早速使ってみましょう。

I2C bitbangを使おう

ここでは、Linux Kernelのドライバを使ってI2C bitbangで通信をします。

ドライバの有効化

まずは、/boot/config.txtを編集してドライバを有効にします。 Device Treeのオーバーレイ機能を利用します。ファイルの最後にでも、次の行を追加します。

dtoverlay=i2c-gpio,bus=11,i2c_gpio_sda=23,i2c_gpio_scl=24

ここで、bus=でI2Cバス番号、i2c_gpio_sda=でSDAのGPIOピン、i2c_gpio_scl=でSCLのGPIOピン番号を指定します。 バス番号はなくても大丈夫です。その場合は自動で番号が割り当てられます。 ここではI2Cバス11、GPIO 23、24を指定しました。

指定するのはGPIOの番号(上の図でいうGPIO XXのXXの部分の番号)です。Raspberry Piのピンヘッダのピン番号ではありません。

デフォルトではI2C、UART、SPIなどは無効になっていますが、有効にしている場合はそれらと被らないGPIOを指定してください。 例えばUARTを有効にしているなら、GPIO 14、GPIO15は使えません。

ファイルを保存したら、Raspberry Piを再起動してください。起動すると指定したピンがI2C bitbang用になっています。

動作確認方法

Linux KernelのI2C bitbangドライバが有効になれば、/dev/i2c-xxというデバイスファイルができます。 次のコマンドで確認できます。

$ ls /dev/i2c*
/dev/i2c-1  /dev/i2c-11

ここでは通常のハードウェアI2Cも有効にしているので、2つのI2Cバスが見えます。 /dev/i2c-1がハードウェアI2C、もう一つがI2C bitbangのソフトウェアI2Cです。バス番号11を指定したので、i2c-11になっています。

さて、新しくできたI2CバスにBME280モジュールをつないでみましょう。一度電源を切って、設定の通り接続します。

RasPiピン番号 RasPi 機能 BME280モジュール 説明
17 3V3 power VIN 電源(3.3V)
20 Ground GND グランド (0V基準電圧)
16 GPIO 23 (SDA用) SDA I2Cデータ
18 GPIO 24 (SCL用) SCL I2Cクロック

これらをジャンプワイヤーで接続すると、次のようになりました。

f:id:kimura_khs:20210622223215j:plain

この状態で再びRaspberry Piの電源を入れ、i2cdetectコマンドでスキャンしてみます。 -yでバス番号を指定します。私の場合は11番でしたので、次のようにします。

$ i2cdetect -y 11
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

このように、BME280のアドレス(0x76)でデバイスが見つかりました。 I2C bitbangでGPIOポートを使って通信できていることがわかります。

Pythonからアクセス

ハードウェアI2C(前回のおさらい)

以前のBME280の記事では、 Adafruit社のライブラリAdafruit_CircuitPython_BME280を使ってPythonからアクセスしました。簡単に言うと、次のようにしました。

$ sudo apt install python3-pip
$ pip3 install adafruit-circuitpython-bme280

で必要ライブラリをインストールして、

import adafruit_bme280
import board, busio
import time

i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)

while True:
    print("\nTemperature: %0.1f C" % bme280.temperature)
    print("Humidity: %0.1f %%" % bme280.relative_humidity)
    print("Pressure: %0.1f hPa" % bme280.pressure)
    time.sleep(1)

というスクリプトファイルを作成して実行、となります。簡単ですね。

このスクリプトでいうi2c = busio.I2C(board.SCL, board.SDA)ではハードウェアI2Cが使われます。

I2C bitbang

親切なことに、外部ライブラリをインストールすることで、ソフトウェアI2C (I2C bitbang)にも対応できるようになっています。 Adafruit_Python_Extended_Busというライブラリをインストールします。

$ pip3 install adafruit-extended-bus

次のようにして好きなI2Cバスを指定できます。

from adafruit_extended_bus import ExtendedI2C
i2c = ExtendedI2C(11)

これで、Pythonから/dev/i2c-11のバスにアクセスできるようになります。

i2c = I2C(1)とすれば、/dev/i2c-1すなわちハードウェアI2Cバスが使えます。

以前のBME280のプログラムからI2Cバスの指定の部分だけ置き換えて、次のようなプログラムを作成します。

from adafruit_extended_bus import ExtendedI2C
import adafruit_bme280
import time

i2c = ExtendedI2C(11)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)

while True:
    print("\nTemperature: %0.1f C" % bme280.temperature)
    print("Humidity: %0.1f %%" % bme280.relative_humidity)
    print("Pressure: %0.1f hPa" % bme280.pressure)
    time.sleep(1)

これをtest.pyとして保存し実行すると、次のようになります。

$ python3 test.py

Temperature: 21.6 C
Humidity: 63.0 %
Pressure: 1002.1 hPa

Temperature: 21.7 C
Humidity: 62.9 %
Pressure: 1002.1 hPa

...

ハードウェアI2Cバスと同じように、温度・湿度・気圧が読めていることがわかります。 とても便利ですね。

注意点としては、ソフトウェア制御なので、CPUの負荷が高い時はI2Cのタイミングがまちまちになったり、 シーケンスの途中で長時間ウェイトが入るなどの可能性があります。 (ドライバなので通常のプロセスよりは優先度が高いですが)

今回はLinuxのドライバを使いましたが、これを使わずPythonでGPIOを直接制御するライブラリもあります。

いろんな使い方ができるRaspberry Piで、いろんな遊び方を見つけましょう!