TOPに戻る

センサ・シリーズ 温湿度①SHT31 その1 測定準備

 湿度は相対湿度(Relative Humidity: RH)のことをいい、空気が水蒸気の形で包含できる水分量(飽和水蒸気量)は、温度の関数になっています。したがって、湿度センサは温度も同時に測定します。

 使用環境
  • Raspberry Pi Raspberry Pi 4 Model B 2MB
  • OS Raspberry Pi OS(32ビット)5.4.72
  • Python3 3.7.3
  • smbusライブラリ 4.1-1
  • i2ctoolsパッケージ 4.1
  • エディタ Mu

使用するセンサSHT31のおもなスペック

  • 電源電圧;2.4~5.5V
  • インターフェース;I2C(最大データ転送速度1MHz)
  • 温度;-40~+125℃(確度±0.2℃)、分解能0.015℃
  • 相対湿度;0~100%(0~90℃時)、分解能0.01%
  • データ・ビット長;16ビット
  • スレーブ・アドレス;ADDRピンをプルアップで0x45、GNDへつなぐと0x44

インターフェースはI2C

 I2Cバスでは、データSDAとクロックSCLの二つの信号で、ラズパイ(マスタ)とセンサ(スレーブ)がデータのやり取りをします。バスには、複数のスレーブがつながっていることが前提です。したがって、スレーブを認識するためにスレーブ・アドレスがあります。工場出荷時に固定されているセンサもありますが、多くは、わずかに変更できます。

 スレーブ・アドレスは7ビットです。10ビットも規格上ありますが、通常使われません。データのやり取りが8ビット単位なのに7ビットである理由は、LSBがRead/Writeの判別に使われるからです。マスタが何かのコマンドを発するとき、最初はスレーブ・アドレスをバス上に出します。LSBが'0'であれば書き込み、'1'であれば読み出しであることをスレーブに対して知らせます。

 すべてのスレーブ・デバイスはバスを常に監視していて、スレーブ・アドレスが自分であれば、8ビットを受け取った直後にACK(Acknowledge)を'1'で返します。これらのデータはSDA信号です。データがある限りクロックSCLも存在します。

 ただし、次の三つの状態では、特別な'1'/'0'の組み合わせがおこります。

  • これから一連のやり取りが始めるという「スタート・コンディション」
  • これでやり取りは終了するという「ストップ・コンディション」
  • ちょっと特別な状態で、ストップ・コンディションを作らずに通信を続ける「リピーテッド・スタート・コンディション」

 特別な状態がもう一つあります。マスタがスレーブにデータをよこせといってきたので、センサ内部でデータを用意しようとしたけど測定自体に時間がかかるのでちょっと待っていてというのを「クロック・ストレッチ」機能で対応します。SHT31ではこのモードが使われることがあります。

 このやり取りを汎用的な書式で表すと次の形になります。

 7ビットのスレーブ・アドレス+LSB(Readは'1'、Writeは'0')、8ビット値、8ビット値、...

 pythonのプログラムでは、I2Cのやり取りにはsmbusライブラリを使うのが一般的です。現在のOSには、最初から入っています。smbusライブラリの書式では、上記の書式を記述しやすいような関数名にしています。

READでよく使う関数

 1バイトだけ;read_byte_data(アドレス, コマンド)

 2バイト以上;read_i2c_block_data(アドレス, コマンド, バイト数)

Writeでよく使う関数

 1バイトだけ;write_byte_data(アドレス,  コマンド、1バイトのデータ)

 2バイト以上;write_i2c_block_data(アドレス, コマンド, 複数バイトのデータのlist) 

