温湿度センサDHT11をライブラリなしで使ってみた【Arduino】

# 温湿度センサDHT11をライブラリなしで使ってみた【Arduino】
# 記事の内容
倉庫や部屋の環境データを、モニタリングして制御できたらいいなと思い、Arduinoと[DHT11搭載温湿度センサモジュール](https://akizukidenshi.com/catalog/g/gM-15491/)を使って、温度と湿度のデータを取得してみました。
外部のライブラリを使わずに通信しているので、多少難易度は高いですが理解しやすくなっていると思います。和訳したデータシートも交えながら、詳しく解説していきます。

使用したArduinoボードは、 **Arduino Uno R3** と、 **Arduino Mega 2560 R3** (互換品)、プログラム開発環境は **Arduino IDE** です。
おそらく他のボードでも応用可能だと思いますので、参考にしてもらえると幸いです。
## DHT11の説明
**DHT11** とは、AOSONG社製の温湿度センサです。温度(Temperature)と相対湿度(Relative humidity)を計測することができます。なぜこのセンサを選んだのかというと、アマゾンで購入した[「ELEGOO Arduino用のMega2560スタータキット最終版」](https://www.amazon.co.jp/Elegoo-JP-EL-KIT-008-Arduino%E7%94%A8Mega2560%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%BF%E3%82%AD%E3%83%83%E3%83%88%E6%9C%80%E7%B5%82%E7%89%88-%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E3%80%81%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%E4%BB%98%E3%80%81MEGA-2560%E3%83%9C%E3%83%BC%E3%83%89-LCD1602/dp/B06Y5SM5D6/ref=asc_df_B06Y5SM5D6/?tag=jpgo-22&linkCode=df0&hvadid=208293289620&hvpos=&hvnetw=g&hvrand=1884477783874380978&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009775&hvtargid=pla-494865615287&psc=1)に付属していたからです。
単品でも、秋月電子通商で[温湿度センサモジュール DHT11](https://akizukidenshi.com/catalog/g/gM-07003/)として販売されています。
精度は、**温度が±2℃、湿度が±5%RH**と、そこまで良くはありませんが、値段相応だと思います。簡単な温湿度測定には十分でしょう。
![DHT11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/2710e7d6-3747-61bd-8bec-743dae27e30e.jpeg)

データはデジタル出力となっており、**1-Wireライク通信**でデータを送信します。端子は3本しかなく（1本は不使用）、2本が電源用で、もう1本が通信用です。
![DHT11単体.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/80cfe99a-b16b-4f4c-e49f-33e05f01a174.jpeg)

> [1-Wire](https://www.marubun.co.jp/service/technicalsquare/a7ijkd000000g7v9.html)とは通信プロトコルの1種であり、複数のセンサを**たった1本の信号線で**接続できます。そのため、信号線や端子がかなり節約できるというメリットがあります。
しかし、DHT11の場合は1-Wire"ライク"通信であり、1つのセンサでまるまる1本の信号線を占有します。**UART通信**ともよく似ています。

## DHT11をArduinoに接続してみよう！
軽い説明は終わりましたので、続いてArduinoとの接続方法について説明していきます。
### ピン配置
![寸法ピン配置.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/20e83826-8aea-dc98-3c95-90ea25312464.png)
上図は、データシートにあるDHT11の寸法図です。
DHT11センサー本体には、4本の足（①~④）が出ています。これらの役割は、

**①VCC   電源用ピン（3.3~5.5V入力）
②DATA  シリアル通信用ピン（データ転送用）
③NC    未接続ピン（空のピン）
④GND   接地（0V入力）**

となっています。すなわち、**4本の足のうち1本使用されていないため、実質は3本しか使わない**ことになります。
という訳で、モジュール化された部品（図2）では、ピンは3本になっています。**ピン配置が変更されていることに注意**してください。
### 回路図
![基本回路図.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/2fcf6e03-7cbb-b00b-8dce-59dde36d2cdd.jpeg)
DHT11を、マイクロプロセッサ（マイコン、ICなど）に接続した例が上図になります。
少し分かりにくいので箇条書きにすると、

**DHT11---> Arduino
①VCC-----> 5Vピン
②DATA----> Port Pin (電圧の入出力ができるピン)
③GND-----> GNDピン**

となります。3本つなげれば良いだけなので楽チンですね！

また、DHT11のDATAピンは[プルアップ](https://qiita.com/nishiwakki/items/e921d44a00a37c72979c)された状態で通信するので、Arduinoの5Vピンと、DATAピンとの間にプルアップ抵抗を接続する必要があります。今回使用するモジュールには、4.7kΩの抵抗が内蔵されているので、Arduinoの5VポートとDATAピンとの間に抵抗を追加する必要はありません。

## DHT11と通信してみよう！
### 通信の流れ
![データタイミング図cut.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/408267bf-ad6c-66fd-09c1-e41b318d2cd4.png)
上図は、データシートに記載されている通信のタイミング図になります。
濃い線がArduinoからの信号で、薄い線がDHT11からの信号です。時間経過（左から右）でDATAピンの電圧が上下し、データが送信されているのが読み取れます。

<dl>
信号の順序は、
<dd><strong>①開始信号(Start signal)
<dd>②応答信号(Response signal)</dd>
<dd>③湿度・温度データ</dd>
<dd>④終了信号(Low end)</strong></dd>
となっています。
</dl>

**①開始信号(Start signal)**
Arduino側からの、通信の開始を宣言する18~50ms(18000~50000µs)のLOW信号です。
![start signal.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/0bf14123-f16b-bf97-2b8e-df0e736eeb16.jpeg)
通信の開始を宣言する信号です。ホスト(Arduino)が18~50ms(標準20ms)の間、DATAピンの電圧をLOW(0V)にします。開始信号が終了すると、電圧はHIGH(5V)に戻ります。

**②応答信号(Response signal)**
開始信号に対して、DHT11側から返答される信号です。LOWとHIGHが約80µsずつ出力されます。
![データタイミング表2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/23ee7e19-d342-4e2e-fabc-7a8cabd83df5.jpeg)
開始信号を受信したDHT11は、直後に78~88µsのLOW信号を出力します。出力後は、80~92µsのHIGH信号を出力します。

**③湿度・温度データ**
40ビット分の計測データを出力します。50µsのLOW出力が40回繰り返され、その合間のHIGH出力の長さで、0と1が区別できます。
<details>
<dt><データ例>
<dd>部屋でDHT11を動かしたときのデータ
0101100100000000000101110000100001110100</dd>
<dt><意味>
<dd>**0~7bit：Humidity High**：湿度の整数部
 hh=01011001=89[%]
**8~15bit：Humidity Low**：湿度の小数部
 hl=00000000=0 → 0.0[%] _※hlは常に0_
**16~23bit：Temperature High**：温度の整数部
 th=00010111=23[℃]
**24~31bit：Temperature Low**：温度の小数部
 tl=00001000=4 → 0.4[℃]
**32~39bit：Check Bit**：チェックビット
 ch=hh+hl+th+tl=01110100 _※ch≠hh+hl+th+tlのときはデータエラー_
</dd>
つまり、データは湿度89%、温度23.4℃であることが分かります（記事を書いた時期が梅雨だったので、だいぶジメジメしてました）。</dt>
![ビットデータ形式.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/d17c5542-74e9-1924-2f86-8bf9bba46369.jpeg)
LOW信号が50µs出力された後、データを表すHIGH信号が出力されます。
HIGH信号の長さは、0の場合23~28µs、1の場合68~74µsです。このHIGH信号の長さをArduinoで検出することで、データを取得することができます。
<dd>
**※チェックビット**
データに漏れやエラーがないかチェックするときに使用するデータです。
Humidity High, Humidity Low, Temperature High, Temperature Lowの8ビットデータを、全て足し合わせた数のデータが出力されます。
</dl>
今回は、各ビットのデータの和は以下のようになります。
$01011001+00000000+00010111+00001000=01110100$
(10進数 : $89 + 0 + 23 + 4 = 116$)
</dl>
これは、出力されたチェックビット01110100(=116)と一致するので、正しいデータが送られている可能性が高いです。逆に、この等式が成り立たないときはどこかでデータが損傷した可能性があります（複数箇所でデータ漏れが発生するとチェックビットが等しくなる可能性はありますが、とても低確率でしょう）。</dd>
</dl></details>
**④終了信号(Low End)**
通信の終了を告げるLOW信号で、約50µs出力されます。出力が終わると、DHT11は通信バス（信号線）を解放します。

### 実際の信号
![IMG_2311.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/4deb1d8d-f666-6794-4779-b34aff3f18bf.jpeg)
波形を見るために、研究室からオシロスコープを借りてきました。

![信号1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/064950ce-6033-4593-fe28-0d79dc626086.png)
実際にDHT11に開始信号を送ってみると、なんか波形が返ってきているのが分かります。
これが、応答信号、データ、終了信号です（開始信号に対して短いので潰れてますが）。
まるで、宇宙からの声明が受信できたみたいでワクワクしますね！

![信号2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/26c479c5-0b27-f785-6985-c4a58198dcbf.png)
拡大してみると、開始信号が終了した直後、応答信号が届き、その後データ信号が着々と送られてくるのが分かります。

![信号3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/ef005b23-fbbb-f451-1d85-a2b793e5af4f.png)
データ信号を拡大すると、確かにHIGH出力の長さが変化していますね。波形を見た感じでは、DHT11は正しく動作しているみたいです。
### 注意点
データシートには、運用の際の注意点がいくつか提示されています。主に以下の点にご注意ください。
・ノイズの影響や電力不足を防ぐため、接続線は5m未満推奨。3.3V電源のときはできるだけ短く。
・回路の発熱の影響を受けやすいので、発熱部品から離したり空冷したりする必要がある。
・化学薬品（酸化性ガス、硫化ガスなど）の影響で、値が変わったり壊れたりする危険性あり。
・信頼性の問題上、安全装置等には使用禁止。
## 温度と湿度のデータを取得してみよう！
### ArduinoIDEでプログラムを書く
DHT11のことが結構分かってきたでしょうか？
それでは、温度と湿度のデータをArduinoで取得しモニタリングができるように、Arduino IDEでプログラムを書いていきましょう！

通常であれば、そのセンサのために準備されたライブラリを探してダウンロードし、プログラミングしていくのでしょうが、 ~~ライブラリを探すとか入れるとかってめんどくさいし、~~ せっかく通信について理解を深めたのですから、今回は何も使わずに通信プログラムを書いていきます。
**接続さえできていれば、プログラムをコピペするだけでいい！** という超絶メリットもありますね。

以下は、 **ArduinoでDHT11からの温湿度データを5秒ごとに取得し、シリアルモニタに出力するプログラム** になります。

```c#:readDHT11.ino
#define signalPin 7  //デジタルピン7を使用(DHT11のDATAピンと接続)


void setup() {
  Serial.begin(9600);
  pinMode(signalPin, INPUT_PULLUP);
}

void loop() {
  pinMode(signalPin, OUTPUT); //開始信号・・・・・・☆
  delay(20);
  pinMode(signalPin, INPUT);  //開始信号 終了
  delay(1000);  //1秒待つ

  while(1){
    readDHT11();  //データの読み取り
    delay(5000);  //5秒待つ
  }
}


//DHT11センサから値を読み取り、シリアルモニタに出力する
void readDHT11() {
  byte dataBit = 40; //データ数は40
  byte stdTime = 50; //データが0か1か判断する基準時間
  bool data[dataBit]; //データ格納用の配列（0か1）
  int toggleTime[dataBit]; //波形が切り替わるのにかかった時間


  pinMode(signalPin, OUTPUT); //開始信号・・・・・・☆
  delay(20);
  pinMode(signalPin, INPUT);

  countBit(HIGH); //初期波形High

  countBit(LOW);  //応答時間Low
  countBit(HIGH); //応答信号High

  for (int i = 0; i < dataBit; i++) {
    countBit(LOW);  //データLow
    toggleTime[i] = countBit(HIGH); //データHighの時間を取得
  }

  for (int i = 0; i < dataBit; i++) {
    if (toggleTime[i] == 0) {
      //波形の時間が0→エラー
      goto error;
    }
    data[i] = (toggleTime[i] > stdTime); //基準よりHighの時間が長いと1
  }

  byte HumH, HumL; //Humidity:湿度のHigh,Lowデータ
  byte TemH, TemL; //Temperature:温度のHigh,Lowデータ
  byte Check;      //Check bit:数値漏れチェック用のデータ

  //データの格納
  for (int i = 0; i < dataBit; i++) {
    if (i < 8) {
      //Humidity High(湿度の整数部)
      HumH *= 2; //シフト演算（ビットを左にずらす）
      HumH += data[i];
    } else if (i < 16) {
      //Humidity Low(湿度の小数部)=0(省略可)
      HumL *= 2;
      HumL += data[i];
    } else if (i < 24) {
      //Temperature High(温度の整数部)
      TemH *= 2;
      TemH += data[i];
    } else if (i < 32) {
      //Temperature Low(温度の小数部)
      TemL *= 2;
      TemL += data[i];
    } else if (i < 40) {
      //Check bit(チェックビット)
      Check *= 2;
      Check += data[i];
    }
  }

  float Humidity = HumH + (HumL / 10.0);
  float Temperature = TemH + (TemL / 10.0);
  if (Check == (HumH + HumL + TemH + TemL)) {
    Serial.println(String(Temperature) + "℃ " + String(Humidity) + "%RH");
  } else {
    goto error; //各データの和とチェックビットが違う→エラー処理
  }

  if (0) {
    //エラー処理
error:
    Serial.println("ERROR:Data is wrong!");
  }
}


inline int countBit(bool pinV) {
  //信号電圧がpinVの間ループする
  byte breakTime = 200;  //ループを抜け出す時間[µs](無限ループ防止)
  unsigned long Time = micros();
  while (digitalRead(signalPin) == pinV) {
    if ((micros() - Time) > breakTime) {
      //波形が長すぎる→エラー処理
      goto error;
    }
  }
  return micros() - Time;

  error:  //エラー処理
  return 0;
}
```
処理の割に、随分長いプログラムになってしまいましたが…
その代わり、コピペして **デジタルポート7ピンとDATAピンを繋げば、** 即座に温度と湿度のデータが得られます（下写真）。
![データ出力.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/fd30758a-440c-9b8d-e950-172965109b15.png)

### 開始信号の謎
以下は、開始信号を出力するプログラムです。

```c#
  pinMode(signalPin, OUTPUT); //開始信号・・・・・・☆
  delay(20);
  pinMode(signalPin, INPUT);  //開始信号 終了
```
`pinMode()`は、指定したピンの入出力方向を決める関数です。
`pinMode(signalPin, OUTPUT);` でDATAにLOW信号が出力され、20µs後、`pinMode(signalPin, INPUT);` でHIGH電圧に戻ります。
これで、前述した開始信号を出力できます。

しかし、書いておきながら**「なぜこれで開始信号が出せるんだ!?」**状態です。
ピンの出力方向を変えてるだけですから。おそらく、プルアップ中なのでピンを`OUTPUT`にすると電流がマイコンに流れてDATAがLOWになる…という感じなんでしょうが…（ポート操作でDDRxを操作するだけではLOW出力になりませんでした）

また、`pinMode(signalPin, INPUT)` の`INPUT`を`INPUT_PULLUP`にすると最初の計測時だけ信号が乱れ、エラーになります。`INPUT_PULLUP`にしたときはマイコンのプルアップ抵抗が悪さをしている?ぽいんですが確信が持てません。
だれか詳しい方がいれば教えて下さると助かります。
###フローチャート
プログラムのフローチャートは以下の通りです。
![通信フローチャート.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/d78d1709-f458-5cb6-b9ee-c0fc13568305.png)

応用のために最適化してない部分がありますが、とりあえず`loop()`関数内で`readDHT11()`関数を走らせれば温湿度データをシリアルモニタに出力できます。

得られた値によって、ファンを稼働させたり、ヒーターを付けたりなどの処理を行いたい場合は、`readDHT11()`内のローカル変数`Humidity`（湿度）、`Temperature`（温度）を利用するといいと思います。
### プログラムを書き換えるときの注意点
今回のプログラムではデータ通信にデジタルピン7を使っていますが、他のピンを使いたい場合は`#define signalPin 7` の"7"を自分の使いたいピンの番号にしてください(5,10,A3など)。

また、DHT11は **前回の開始信号を送った時のデータを出力します。**
つまり今回のプログラムの場合は、**約5秒前のデータが出力される** ことになります。計測間隔をとても長くする場合やリアルタイムの時間を取得したい場合は、
<dd>
・2連続で`readDHT11()`を呼び出す
・`readDHT11()`の冒頭に もうひとつ開始信号を追加する</dd>
等の処理によって、値を再計測する必要があります。

かと言って、計測の待機時間が短すぎるとエラー率が格段に高まるので（下写真）、開始信号の送信には2秒以上の間隔を空けることを推奨します。
![エラー.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/94ba09f6-9583-0ad1-3db1-d226c973662b.png)
### 他の機器との通信への応用
今回のプログラムでは下手にライブラリを使っていませんので、信号からデータを取得する部分は他のセンサとの通信にも応用が利くと思います。しかし、すこし複雑な通信方式（I2C, SPIなど）になると、プログラムが難しくなり可読性も著しく低下するので、大人しくライブラリを使ったほうが無難です（使わないほうが通信の理解力は鍛えられます）。

また、信号の取得に`digitalRead()`関数と`micros()`関数を使っていますが、`digitalRead()`の実行には3~6µsもかかりますし(Arduino Unoの場合)、`micros()`の分解能は4µsになっています。そのため、最大でも**100kHz程度までの通信しかできない**と思われます。
>※それ以上の速度で通信したい場合は、直接マイコンのポートを操作する必要があります。
[Arduino 日本語リファレンス　ポート操作](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=849)

##　終わりに
今回は、DHT11で温湿度データを取得するプログラムを書いてみました。

ネットにはプログラムが山ほどあるのに、コピペだけで完結できる資料って意外と少ないですよね。私のようなめんどくさがり屋にとっては、もう少し標準ライブラリだけで構成されたサンプルコードが増えて欲しいな、とか、もっと直感的で分かりやすいライブラリを作ってほしいなとか思っています（他力本願）。

最後まで読んでいただきありがとうございました。

[別に電子回路についてはつぶやかないこきづちのツイッター](https://twitter.com/Ko_Kiduchi)
![ジャンプこきづち.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1230173/04063ebf-a105-8f98-3e4e-4a02abbb22c6.png)