LoginSignup
0

More than 1 year has passed since last update.

BLEモジュールnRF52による温湿度センサDHT11の制御

Last updated at Posted at 2021-11-19

記事の概要

NordicのBLEモジュールを使用して、シリアル単線(1-wireライク)通信方式の温湿度センサDHT11を制御します

他のマイコンでもGPIO端子設定を各マイコン向けの関数に置き換えれば、データ取得できるはずです。

準備

nRF52840

スイッチサイエンスさんで開発ボードを購入しました。

nRF52840 MDBT50Q 評価ボード

今回はGPIO端子のP1.09とDHT11を接続しました。

DHT11

秋月電子でDHT11を購入しました。

DHT11使用温湿度センサモジュール
DHT11温湿度モジュール(Temperature and Humidity Sensor)

シリアル単線(1-wireライク)通信の仕様

通信仕様の詳細は以下のマニュアルをご覧ください。
https://akizukidenshi.com/download/ds/aosong/DHT11_20180119.pdf

IO端子の設定

シリアル単線(1-wireライク)通信は1つのIO端子だけで、送信と受信を行わないといけません。
よってGPIO端子の機能を小まめに入力と出力に切り替えます。

nRF52とDHT11が通信していない時はPull Upした入力端子か、High出力端子のどちらかにしておきます。
GPIO端子のHighからLowへの立ち下がりを合図にセンサとの通信が開始するので、IO端子がLOWだと通信開始ができません。

サンプルプログラム中では初期化時にHigh出力設定にしています。

cpu_set.c
static void gpio_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    //-----------------------------------------------
    // DHT11 BUS
    nrf_gpio_cfg_output(DHT11_BUS_PIN);
    nrf_gpio_pin_set(DHT11_BUS_PIN); // default:High

    //nrf_gpio_cfg_input(DHT11_BUS_PIN, NRF_GPIO_PIN_PULLUP);
}

通信開始

通信を開始するには、最初に以下の図のような信号を送受信します。
赤が出力で、緑が入力を意味します。

image.png

通信開始信号は、最低で10ms、最大で50ms、標準では20msのLow出力を送信します。

static void DHT11_start(void)
{
    // setting OUTPUT
    nrf_gpio_cfg_output(DHT11_BUS_PIN);

    nrf_gpio_pin_set(DHT11_BUS_PIN);
    nrf_delay_ms(200);
    nrf_gpio_pin_clear(DHT11_BUS_PIN);
    nrf_delay_ms(30); // min:10ms, typ:20ms, max:50ms
    nrf_gpio_pin_set(DHT11_BUS_PIN);
    nrf_delay_us(40);
}

出力が終わったら、端子機能を入力端子に切り替えます。
準備完了信号はLow入力が83usec、High入力が87usecの信号です。
特に時間を測る必要がないので、Low入力→High入力→Low入力の順番で信号を受信できたかを監視します。

static bool DHT11_ready_for(void)
{
    uint8_t count = 0;

    // setting INPUT
    nrf_gpio_cfg_input(DHT11_BUS_PIN, NRF_GPIO_PIN_PULLUP);

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    // High入力待機
    count = 0;
    while(0 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    return true;
}

もし200usecを超えても入力信号に変化がなければエラーと判定して、通信を中止します。

データ受信

準備完了信号の受信が終わると、続いてデータ受信が始まります。
データは1bitずつ受信されます。
Low入力とHigh入力の長さでbitが0か1かを判定します。

image.png

// 端子設定が入力で、端子状態が0で開始することを前提とする
static uint8_t DHT11_get_bit(void)
{
    uint8_t count = 0;

    // High入力待機
    count = 0;
    while(0 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return 0xFF;
    }

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return 0xFF;         
    }

    // bit 0 or 1 判定
//    if(count > 65 && count < 75)
//        return 1;
//    else if(count > 20 && count < 28)
//        return 0;
//    else
//        return 0xFF;
    if(count > 28)
        return 1;
    else if(count > 10)
        return 0;
    else
        return 0xFF;
}

データは5byte分あるので、合計で40回bitの0と1の判定を行います。
1バイト目は湿度の整数値、2バイト目は湿度の小数点以下の値です。
3バイト目は温度の整数値、4バイト目は温度の小数点以下の値です。

5バイト目は1バイト目から4バイト目までの和の下位8bitの値で、パリティと呼びます。
データを正常に受信できたかを確認するのに使用します。
1バイト目から4バイト目までの和を計算して、パリティと一致していれば、全データが正常に受信できていたと判定できます。
今回は使用しませんでした。

