Rubyと一緒(8) GR-CITRUS、DHT11、ソフトで読み取り、湿度だけ?

Joseph Halfmoon

特価品(見切り品?)のGR-CITRUSボード(ルネサスRX631搭載)で組み込みRuby(mruby)してます。今回はMicroPython上で動いている「フルソフトウエア」のDHT11センサ読み取りプログラムのRubyへの移植にトライ。しかし湿度の読み取りだけ成功?したところで止めました。タイミング違い過ぎ。

※Rubyと一緒 投稿順indexはこちら

今回は、以下の別シリーズ記事にて動作確認済のMicroPythonスクリプトのRuby(mruby)への移植トライアルです。元になったMicroPythonソースは以下の記事の後ろの方に貼り付けてあります。

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

DHT11は、Arduinoなどへの接続で定番の温湿度センサです。インタフェースはDHT11規格とでもいうべき「1-wire風」の双方向通信です。1本の線でマイコン側からのトリガを受信すると今度はDHT11が自分のタイミングで、湿度、温度、CRCを送信してくるもの。

通信スピードは結構遅いので、MicroPython上では遅延関数以外のタイミングを用いず、ソフトウエアで「タイミング」をとって通信してました。特段ハードウエアを使っていないので、「タイミング」さえMicroPythonと合わせられればRubyちゃんでも通信可能な筈。ただ、ラズパイPico上のMicroPythonで元のプログラムを作成したときはオシロで波形を見ながら「現物あわせでタイミングを調整」してます。成り行きまかせ。ただ、Pico上のMicroPythonはそこそこ速かったので「なんとかできた」感じ。Rubyちゃんではどうか?

Rubyちゃん上での実装、湿度だけ

最初、湿度、温度、CRC全てを受信すべくMicroPythonコードをほぼほぼ1対1な形でRubyに移植しました。Ruby素人は沢山コーディングミスをして結構手間取りました。なにせ使用している、Rubic処理系のエラー表示が素っ気なさすぎます。buildError
前にも書きましたが、どこにエラーがあるのかサッパリです。まあ、Runtimeエラーの方が多少筋がいいですが、「多少」です。こんな感じ。RuntimeError0

苦難の道のりを乗り越え、ようやく移植完了。当然ながら動きはしません、はい。

そこからはオシロでタイミング調整です。成り行きまかせの現物あわせ。なおDHT11との通信タイミングを外部から観察するのに

    • DHT11はピン4で双方向通信(以下のオシロ画面で黄色C1)
    • 通信タイミングを知るためにピン6にパルス出力(青色C2)

を使っています。まずはフル移植直後。HWtiming

DHT11は生きてなにやらデータを送ってきている感じですが、受け取る側のスクリプト、青色パルスの方はほとんどデータ終わってから動き始めてないかい?

MicroPythonのコードはデータシートの波形に従って「マイコンとDHT11間の頭出しプロトコル」的なところを実装していたのですが、そんなものを受け入れる余裕はRubyちゃんには無さそうでした。仕方ないのでバッサリやって頭を合わせました。こんな感じ。HWtiming2

青色パルスの最初の一発は頭出しのところですが、引き続く4発は4ビットを「捕獲」するためのタイミングです。全然ダメじゃん。捕獲側が4回回っている間にDHT11の方は5ビット以上送ってきてるじゃん。ぜんぜん間に合ってねずら。

タイミング表示の青パルスの生成だけでも負荷重そうだったのでさらに軽減。最初の8ビットの頭とお尻だけに青パルスを入れ、ビットを拾うことに。最初の太いハイは無視して次の細いパルスが0、太いパルスが1ということであります。HWtimingFirst8bitSYNC

 

ソフトでやることはこのパルス幅を測って0/1決定すれば良いのですが、MicroPythonで余裕をもってできていた観察ができません。forループでビット処理を回転させることすら負荷重いみたい。それでもかなり無理やりだけれども最初の8ビットは受信できる感じになってきました(でも8ビット以上長くするとどこかでSYNCハズレそうな感じがヒシヒシと。)

最初の8ビットに%表示の湿度が載っているので、そこだけ取り出してみることにいたしました。

実験に使用したRubyスクリプト

