こんにちは!駆け出しエンジニアのまっきーです。
温湿度の測定がラズパイでできたら汎用性があるだろうなと思い、osoyooスターターキットに付属していたDHT11温湿度モジュールを使ってみました。
今やpythonが主流ですよね。DHT11をC言語で使う人は少数かもしれませんが、C言語のサンプルコードを自分なりに解読してみたので困っている人の助けになればと思います!
自身の理解力が全くなく、サンプルコードを読み解くのに想像以上の時間をかけてしまいました。自分なりには理解したつもりですが、もし間違いがあれば教えてください!
目次
DHT11の基本仕様
データシートは、秋月電子さんのものを使用しました。
実際の温度センサーは以下のようなものです。
ピン説明

- VDD(3.3~5.5V)
- シリアルDATAバス
- NC(NotConnect)
- GND
測定精度と範囲
・湿度の測定範囲は5%~95%、測定精度は±5%(25℃のとき)
・温度の測定範囲は̠-20~60℃、測定精度は±2℃(25℃のとき)
測定精度±2℃は意外と大きいですが個人的に使う分には十分に使用できる性能ですね。
2017年3月31日にバージョンアップがあり、上記の仕様になったみたいです。古いものを使用している際は実際にデータシートを見てみてください。(温湿度の測定範囲が少し狭いようです)
シリアル通信
シリアル通信の手順をタイミングダイヤグラムと併せて説明します。
タイミングダイヤグラムはこちらのサイトのものをお借りしました。
以下、シリアル通信の手順です。


タイミングダイヤグラムを見た方がわかりやすいと思うので、下の図をみて流れを追っていきましょう。

・ラズパイ側からスタート信号(赤色)を送ることでDHT11側から信号(青色)が返ってきます。
- 18ミリ秒のLOW信号を送る
- 20~40㎲のHIGH信号を送る
これでDHT11から信号を受け取ることができます。
Data transfer begins のところからデータ送信が始まります。
データの形式

温度・湿度の各データは8bit×5、つまり40bitのデータとして送られてきます。
チェックビットはデータが正しいかどうかをチェックするためのものです。
チェックビット = ① + ② + ③ + ④
となっていればデータの整合性が保たれているとわかるわけです。
また、小数部とありますが残念ながら小数での計測はできません。
つまり②と④は常に0となります。
計算例

例えば上のようにデータが送られてきた場合。
湿度00110101は10進数で53 湿度:53%
温度00011000は10進数で24 温度:24℃
というように計算されます。
パリティチェックビットも左4つのデータの和と一致していますね。
データの0と1の判断
データが1か0かの判断はこの部分で行います。

図から分かるように、HIGHの時間の長さで0か1かを判断します。
配線

