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

Joseph Halfmoon

特価品(見切り品?)のGR-CITRUSボード(ルネサスRX631搭載)で組み込みRuby(mruby)してます。前回は定番の温湿度センサDHT11の読み取りプログラムを作成しましたが、湿度の読み取りまでで止まってました。今回は温度も読み取れるようにしてみました。成り行きで現物合わせのタイミング制御、危ないっす。

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

ソフトウエア制御のDHT11読み取り3レベル

DHT11は、Arduinoなど「電子工作業界」定番の温湿度センサですが、1-wire的な半二重双方向通信(マイコンからDHT11へは測定のトリガをかけるだけだけれども)で、かつ速度も遅いので「ソフトウエア制御」でインタフェースすることが多いのではないかと思われます。しかし同じソフトウエアでタイミングを取るといっても処理系の性能によってレベルが違う、と。

    1. 機械語命令コードで制御している場合(C言語などで記述されているライブラリの使用。Arduino環境、あるいはMicroPythonでのDHT向けライブラリの使用)
    2. DHT11の「プロトコル」速度にそこそこ対応できる速度の処理系(MircoPythonのPython記述によるソフトウエア制御)の場合
    3. DHT11の「プロトコル」速度にキチンと追従するのが難しい速度の処理系(今回のmruby)で、無理やり現物あわせの場合

上記の1のケースは何度となくやってますが、タイミングが遅れるということもなく安定。最近も以下の別シリーズで、MicroPython処理系でやってみています。

MicroPython的午睡(106) ESP32版、DHT11を接続してみる

ライブラリが存在しなくても、処理系の速度がそこそこ速いのであれば、以下の別シリーズ記事のように、MicroPython記述でタイミング制御しても安定してデータ受信(CRC検証までOK)できてます。

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

しかし今回のGR-CITRUSボード上のmrubyは、ソフトウエア的にタイミングがクリティカルです。ちょっとループ処理などでオーバヘッドがあるとデータを取りこぼしてしまいます。また、タイミングの頭出し(シンクロナイズ)も困難です。失敗するとこんな感じ。‐25℃ってなによ?きょうはちょっと蒸し蒸しする感じなんだけれども。

TimingTooCritical

温度、湿度対応に「改良」したコード

DHT11は先に湿度16ビット、後に温度16ビット、末尾に検証用のCRC8ビットというデータ構成です。前回は湿度の整数部分8ビットでタイミング的にギリギリで残りは捨ててました。しかし、折角の温湿度センサです。温度、湿度両方読みたいということで今回再びのタイミング現物合わせに挑戦。

    1. 前回はタイミングを外部から観察するために1ピン使っていたが、その制御のタイミングすら惜しいのでその制御を削除した。闇夜に手探りで調整するがごとし。
    2. どうせ湿度の小数部分にはデータが載ってないと思われるので無視
    3. 温度の整数部分まで読み取る、小数部分は断念。また一部読んでないフィールドがあるのでCRCは計算できない。

現物合わせの「やっつけ」なRubyコードが以下に。

#!mruby

class Dht11
    def initialize(pinname)
        @pin = pinname
        @buf = []
        @mjVH = [0, 0, 0]
        @mjVT = [0, 0, 0]
        @temperature = 0
        @humidity = 0
        @error = 0
        pinMode(@pin, 3) #output, opendrain
    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 majorityVotesH(v)
        @mjVH.shift
        @mjVH.push(v)
        if @mjVH[0] == @mjVH[1] then
            return @mjVH[0]
        else
            return @mjVH[2]
        end
    end

    def majorityVotesT(v)
        @mjVT.shift
        @mjVT.push(v)
        if @mjVT[0] == @mjVT[1] then
            return @mjVT[0]
        else
            return @mjVT[2]
        end
    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 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[1..8])
        tmI = b2B(@buf[10..17])
        temp= tmI & 0x7F
        if (tmI & 0x80) != 0 then
            temp *= -1
        end
        @temperature = majorityVotesT(temp)
        if rhI < 99 then
            @humidity = majorityVotesH(rhI)
        end
    end

    def read
        @error = 0
        startSignal
        if !waitSIG(0, 100) then
            @error = 1
            return false
        end
            # rhI
            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)
            # rhD
            b1 = bitRead #bit 8
            @buf.push(b1)
            b1 = bitRead #bit 9
            b1 = bitRead #bit 10
            b1 = bitRead #bit 11
            b1 = bitRead #bit 12
            b1 = bitRead #bit 13
            b1 = bitRead #bit 14
            b1 = bitRead #bit 15
            # tmI
            b1 = bitRead #bit 16
            @buf.push(b1)
            b1 = bitRead #bit 17
            @buf.push(b1)
            b1 = bitRead #bit 18
            @buf.push(b1)
            b1 = bitRead #bit 19
            @buf.push(b1)
            b1 = bitRead #bit 20
            @buf.push(b1)
            b1 = bitRead #bit 21
            @buf.push(b1)
            b1 = bitRead #bit 22
            @buf.push(b1)
            b1 = bitRead #bit 23
            @buf.push(b1)
            #
            b1 = bitRead #bit 24
            @buf.push(b1)
        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("TEMPERATURE:" + dht11.readTemperature.to_s)
        usb.println("HUMIDITY:   " + dht11.readHumidity.to_s)
        usb.println("ERROR:" + dht11.readERROR.to_s)
    end
    delay(loop_interval)
end

上記ソース読んでいただけると、タイミング調整のためのやっつけな工夫が随所に。わざわざ読むようなもんでもないですが。

実機実行結果

Rubic環境から、GR-CITRUSボード上で上記プログラムを動作させている様子が以下に。DH11adustedEC

上記みると、温度25℃、湿度66%とな。ちょっと手元の温度計より温度は高めか。湿度はほぼほぼ一致。まあ、ここだけだと読めているような気がする。大丈夫か?ま、成り行きの現物合わせなので信用しないでね。

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

Rubyと一緒(10) GR-CITRUS、I2C経由、AQM1602に文字表示 に進む