Raspberry Piでマルチプレクサで複数の温度湿度計(SHT31)を扱う

本記事では Raspberry Pi Zero W と マルチプレクサ(TCA9548A) で 湿度・温度計(SHT31)を複数利用する方法 を紹介します。

また、i2cdetect / i2cset / i2cget 等のi2c-toolsでの方法で確認しながら、Pythonで交互に値を取得する方法 まで分かりやすくまとめて説明します。

まず初めに、構成は以下の通りです。


✅ TCA9548A の基本

TCA9548A は I2C マルチプレクサで、
Raspberry Pi の I2C バス(SDA/SCL)を最大 8 本のサブバス(SC0/SD0〜SC7/SD7)に切り替えるための IC です。

切り替えは以下の制御バイトを TCA9548A のアドレス(通常 0x70)へ書き込むだけです:

チャネル番号ビット書き込み値(hex)
0 (SC0/SD0)bit00x01
1 (SC1/SD1)bit10x02
2 (SC2/SD2)bit20x04

✅ 配線状況

  • TCA9548AのSC0/SD0(回路図の 4-SCL/4-SDA) に SHT31 を 1個接続
  • TCA9548AのSC2/SD2(回路図の 1-SCL/1-SDA) に SHT31 を 2個接続
  • Pi Zero WH の I2C (PIN3:SDA, PIN5:SCL)(/dev/i2c-1)→ TCA9548A の SDA/SCL

✅ 回路図

SHTですがFritzingの素子ではADRが無かったので図示できていませんが同じバスに2つ繋ぐ場合はADRをHiにした0x45と、Lo(解放 or GND)にした0x44とでアドレスをかぶらないようにしないといけないのでご注意下さい

✅ コマンド一覧

やりたいことコマンド
SC0/SD0 を選択i2cset -y 1 0x70 0x01
SC2/SD2 を選択i2cset -y 1 0x70 0x04
センサー検出i2cdetect -y 1
測定開始i2cset -y 1 0x44 0x24 0x00
測定値取得i2cget -y 1 0x44 0x00 w

✅ マルチプレクサの切り替えについて

SC0/SD0を選択させる際にはマルチプレクサのADRである0x70に0x01をセットします。SC2/SD2を選択させるには0x70に0x04をセットします。

これは、マルチプレクサの端子をSC7/SD7~SC0/SD0の8ビットに見立てて、以下の2進数を16進数で表現したものです。

Sx7Sx6Sx5Sx4Sx3Sx2Sx1Sx016進数
000000010x01
000000100x02
000001000x04
000010000x08
000100000x10
001000000x20
010000000x40
100000000x80

SC0/SD0 のデバイスを検出する方法(i2cdetect)

1. i2cdetectでTCA9548A の アドレスを確認する

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

1. TCA9548A の 0 番チャネルを有効にする

user@raspberrypi:~ $ i2cset -y 1 0x70 0x01

2. SC0/SD0 のバスをスキャン

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

結果: SHT31 が 0x44 のアドレスで1つ見えている


SC2/SD2 のデバイスを検出する方法

1. TCA9548A の 2 番チャネルを有効にする

user@raspberrypi:~ $ i2cset -y 1 0x70 0x04

2. スキャン

user@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 45 -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: 70 -- -- -- -- -- -- --

結果:SHT31が 0x44 と 0x45 のアドレス2つで見えている


測定値を取得する方法(i2cset / i2cget)

SHT31 の測定コマンドは:

  • 単発測定(High Repeatability)
    コマンド:0x24 0x00

例:SC0/SD0 → 0x44 のセンサーから読み取る

# チャネル0を有効化
i2cset -y 1 0x70 0x01

# 測定開始
i2cset -y 1 0x44 0x24 0x00

# 15ms程度待つ
sleep 0.05

# 6バイト読み取る
i2cget -y 1 0x44 0x00 w

チャネルを交互に切り替えながら読み取る方法(コマンドのみ)

# --- SC0/SD0 ---
i2cset -y 1 0x70 0x01        # channel 0
i2cset -y 1 0x44 0x24 0x00   # SHT31 measure
sleep 0.05
i2cget -y 1 0x44 0x00 w

# --- SC2/SD2 ---
i2cset -y 1 0x70 0x04        # channel 2
i2cset -y 1 0x44 0x24 0x00
sleep 0.05
i2cget -y 1 0x44 0x00 w

Pythonで複数チャネルを順番に読み取るサンプル

サンプルコード
BUS = 1
TCA_ADDR = 0x70

# すべてのSHT31センサーの定義: (チャネル番号, I2Cアドレス, センサー名)
SENSORS = [
    (0, 0x44, "SHT31-Ch0"),      # チャネル 0 の SHT31 (アドレス 0x44)
    (2, 0x44, "SHT31-Ch2-A"),    # チャネル 2 の SHT31 (アドレス 0x44)
    (2, 0x45, "SHT31-Ch2-B"),    # チャネル 2 の SHT31 (アドレス 0x45)
]