ここで注意しないといけないのは、各バイトは8bit目から1bit目の順番でデータ受信することです。
逆の順番で計算すると誤った計測値になってしまいます。
8回データを受信するごとに1バイトにまとめます。

static uint8_t DHT11_get_byte(void)
{
    uint8_t byte_data = 0;
    uint8_t bit_data = 0;

    for(int i=0; i<8; i++)
    {
        bit_data = DHT11_get_bit();
        if(0xFF == bit_data)
            return 0xFF;
        else
            byte_data |= (bit_data) << (7-i);
    }

    return byte_data;
}

通信終了

通信が完了したら、IO端子を出力端子に設定して、High出力にします。

static void DHT11_end(void)
{
    // setting OUTPUT
    nrf_gpio_cfg_output(DHT11_BUS_PIN);

    nrf_gpio_pin_set(DHT11_BUS_PIN);
}

エラーで通信途中で終了してしまった場合も、出力端子に設定してHigh出力にするのを忘れないようにします。

まとめ

以上を実行すると、DHT11からのデータを取得できます。

static bool DHT11_get_data(void)
{
    uint8_t dht11_data_buf[5] = {0};

    DHT11_start();
    if(false == DHT11_ready_for())
    {
        DHT11_end();
        return false;
    }

    for(int i=0; i<5; i++)
    {
        dht11_data_buf[i] = DHT11_get_byte();
        if(0xFF == dht11_data_buf[i])
        {
            nrf_delay_ms(10); //センサからの送信完了を待機
            DHT11_end();
            return false;
        }
    }

    // データ取得成功した場合
    humidity_high    = dht11_data_buf[0];
    humidity_low     = dht11_data_buf[1];
    temperature_high = dht11_data_buf[2];
    temperature_low  = dht11_data_buf[3];
    parity           = dht11_data_buf[4];

    DHT11_end();
    return true;
}

ハマった点

取得できる計測値が異常

データ受信時、各バイトは8bit目から1bit目の順番で処理しないといけないのに、誤って以下のように1bit目から8bit目の順番で処理していたのが原因でした。

    for(int i=0; i<8; i++)
    {
        byte_data |= (bit_data) << i;
    }

入力信号の長さを正しくカウントできない

データ受信においてbit判定する際、High期間が15usec前後と45usec前後のいずれかの値になってしまいました。
仕様書では0信号は最低23usec以上、1信号は最低68usec以上、High期間を保持するはずです。
nrf_delay_us(1)によりusecの長さをカウントしているのですが、うまくいきません。

何度やってもうまくいかないので、やむなく仕様書とは異なる判定をすることにしました。
28usecを超えるならbit 1で、28usec以下かつ10usecを超えるならばbit 0と判定します。

    if(count > 28)
        return 1;
    else if(count > 10)
        return 0;
    else
        return 0xFF;

頻繁にエラーになる

準備完了信号受信のカウント200usecを超えてしまうことが頻繫に発生しました。
他にもデータ受信時にも200usecを超えることもよく起きます。
色々と工夫しましたが、測定が安定することはありませんでした。

よってこれらのエラーが起きた場合は、成功するまで何度もやり直すようにしました。

static uint8_t dht11_error_count;
void DHT11_certainty_get_data(void)
{
    uint8_t retry_count = 0;

    while(1)
    {        
        // エラーの場合、100回までやり直す
        if(false == DHT11_get_data())
        {
            retry_count += 1;
            if(100 == retry_count)
            {
                dht11_error_count += 1;
                return;
            }
        }
        else
        {
            printf("temperature is %d.%d C\n",temperature_high, temperature_low);
            printf("humidity is %d.%d %RH\n",humidity_high, humidity_low);
            return;
        }
    }
}

サンプルプログラム

static uint8_t humidity_high;
static uint8_t humidity_low;
static uint8_t temperature_high;
static uint8_t temperature_low;
static uint8_t parity;

// DHT11
#define DHT11_BUS_PIN (41) //P1.09 //32+09=41

static void gpio_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);

    //-----------------------------------------------
    // DHT11 BUS
    nrf_gpio_cfg_output(DHT11_BUS_PIN);
    nrf_gpio_pin_set(DHT11_BUS_PIN); // default:High

    //nrf_gpio_cfg_input(DHT11_BUS_PIN, NRF_GPIO_PIN_PULLUP);
}



