公開日:2023/06/10
title.png

25th Season


#25-10 I2C温度・湿度センサ化328duino(ii)

 前回から引き続き、328duinoを使って、DHT11と言う温度・湿度センサの通信インターフェイスをI2Cに変換して行きます。ソフトウェア設計編かつ、完結編です。

I2Cレジスタ仕様

 ここからはI2Cスレーブモジュールとしての仕様を検討して行きます。以下に、I2C側からアクセス可能なI/Oレジスタの目標仕様を示します。

  I2C通信I/Oレジスタコマンド仕様:

 I2C側からのデータ読み出し要求と、DHT11からのデータ読み出し処理は、互いに非同期に実行されるため、16ビットレジスタの読み出しには制限があります。データレジスタの読み出し中にデータが更新されない様にしなくてはならないので、レジスタの読み出しはブロックリードを使って、アトミックに実行される必要があります。
 以下にブロックリードコマンドの目標仕様を示します。

  ブロックリードコマンド仕様:

 上述のコマンド仕様に存在しないコマンドは、すべて予約(リザーブ)領域であり、アクセスは禁止です。

※なお、温度レジスタTMPから負数が読み出せるのは、AM2302を使った場合に限られます。DHT11でも、負の温度が読み出せる場合がありそうなのですが、データシートの記述が不明確で、負数の符号化形式が判らなかったためです。

I2Cアクセスプロトコル

 前節のI/Oレジスタ仕様に於いて、CMD=0x2?番地台は、ワードリードだけが許可され、0x6?番地台は、ロングワードリードだけが許可されます。
 アクセスプロトコルは、SMBusバイトリード/ライトをベースに、ブロックリードに対応した、以下の仕様とします。

 エンディアンはビッグなので、SMBusプロトコルとは異なります。ただし、エンディアンさえ合わせれば、SMBusプロトコルのワードリードで読み出すことが可能な仕様となっています。

プログラム

 DHT11の制御プログラムに関しては、これまで何度も使って来たので省略します。DHT11の逆引き索引から参照してください。
 Arduino UNOを、I2Cスレーブとして使用するプログラムについては、以下の記事で解説済みです。

  「#3-2 Arduino間I2C通信を試す

 328duinoは、ほぼほぼArduino UNOなので、同様にプログラムすれば良いです。以下に完成したプログラムをリンクしておきます。

  完成プログラム:i2c_tmphum.ino

 328duino(第二形態)にコンパイル&書き込みしてください。328duino(第二形態)のArduino IDEでのターゲットは、以下メニューの「ATmega328P (RC *)」です。

a106_328menu.png

 特段問題が無ければ、「RC 8 MHz」を使ってください。

328duino(第二形態)を「RC 8 MHz」以外の周波数で動作させたい場合には、こちらの記事「#9-7 内蔵RC発振器を校正する」に従って内蔵RC発振器の周波数−OSCCAL特性を測定し、校正テーブルを作成しておく必要があります。


 プログラムを書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。

  シリアルモニタコマンド仕様:

 上述コマンドは全て、デバッグ用です。デバッグによって動作確認が終了すればシリアルモニタ無しで自立できるので、プログラムを書き込んだ時点でUSB⇔UART変換モジュールの接続は解除して良いです。

単体デバッグ

 ここで、単体デバッグを実施しておきます。
 USB⇔UART変換モジュール(FTDIなど、5V定格の物)を以下表の様に接続してください。

I2C温度・湿度センサ化328duino 接続 FTDI
Vcc VCC(ジャンパ:5V側)
GND GND
RXI TXD
TXO RXD

※FTDIの電圧ジャンパを5V側に設定して、FTDIから328duinoへ5V電源を供給して使います。


 以下図に、プログラム書き込み、および、デバッグ中の様子を示します。

a25a_prog.jpg


 実験を進めましょう。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【req】
  OK: 26.8,32.0

 上例の様にOKが返って来たら、DHT11からの読み出しは成功しています。回路やプログラムに誤りは無いと思って良いでしょう。読み出したデータ、26.8℃、32.0%RHも、実験した時の温度・湿度と見て矛盾はありませんでした。


  【cnfg,09】
  OK: CNFG=0x09

 設定CNFGレジスタに0x09を書き込み、サンプリングレートを2s、オーバサンプル数を10サンプルに設定しました。最長応答時間は、(CNFG.TUP[7:4]+2)×(CNFG.NOS[3:0]+1)=20sほどなので、30sくらい待ってから以下のコマンドを実行します。

  【show】
  OK: CNFG=0x09 TMP=26.81 HUM=32.10

※少し時間を待ったのは、最初のオーバサンプル数10回を測定し終わって、十分な解像度が得られた頃合いを見計らって読み出すためです。解像度が上がりきる前であっても読み出し値は正常なので、特に時間を気にすることなく読み出し可能です。

 何度か【show】コマンドを実行して、それらしき値が読み出せたらOKとします。オーバサンプル数を10サンプルに設定しているので、本来DHT11から読み出すことのできる、温度で小数点以下1桁、湿度で小数点以下0桁よりも多い桁数で読み出せるはずです。

UNOからアクセスする実験

 単体での検証が済んだので、今度はI2CマスタとしてArduino UNOを使い、I2C通信でアクセスする実験を実施します。
 Arduino UNOへは、こちらの記事「#22-1 文字コード変換回路」で作成したプログラム(smbus_master.ino)をコンパイル&書き込みしておいてください。