現物あわせのやっつけが以下に。結構あちこちにコメントアウトしてあるのは、当初組み込んであった、温度やCRC計算やらの残骸デス。「ハードウエア」でのデバッグ用のコードもありで、かなり汚い。

#!mruby

class Dht11
    def initialize(pinname)
        @pin = pinname
        @buf = []
        @crcR = 0
        @crcC = 0
        @crc = true
        @temperature = 0
        @humidity = 0
        @error = 0
        pinMode(@pin, 3) # output, opendrain
        pinMode(6, 1)    # Hardware debug only
    end

    def readCRC
        return @crc
    end

    def readERROR
        return @error
    end

    def readTemperature
        return @temperature
    end

    def readHumidity
        return @humidity
    end

    def readERROR
        return @error
    end

    def readBUF
        return @buf.join
    end

    def clearBUF
        @buf = []
    end

    def markSignal
        digitalWrite(6, 0)
        digitalWrite(6, 1)
        digitalWrite(6, 0)
    end

    def startSignal
        digitalWrite(@pin, 0)
        delay(19) #min low pulse > 18msec
        digitalWrite(@pin, 1)
    end

    def waitSIG(sig, timeOut)
        count = 0
        while digitalRead(@pin) == sig do
            count +=1
            if count > timeOut then
                return false
            end
        end
        return true
    end

    def bitRead
        while digitalRead(@pin) == 0 do
        end
        return 0 if digitalRead(@pin) == 0
        return 1 if digitalRead(@pin) == 0
        return 1
    end

    def b2B(lis)
        work = 0
        for i in lis do
            work <<=1
            work += i
        end
        return work
    end

    def convertData
        rhI = b2B(@buf[0..7])
#        rhD = b2B(@buf[8..15])
#        tmI = b2B(@buf[16..23])
#        tmD = b2B(@buf[24..31])
#        @crcR = b2B(@buf[32..39])
#        @crcC = (rhI + rhD + tmI + tmD) & 0xFF
#        if @crcR == @crcC then
#            @crc = true
#        else
#            @crc = false
#        end
#        @temperature = (tmI & 0x7F) + (tmD / 10)
#        if (tmI & 0x80) != 0 then
#            @temperature *= -1
#        end
        @humidity = rhI
#        @humidity = rhI + (rhD / 10)
#        @buf = []
    end

    def read
        @error = 0
        startSignal
        markSignal
        if !waitSIG(0, 100) then
            @error = 1
            return false
        end
        markSignal
        b1 = bitRead #bit 0
        @buf.push(b1)
        b1 = bitRead #bit 1
        @buf.push(b1)
        b1 = bitRead #bit 2
        @buf.push(b1)
        b1 = bitRead #bit 3
        @buf.push(b1)
        b1 = bitRead #bit 4
        @buf.push(b1)
        b1 = bitRead #bit 5
        @buf.push(b1)
        b1 = bitRead #bit 6
        @buf.push(b1)
        b1 = bitRead #bit 7
        @buf.push(b1)
        markSignal
        convertData
        return true
    end

end

usb = Serial.new(0, 9600)
usb.println("DHT11 Read, Pin.4")
dht11pin=4
dht11 = Dht11.new(dht11pin)

loop_interval = 2000
while true do
    usb.println("DHT11 temperature & humidity")
    if dht11.read then
        usb.println("BUF:" + dht11.readBUF)
        dht11.clearBUF
        usb.println("HUMIDITY:   " + dht11.readHumidity.to_s)
        usb.println("ERROR:" + dht11.readERROR.to_s)
    end
    delay(loop_interval)
end
実験に使用した回路

遅ればせながらの回路図が以下に。DHT11_RubyDUTschematic

現物写真が以下に。DHT11_RubyDUT

実際にスクリプトから報告されている湿度が以下に。ReadHumidity

息をDHT11に吹きかけたら値が大きくなったので、湿度センサのデータを拾っているみたいっす。しまらんなあ。

Rubyと一緒(7) GR-CITRUS、出力端子(OD)、読み取りも出来るよね、念のため に戻る

Rubyと一緒(9) GR-CITRUS、DHT11、温湿度読み取りOK?、危ういタイミング へ進む