温度(湿度)センサを使ってみた(2)
RaspberryPi/pigpioでDHT11

2021年2月2日

どもです。
前回は、DHT11をAdrudinoにつないで温度/湿度を測定してみました。
そこで今回は、Arduinoの代わりにRaspberryPiで同様に温度/湿度を測定してみました。

1.開発環境

本エントリでは、以下の環境で開発・実験を行いました。

OS Windows10 Pro
(バージョン:1903)
CPU Intel Core i7-8700
メモリ 16.0 GB
pigpio
(RaspberryPiのライブラリ)
Ver.71

なお、クロスコンパイラ/クロス環境の構築手順については、こちらを参照して下さい。
コンパイラのバージョンは少し古いですが、基本的な手順は変わっていません。

2.配線

まず、ブレッドボード上の配線です。
配線図は、下記の通りです。
temp_humidity_sensor_002_dht11
基本的には、前回と同じです。
ただし、RaspberryPiでは波形のノイズの解消のための、コンデンサを追加しています。
使用したコンデンサは、積層セラミックコンデンサ(0.1μF)です。

3.実装

今回の実装では、Arduinoの際に使用したライブラリのコード、およびDHT11のデータシートを元に作成しています。
コードは下記の通りです。
まずmain()です。

#include <iostream>
#include "Sensor/CDHT11.h"
#include "CGpio/CGpio.h"

using namespace std;

#define DHT11_DATA      (21)

int main() {

    CDHT11 dht11;
    dht11.setupGpioPin(DHT11_DATA);
    while (1) {
        if (CDHT11::CDHT11_ERROR_OK == dht11.read()) {
            printf("Temperature = %d.%d[C] ",
                    dht11.getTemperature() / 256,
                    abs(dht11.getTemperature()) % 256);
            printf("humidity = %d.%d[%%]\n",
                    dht11.getHumidity() / 256,
                    abs(dht11.getHumidity()) % 256);

        }
    }
    return 0;
}

次に、CDHT11クラスのデータ読み出し処理です。
なお、全部を載せると結構な量になってしまいますので、重要と思われる部分のみ抜粋します。

/**
 * @brief   Read sensor value.
 * @return  Returns 0 if reading sensor value finished succeeded, otherwise
 *          returns none 0 value.
 */
int CDHT11::read() {

    if (false == this->isScanIntervalPassed()) {
        return CDHT11_ERROR_SENSOR_NO_UPDATA;
    }

    int sequenceResult = this->readSequence();
    if (CDHT11_ERROR_OK != sequenceResult) {
        CLog::Error("Reading sensor failed.");
    } else {
        //Nothing to do.
    }
    this->updateInterval();

    return sequenceResult;
}

/*
 * A wait time to set data signal level keeping low to send start signal.
 * From the data sheet, the time should be larger than 18 msec, no more than
 * 30 msec.
 */
//#define   DHT11_START_SIGNAL_LOW_TIME_MILLISEC                (18)
//A little bit longer than the value in data sheet...
#define DHT11_START_SIGNAL_LOW_TIME_MILLISEC                (20)
#define DHT11_START_SIGNAL_HIGH_TIME_MILLISEC               (80)
#define MILLI2MICRO_SEC(milli_sec)                          ((milli_sec) * 1000)
#define DHT11_SENSOR_READY_TO_OUTPUT_SIGNAL_TIME_MICROSEC   (80)

#define DHT11_DATA_START_LOW_BIT_WAIT_TIME                  (50)
#define DHT11_DATA_FOLLOW_HIGH_BIT_WAIT_TIME                (70)

/**
 * @brief   Run sequence to read the data from sensor, DHT11.
 *          A sequence run in this method is on the data sheet.
 * @return  Returns true if the sequence finished succesfully, otherwise
 *          retunrs none 0 value.
 */
