充実の?中華部品キットKuman K4の箱に入っている部品を端から、RISC-V搭載32ビットMCU「GD32VF103で動かしてみて」おります。今回は温湿度センサーDHT-11です。これはメジャー、怪しくない。ただ、Arduinoであればライブラリあり、関数呼べば1発ですが、GD32-SDKではそうはいかないです。とりあえず簡単なドライバ関数を自作して動作を確かめます。
※「部品屋根性」投稿順Indexはこちら
温湿度センサに関しては、別シリーズで何度も取り上げさせていただいた記憶があります。全般的なところでは、こちらあたり。
それらの中でも今回のDHT-11と「兄弟」デバイスと思われるDHT-12センサについては実際に使ってみた回もありました。
IoT何をいまさら(57) M5Stack, ENV UNIT
これら両デバイス(DHT-12に後継品種に置き換えらしいですが)は、ASAIRというブランド名の Guangzhou Aosong electronics co., LTD., 社の製品です。DHT-11のパッケージは印字する場所がかなりあるので、裏面みるといろいろ書いてあって嬉しい、そして安心。(虫眼鏡いるけれど)
今回のDHT-11と上の回のDHT-12は似たようなデバイスではあるのですが、決定的な違いが、
- DHT-11は1ワイヤインタフェース
- DHT-12はI2Cインタフェース
です。GD32VF103はI2Cを複数チャネル搭載していますが、1ワイヤのI/Fはありません。よって、適当なドライバを自作しないとなりません。1回の通信時間は、DHT-11の仕様でだいたい4msと決まっているので、その区間、CPUを占有するのも何だかな~とは思ったのですが、CPUの利用は別シリーズのテーマ、こちらは、ひたすら部品を動かすのみ、ということで
今回は、GPIOとソフトタイマのフル・ソフト
で作成することにいたしました。
まずは、ソフトの前にセンサをマイコンに接続する回路はこちら。(例によって回路図エディタは水魚堂さん使わせていただいとります。)
GD32VF103のPC12ポートにDATA線を接続しています。これはたまたまSeeed社のデモボードで取り出しやすい場所だったからでどこでもいいです。DHT11のマニュアルには、プルアップ抵抗5.1kΩとありましたが、手元に無いので4.7kΩで勝手に手をうちました。電源は5VでもOKですが、GD32VF103が3.3V入出力なので3.3Vで使うことにします。また、100nFのパスコン入れろ、とマニュアルのどこかに書いてあったので、その通りにしました。
一応、通信時の波形を測るのに いつものとおり、Digilent社Analog Discovery2にお出ましいただき、セットアップしたブレッドボードがこちらです。
DHT11のワン・ワイヤ・インタフェースは、MCU側から1発ロウパルス(18msとかなり長い)を入れて、手を離して(オープンドレインポートをHiZにする)やれば、後はDHT11側でデータごとにロウパルス(50μsくらい)を返してきます。その後のハイの部分の時間長さで短ければ0、長ければ1という感じです。上の波形を眺めると、目でも0/1復調できるのが分かります。
ここで一つ注意点を。
DHT11には、毎秒1回以上、測定をお願いしてはいけない
です。1回目読めたのに2回目がボロボロの通信になって読めない現象があって、調べたらこの違反でした。この決まりを守らないとうまく動きませぬ。実体験いたしました。
さてGD32-SDKでフル・ソフトで通信する場合、端子はオープンドレインにして、デフォルトは1(オープンドレインのハイなのでHiZとなる)としておきます。出力指定ですが、この状態でも端子の状態は読むことができるので、DHT11側からの信号を拾うことができます。
gpio_init(GPIOC, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_12); gpio_bit_set(GPIOC, GPIO_PIN_12); //OD-HIGH = pull up
実際に0/1に変換するためには、ハイ区間の時間長さを測らねばならないので、以下のような関数を作りました。一応、タイムアウトもいれ、何かあったときにも無限ループには入らないようにしたつもり。なお、softLoop108()は大体1μSを消費する自作のアセンブラ関数。別シリーズのRISC-Vのアセンブラねたにて作ったものの改造。
int waitPC12(FlagStatus waitStatus, int timeout) { FlagStatus stat; int cnt = 0; while (++cnt < timeout) { stat = gpio_input_bit_get(GPIOC, GPIO_PIN_12); if (waitStatus == stat) { break; } softLoop108(); } if (cnt >= timeout) { return 0; } return cnt; }
テスト用にパルス幅を測ったときに使った時のコード。これだと、0/1を判定せず、幅をそのまま数値化しています。
void testDHT11(int* cnt) { gpio_bit_reset(GPIOC, GPIO_PIN_12); //Low delay_1ms(18); gpio_bit_set(GPIOC, GPIO_PIN_12); //High cnt[0] = waitPC12(RESET, 100); cnt[1] = waitPC12(SET, 100); cnt[2] = waitPC12(RESET, 100); //bit 0 start cnt[3] = waitPC12(SET, 100); //bit 0 wait cnt[4] = waitPC12(RESET, 100); //bit 0 data cnt[5] = waitPC12(SET, 100); //bit 1 wait cnt[6] = waitPC12(RESET, 100); //bit 1 data cnt[7] = waitPC12(SET, 100); //bit 2 wait cnt[8] = waitPC12(RESET, 100); //bit 2 data
こんなコードで、識別の閾値を32と決めました。32より小さければ0、大きければ1。後は、DHT11のマニュアル通りに解読するのみ。
解読結果はこんな感じ。
RH. 78.0 TEMP. 23.1 CSUM: 102 102 CSUM=OK
相対湿度 78パーセント(手元のカシオ様温湿度付き目覚まし時計は79パーセントを示しており)、温度23.1℃(同じく21.9℃を示しており)、ま、こんなもんですかねえ。チェックサムもOK、通信OK。
しかし、実用にするとなると4ms連続してCPU使ってしまうのはマズイ。タイマとか使ってCPUを解放する工夫をしないと。