市販品

 I2Cバスは内部バスなので、マスタとスレーブの距離は長くても10~20cmで使うのが前提です。ブレークアウト・ボードはGPIOピンからの配線に適しています。下記のボードは秋月電子通商で入手しました。

 長いケーブルがつながった製品もあります。アマゾンで入手しました。

 長いケーブルをつなぐと、外来ノイズの影響を受けやすくなります。キャパシタンスが増加してバスをドライブしきれなくなって波形がなまることもあるでしょう。そのために?、SHT31は、2バイトのデータが健全であるかを判断するためにCRCデータが一緒に送られてきます。
 CRCは8ビットのダラス・マキシム(x8 + x5 + x4 + 1)という形式です。

接続

 ケーブルの先はオスだったので、切り取り、メスのジャンパ・ケーブルを取り付けました。

SHT31ケーブル ラズパイ
赤色 3.3V 1番ピン
黒色 GND 9番ピン
青色 SDA 3番ピン
黄色 SCL 5番ピン

スレーブ・アドレスを確認

 ブレークアウト・ボードならば、ADDRピンをVddもしくはGNDにつなぐことでスレーブ・アドレスを決められる製品があります。そうでない製品もあるので、上記のようにラズパイとつなぎ、ターミナルで確認します。

 smbusライブラリもi2ctoolsパッケージに入っています。i2ctoolsのi2cdetectコマンドでバス1を探ります。0x44に見つかりました。

プログラムの最初に設定する事柄

 センサによっては、最初からデータの入ったレジスタを読むだけという使いやすいデバイスもあります。SHT31では、電源が入ったら、パワーオン・リセットがかかり、待機状態になります。待機状態では、測定コマンドを待ちます。

 測定コマンドは、単発測定コマンド周期的連続測定コマンドの2種類があります。単発測定コマンドでは、消費電力を下げる用途に向いています。どちらのコマンドも、

  • 繰返し精度設定レベル:低 0.25%RH、0.24℃
  • 繰返し精度設定レベル:中 0.15%RH、0.12℃
  • 繰返し精度設定レベル:高 0.1%RH、0.06℃

の選択ができます。周期的連続測定コマンドはさらに、測定頻度を0.5、1、2、4 および10(回/秒)から選べます。頻度が高いと発熱するとデータシートに書かれています。

 単発測定コマンドでは、クロック・ストレッチの有無が選べます。

 設定できるコードをまとめます。

◆単発測定コマンド

設定条件 コマンド・コード
繰返し精度
レベル
クロック・ストレッチ
設定
上位バイト 下位バイト
有効 0x2c 0x06
0x0d
0x10
無効 0x24 0x00
0x08
0x16

◆周期的連続測定コマンド

設定条件 コマンド・コード
繰返し精度
レベル
測定頻度[mps]
(測定回数/秒)
上位バイト 下位バイト
0.5 0x20 0x32
0x24
0x2f
1 0x21 0x30
0x26
0x2d
2 0x22 0x36
0x20
0x2b
4 0x23 0x34
0x22
0x29
10 0x27 0x37
0x21
0x2a

 念のため、最初にソフト・リセット(0x30、0xA2)を送ります。データシートには、リセットの完了は最大1msかかるとなっているので、その時間待ってから次のコマンドを送ります。

 動作確認のためにステータス・レジスタを読み出します。レジスタの内容です。

ビット 内容 デフォルト
15 発信中のアラートの有無
'0':発信中アラートなし
'1':ひとつ以上の発信中アラートあり
1
14 リザーブド 0
13 ヒータの動作状態
'0':ヒータ停止中
'1':ヒータ稼働中
0
12  リザーブド 0
11 温度のアラート発信状態
'0':アラート発信なし
'1':アラート発信中
0
10 相対湿度のアラート発信状態
'0':アラート発信なし
'1':アラート発信中
0
9~5  リザーブド 00000
4 リセット履歴
'0':リセット履歴なし(ステータス・レジスタのデータ消去コマンドをその時点で最後に受信した以降)
'1':リセット履歴あり(ハード・リセット、ソフト・リセット・コマンドまたは電源電圧低下によるリセット)
1
3、2  リザーブド 00
1 その時点で最後に受取ったコマンドの実行状態
'0':当該コマンドを正常に実行
'1':当該コマンドは未実行(コマンド自体が無効またはコマンドのチェック・サムが異常なため)
0
0 その時点で最後に受け取った書き込み
データのチェック・サム照合結果
'0':当該データのチェック・サムは正常
'1':当該データのチェック・サムは異常
0