I2C温度・湿度センサ化328duino 接続 Arduino UNO
Vcc 5V (※)
GND GND
SCL SCL(A5)
SDA SDA(A4)

328duino単体で実行した時の様に、FTDIから328duinoへ電源を供給する場合は、UNOの5Vを接続しないでください。328duinoの電源Vccは、FTDIないしUNOのいずれか一方から供給しなくてはなりません。臨機応変にご対応下さい。

 328duino側のデバッグをシリアルモニタから実行する必要が無い場合は、上表の様に328duinoとFTDIの接続は解除して、UNOとだけ接続すればOKです。この場合、電源(5V)はUNOから供給を受けます。

 以下図に実験中の様子を示します。

a25a_expr.jpg


 以下、UNO側のシリアルモニタから実験を進めます。

  実験サンプル:【 】内はシリアルモニタからの送信データ
  【scan】
  OK+ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
  00:             -- -- -- -- -- -- -- --
  10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
  30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  70: -- -- -- -- -- -- -- --
  OK: scan i2c slave address


 まずは、I2Cスレーブアドレスをスキャンしてみました。I2C温度・湿度センサ化328duinoのスレーブアドレスを設定する端子は開放状態なので、スレーブアドレス=0x23に設定されており、それが検出できています。


  【slv,23】
  OK: i2c_slv_adr=0x23

  【clk,400】
  OK: f_scl=400[kHz]

 アクセス対象となるスレーブアドレスを0x23、アクセススピードを400kHzに設定しました。I/Oレジスタを読み出してみましょう。

  【srd2,20】
  OK: srd2.23[0x20]=0x4854

 まずはIDレジスタ(16ビット、CMD=0x20)を読み出してみました。
 I2C温度・湿度センサ化328duinoのレジスタはビッグエンディアンですが、プログラム(smbus_master.ino)はリトルエンディアンなので、

  エンディアン変換:0x4854 → 0x5448="TH"

と、IDコード🆔が読み出せました。OKです。


  【srd2,24】
  OK: srd2.23[0x24]=0x780a

 今度は、温度TMPレジスタ(16ビット、CMD=0x24)を読み出しました。IDレジスタと同様、エンディアン変換して読み解くと、10進数でTMP=2680なので、100で割って26.80℃が読み出せました。実験した時の室温と同程度でしたので、これもOKと判断します。

  【srd2,26】
  OK: srd2.23[0x26]=0x800c

 続いて湿度HUMレジスタ(16ビット、CMD=0x26)を読み出しました。同様に、HUM=3200なので、100で割って32.00[%RH]と、こちらも、実験した時の室内湿度と同程度、OKです。

 この様に、温度と湿度のレジスタは16ビット長であり、上位8ビットと下位8ビットのアクセス間で、DHT11からの読み出しが割り込んでしまうと、上位と下位でデータが不整合を起こすため、必ず、アトミックにワードデータとして読み出す必要があります。つまり、バイトリード×2回に分割してはダメです。
 実は、温度レジスタと、湿度レジスタの2つに関しても、同じ様に同時読み出しすべきです。ただ、こちらは測定1回分の時間差が生ずるだけで、読み出したデータ自体は正しいので、ワードリード×2回に分割しても、ロングワードリード×1回で読み出しても、どちらでもOKとしています。
 上の実験サンプルでは、ワードリード×2回に分割して読み出しましたが、合法です。


 続けて、設定CNFGレジスタを設定して、動作が変化する様子を確かめます。

  【swr1,02,09】
  OK: swr1.23[0x02]=0x09

  【srd1,02】
  OK: srd1.23[0x02]=0x09

 サンプリングレートを2s、オーバサンプル数を10サンプルに設定するため、CNFGレジスタに0x09を書き込み、書き込んだ値が読み出せる事を確認しました。
 オーバサンプリングを設定したので、しばらく時間を待って読み出してみましょう。最長応答時間は、(CNFG.TUP[7:4]+2)×(CNFG.NOS[3:0]+1)=20sほどなので、30sくらい待ってから以下のコマンドを実行します。

  【srd2,24】
  OK: srd2.23[0x24]=0x790a

  【srd2,26】
  OK: srd2.23[0x26]=0x8a0c

 TMP=2681なので26.81℃、HUM=3210なので32.10%RHと、小数点以下2桁まで、見かけの解像度が向上しました。設計通り、良いみたいです。

※少し時間を待ったのは、最初のオーバサンプル数10回を測定し終わって、十分な解像度が得られた頃合いを見計らって読み出すためです。解像度が上がりきる前であっても読み出し値は正常なので、特に時間を気にすることなく読み出し可能です。

 以上を持って、基本的動作を確認できたので、実験を終了します。


 あ〜、「何で今更、DHT11の通信インターフェイスをI2Cにするのかしら?」って思ったかもしれませんが、最初に言及した通り、μsオーダのリアルタイム処理を不得意とする、UIFlow(やRaspberry Pi)でDHT11を手軽に使いたい、と言うのがスタートです。次のステップでは、I2C温度・湿度センサ化328duinoをUIFlowで使って見ようと思います。


btn_arduino.png

btn_prev.png btn_next.png

home