Raspi Picoで Grove Temperature & Humidity Sensor (DHT11) を使う (C++)

[**Grove Starter Kit for Raspberry Pi**](https://www.switch-science.com/catalog/7103/)に付属する[**Grove Shield for Pi Pico**](https://wiki.seeedstudio.com/Grove_Shield_for_Pi_Pico_V1.0/)と[**Grove Temperature&Humidity Sensor (DHT11)**](https://wiki.seeedstudio.com/Grove-TemperatureAndHumidity_Sensor/)をRaspberry Pi Picoで使う方法を説明します。
公式にはMicroPythonのサンプルコードがありますが、ここではC++を使って実装しました。

**Grove Starter Kit for Raspberry Pi**や**Grove Shield for Pi Pico**については、[こちら](https://qiita.com/hiro-han/items/ffd2fb2501a1aeef4549)の記事を参照してください。Starter Kitに付属する他のGroveモジュールの使い方へのリンクもこの記事にあります。

今回実装したソースコードは[Github](https://github.com/hiro-han/raspi-pico-grove-starter-kit)にアップしています。

# Grove - Temperature&Humidity Sensor (DHT11)について
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/3422783d-0ec9-d26d-e772-d8b657fef51f.png)

温度と湿度を測定できるセンサです。このセンサで計測できる範囲、精度は以下になります(*※注意*)。

||レンジ|解像度|精度|
|-|-|-|-|
|温度|-20 ~ 60℃|0.1℃|±2℃|
|湿度|5 ~ 90%|1%|±5%|

***※注意***
[Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)によると、DHT11センサは新しいバージョンと古いバージョンがあるそうで、新しいバージョンでは温度と湿度を想定できる範囲が広がっています。
[GroveのDHT11のwiki](https://wiki.seeedstudio.com/Grove-TemperatureAndHumidity_Sensor/)に記載されているセンサの情報は古いもののようなので注意して下さい。
また、[Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)のページ下部からデータシートがダウンロードできますが、こちらも古いバージョンのデータシートのようです。

### wikiなど
参考となる販売ページやwikiの一覧は以下になります。

- [Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)
  - 新しいバージョンと古いバージョンの違いについて説明がある
  - データシートのダウンロードリンクがあるが、これは古いバージョンのデータシート
- [DHT11のwiki](https://wiki.seeedstudio.com/Grove-TemperatureAndHumidity_Sensor/)
  - Arduino,、Raspberry Pi 3のサンプルコードあり
  - センサの情報は古い情報
- [Grove Shieldのwiki](https://wiki.seeedstudio.com/Grove_Shield_for_Pi_Pico_V1.0/#project-of-detecting-temperature-and-humidity)
  - MicroPytonのサンプルコードあり
- [旧DHT11 データシート](https://files.seeedstudio.com/wiki/Grove-TemperatureAndHumidity_Sensor/res/DHT11.pdf)
  - [Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)からダウンロードできる古いもの
- [新DHT11 データシート](http://akizukidenshi.com/download/ds/aosong/DHT11_20180119.pdf)
  - [秋月電子](https://akizukidenshi.com/catalog/g/gM-07003/)からダウンロードできるもの

# センサの使い方
## 参考にしたソースコード
今回C++で実装するため、C++で実装されたSeeedのwikiのArduinoのサンプルコードとRaspy Picoのサンプルコードを参考にしました。
Raspy Picoのサンプルコードは、DHT-22用なのかArduinoのサンプルコードと差異があります。

そのため、Raspy PicoのサンプルコードをベースにしつつArduinoのサンプルコードからDHT-11用の実装を移植しました。
しかし、その移植したコードもそのままでは動かなかったため、修正を加えました。この修正点は以下で説明します。

1. [Picoのサンプルコード (pico-examples)](https://github.com/raspberrypi/pico-examples)
    - C++で実装されたサンプルコード
    - [DHT-22のサンプルコード](https://github.com/raspberrypi/pico-examples/blob/master/gpio/dht_sensor/dht.c)
1. [Grove- Temperature&Humidity Sensorのサンプルコード](https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor)
    - Arduino用のサンプルコード
    - *DHT11.cpp* / *DHT11.h*というC++で実装されたソースコードがある。
        - Arduinoで使うことを想定しているため、Picoではそのまま使えない。（具体的にはGPIOを使うための関数を変える必要がある）

## センサからのデータ取得方法
ソースコードの説明をする前に、センサからデータを取得する方法を説明します。
サンプルコードに修正を加えたと言いましたが、その修正点がデータ取得に関する処理であるためです。

DHT11のセンサデータの取得方法はデータシートの5ページあたりで説明されています。それを図にまとめると以下のようになります。
縦軸がGPIOピンの電圧がHighかLowかを表しており、横軸は時間です。

![DHT11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/76311338-623f-8a1f-7b65-d1ca97e41c53.jpeg)

データの取得は大きく分けて３つのステップに分かれています。

- Step 1
    - Raspi Picoからセンサに信号を送ります。これがセンサへの、データ出力要求になります。
    - 次のパターンでGPIOの電圧を切り替えます。 : `High -> Low (18μs) -> Hi![DSC_1126.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/e0e7d91c-995f-bce3-6fb5-88a4ec2213cb.jpeg)
gh (40μs)`
    - その後、GPIOピンの設定を出力から入力に切り替えます。
- Step 2
    - センサからデータを送信するための準備をします
    - 次のパターンで電圧が切り替わります。 : `Low (80μs) -> High (80μs)`
- Step 3
    - センサから湿度(16bit)、温度(16bit)、パリティビット(8bit)の順で、計40bitのデータが送られてきます。
    - `Low (50μs) -> High (26~28μs もしくは 70μs)`と切り替わるのが１セットで、1bitを表しています。
    - 1bitの値が0か1かは以下のように区別します
        - `Low (50μs) -> High (26~28μs)` : 0
        - `Low (50μs) -> High (70μs)`    : 1

センサから受信したデータ意味は以下になります。

|byte|種類|意味|
|-|-|-|
|1byte目|湿度|整数部|
|2byte目|温度|小数部(常に0)|
|3byte目|温度|整数部|
|4byte目|温度|小数部(先頭bitが1の場合、温度がマイナスであることを表す)|
|5byte目|パリティビット|1~4byteまでの合計と等しくなる|

温度と湿度のデータは最初の8bitが整数、次の8bitが小数点以下の値となっています。
たとえば、`00010010 00000100`だった場合、

```
8b'00010010 + 8b'00000100 * 0.1 = 18 + 4 * 0.1 = 18.4
```
となります。

パリティビットの値は、温度と湿度のデータが正しいかどうか確認するために使います。
1~4byteまでのデータを合計し、パリティビットの値と一致していれば正常にデータを取得できています。
(例)

```
受信したデータ : 00110101 00000000 00011000 00000000 01001101
先頭4byteデータの合計 : 00110101+00000000+00011000+00000000=01001101 // バリティビットと一致
```

### ソースコード解説
DHT11からデータを取得するソースコードは以下になります。
[実装したコード](https://github.com/hiro-han/raspi-pico-grove-starter-kit/blob/main/src/dht11.cpp)

```c++
  uint8_t last = 1;
  uint j = 0;
  float humidity = 0.0;
  float temperature = 0.0;

  // step 1 start
  gpio_set_dir(pin_, GPIO_OUT);
  gpio_put(pin_, 0);  // LOW
  sleep_ms(18);
  gpio_put(pin_, 1);  // HIGH
  sleep_us(40);
  gpio_set_dir(pin_, GPIO_IN);
  // step 1 end

  // step 2, 3 start
  for (uint i = 0; i < 85; i++) {
    uint count = 0;
    while (gpio_get(pin_) == last) {  // HIGH -> LOW, LOW -> HIGHのときbreak
      count++;
      sleep_us(1);
      if (count == 255) break;
    }
    last = gpio_get(pin_);
    if (count == 255) break;

    // データ読み込み
    if ((i >= 4) && (i % 2 == 0)) {
      data[j / 8] <<= 1;
      if (count > 16) data[j / 8] |= 1;
      j++;
    }
  }
  // step 2, 3 end

  // パリティビットのと比較
  if ((j >= 40) &&
      (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))) {
    // データ変換
    humidity = data[0] + (float)data[1] * 0.1;
    temperature = data[2] + (float)(data[3] & 0x7F) * 0.1;
    if (data[3] & 0x80) {
      temperature = -result.temperature;
    }
  }
```

#### Step 1

最初の `gpio_set_dir(pin_, GPIO_OUT)`から`gpio_set_dir(pin_, GPIO_IN)`は図のstep 1に相当します。

#### Step 2 & 3
次の`for (uint i = 0; i < MAX_TIMINGS; i++)`以降がstep 2, step 3に相当します。こちらは少し分かりづらいため、詳しく解説します。

最上段の`for`文の`i`は、HIGHとLOWの回数にをカウントします。
step 1で1回、step 2では２回、step 3ではデータが40bitなので80回、合計83回HIGHとLOWがあります。そのためサンプルコードでは余裕を持ってループ回数は`85`となっています。（図では0からカウントしているため、最大82となっています）

次は`while`文です。`last`には直前のpinの電圧がHIGHかLOWかが入っています。
よってpinの電圧が、HIGH -> LOWもしくはLOW -> HIGHと切り替わったときに`while`文を抜けます。`while`文の中では何マイクロ秒同じ電圧の状態が続いたかを数え、その結果を`count`変数に入れます。
仕様上countが80マイクロ秒以上となることはないため、エラー処理としてcountが255に達したらwhile, forを抜けるように実装されています。

（※）正確には1 count = 1マイクロ秒にはなりません。1マイクロ秒のスリープの他にもいくつか処理があるため、実際にはもう少し大きい時間となります。その時間はCPUの性能に依存します。

#### データの読み込み
そして次の`if`文でセンサデータの読み取りを行います。
データの読み取りを行いたいのは、step 3かつ、HIGHのときのみです。よって、iが4以上かつiが偶数のときのみデータを読み取るよう`if ((i >= 4) && (i % 2 == 0))`という条件となります。
`j`はデータの読み取りを行った回数です。よって`j/8`で何バイト目のデータであるかを計算できます。データは1bitずつ読み込んでいくので、新しいデータを読み込む前に1bit左にシフトしておきます。


さらにもう1つif文がありますが、この`if (count > COUNT_TH) data[j / 8] |= 1`が、今回**変更を加えた処理**です。
上で説明したように、HIGHの状態が26~28μs続けばデータは0、HIGHの状態が70μs続けばデータは1、となります。
よって、`count`の値が28以上であればデータは1、そうでなければデータは0ということになります。

しかしサンプルコードでは`16`となっていました。その結果、データが正しく読み取れず、温度と湿度が分かりませんでした。
よって自分のコードでは`30`というように変更しました。

```
// 変更前
if (count > 16) data[j / 8] |= 1;

// 変更後
if (count > 30) data[j / 8] |= 1;
```

サンプルコードはArduino用のコードだったので、Arduinoの性能では`16`で大丈夫なのかもしれません。
`pico-example`の方のサンプルも16のままであった理由は分かりませんでした。

#### パリティビットとの比較
前述の通り1~4byte目までの合計と5byte目が一致するかどうかでデータが正しいかどうか判定できます。
40bit読み込んだ場合にこの判定を行い、問題なければ次のデータ変換を行います。

```c++
if ((j >= 40) && (data[4] == ((data[0] + data[1] + data[2] + data[3])))) {
  // データ変換
}
```

#### データ変換
上で説明したように受信したデータは、

|byte|種類|意味|
|-|-|-|
|1byte目|湿度|整数部|
|2byte目|温度|小数部(常に0)|
|3byte目|温度|整数部|
|4byte目|温度|小数部(先頭bitが1の場合、温度がマイナスであることを表す)|

となっているので、以下のように読み込みます。

(湿度)

```c++
humidity = data[0] + (float)data[1] * 0.1;
```

(温度)
小数部の先頭bitは温度がマイナスであるかどうかを表します。
そのため温度を計算するときは先頭bitを覗いて計算します。その後、先頭bitが1であったら、温度をマイナスとします。

```c++
temperature = data[2] + (float)(data[3] & 0x7F) * 0.1;
if (data[3] & 0x80) {
  temperature = -result.temperature;
}
```


# 実行結果
自分は、取得した値をLCDに表示するようにしました。
- DHT-11からデータを取得して、LCDに表示するサンプルは[こちら](https://github.com/hiro-han/raspi-pico-grove-starter-kit/tree/main/examples/temperature_humidity)

![DSC_1126.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/c62bfff6-c9b7-e026-ac2f-9db513a198b9.jpeg)

# 参考
- 今回実装したコード
    - Github リポジトリ : [raspi-pico-grove-starter-kit](https://github.com/hiro-han/raspi-pico-grove-starter-kit)
    - [DHT11のセンサ用のコード](https://github.com/hiro-han/raspi-pico-grove-starter-kit/blob/main/src/dht11.cpp)
    - [DHT-11からデータを取得してLCDに表示するコード](https://github.com/hiro-han/raspi-pico-grove-starter-kit/tree/main/examples/temperature_humidity)
- [Raspi Pico用Grove Starter Kitを使ってみる](https://qiita.com/hiro-han/items/ffd2fb2501a1aeef4549)
- [Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)
  - 新しいバージョンと古いバージョンの違いについて説明がある
  - データシートのダウンロードリンクがあるが、これは古いバージョンのデータシート
- [DHT11のwiki](https://wiki.seeedstudio.com/Grove-TemperatureAndHumidity_Sensor/)
  - Arduino,、Raspberry Pi 3のサンプルコードあり
  - センサの情報は古い情報
- [Grove Shieldのwiki](https://wiki.seeedstudio.com/Grove_Shield_for_Pi_Pico_V1.0/#project-of-detecting-temperature-and-humidity)
  - MicroPytonのサンプルコードあり
- [旧DHT11 データシート](https://files.seeedstudio.com/wiki/Grove-TemperatureAndHumidity_Sensor/res/DHT11.pdf)
  - [Grove DHT11の販売ページ](https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-DHT11.html)からダウンロードできる古いもの
- [新DHT11 データシート](http://akizukidenshi.com/download/ds/aosong/DHT11_20180119.pdf)
  - [秋月電子](https://akizukidenshi.com/catalog/g/gM-07003/)からダウンロードできるもの