VDDは3.3v、DATAバスはどのピンでも構いませんが今回は14で行います。
ソースコード
osoyooさんのサンプルコードを使用しています。
このサンプルコードでうまく値を取得できなかったので変更を加えました。
変更点には日本語でコメントを加えてあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
#include <wiringPi.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #define MAXTIMINGS 85 #define DHTPIN 14 int dht11_dat[5] = { 0, 0, 0, 0, 0 }; void read_dht11_dat() { uint8_t laststate = HIGH; uint8_t counter = 0; uint8_t j = 0, i; float f; /* fahrenheit */ dht11_dat[0] = dht11_dat[1] = dht11_dat[2] = dht11_dat[3] = dht11_dat[4] = 0; /* pull pin down for 18 milliseconds */ pinMode( DHTPIN, OUTPUT ); digitalWrite( DHTPIN, LOW ); delay( 18 ); /* then pull it up for 40 microseconds */ digitalWrite( DHTPIN, HIGH ); delayMicroseconds( 40 ); /* prepare to read the pin */ pinMode( DHTPIN, INPUT ); /* detect change and read data */ for ( i = 0; i < MAXTIMINGS; i++ ) { counter = 0; while ( digitalRead( DHTPIN ) == laststate ) { counter++; delayMicroseconds( 1 ); if ( counter == 255 ) { break; } } laststate = digitalRead( DHTPIN ); if ( counter == 255 ) break; /* ignore first 3 transitions */ if ( (i >= 3) && (i % 2 == 0) )//3回無視とありますが2回無視に変更してあります { /* shove each bit into the storage bytes */ dht11_dat[j / 8] <<= 1; if ( counter > 28 ) //16→28と値を変えています dht11_dat[j / 8] |= 1; j++; } } /* * check we read 40 bits (8bit x 5 ) + verify checksum in the last byte * print it out if data is good */ if ( (j >= 40) && (dht11_dat[4] == ( (dht11_dat[0] + dht11_dat[1] + dht11_dat[2] + dht11_dat[3]) & 0xFF) ) ) { f = dht11_dat[2] * 9. / 5. + 32; printf( "Humidity = %d.%d %% Temperature = %d.%d *C (%.1f *F)\n", dht11_dat[0], dht11_dat[1], dht11_dat[2], dht11_dat[3], f ); }else { printf( "Data not good, skip\n" ); } } int main( void ) { printf( "Raspberry Pi wiringPi DHT11 Temperature test program\n" ); if ( wiringPiSetup() == -1 ) exit( 1 ); while ( 1 ) { read_dht11_dat(); delay( 1000 ); /* wait 1sec to refresh */ } return(0); } |
ソースコード解説
1~7行目 各ヘッダーのインクルード、グローバル変数、マクロ定義です。
MAXTIMINGS 85は、必ず85である必要はありません。データ受信のマージンです。(83~88ぐらいなら変わらず値を取得できました)
9行目~ dht11のデータ読み取り用の関数です
11~16行目 必要な変数の定義、初期化です。laststateがHIGHなのは0,1判断の基準がHIGHの時間だからです。
19~26行目 DHT11と通信をするために必要なマイコン側の設定です。タイミングダイアグラムの赤線部分を行っています。
29行目~ ここからデータの読み取りをする処理です。全体の流れは
- HIGHの時間を㎲でカウント
- カウンターの条件によって dht11_dat[]に0、1を格納
この2つです。ごちゃごちゃしているように見えますが、やっていることはシンプルです。(この部分の理解に数時間を要しましたが笑)
32~40行目 HIGHまたはLOWの時間を㎲でカウントするwhile文です。(255という数字はマージンです)
41行目 while文から抜けたということは、ピンの状態が変わったということです。そこで一度laststateに現在のピンの状態(0or1)を格納します。
47~54行目 40ビットのデータを格納します。データシートによると2回目のデータがリアルタイムのデータだといっています。なので、最初の2回のデータ通信を無視するためにi>=3と設定します。

また、i%2==0とすることで偶数回のみ値を読み取るようにしています。
これはLOWとHIGHでひとつのデータ(1bit)送信を行っているためです。
1 |
dht11_dat[j / 8] <<= 1; |
この部分は1ビットずつ配列に値を格納する操作です。
1 2 |
if ( counter > 28 ) dht11_dat[j / 8] |= 1; |

カウンターが28より大きい場合、つまり1bitを送っている場合はor演算で1を立てています。(25でもうまくいきましたが24だと一切データは取れません出した)
61~70行目 データの整合性が取れている場合は温度、湿度を出力。整合性が取れなかった場合は「Data not good 」を出力します。
72~86行目 1秒ごとにデータを取得するよう設定しています。

上記のように、2秒待つと良いと書いてあるので2秒でもやってみました。当たり前ですが取得精度はこちらの方がよいです。実際にデータを取得してみるとこんな感じでターミナルに表示されます。
1 2 3 4 5 6 7 8 9 10 |
1秒の時 Data not good, skip Data not good, skip Humidity = 75.0 % Temperature = 29.3 *C (84.2 *F) Data not good, skip Humidity = 75.0 % Temperature = 29.3 *C (84.2 *F) Data not good, skip Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Data not good, skip Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) |
1 2 3 4 5 6 7 8 9 10 |
2秒の時 Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 73.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Data not good, skip Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 74.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 73.0 % Temperature = 29.3 *C (84.2 *F) Humidity = 73.0 % Temperature = 29.3 *C (84.2 *F) |
まとめ
いかがでしたでしょうか。
サンプルコードを信じすぎると痛い目に合うということを学びました…
何とか理解できましたので、C言語で挑戦しようと思っている方は参考にしてみてください!
それでは!