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 版も (ちょいちょい失敗しながらも) 値が取得できるようになりました。
//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++;
}
※タイミングの問題なので、環境によっては上記の値でもうまく取得できない可能性があります。適宜調整が必要です。
上記の 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 が使われています。
//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 );
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 版の意図は次のようなことだと推測しました。
# 事前準備として念のため 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 版を以下のように変更しても値を取得できたからです。
//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 版のように変更してみたのですが、タイミングがずれてしまい、値を取得できなくなりました。
# 事前準備として念のため 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 秒に変更してみました。 変更前よりは値の取得に失敗する回数が減ったような気がします (気のせいかもしれませんが…)。
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);
}
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秒間隔にしてみた
Python 版のコードでは、入力時の設定で GPIO 側の内部プルアップ抵抗を有効にしていますが、C 版にはありません。
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 だけを利用された場合でも大丈夫なように、プルアップ抵抗を有効にしておくのがお作法なのかもしれません。
//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 となっています (同じピンを意味しています)。