static void DHT11_start(void)
{
    // setting OUTPUT
    nrf_gpio_cfg_output(DHT11_BUS_PIN);

    nrf_gpio_pin_set(DHT11_BUS_PIN);
    nrf_delay_ms(200);
    nrf_gpio_pin_clear(DHT11_BUS_PIN);
    nrf_delay_ms(30); // min:10ms, typ:20ms, max:50ms
    nrf_gpio_pin_set(DHT11_BUS_PIN);
    nrf_delay_us(40);
}

static bool DHT11_ready_for(void)
{
    uint8_t count = 0;

    // setting INPUT
    nrf_gpio_cfg_input(DHT11_BUS_PIN, NRF_GPIO_PIN_PULLUP);

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    // High入力待機
    count = 0;
    while(0 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return false;
    }

    return true;
}

// 端子設定が入力で、端子状態が0で開始することを前提とする
static uint8_t DHT11_get_bit(void)
{
    uint8_t count = 0;

    // High入力待機
    count = 0;
    while(0 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return 0xFF;
    }

    // Low入力待機
    count = 0;
    while(1 == nrf_gpio_pin_read(DHT11_BUS_PIN))
    {
        nrf_delay_us(1);
        count += 1;
        if(count > 200)
            return 0xFF;         
    }

    // bit 0 or 1 判定
//    if(count > 65 && count < 75)
//        return 1;
//    else if(count > 20 && count < 28)
//        return 0;
//    else
//        return 0xFF;
    if(count > 28)
        return 1;
    else if(count > 10)
        return 0;
    else
        return 0xFF;
}

static uint8_t DHT11_get_byte(void)
{
    uint8_t byte_data = 0;
    uint8_t bit_data = 0;

    for(int i=0; i<8; i++)
    {
        bit_data = DHT11_get_bit();
        if(0xFF == bit_data)
            return 0xFF;
        else
            byte_data |= (bit_data) << (7-i);
    }

    return byte_data;
}

static void DHT11_end(void)
{
    // setting OUTPUT
    nrf_gpio_cfg_output(DHT11_BUS_PIN);

    nrf_gpio_pin_set(DHT11_BUS_PIN);
}

static bool DHT11_get_data(void)
{
    uint8_t dht11_data_buf[5] = {0};

    DHT11_start();
    if(false == DHT11_ready_for())
    {
        DHT11_end();
        return false;
    }

    for(int i=0; i<5; i++)
    {
        dht11_data_buf[i] = DHT11_get_byte();
        if(0xFF == dht11_data_buf[i])
        {
            nrf_delay_ms(10); //センサからの送信完了を待機
            DHT11_end();
            return false;
        }
    }

    // データ取得成功した場合
    humidity_high    = dht11_data_buf[0];
    humidity_low     = dht11_data_buf[1];
    temperature_high = dht11_data_buf[2];
    temperature_low  = dht11_data_buf[3];
    parity           = dht11_data_buf[4];

    DHT11_end();
    return true;
}

static uint8_t dht11_error_count;
static void DHT11_certainty_get_data(void)
{
    uint8_t retry_count = 0;

    while(1)
    {        
        // エラーの場合、100回までやり直す
        if(false == DHT11_get_data())
        {
            retry_count += 1;
            if(100 == retry_count)
            {
                dht11_error_count += 1;
                return;
            }
        }
        else
        {
            printf("temperature is %d.%d C\n",temperature_high, temperature_low);
            printf("humidity is %d.%d %RH\n",humidity_high, humidity_low);
            return;
        }
    }
}


int main(void)
{
    gpio_init();

    // Enter main loop.
    for (;;)
    {
        DHT11_certainty_get_data();
        nrf_delay_ms(1000);

        idle_state_handle();
    }
}

感想

使用した感想は、通信制御のタイミングの取り方が難しいというものでした。
今回は、正確に1usecを測れずに四苦八苦したり、計測が頻繫に失敗しました。
それに、もし計測途中に割り込みが発生したら、タイミングがずれてしまいます。

GPIO端子を1個だけしか使わず、I2Cモジュールが使えない場合も使用できるのは魅力ですが、逆に言えば、マイコンにI2Cモジュール機能があり、端子の数が足りているならば、普通のI2Cセンサの方を使いたいと思いました

参考

今回の記事を作成するにあたり、以下を参照しました。

Nordic nRF52840 で温度湿度センサ DHT11 を使う

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
What you can do with signing up
0