前回は、温湿度センサ DHT11 のデータシートを読んでみました。
通信インターフェースは独自プロトコルのようですが、そんなに難しい手順ではありません。自前でスケッチを書いて、テストしてみることにします。
回路図
データシートにしたがって、Arduino につなぎます。
データはシリアルモニタで確認しますので、Arduino の電源も USB からとることにしましょう。センサの電源は、Arduino の 5V ピンから供給します。
DATA ピンは、4.7KΩ で 5V へプルアップします。I/O ピンは、どれでも良いのですが、D7 としました。
スケッチ
DHT11 からデータを読み出し、シリアルモニタに表示させるスケッチです。ライブラリは使っていません。
以下いつものように、備忘録的解説です。
- // test DHT11 2022.05.31 by meyon
- const byte pinDHT11 = 7;
- void setup() {
- Serial.begin(9600);
- }
ピン定義とシリアル通信の指定です。
- void readDHT11(byte *temp, byte *hmd) {
- const int timeout = 500; // us
- byte startResponse = 0;
- byte detectedValue[5] = {0};
- byte checkDigit = 0;
- pinMode(pinDHT11, OUTPUT);
- delay(20);
- pinMode(pinDHT11, INPUT);
- startResponse = pulseIn(pinDHT11, HIGH, timeout);
- if(startResponse) {
- for(byte i=0; i<5; i++) {
- for(byte j=0; j<8; j++) {
- byte duration = pulseIn(pinDHT11, HIGH, timeout);
- detectedValue[i] *= 2;
- if(54 < duration) detectedValue[i] |= 1;
- }
- }
- for(byte i = 0; i < 4; i++) {
- checkDigit += detectedValue[i];
- }
- if((detectedValue[4] == checkDigit) && checkDigit) {
- *hmd = detectedValue[0];
- *temp = detectedValue[2] + (0x04 < detectedValue[3] ? 1 : 0);
- if(0x7F < detectedValue[3]) *temp = 0;
- }
- for(byte i=0; i<5; i++) {
- Serial.print(detectedValue[i]);
- Serial.print(" ");
- }
- }
- }
DHT11 からデータを読み出し、温度と湿度の測定値を返す関数です。
15〜17 行で、起始信号を送ります。
I/O ピンのモードを OUTPUT にすることで、I/O ピンはローインピーダンス (Low) になります。20ms 待って、今度は INPUT に変更します。すると、I/O ピンはハイインピーダンスになり、プルアップされた DATA ラインは High に戻ります。
19 行で响应信号を受け取ります。
もし信号を受け取れなかったとき、pulseIn() はタイムアウトして 0 を返しますので、データ読み出しは行いません (21 行) 。
22〜29 行で、5 バイトのデータを受け取ります。受け取ったデータは 8 ビットごとに detectedValue[] に格納していきます。
ビットが 0 か 1 かの判断は、High の時間が 54μs 以下か超えているかで行なっています (27 行) 。
格納したデータを 2 倍することで、ビットが左へひとつシフトします (26 行) 。ちなみに、ビットを左へ n 個シフトするには、2n を掛けます。へぇ (^_^;) またひとつ賢くなったよ。
31〜33 行は、チェックデジットの計算です。単純な足し算です。
35〜39 行、チェックデジットが合格したとき、かつ、チェックデジットが 0 でないときには、読み出したデータを温度と湿度の値として更新します。
温度について、小数部の値は四捨五入しています (37 行) 。測定精度が ±2℃ ですから、小数点以下の値を表示する意味がありません。また、氷点下の温度は 0 表示することにします (38 行) 。室温を測定することが目的で、氷点下になることはまず、ないです。また、負記号を表示させるために、表示部を一桁増やす価値も、ないです。
41〜44 行で、読み出した各データをシリアルモニタへ送ります。
- void loop() {
- static const int interval = 2500; // ms
- static unsigned long previousMillis = 0;
- static byte humidity = 0;
- static byte temperature = 0;
- if(interval < millis() - previousMillis) {
- readDHT11(&temperature, &humidity);
- Serial.print("/ ");
- Serial.print(temperature);
- Serial.print("C ");
- Serial.print(humidity);
- Serial.print("%\n");
- previousMillis = millis();
- }
- }
DHT11 からデータを読み出す間隔は 2.5 秒としました (49 行) 。
55 行で関数 readDHT11() を呼び出しています。温度と湿度の二つの値を返したいので、引数としてポインタを渡しています。関数側で、*temp と *hmd の仮変数が示すアドレスにそれぞれのデータを書き込むことで、二つの値を得ることができます。
うーん、エクセレントぉ (^_^;) まぁね、グローバル関数使っちゃえば簡単なんだけど、さ。
57〜61 行で、取得した温度と湿度の値を、シリアルモニタへ送ります。
シリアルモニタの表示
ある日の温度、湿度データです。蒸し暑い日です。
数値は左から、湿度整数部、湿度小数部、温度整数部、温度小数部、チェックデジット、です。
チェックデジットは 54+0+26+5=85 で、合格です。
/ の右は、関数が返した温度と湿度の値です。温度は、小数部が四捨五入されています。
出力波形
DHT11 の出力を見てみましょう。
起始信号とデータ転送の全体です。
20ms Low の起始信号の後、データが送られてきていることがわかります。
响应信号の周辺です。
最初の Low は起始信号です。起始信号が High に戻った後、ホストを解放するための 13μs の High があります。
その後、响应信号として 83μs の Low と 87μs の High が届きます。
その次からがデータで、左図では 0011 ときていることがわかります。
ということで、DHT11 から温度と湿度の測定データを、ライブラリ使わずとも、わりと簡単に、取り出すことができました。
次回は、温度と湿度を 7 セグメント LED に表示させて、温湿度計を作ってみましょう。