MicroPython的午睡(27) ラズパイPico、DHT11接続、ソフト現物合わせ

Joseph Halfmoon

Raspberry Pi Pico上のMicroPythonで各種デバイスを制御してみております。今回は「電子工作業界?」定番の温湿度センサといってよいでしょう、DHT-11であります。「とりあえず」今回はソフトでタイミング制御、現物合わせです。動いているみたいなので、ま、いいか。

※「MicroPython的午睡」投稿順 Indexはこちら

(末尾に実験に使ったMicroPythonコード全文を掲げました。)

DHT-11とマイコン間のインタフェースは「ワンワイヤ」と言われますが、Maxim社の1-WIREプロトコルと似せてはいても「ちょっと違う」のではないかと理解しています。ラズパイPicoのMicroPythonには、”onewire” という名前のモジュールが含まれており、これを使えば本来のMaxim式の1-WIREデバイスと接続できるのだと思われます(まだ確かめてませんが。)もしかするとモジュール関数の動作上、うまく違いを「隠ぺい」できたらこのモジュールをDHT-11に流用できるのかもしれません。その辺不明です。

また、MicroPythonの実装によっては DHT系センサ用のソフトウエア・モジュールが含まれているものもあるようです。しかし残念ながらラズパイPico用ではありません。参考のため、MicroPythonの onewire および dht系 該当ドキュメントへのリンクを貼っておきます。いずれもESP8266用の実装です。

1-wire デバイスの制御(ESP8266用MicroPython)

DHT ドライバー(ESP8266用MicroPython)

ラズパイPicoのドキュメントを調べると、Raspberry Pi Pico C/C++ SDKのAppendix Aの中に DHT11, DHT-22, and AM2302 Sensorsという項目があます。こちらに、C++のコードでDHT-11を扱っている例が掲載されています。コードをみるとソフトウエア制御主体のインタフェースです。タイミングをとる部分だけはsleep_ms(), sleep_us()を使っています。

MicroPython上での実装検討

DHT-11のハードウエアからとりあえず温度、湿度が読み取れれば良い、という緩い方針のもと、上記のC++の実装を参考にMicroPython上でもソフト制御でまずはやってみることにいたしました。MicroPythonのtimeモジュール内にもsleep_ms(), sleep_us()などの関数が準備されています。「似た」感じに論理を組めないこともない筈、という浅はかな考えであります。

ちょっと書いてみて、この方法はあまり上手く行きそうにない、と直ぐに諦めました。前回の結果では、ラズパイPicoのMicroPythonではIO処理の1ステートメントの処理に6マイクロ秒くらいかかっていました。DHT-11の場合20マイクロ秒から70マイクロ秒くらいのパルス幅を測定する必要があるのです(あわせて無限ループに落ち込まないような配慮も同時に必要。)ループの中でIO読み取り、条件判断、sleep_usなどを行うと、タイミングによってはパルスの取りこぼしが発生してしまっている感じでした。sleep系関数に待ち時間を与える方法であれば、MicroPython処理系の実行速度とは一応独立にできるなと思ったのですが、そもそもCで書いたときのような速度は出ないので、ちと難しいです。当然か。

まあ、動けばいい、この際「処理系の実行速度依存」でもいい、現物合わせでいい、とユルユルな考えでコードを書きなおし、動作OKとなったものを末尾に掲げてあります。まあ、ユルユルでベタな方法であればこのくらいのタイミングはMicroPythonのソフトでも扱える、と。試行錯誤法は貶められがちですが、これなくして人類の進歩は無かったとも言える最強の方法であります(と言い訳。)

DHT11のテスト用の回路

この間から使っているブレッドボードの回路の小さな空き地にDHT11を搭載しました。DATA線に接続しているのはGP2端子です。GP2にもPin設定でプルアップ指定しています。外づけのプルアップ抵抗なくてもいいか、あるいは、もっと値大きくしてもいいんじゃね、とも考えましたが、以前からブレッドボードに載っていた4.7kΩをそのまま流用しました。ともかくワンワイヤというくらいで簡単(でも本家のMaxim式であれば、デバイスによっては電源線なしでもいける筈。)現物写真はアイキャッチ画像にあります。
RPiPico_DHT11_schematic

DHT-11に接続したGP2端子は、オープンドレイン設定にしてあります。この設定であれば、

  1. DHT-11をスタートさせるためにGP2側からロウパルス(18ms以上)を駆動できる
  2. GP2からハイを出力しておけば、DHT-11側からの駆動は可能な筈
  3. MicroPython上は特に端子を入力に切り替えなくてもDHT-11からの信号駆動は見える筈

