LoginSignup
1
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 を使う

1
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
1
0