メインのプログラム

 周期的連続測定コマンドを送ります。測定頻度は4回/秒、繰返し精度設定レベル:高の0x23、0x34を書き込みます。過去、エラーなくデータが取得できた実績があります。

 このコマンドを送ると、読み出しの要求をしなくとも連続でデータを送ってきます。そしてこのコマンドは任意に変更できるので、次のコマンドは測定完了時間分待ってから送ります。データシートから最大15msです。

 周期的連続測定モードでの測定値の読み出しコマンドは0xe0、0x00です。最初に、このコマンドを送らずにデータを読み取ります。

 smbusライブラリには2バイトのコマンドを書き込む関数はありません。「アドレス、コマンド、データ」を書き込む関数write_byte_data()を流用します。

 読み取りは「アドレス、コマンド、読み取り数」です。しかし、このときコマンド1バイトは何を送ればいいのか不明です。下記のプログラムでは0xffを送りましたが、0x00でも結果は変わりませんでした。

 読み出した16ビットのデータは、測定データを、温度(摂氏)を求める換算式は次のとおりです。rawT は測定値です。

  temp = -45 + 175 * rawT / 2^16 - 1

 相対湿度の換算式は次のとおりです。rawR は測定値です。

  RH = 100 * rawR / 2^16 - 1

import smbus
import time
i2c = smbus.SMBus(1)
addr = 0x44

#main
i2c.write_byte_data(addr, 0x30, 0xa2)
#i2c.write_byte_data(addr, 0x30, 0x41)
time.sleep(0.1)

i2c.write_byte_data(addr, 0xf3, 0x2d)
statusMsb,statusLsb,statuscrc = i2c.read_i2c_block_data(addr,0xff,3)
print(' status= {0:0>8b}  {0:0>8b} {0:0>8b}'.format(statusMsb, statusLsb, statuscrc) )

i2c.write_byte_data(addr, 0x23, 0x34)
time.sleep(0.015)
while 1:
    #i2c.write_byte_data(addr,0xe0,0x0)
    dataTmsb, dataTlsb, dataTcrc, dataHmsb, dataHlsb, dataTcrc = i2c.read_i2c_block_data(addr,0xff,6)
    rawT = dataTmsb << 8 | dataTlsb
    rawR = dataHmsb << 8 | dataHlsb
    print (' tempData= {0:0>16b} 0x{1:x}  HumidityData= {2:0>16b} 0x{3:x}'.format(rawT,rawT,rawR,rawR) )
    temp = -45 + rawT * 175 / 65535
    RH = 100 * rawR / 65535
    print(' temp= {0:.1f} `C,  humi= {1:.1f} RH%'.format(temp, RH))
    time.sleep(3)
    print ("---")

 実行結果です。ステータスはうまく読み出せていないようですが、温度と湿度は正常に読めているようです。ソフト・リセットは何度も繰り返すとI/Oエラーになりました。

連載 ラズパイ センサ・シリーズ

(1) 温度①確度±0.1℃ TMP117 その1 温度の読み出し

(2) 温度①確度±0.1℃ TMP117 その2 アラートの設定

(3) 温度①確度±0.1℃ TMP117 その3 アラートでLED点灯

(4) 温度①確度±0.1℃ TMP117 その4 アラートでリレー駆動

(5) 温度②温度調節器 その1 Modbusの設定

(6) 温度②温度調節器 その2 Modbusで読み書き

(7) 温湿度①SHT31 その1 測定準備

(8) 温湿度①SHT31 その2 測定と波形

(9) 温湿度①SHT31 その3 CRC

(10) 温湿度①SHT31 その4 BLE