回路的にはPinを入力設定しなくてもPinの値は読める筈なのでMicroPythonでもそうであろうと考えました。実際その通りでした。

以下に、DHT-11を叩き起こすための長いロウパルス(18m秒なり)をGP2側から駆動した後の信号波形を観察したものを貼り付けました。最初の細いHighのところだけがGP2側からのHigh出力(オープンドレインのトランジスタがOFFするだけ)で、以降の波形はDHT-11側の駆動です。ちゃんとDHT-11がデータを返してきていることが分かります。

DHT11_WAVE

実行結果

末尾の「タイミング現物あわせ」プログラムの実行結果はこんな感じです。T=は摂氏の温度℃、RH=は相対湿度%です。

T=24.8, RH=57.0
T=24.7, RH=57.0
T=24.8, RH=57.0
T=24.6, RH=57.0
T=24.7, RH=57.0
T=24.7, RH=58.0
T=24.7, RH=58.0
T=24.5, RH=58.0
T=24.6, RH=58.0
T=24.6, RH=58.0
T=24.7, RH=58.0
T=24.5, RH=58.0

「一応」温度、湿度が読み取れたみたいなので結果オーライ。しかし処理系の速度にガッチリ依存です。何か設定を変えたりすると動かなくなる(再現物あわせ)こと必定。他にもいろいろやる事あるなら、やはりハードウエアできっちりタイミングとれる方法を考えるべきかと。今日は、ま、いいか。

MicroPython的午睡(26) ラズパイPico、入力端子からの割り込みの反応時間 へ戻る

MicroPython的午睡(28) ラズパイPico、lightとdeepなsleep へ進む

DHT-11から温度/湿度を読み取る「現物合わせ」コード
from machine import Pin 
import time

class Dht11:
    """DHT11 temperature/humidity sensor IF class

    """
    def __init__(self, pinN):
        self.dht11 = Pin(pinN, Pin.OPEN_DRAIN, Pin.PULL_UP)
        self.dht11.value(1)
        self.buf = []
        self.temperature = None
        self.humidity = None
        self.crc = False
        self.crcC = None
        self.crcR = None
        self.ERROR = 0

    def startSignal(self):
        self.dht11.value(0)
        time.sleep_ms(19)
        self.dht11.value(1)
        time.sleep_us(20)
    
    def waitSIG(self, sig, timeOut):
        count = 0
        while self.dht11.value() == sig:
            count += 1
            if count > timeOut:
                return False
        return True
    
    def bitRead(self):
        while self.dht11.value() == 0:
            pass
        if self.dht11.value() == 0:
            return 0
        if self.dht11.value() == 0:
            return 0
        if self.dht11.value() == 0:
            return 0
        if self.dht11.value() == 0:
            return 0
        if self.dht11.value() == 0:
            return 0
        if self.dht11.value() == 0: #6
            return 1
        if self.dht11.value() == 0:
            return 1
        return 1
    
    def b2B(self, lis):
        work = 0
        for i in lis:
            work <<=1
            work += i
        return work
    
    def convertData(self):
        rhI = self.b2B(self.buf[0:7])
        rhD = self.b2B(self.buf[7:15])
        tmI = self.b2B(self.buf[15:23])
        tmD = self.b2B(self.buf[23:31])
        self.crcR = self.b2B(self.buf[31:39])
        self.crcC = (rhI + rhD + tmI + tmD) & 0xFF
        if self.crcR == self.crcC:
            self.crc = True
        else:
            self.crc = False
        self.temperature = (tmI & 0x7F) + (tmD / 10)
        if tmI & 0x80:
            self.temperature *= -1
        self.humidity = rhI + (rhD / 10 )
        self.buf.clear()

    def read(self):
        self.ERROR = 0
        self.startSignal()
        if not self.waitSIG(0, 100):
            self.ERROR = 1
            return False
        if not self.waitSIG(1, 1000):
            self.ERROR = 2
            return False
        if not self.waitSIG(0, 1000):
            self.ERROR = 3
            return False
        for idx in range(40):
            b1 = self.bitRead()
            self.buf.append(b1)
        self.convertData()
        return True

dht11 = Dht11(2)

while True:
    if dht11.read() and dht11.crc:
        print("T={0:3.1f}, RH={1:3.1f}".format(dht11.temperature, dht11.humidity))
    else:
        print("ERROR=", str(dht11.ERROR))
    time.sleep(5)