def select_channel(bus, ch):
    """TCA9548AでI2Cチャネルを選択する"""
    # TCA9548Aのアドレスに、選択したいチャネルビット (1 << ch) を書き込む
    bus.write_byte(TCA_ADDR, 1 << ch)

def crc8(data):
    """SHT31のCRC8チェックサムを計算する"""
    # SHT CRC8 (polynomial 0x31)
    crc = 0xFF
    for b in data:
        crc ^= b
        for _ in range(8):
            if crc & 0x80:
                crc = ((crc << 1) & 0xFF) ^ 0x31
            else:
                crc = (crc << 1) & 0xFF
    return crc

def read_sht31(bus, sht_addr, sensor_name):
    """指定されたアドレスのSHT31からデータを読み取り、CRCチェックと変換を行う"""

    print(f"\n--- センサー: {sensor_name} (Addr: 0x{sht_addr:02X}) ---")

    try:
        # 1. 測定コマンド送信: High Repeatability, Single Shot (0x2400)
        # i2c_msg.writeの最初の引数には、ターゲットとするSHT31の単一アドレスを指定
        write = i2c_msg.write(sht_addr, [0x24, 0x00])
        bus.i2c_rdwr(write)
        time.sleep(0.05) # 測定完了を待つ (High Repeatabilityで約15ms必要)

        # 2. 6バイト読み取り (Temp[2], CRC[1], Hum[2], CRC[1])
        read = i2c_msg.read(sht_addr, 6)
        bus.i2c_rdwr(read)
        data = list(read)

        # 3. データ抽出
        temp_raw = (data[0] << 8) | data[1]
        t_crc_rx = data[2]
        hum_raw  = (data[3] << 8) | data[4]
        h_crc_rx = data[5]

        # 4. CRC チェック
        t_crc_calc = crc8(data[0:2])
        h_crc_calc = crc8(data[3:5])

        t_crc_ok = (t_crc_calc == t_crc_rx)
        h_crc_ok = (h_crc_calc == h_crc_rx)

        # 5. 結果出力(デバッグ情報)
        print(f"  Temp CRC: Calc=0x{t_crc_calc:02X}, Rx=0x{t_crc_rx:02X}, OK={t_crc_ok}")
        print(f"  Hum CRC: Calc=0x{h_crc_calc:02X}, Rx=0x{h_crc_rx:02X}, OK={h_crc_ok}")

        if not (t_crc_ok and h_crc_ok):
             print("  !!! 警告: CRCチェックに失敗しました。データが信頼できない可能性があります。 !!!")

        # 6. 測定値変換
        temperature = -45 + (175.0 * temp_raw / 65535.0)
        humidity = 100.0 * hum_raw / 65535.0

        print("  Temperature: {:.2f} °C  Humidity: {:.2f} %".format(temperature, humidity))

    except Exception as e:
        print(f"  !!! エラー: SHT31 (0x{sht_addr:02X}) の読み取り中に問題が発生しました: {e} !!!")


# --- メイン処理 ---
with SMBus(BUS) as bus:

    current_channel = -1

    for channel, address, name in SENSORS:

        # TCA9548Aチャネル切り替えが必要な場合のみ実行
        if channel != current_channel:
            print(f"\n--- TCA9548A チャネル {channel} に切り替え中 (0x{1 << channel:02X}) ---")
            select_channel(bus, channel)
            current_channel = channel
            time.sleep(0.01) # チャネル切り替え後の安定化を待つ (オプション)

        # SHT31の読み取りを実行
        read_sht31(bus, address, name)

    # 終了時にマルチプレクサをリセット(全チャネルオフ)
    bus.write_byte(TCA_ADDR, 0x00)
    print("\n--- TCA9548A: 全チャネルをオフに設定して終了 ---")

print("\nプログラムを終了します。")

出力結果
--- TCA9548A チャネル 0 に切り替え中 (0x01) ---

--- センサー: SHT31-Ch0 (Addr: 0x44) ---
  Temp CRC: Calc=0xCD, Rx=0xCD, OK=True
  Hum CRC: Calc=0xA9, Rx=0xA9, OK=True
  Temperature: 26.13 °C  Humidity: 51.05 %

--- TCA9548A チャネル 2 に切り替え中 (0x04) ---

--- センサー: SHT31-Ch2-A (Addr: 0x44) ---
  Temp CRC: Calc=0xD8, Rx=0xD8, OK=True
  Hum CRC: Calc=0x26, Rx=0x26, OK=True
  Temperature: 27.32 °C  Humidity: 48.72 %

--- センサー: SHT31-Ch2-B (Addr: 0x45) ---
  Temp CRC: Calc=0x40, Rx=0x40, OK=True
  Hum CRC: Calc=0x5F, Rx=0x5F, OK=True
  Temperature: 25.96 °C  Humidity: 51.69 %

--- TCA9548A: 全チャネルをオフに設定して終了 ---
タイトルとURLをコピーしました