作成日: 2017/11/29

Raspberry Pi 3 Model B と OSOYOO 初心者キットの温湿度センサー (DHT11) のサンプル

温湿度センサー

Raspberry Pi 3 Model B で電子工作なるものを体験したくて、「OSOYOO(オソヨー)」の初心者向け電子工作キット「オソヨー電子工作キット(改良版) for Raspberry Pi 3 2 Model B」 を購入しました。

このキットには、ラズパイとつないで遊べる LED やセンサー等色々付いており、http://osoyoo.com/ からキットに対応する配線図やお試し用のソースコードを参照・ダウンロードできるようになっています。 さっそく温度を測ってみたくて、付属の温湿度センサーを使って試してみました。

キットに付いている温湿度センサーは、DHT11 という温湿度センサーを基盤に取り付けてモジュール化したもので、基盤には抵抗と LED がくっついており、ラズパイにそのままつなげます。

ソースコードを先のサイトからダウンロードして、C 版のサンプル (dht11.c) と、Python 版のサンプル (dht11.py) のそれぞれを動かしてみたのですが、C 版だけ値を取得できませんでした。

Interfacing Temperature and Humidity Sensor (DHT11) With Raspberry Pi という記事を見つけ、色々調べていると、センサから 0 か 1 のどちらが送られてきたかを判別する際のしきい値が、タイミングの関係で、私の環境には小さ過ぎたためでした。値をそれなりに変更した結果、C 版も (ちょいちょい失敗しながらも) 値が取得できるようになりました。

dht11.c の該当部分 (変更前)
        //ignore first 3 transitions
        if ( (i >= 4) && (i % 2 == 0) )
        {
            //shove each bit into the storage bytes
            dht11_dat[j / 8] <<= 1;
            if ( counter > 16 )
                dht11_dat[j / 8] |= 1;
            j++;
        }
(変更後)
        //ignore first 3 transitions
        if ( (i >= 4) && (i % 2 == 0) )
        {
            //shove each bit into the storage bytes
            dht11_dat[j / 8] <<= 1;
            if ( counter > 28 ) // この値を 16 から 28 に変更
                dht11_dat[j / 8] |= 1;
            j++;
        }

※タイミングの問題なので、環境によっては上記の値でもうまく取得できない可能性があります。適宜調整が必要です。

C と Python の初期化の違い

上記の Interfacing Temperature and Humidity Sensor (DHT11) With Raspberry Pi には DHT11 と通信するための手順表 (タイミング表?。正式名がわからない…) も載っており、ソースと突き合わせながら見ていたのですが、C 版と Python 版では微妙に違うところがあります。

センサから値を取得する際の初期手順が、C 版は手順表に則ったコードになっているのですが、Python 版では手順との関係がよくわかりません。

手順表によれば、出力を LOW にして少なくとも 18ms保つことでセンサーへの通信開始の合図となり、 その後 HIGH で 20 から 40 μs保ってセンサーからの応答を待つことになっています。

C 版はその通りに書かれているのですが、Python 版では HIGH で 50ms、その後 LOW で 20msとなっています。

※GPIO の操作には、C 版は Wiring Pi、Python 版は RPi.GPIO が使われています。

dht11.c の該当部分
    //pull pin down to send start signal
    pinMode( DHTPIN, OUTPUT );
    digitalWrite( DHTPIN, LOW );
    delay( 18 );
    //pull pin up and wait for sensor response
    digitalWrite( DHTPIN, HIGH );
    delayMicroseconds( 40 );
dht11.py の該当部分
    GPIO.setup(DHTPIN, GPIO.OUT)
    GPIO.output(DHTPIN, GPIO.HIGH)
    time.sleep(0.05)
    GPIO.output(DHTPIN, GPIO.LOW)
    time.sleep(0.02)

Linux 上でポーリングして値を取得するサンプルのため、ソースに書いたとおりのタイミングで動作するわけではないのでしょうが、それにしても内容が違うので混乱してしまいました。C 版 および Python 版のソースを色々いじって試してみた結果、Python 版の意図は次のようなことだと推測しました。

dht11.py の該当部分
    # 事前準備として念のため HIGH にしておく。待ち時間は適当に…
    GPIO.output(DHTPIN, GPIO.HIGH)
    time.sleep(0.05)
    # 通信開始の合図を送る。
    GPIO.output(DHTPIN, GPIO.LOW)
    # Python 上で 40 μsの待ちを指定しても期待通りにはならないだろうし、
    # ポーリングして取得するため HIGH にしておかなくても受信できるので、18msに少し加えた時間だけ待つことにしよう
    time.sleep(0.02)

上の Python 版に追記したコメントで「ポーリングして取得するため HIGH にしておかなくても受信できる」と書いたのは、 C 版を以下のように変更しても値を取得できたからです。

dht11.c の該当部分
    //pull pin down to send start signal
    pinMode( DHTPIN, OUTPUT );
    digitalWrite( DHTPIN, LOW );
    delay( 18 );
    // HIGHにせず待つ
    ////digitalWrite( DHTPIN, HIGH );
    delayMicroseconds( 40 );
    //prepare to read the pin
    pinMode( DHTPIN, INPUT );

ちなみに、、、Python 版を C 版のように変更してみたのですが、タイミングがずれてしまい、値を取得できなくなりました。