int CDHT11::readSequence() {

    CGpio* gpio = CGpio::getInstance();
    uint32_t cycleBuff[80] = { 0 };

    //Sending start signal.
    //Step1:Sending start signals.
    gpio->SetMode(this->m_pin, CGpio::CGPIO_PIN_IN_OUT_OUTPUT);
    gpio->SetPullUpDownMode(this->m_pin, CGpio::CGPIO_PULL_UP_DOWN_OFF);
    gpio->Write(this->m_pin, CGpio::CGPIO_PIN_LEVEL_LOW);
    gpio->DelayMilli(DHT11_START_SIGNAL_LOW_TIME_MILLISEC);

    //Step2:Waiting for response signal by setting GPIO pin pulled up.
    gpio->SetMode(this->m_pin, CGpio::CGPIO_PIN_IN_OUT_INPUT);
    gpio->SetPullUpDownMode(this->m_pin, CGpio::CGPIO_PULL_UP_DOWN_UP);

    this->waitForPulse(CGpio::CGPIO_PIN_LEVEL_LOW,
            MILLI2MICRO_SEC(DHT11_START_SIGNAL_HIGH_TIME_MILLISEC));
    if (WAIT_SIGNAL_TIMEOUT == this->waitForPulse(
            CGpio::CGPIO_PIN_LEVEL_HIGH,
            DHT11_START_SIGNAL_HIGH_TIME_MILLISEC))
    {
        CLog::Warn("Response to pulled up signal can not be detected.");
        return CDHT11_ERROR_PULLED_UP_TIMEOUT;
    }

    /*
     * Step3:Waiting for response signal sensor send to notify host,
     * raspberry-pi, to ready to output data.
     */
    uint32_t startSignalWaitTime_Low = this->waitForPulse(
            CGpio::CGPIO_PIN_LEVEL_LOW,
            DHT11_SENSOR_READY_TO_OUTPUT_SIGNAL_TIME_MICROSEC);
    if (WAIT_SIGNAL_TIMEOUT == startSignalWaitTime_Low)
    {
        CLog::Warn("Response to start signal of low can not be detected.");
        return CDHT11_ERROR_SENSOR_PULL_LOW_TIMEOUT;
    }
    uint32_t startSignalWaitTime_high = this->waitForPulse(
            CGpio::CGPIO_PIN_LEVEL_HIGH,
            DHT11_SENSOR_READY_TO_OUTPUT_SIGNAL_TIME_MICROSEC);
    if (WAIT_SIGNAL_TIMEOUT == startSignalWaitTime_high)
    {
        CLog::Warn("Response to start signal of high can not be detected.");
        return CDHT11_ERROR_SENSOR_PULL_HIGH_TIMEOUT;
    }

    //Step4:Read signal sensor sends.
    for (int buffIndex = 0; buffIndex < 40; buffIndex++) {
        cycleBuff[buffIndex * 2] = this->waitForPulse(
                (const unsigned int)CGpio::CGPIO_PIN_LEVEL_LOW,
                (const unsigned int)DHT11_DATA_START_LOW_BIT_WAIT_TIME);
        cycleBuff[(buffIndex * 2) + 1] = this->waitForPulse(
                (const unsigned int)CGpio::CGPIO_PIN_LEVEL_HIGH,
                (const unsigned int)DHT11_DATA_FOLLOW_HIGH_BIT_WAIT_TIME);
    }
//  for (int buffIndex = 0; buffIndex < 40; buffIndex++) {
//      printf("cycleBuff[%2d] = %7d, cycleBuff[%2d] = %7d\n",
//              buffIndex * 2,
//              cycleBuff[buffIndex * 2],
//              (buffIndex * 2) + 1,
//              cycleBuff[(buffIndex * 2) + 1]);
//  }

    //Step5:Convert Low-High time to bit data.
    this->InitDataBuff();
    for (int index = 0; index < 40; index++) {
        uint32_t startBit = cycleBuff[index * 2];
        uint32_t followBit = cycleBuff[index * 2 + 1];
        if ((CDHT11::WAIT_SIGNAL_TIMEOUT == startBit) ||
            (CDHT11::WAIT_SIGNAL_TIMEOUT == followBit))
        {
            CLog::Warn("Reading data failed.");
            return CDHT11_ERROR_SENSOR_READ_DATA_TIMEOUT;;
        }

        this->m_dataBuff[index / 8] <<= 1;
        if (startBit < followBit) {
            this->m_dataBuff[index / 8] |= 1;
        }
    }
//  this->ShowBuff();

    if (false == this->validateCheckSum()) {
        CLog::Error("Receive data invalid.");
        return CDHT11_ERROR_SENSOR_READ_DATA_INVALID;
    } else {
        CLog::Debug("Receive data valid.");
        return CDHT11_ERROR_OK;
    }
}

