海霧の辿り着く場所

湿度センサDHT11でC++組み込み開発の練習を

気温と気圧だけではさみしいので

気温と気圧は,本Webサイトで測定値を公開しておりますが,やっぱり湿度計も欲しいと思いました。 そこで,秋月電子通商で安い湿度センサDHT11を購入して参りました[1]。 DHT11では温度も測れます(既にSCP-1000で測れますが)。 おおぉ!? 温度の分解能が1°Cで湿度の分解能が1%? いくら安いと言ってももう少し頑張って欲しかった…(^^;

DHT11の電源電圧は3.5 V 〜 5.5 Vということで,3.3 Vでは動作しないようです。そこで,USBから供給される5 Vを使って制御することにします。LPC1768の入出力は5 Vトレラントなのでそういう使用方法もできるようです。DHT11とのインターフェイスに使用するピンをオープンドレインに設定します。

[追記]色々修正しました

湿度センサモジュールDHT11

DHT11は,湿度,温度,チェックサムからなる40 bitのデータを,1-Wireライクなシリアル通信で送ってきます。 mbedを利用しているのであれば,先人のライブラリを利用するという手もあったのですが,自分でライブラリを作る練習をしてみたいので,1-Wireライクなシリアル通信のドライバを作ってみることにしました。

作成したライブラリは“Publish”して世の中に公開してみました[2]。使ってくれる人は現れるんでしょーか?

拙作のDHT11ライブラリはこちらから

1-Wireライクなシリアル通信の波形

私はCにしてもC++にしても初心者なので,何でもかんでも1つのソースファイルのmain()に詰め込んでしまう書き方をしていました。また,mbedではC++を使いますが,C++の大きな特徴である「クラス」を使ったオブジェクト指向のプログラムというのも書いたことがありません。そこで,キャラクタLCDOLEDディスプレイのライブラリの真似をして,DHT11のインターフェイスとなるDHT11クラスを作ってそのオブジェクトを生成し,オブジェクトのメソッドを通じて湿度と気圧のデータを得るようなライブラリを作ってみることにしました。ちゃんと.hファイルと.cppファイルに分けてみます。

作ってみたプログラム

先人のライブラリ(TextLCDなど)を参考にしながら,格闘した結果,以下のようなmain.cpp,DHT11.h,DHT11.cppを書いてみました。1-Wireライクなシリアル通信の受信では,割り込みを使ったパルス幅の測定にも挑戦してみました。

main.cpp
/* Copyright (c) 2014 Shigenori Inoue, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
 
#include "mbed.h"
#include "TextLCD.h"
#include "DHT11.h"

// LEDs for debugging
BusOut leds(LED4, LED3, LED2, LED1);

// LCD module
TextLCD lcd(p25, p24, p12, p13, p14, p23);

// Humidity sensor
DHT11 d(p18);

// The main function
int main()
{
    int state;

    lcd.cls();
    lcd.locate(0, 0);
    lcd.printf("DHT11 Humidity");

    while(true) {
        state = d.readData();

        if (state != DHT11::OK) {
            lcd.locate(0, 1);
            lcd.printf("Error: %d", state);
        } else {
            lcd.locate(0, 1);
            lcd.printf("T: %dC, H: %d%%", d.readTemperature(), d.readHumidity());
        }
        leds = leds + 1;
        wait(2.0);
    }
}
DHT11.h
/* Copyright (c) 2014 Shigenori Inoue, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
 
#ifndef __DHT11__
#define __DHT11__
#include "mbed.h"

/**  Example:
 * @code
 * #include "mbed.h"
 * #include "DHT11.h"
 *
 * DHT11 d;
 *
 * main()
 * {
 *     int s;
 *     s = d.readData();
 *     if (s != DHT11::OK) {
 *         printf("Error!\r\n");
 *     }
 *     else {
 *         printf("T:%d, H:%d\r\n", d.readTemperature(), d.readHumidity());
 *     }
 * }
 * @endcode
 */

class DHT11
{   
public:
    /** Create a DHT11 interface
     * @param pin 1-wire-like serial I/O port of DHT11
     */
    DHT11(PinName pin);
    ~DHT11();

    /** Reading the data from the DHT11
     * @return Error code
     *     0: OK.
     *     1: Reading the data too often.
     *     2: 1-wire bus is busy.
     *     3: DHT11 does not respond.
     *     4: DHT11 is not ready.
     *     5: Checksum is incorrect.
     *     6: Timeout.
     */
    int readData(void);

    /** Reading the humidity from the data
     * @return Humidity in %,
     * regardless of the error from readData()
     */
    int readHumidity(void);

    /** Reading the temperature from the data
     * @return Temperature in Celcius,
     * regardless of the error from readData()
     */
    int readTemperature(void);

    enum ErrorDHT11 {
        OK = 0,
        READ_TOO_OFTEN = 1,
        BUS_BUSY = 2,
        NOT_PRESENT = 3,
        NOT_READY = 4,
        CHKSUM_ERR = 5,
        WATCHDOG_ERR = 6,
    };

private:
    DigitalInOut io;
    InterruptIn io_irq;
    Timer t;
    uint32_t t_pulse_us;
    const static int t_tol_start;    
    const static int t_tol_pulse;
    bool first_time;
    uint64_t data;
    uint32_t chksum;
    uint32_t cnt;
    uint32_t wdt;
    bool eod;
    void init(void);
    void pos_edge(void);
    void neg_edge(void);
};

#endif
DHT11.cpp
/* Copyright (c) 2014 Shigenori Inoue, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "DHT11.h"

// Constructor
DHT11::DHT11(PinName pin) : io(pin, PIN_INPUT, OpenDrain, 1), io_irq(pin)
{
    io_irq.rise(this, &DHT11::pos_edge);
    io_irq.fall(this, &DHT11::neg_edge);
    io_irq.disable_irq();
    t.start();
    first_time = true;
}

// Destructor
DHT11::~DHT11(void) {}

// Constants
const int DHT11::t_tol_start = 2;
const int DHT11::t_tol_pulse = 10;

// Reading the data bits from the DHT11
int DHT11::readData(void)
{
    // Checking the measurement frequency
    if (t.read_ms() < 2000 && first_time == false) {
        t.reset();
        return READ_TOO_OFTEN;
    }
    
    // Initialize
    init();

    // Checking the data bus
    if (io == 0) {
        t.reset();
        return BUS_BUSY;
    }

    // Sending start signal, low signal for around 10 ms
    t.reset();
    do {
        io = 0;
    } while (t.read_ms() < 20 + t_tol_start);
    io = 1;

    // Waiting for the start of the response signal
    t.reset();
    do {
        if (t.read_us() > 100) {
            t.reset();
            return NOT_PRESENT;
        }
    } while (io == 1);

    // Wainting for the start of the ready signal
    t.reset();
    do {
        if (t.read_us() > 100) {
            t.reset();
            return NOT_READY;
        }
    } while (io == 0);

    // Wainting for the end of the ready signal
    t.reset();
    do {
        if (t.read_us() > 100) {
            t.reset();
            return WATCHDOG_ERR;
        }
    } while (io == 1);

    // Starting the pulse width sensing
    // by the use of interruptions
    io_irq.enable_irq();

    do {
        wait_us(100);
        if (wdt > 50) {
            t.reset();
            return WATCHDOG_ERR;
        }
        wdt++;
    } while (eod == false);

    // Calculating the check sum
    chksum = ((data & 0xff00000000) >> 32)
             + ((data & 0x00ff000000) >> 24)
             + ((data & 0x0000ff0000) >> 16)
             + ((data & 0x000000ff00) >> 8);

    if (chksum != (data & 0x00000000ff)) {
        t.reset();
        return CHKSUM_ERR;
    } else {
        t.reset();
        first_time = false;
        return OK;
    }
}

// Extracting humidity data from the received data
int DHT11::readHumidity(void)
{
    return (data & 0xff00000000) >> 32;
}

// Extracting temperature data from the received data
int DHT11::readTemperature(void)
{
    return (data & 0x0000ff0000) >> 16;
}

// Initialization of variables
void DHT11::init(void)
{
    t_pulse_us = 0;
    data = 0;
    chksum = 0;
    cnt = 0;
    wdt = 0;
    eod = false;
    t.reset();
}

void DHT11::pos_edge(void)
{
    // Disabling the interruptions
    io_irq.disable_irq();

    // Initializing the Timer
    t.reset();

    // Enabling the interruptions
    io_irq.enable_irq();
}

void DHT11::neg_edge(void)
{
    // Disabling the interruptions
    io_irq.disable_irq();

    // Reading the positive pulse width
    t_pulse_us = t.read_us();

    // Detecting 0 if the pulse width ranges around 25 us
    if (25 - t_tol_pulse <= t_pulse_us && t_pulse_us <= 30 + t_tol_pulse) {
        // Shifting the data buffer and not adding 1 (because this bit is zero)
        data = data << 1;

        // Counting up the bits
        cnt++;
    }

    // Detecting 1 if the pulse width ranges from 70 us
    else if (70 - t_tol_pulse <= t_pulse_us && t_pulse_us <= 70 + t_tol_pulse) {
        // Shifting the data buffer and adding 1 (because this bit is one)
        data = data << 1;
        data++;

        // Counting up the bits
        cnt++;
    }

    // Detecting the end of Data
    if (cnt < 40) {
        // Enabling the interruptions
        io_irq.enable_irq();
    } else {
        eod = true;
    }
}

測定結果

演算子の優先順位の誤解に起因するバグなど,色々悩みましたが,一応,動くものが完成しました。温度と湿度を測ることができています。これを,MARY基板に移植し,先日作った気圧計もmbedに移植し,Raspberry Piとつなげることで気温・湿度・気圧を記録するWebサーバを作る計画です。

DHT11による気温と湿度をMaple BoardのLCDに表示する

MARY-OB基板(OLEDディスプレイ)には加速度センサも付いているので,地震計も作れるかもしれません。 超音波風速計も追加して,マイ気象台を作るか!?

  1. 温湿度センサモジュールDHT11
  2. DHT11
  3. HD44780 Text LCD

<< Go back to the previous page