dht11.py の該当部分 (正しく書いたつもりだけれどうまくいかない…)
    # 事前準備として念のため HIGH にしておく。待ち時間は適当に…
    GPIO.output(DHTPIN, GPIO.HIGH)
    time.sleep(0.05)
    # 通信開始の合図を送る。
    GPIO.output(DHTPIN, GPIO.LOW)
    time.sleep(0.018)
    # HIGH にして待つ
    GPIO.output(DHTPIN, GPIO.HIGH)
    time.sleep(0.00004)

ポーリング方式だと Linux 上では、ましてや Python で実行すると、タイミングで悩まされるのですね…。

温湿度センサーのサンプリング間隔

DHT11 についてさらに調べていると、サンプリング間隔は 2 秒以上と書いてあるページを見つけました。 サンプルでは C 版、Python 版ともに 1 秒間隔で実行しているため、実行間隔を 2 秒に変更してみました。 変更前よりは値の取得に失敗する回数が減ったような気がします (気のせいかもしれませんが…)。

dht11.c の該当部分 (変更前)
int main( void )
{
    if ( wiringPiSetup() == -1 )
    {
        fprintf(stderr,"Can't init wiringPi: %s\n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    print_info();
    while ( 1 )
    {
        read_dht11_dat();
        delay(1000);//wait ls to refresh
    }

    return(0);
}
(変更後)
int main( void )
{
    if ( wiringPiSetup() == -1 )
    {
        fprintf(stderr,"Can't init wiringPi: %s\n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    print_info();
    while ( 1 )
    {
        read_dht11_dat();
        delay(2000);//wait ls to refresh    // 2秒間隔にしてみた
    }

    return(0);
}
dht11.py の該当部分 (変更前)
def main():
    print "Raspberry Pi wiringPi DHT11 Temperature test program\n"
    while True:
        result = read_dht11_dat()
        if result:
            humidity, temperature = result
            print "humidity: %s %%,  Temperature: %s C" % (humidity, temperature)
        time.sleep(1)
(変更後)
def main():
    print "Raspberry Pi wiringPi DHT11 Temperature test program\n"
    while True:
        result = read_dht11_dat()
        if result:
            humidity, temperature = result
            print "humidity: %s %%,  Temperature: %s C" % (humidity, temperature)
        time.sleep(2)   # 2秒間隔にしてみた

GPIO 入力時のプルアップ抵抗

Python 版のコードでは、入力時の設定で GPIO 側の内部プルアップ抵抗を有効にしていますが、C 版にはありません。

dht11.py の該当部分
GPIO.setup(DHTPIN, GPIO.IN, GPIO.PUD_UP)

これまた色々調べてみると、例えば、GPIO に単純なスイッチをつなげた場合、スイッチが OFF の時は、当該 GPIO がどこにもつながっていないのと同じになり、電位が不安定になるようです。

ただ、ここでつなげているのはセンサーですし、自分の理解の限りでは、このサンプルで使っている DHT11 & 基盤のモジュールの場合、電位が不安定になることはなさそうに思えます。

しかし、DHT11 を利用する他の Python のサンプルも調べてみると、やはり内部プルアップ抵抗を有効にしていました。

自分の理解に自信がないため、とりあえず、C 版も同じようにしておくことにしました。また、Python 版にあわせ、出力時に事前準備として HIGH にしておくコードも追加しました (これも不要なのかも)。

※ちなみに、C 版、Python 版それぞれについて、内部抵抗の設定を「無し」、「プルアップ」、「プルダウン」と試してみましたが、いずれも動作しました。 このセンサーモジュールと GPIO ピン 14 の組合せでは、設定しないのが正しいのかもしれません。

追記:Arduino の DHTxx 系を扱うライブラリを見ていたら、やはり内部のプルアップ抵抗を有効にしていました。 試しに、内部抵抗の設定を「無し」にしても動くのですが、よく考えると、この DHT11 & 基盤のモジュールの場合、基盤に抵抗がくっついています(おそらくプルアップ抵抗)。 抵抗を付けず、素の DHT11 だけを利用された場合でも大丈夫なように、プルアップ抵抗を有効にしておくのがお作法なのかもしれません。

dht11.c の該当部分
    //pull pin down to send start signal
    pinMode( DHTPIN, OUTPUT );

    // 事前準備として念のため HIGH にしておく。待ち時間は適当に…
    digitalWrite( DHTPIN, HIGH );
    delay( 50 );

    digitalWrite( DHTPIN, LOW );
    delay( 18 );
    //pull pin up and wait for sensor response
    digitalWrite( DHTPIN, HIGH );
    delayMicroseconds( 40 );
    //prepare to read the pin
    pinMode( DHTPIN, INPUT );

    // GPIO 側の内部プルアップ抵抗を有効にする
    pullUpDnControl( DHTPIN, PUD_UP );

参考にさせていただいたのは、以下のサイトです。

修正後のサンプルソース

元のソースは OSOYOO 様のサイト からダウンロードし、それを変更しました。

C 版は別途 Wiring Pi が必要です。なお、サンプルソースは以下のコマンドでビルドできます。

gcc -Wall -o dht11 dht11.c -lwiringPi

また、Python 版は別途 RPi.GPIO が必要です。

※センサーモジュールをつなぐ DATA のための GPIO ピンは 14 ですが、C 版では Wiring Pi の番号体系で指定するため 15 となっています (同じピンを意味しています)。

接続図 (Fritzing で作成)

おまけ: DHT11 Humitidy and Temperature Sensor Module (fzpz)