/**
 * Wait while the pin level is kept.
 *
 * @param   level   The pin level to wait while.
 * @param   time    Max time to wait for, specified by micro seconds.
 * @return  Returns wait time. If timeout occurred, returns 0xFFFFFFFF, meaning
 *          -1.
 */
uint32_t CDHT11::waitForPulse(
        const unsigned int level,
        const unsigned int time)
{
    CGpio* instance = CGpio::getInstance();

    unsigned int readLevel = 0;
    instance->Read(this->m_pin, &readLevel);

    uint32_t passedTime = 0;
    while (level == readLevel) {
        if ((time) < passedTime) {
            //Time out!
            return CDHT11::WAIT_SIGNAL_TIMEOUT;
        }
        instance->Read(this->m_pin, &readLevel);
        passedTime += instance->DelayMicro(1);
    }
    //CLog::Debug("Wait for sensor start signal : OK");
    return passedTime;
}

4.解説

上記コード、特にデータの読み出しを行っているCDHT11::readSequence()について解説します。

4.1.開始信号の送信

RaspberryPiでDATAピンの電位を操作します。
DHT11では、データの送信開始を要求するために、以下のようにDATAピンの電位を変化させます。
temp_humidity_sensor_002_start_sending_signal
「Step1」~「Step2」のコードが該当します。
なお、RaspberryPiのGPIOのプルアップ/ダウンの変更は、入力/出力の設定後に行う必要があるようです。
プルアップ/ダウンを変更させてから入力/出力を変更した場合、プルアップ/ダウンの変更が期待通りの設定になりません。
処理の実行手順に注意する必要があります。

4.2.DHT11の開始待ち

DATAピンの電位を一定時間LOWに維持すると、次にDHT11がDATAの電位のHIGH/LOWを動作させます。
その際の仕様は、下記の通りです。
temp_humidity_sensor_002_dht11_sending_signal
これを検知するのが、「Step3」の処理に該当します。

4.3.温度/湿度データの読み出し

DHT11では、DATAピンの電位がHIGHになっている時間が、ビットの0/1に対応しています。
データシートによれば、HIGHで保持されている時間とビットの0/1の対応は下記の通りです。
temp_humidity_sensor_002_data_timing_diagram
ここに記載されているHIGHからLOWへの変化を割り込みで検知、時間を計測して0/1を判定する、という方法もアリかもしれません。
しかしここでは、違う方法を採用します。
(ていうか、Arduino向けライブラリでは違う方法を採用しているので、それに倣います。)

即ち、LOW/HIGHに保持されていた時間を保持しておき、その長さを比較することでビットの0/1を判定する、という方法です。
この「LOW/HIGHに保持されていた時間を保持」しているのがStep4、「長さを比較することでビットの0/1を判定」しているのが「Step5」となります。

4.4.整合性確認

最後に整合性の確認です。
DHT11は、全部で5バイトのデータを送信します。
受信したビットデータの前半4バイトの総和と5バイト目の値が一致していれば、受信したデータは正常と判断できます。
一致しない場合には、不正なデータと判断します。
なお、総和は16ビットのデータになる可能性がありますが、上位8ビットのデータは無視します。

4.5.測定結果

作成したアプリケーションの実行すると、下記のようになります。
temp_humidity_sensor_002_running
結果は…前回と同様ですね。

5.まとめ

前回に引き続き、今回もDHT11を使ってみました。
ネットで調べてみると、RaspberryPi上ではpythonで動かした例はたくさん見つかります。
しかし、C言語またはC++とpigpioで動作させた例は殆ど見つかりません。
今回のエントリで、C言語またはC++とpigpioでDHT11を操作/動作させる際に困っているヒトの一助にでもなれば、うれしいなぁ…。

ではっ!

追記:公開しています

DHT11を使用するためのソースコードは、GitHubで公開しています。
ArduinoとRaspberryPiの両方を公開しているので、必要に応じて、それぞれ参照してみてください。