LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-10-07

Grove Starter Kit for Raspberry Piに付属するGrove Shield for Pi PicoGrove Temperature&Humidity Sensor (DHT11)をRaspberry Pi Picoで使う方法を説明します。
公式にはMicroPythonのサンプルコードがありますが、ここではC++を使って実装しました。

Grove Starter Kit for Raspberry PiGrove Shield for Pi Picoについては、こちらの記事を参照してください。Starter Kitに付属する他のGroveモジュールの使い方へのリンクもこの記事にあります。

今回実装したソースコードはGithubにアップしています。

Grove - Temperature&Humidity Sensor (DHT11)について

image.png

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

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

※注意
Grove DHT11の販売ページによると、DHT11センサは新しいバージョンと古いバージョンがあるそうで、新しいバージョンでは温度と湿度を想定できる範囲が広がっています。
GroveのDHT11のwikiに記載されているセンサの情報は古いもののようなので注意して下さい。
また、Grove DHT11の販売ページのページ下部からデータシートがダウンロードできますが、こちらも古いバージョンのデータシートのようです。

wikiなど

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

センサの使い方

参考にしたソースコード

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

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

  1. Picoのサンプルコード (pico-examples)
  2. Grove- Temperature&Humidity Sensorのサンプルコード
    • Arduino用のサンプルコード
    • DHT11.cpp / DHT11.hというC++で実装されたソースコードがある。
      • Arduinoで使うことを想定しているため、Picoではそのまま使えない。(具体的にはGPIOを使うための関数を変える必要がある)

センサからのデータ取得方法

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

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

DHT11.jpg

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

  • 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)と切り替わるのが1セットで、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からデータを取得するソースコードは以下になります。
実装したコード

  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では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読み込んだ場合にこの判定を行い、問題なければ次のデータ変換を行います。

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

データ変換

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

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

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

(湿度)

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

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

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

実行結果

自分は、取得した値をLCDに表示するようにしました。
- DHT-11からデータを取得して、LCDに表示するサンプルはこちら

DSC_1126.JPG

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0