メインコンテンツへスキップ
見出し画像

製品レビュー|電子機器10:温湿度センサ(DHT20)

    1.概要

     購入した製品の使い方および感想用記事です。
     今回は「温湿度センサ モジュール DHT20(420円/個(税込))」をレビューしました。

    画像

    1-1.基本仕様

     温度と湿度を同時に測定する複合センサーモジュールであり、I2C通信でホスト(マイコン等)のデータ要求に返答する形式でデジタル出力されます。
     旧型DHT11の後継機であり性能も改善されています。適用先としてはHVAC(空調システム)や除湿器、気象台などが可能です。

    画像

     1-1-1.仕様の概要

     基本の概要は下記参照。

    • 電源電圧:DC2.2-5.5V

    • 測定温度:-40~80℃

    • 測定湿度:0~100% RH

    • インターフェース:I2C

    画像

    【湿度センサの詳細】

    画像

    【温度センサの詳細】

    画像

     1-1-2.ピン配置

     ピンの配置は下図の通り

    画像
    図 ピン配置
    画像
    図 一般的な配線図

    1-2.詳細仕様

     1-2-1.I2C関係:コマンド送付

     I2Cアドレスは7bitの0x38(2進数:0111000)であり、追加でread-write bit(read R: ‘1’, write W: ‘0’)が付く。

    画像

     1-2-2.I2C関係:データの読み取り

    画像
    画像

     1-2-3.デジタル値の換算

    画像

    2.製品原理

     製品の動作原理に関する部分を説明します。

    2-1.測定原理

     仕様書をみても測定原理が確認できませんでした。

    おそらくBME280と同じ原理で計測していると推測されます。

    • 温度:ダイオード電圧方式

    • 湿度:静電容量方式

    3.部材購入

    3-1.購入品

     部品は本体のみ購入しました。

    3-2.準備必須品

     その他必需品は下記の通りです。

    • マイコン/シングルボード(Raspberry Pi/Pico)

    • ブレッドボード

    • ジャンピングワイヤー

    4.環境構築

    4-1.マイコン準備

     センサを制御するためのシングルボードやマイコンの準備を行います。
    Raspberry PiやPicoの準備は下記記事参照のこと

     Raspberry PiにGPIOを制御するためのライブラリが無い場合は”RPi.GPIO”を事前にインストールしておきます。
     Picoの場合はMicropythonを使用できるようにしておきます。

    [Terminal]
    pip install rpi.gpio

    4-2.ライブラリのインストール

     4-2-1.Case1:Pico

     Raspberry Pi Picoは組み込み関数と標準ライブラリで対応できるため、追加の環境構築は不要です。

     4-2-2.Case2:Raspberry Pi

     RPi.GPIOはシリアル通信のクラスが無いため、I2C用に別途smbus2をインストールします。

    [Terminal]
    pip install smbus2
    画像

     なおsmbus2ではある程度スクラッチでコード作成する必要がありますが、Adafruit提供のライブラリを使用すればより簡単に実装できます。

    [Terminal]
    pip3 install adafruit-circuitpython-ahtx0
    画像

    5.使用前の準備

    5-1.はんだ付け

     本製品の既にピンがつけられているため、はんだ付けは不要です。

    5-2.部品の組付け

     部品の組付けはジャンパー線を使用して下記の通り繋ぎました。

    【Raspberry Pi】

    No.センサーRaspberry Pi1VDDPIN1(3.3V)2GNDPIN9(GND)3SDAGPIO2(SDA1 I2C)4SCLGPIO3(SCL1 I2C0)\begin{array}{|c|c|c|} \hline \textbf{No.} &\textbf{センサー} & \textbf{Raspberry Pi} \\ \hline \text{1} & \text{VDD} & \text{PIN1(3.3V)}\\ \hline \text{2} & \text{GND} & \text{PIN9(GND)}\\ \hline \text{3} & \text{SDA} & \text{GPIO2(SDA1 I2C)}\\ \hline \text{4} & \text{SCL} & \text{GPIO3(SCL1 I2C0)}\\ \hline \end{array}

    画像
    画像

    【Raspberry Pi Pico】

    No.センサーRaspberry Pi Pico1VDDPIN36(3.3V)2GNDPIN38(GND)3SDAGPIO16(I2C0 SDA)4SCLGPIO17(I2C0 SCL)\begin{array}{|c|c|c|} \hline \textbf{No.} &\textbf{センサー} & \textbf{Raspberry Pi Pico} \\ \hline \text{1} & \text{VDD} & \text{PIN36(3.3V)}\\ \hline \text{2} & \text{GND} & \text{PIN38(GND)}\\ \hline \text{3} & \text{SDA} & \text{GPIO16(I2C0 SDA)}\\ \hline \text{4} & \text{SCL} & \text{GPIO17(I2C0 SCL)}\\ \hline \end{array}

    画像
    画像

    6.MicroPythonスクリプト(Pico)

     Micropythonのコードは下記記事を参考にさせていただきました。

    6-1.任意:デバイス接続の確認

     I2CクラスのI2C.scan()でデフォルトアドレスを確認しておきます。

    [IN]
    from machine import Pin, I2C
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    print([hex(i) for i in i2c.scan()])
    [OUT]
    ['0x38']

    6-2.コードの設計思想

     設計思想は下記の通りです。

    1. 基本的にはコードを写経しながら仕様書を読解していきます。

      • 設計思想の順番は次項の備忘録に記載

    2. ビット演算やバイト型は下記記事参照

      • Byte型出力確認のためにASCII文字コード表を下図に記載

    画像
    https://www.isc.meiji.ac.jp/~ri03037/ICTdb1/step09.html

     6-2-1.備忘録:初期状態の確認と初期化

    【初期状態の確認】
     仕様書記載の通りI2Cアドレス(0x38)からデータを読み取ると最上位ビット(MSB)はBusy/Idle状態を表示します。つまりMSB(Bit[7])の0/1を確認することで初期状態を確認することが出来ます。
     設計思想として、もし測定開始前(スクリプト実行前)の状態がビジー(MSB=1)ならDHT20を初期化(コマンドを送付)します。もしアイドル(MSB=0)なら追加処理は行いません。

    画像

     まずはi2c.readfrom()でアドレス0x38から1Byte分読み出し、転送終了時に STOP 条件(True)を加えました。結果として16進数で1Cが得られました。

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    #I2Cアドレス0x38から1バイト読み込む
    status = i2c.readfrom(0x38, 1, True)
    print(f'Byte型:{status}, 整数:{status[0]}, 2進数:{bin(status[0])}')  
    [OUT]
    Byte型:b'\x1c', 整数:28, 2進数:0b11100

     得られた出力(0x1C)のMSBが1かどうかを確認するには0x80でAND演算を行います。AND演算は(1,1)->1でありそれ以外は0を出力します。0x80はMSB=1でそれ以外0のため、比較対象のMSBが0か1かで出力が0x0か0x80の2択になります。
     よって「MSB=1の確認は"0x80のAND演算==0x80"で可能」です。
     私の環境で実行するとI2Cアドレス0x38からの出力は0x1CでありMSB=0のため、出力は0x0(つまりアイドル状態)となりました。

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    #I2Cアドレス0x38から1バイト読み込む
    status = i2c.readfrom(0x38, 1, True)
    print(f'Byte型:{status}, 整数:{status[0]}, 2進数:{bin(status[0])}')  
    
    #ビット演算
    y = status[0] & 0x80
    print(f'AND演算:{y}, 型:{type(y)}, 2進数:{bin(y)}, 16進数:{hex(y)}')
    print(f'AND演算:{y == 0x80}')
    [OUT]
    Byte型:b'\x1c', 整数:28, 2進数:0b11100
    AND演算:0, 型:<class 'int'>, 2進数:0b0, 16進数:0x0
    AND演算:False
    画像

    【初期状態の確認】
     初期化のコードはI2C.writeto(addr, buf, stop=True, /)で指定したI2Cアドレスにバッファーで指定したbytes 型オブジェクトを書き込みます。
     コードは下記コマンドを使用しているのですが、仕様書のどこの値なのかが確認できませんでした。(分かったら追って記載)

    [IN]
        def dht20_init(self):
            i2c.writeto(0x38, bytes([0xa8,0x00,0x00]))
            utime.sleep_ms(10) #10ms待機
            i2c.writeto(0x38, bytes([0xbe,0x08,0x00]))

     「details Please refer to our official website routine for the initialization process;」とあるけど、どこかに記載があるのかも?

    画像

     6-2-2.データの読み込み

     データの読み込み関数は下記の通りです。値は仕様書ベースとなります。

    画像
    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    def dht20_read_status(i2c):
        data = i2c.readfrom(0x38, 1, True) #指定アドレスからnbyte分(1Byte)を読み込む(Stop=Trueで通信終了)
        return data[0]
    
    def read_dht20(i2c):
        i2c.writeto(0x38, bytes([0xac,0x33,0x00]))
        utime.sleep_ms(80) #80ms待機
        cnt = 0
        while (dht20_read_status(i2c) & 0x80) == 0x80:
            utime.sleep_ms(1) #1ms待機
            if cnt >= 100:
                cnt += 1
                break
        data = i2c.readfrom(0x38, 7, True)
        n = []
        for i in data[:]:
            n.append(i)
        return n
    
    data = read_dht20(i2c)
    print(data)
    print(type(data))
    [OUT]
    [28, 128, 255, 117, 238, 119, 8]
    <class 'list'> 7

    【測定開始コマンド(Trigger Measurement)】
     まずI2C.writeto(addr, buf, stop=True, /)で指定したI2Cアドレスにバッファーで指定したbytes 型オブジェクト(センサに送るコマンド)を書き込みます。
     コマンドとしては下記記載の通り0xAC, 0x33, 0x00を送付します。80ms経過すると計測が完了します。

    画像
    画像

     参考までに計測時(コマンド送付から80ms内)と計測後のI2Cアドレスの情報を確認しました。計測中のMSB=1であり、測定終了後(80ms後)はMSB=0であることが確認できました。

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    i2c.writeto(0x38, bytes([0xac,0x33,0x00])) #計測開始
    status = i2c.readfrom(0x38, 1, True) #計測中のステータスを読み込む
    print(f'Byte型:{status}, 整数:{status[0]}, 2進数:{bin(status[0])}, MSB確認:{status[0] & 0x80}')
    utime.sleep_ms(80) #80ms待機※計測完了予定時間
    
    status = i2c.readfrom(0x38, 1, True) #計測完了後のステータスを読み込む
    print(f'Byte型:{status}, 整数:{status[0]}, 2進数:{bin(status[0])}, MSB確認:{status[0] & 0x80}')
    [OUT]
    Byte型:b'\x9c', 整数:156, 2進数:0b10011100, MSB確認:128
    Byte型:b'\x1c', 整数:28, 2進数:0b11100, MSB確認:0

     参考コードでは80ms後も計測終了(MSB=0)を確認し、最大100ms待機するようにしています。
    ※私の方で元コードのcnt += 1の位置を修正しています。

    []
    while (dht20_read_status(i2c) & 0x80) == 0x80:
        utime.sleep_ms(1) #1ms待機
        cnt += 1
        if cnt >= 100:
            break

    【データ読み込み:6byte+CRC】
     計測が終了するとI2Cアドレス0x38のバッファーには計7Byteのデータが格納されます。

    • 1Byte:State

    • 2Byte, 3Byte, 4Byteの上位4bit:湿度データ

    • 4Byteの下位4bit, 5Byte, 6Byte:温度データ

    • 7Byte:CRCデータ(巡回冗長検査)

    画像
    画像

     コードでは得られたByte型データ(7Byte)をリストにして出力する関数としています。
     参考までにfor文のdata[:]は全部抽出する表現のため”for i in data:”と同じ意味だと思います。

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    i2c.writeto(0x38, bytes([0xac,0x33,0x00])) #計測開始
    utime.sleep_ms(80) #80ms待機※計測完了予定時間
    data = i2c.readfrom(0x38, 7, True)
    print(data)
    print(type(data))
    print(len(data))
        
    n = []
    for i in data[:]:
        n.append(i)
    print(n)
    [OUT]
    b'\x1c\x83\xb1\xa5\xec\x19G'
    <class 'bytes'>
    7
    [28, 131, 177, 165, 236, 25, 71]

     6-2-3.湿度(Humidity)計算

     湿度は得られた7Byte(56bit)の内、2Byte目から計20bitのデジタル値を取得し、下記計算式で湿度を算出します。

    湿度RH[%]=デジタル値SRH220×100湿度RH[%]=\frac{デジタル値S_{RH}}{2^{20}}\times 100

    画像

     計算はすべてビット演算で実施しますが手順は下記の通りです。

    1. 最初に0のデータを用意

    2. 2Byte目のデータをOR演算で結合し、8bit左シフトを実施

    3. 3Byte目のデータをOR演算で結合し、8bit左シフトを実施

    4. 4Byte目のデータをOR演算で結合し、4bit右シフトを実施

      • 4Byte目の不要なbitを右シフトで削除

    5. 上記計算式で湿度を算出

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    def dht20_read_status(i2c):
        data = i2c.readfrom(0x38, 1, True) #指定アドレスからnbyte分(1Byte)を読み込む(Stop=Trueで通信終了)
        return data[0]
    
    def read_dht20(i2c):
        i2c.writeto(0x38, bytes([0xac,0x33,0x00]))
        utime.sleep_ms(80) #80ms待機
        cnt = 0
        while (dht20_read_status(i2c) & 0x80) == 0x80:
            utime.sleep_ms(1) #1ms待機
            cnt += 1
            if cnt >= 100:
                break
        data = i2c.readfrom(0x38, 7, True)
        n = []
        for i in data[:]:
            n.append(i)
        return n
    
    def format_bin(x: int, digits: int = 8):
        binary_string = bin(x)[2:]  # '0b' プレフィックスを削除
        formatted_binary = '{:0>{}}'.format(binary_string, digits)  # 指定された桁数で左を0で埋める
        return formatted_binary
    
    
    data = read_dht20(i2c)
    print(data)
    
    humid = 0
    #ビット演算による計算
    print(f'Data[1](2Byte目):{data[1]}, 2進数:{format_bin(data[1], 8)}')
    humid = (humid | data[1])
    print(f'OR演算後:{humid}, 2進数:{format_bin(humid, 8)}')
    humid = (humid << 8)
    print(f'左シフト後:{humid}, 2進数:{format_bin(humid, 16)}')
    
    print(f'Data[2](3Byte目):{data[2]}, 2進数:{format_bin(data[2], 8)}')
    humid = (humid | data[2])
    print(f'OR演算後:{humid}, 2進数:{format_bin(humid, 16)}')
    humid = (humid << 8)
    print(f'左シフト後:{humid}, 2進数:{format_bin(humid, 24)}')
    
    print(f'Data[3](4Byte目):{data[3]}, 2進数:{format_bin(data[3], 8)}')
    humid = (humid | data[3])
    print(f'OR演算後:{humid}, 2進数:{format_bin(humid, 24)}')
    humid = (humid >> 4)
    print(f'右シフト後:{humid}, 2進数:{format_bin(humid, 20)}')
    
    humid = (humid / 1024 / 1024)*100
    print(f'湿度:{humid}')
    [OUT]
    [28, 133, 202, 229, 233, 181, 227]
    
    Data[1](2Byte目):133, 2進数:10000101
    OR演算後:133, 2進数:10000101
    左シフト後:34048, 2進数:1000010100000000
    Data[2](3Byte目):202, 2進数:11001010
    OR演算後:34250, 2進数:1000010111001010
    左シフト後:8768000, 2進数:100001011100101000000000
    Data[3](4Byte目):229, 2進数:11100101
    OR演算後:8768229, 2進数:100001011100101011100101
    右シフト後:548014, 2進数:10000101110010101110
    
    湿度:52.26269
    画像

     6-2-4.温度(Temperature)計算

     湿度は得られた7Byte(56bit)の内、4Byte目の真ん中から計20bitのデジタル値を取得し、下記計算式で湿度を算出します。

    温度T[℃]=デジタル値ST220×200−50温度T[℃]=\frac{デジタル値S_{T}}{2^{20}}\times 200 -50

    画像

     計算はすべてビット演算で実施しますが手順は下記の通りです。

    1. 最初に0のデータを用意

    2. 3Byte目のデータをOR演算で結合し、8bit左シフトを実施

    3. 4Byte目のデータをOR演算で結合し、8bit左シフトを実施

    4. 5Byte目のデータをOR演算で結合

    5. 0xfffffでビットマスキングし上位ビットを削除

      • 4bit=24=164bit=2^4=16だと2進数:1111=16進数:Fとなる。つまり0xfffffは20bitがすべて1である。

      • AND演算は(1,1)->1である。0xfffffは20bitまでが1でそれ以上はすべて0であるため、AND演算をすると自動的に20bitが抽出される

    6. 上記計算式で湿度を算出

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    def dht20_read_status(i2c):
        data = i2c.readfrom(0x38, 1, True) #指定アドレスからnbyte分(1Byte)を読み込む(Stop=Trueで通信終了)
        return data[0]
    
    def read_dht20(i2c):
        i2c.writeto(0x38, bytes([0xac,0x33,0x00]))
        utime.sleep_ms(80) #80ms待機
        cnt = 0
        while (dht20_read_status(i2c) & 0x80) == 0x80:
            utime.sleep_ms(1) #1ms待機
            cnt += 1
            if cnt >= 100:
                break
        data = i2c.readfrom(0x38, 7, True)
        n = []
        for i in data[:]:
            n.append(i)
        return n
    
    def format_bin(x: int, digits: int = 8):
        binary_string = bin(x)[2:]  # '0b' プレフィックスを削除
        formatted_binary = '{:0>{}}'.format(binary_string, digits)  # 指定された桁数で左を0で埋める
        return formatted_binary
    
    
    data = read_dht20(i2c)
    print(data)
    
    #温度計算
    temper = 0
    print(f'Data[3](4Byte目):{data[3]}, 2進数:{format_bin(data[3], 8)}')
    temper = (temper | data[3])
    print(f'OR演算後:{temper}, 2進数:{format_bin(temper, 8)}')
    temper = (temper << 8)
    print(f'左シフト後:{temper}, 2進数:{format_bin(temper, 16)}')
    
    print(f'Data[4](5Byte目):{data[4]}, 2進数:{format_bin(data[4], 8)}')
    temper = (temper | data[4])
    print(f'OR演算後:{temper}, 2進数:{format_bin(temper, 16)}')
    temper = (temper << 8)
    print(f'左シフト後:{temper}, 2進数:{format_bin(temper, 24)}')
    
    print(f'Data[5](6Byte目):{data[5]}, 2進数:{format_bin(data[5], 8)}')
    temper = (temper | data[5])
    print(f'OR演算後:{temper}, 2進数:{format_bin(temper, 24)}')
    temper = temper & 0xfffff #下位20bitを取得
    print(f'下位20bit取得:{temper}, 2進数:{format_bin(temper, 20)}')
    
    temper = (temper * 200 / 1024 / 1024) - 50
    print(f'温度:{temper:.2f}°C')
    [OUT]
    [28, 133, 200, 5, 233, 205, 251]
    
    Data[3](4Byte目):5, 2進数:00000101
    OR演算後:5, 2進数:00000101
    左シフト後:1280, 2進数:0000010100000000
    Data[4](5Byte目):233, 2進数:11101001
    OR演算後:1513, 2進数:0000010111101001
    左シフト後:387328, 2進数:000001011110100100000000
    Data[5](6Byte目):205, 2進数:11001101
    OR演算後:387533, 2進数:000001011110100111001101
    下位20bit取得:387533, 2進数:01011110100111001101
    温度:23.92°C
    画像

     6-2-5.CRC計算

     データの7Byte目はCRCチェックデータが含まれます。CRCとは巡回冗長検査であり、主にデータ転送などに伴う偶発的な誤りの検出に使われます。

     CRC確認の多項式は仕様書より下記の通りです(おそらくCRC-8-CCITTと呼ばれる手法)。

    CRC[7:0]=1+X4+X5+X8CRC [7:0] = 1+X^4+X^5+X^8

    画像
    画像

     CRCデータ(7Byte目:リストの最後のため-1で取得)と、データ(data[:-1])からの計算値CRC[7:0]=1+X4+X5+X8CRC [7:0] = 1+X^4+X^5+X^8をを比較しました。結果として「実データ=計算値」であることを確認しました。
     メモ用に計算のコメント記載しておきます。

    • 計算に使用した16進数

      • 0xff=11111111

      • 0x80=10000000

      • 0x31=00110001▶1+X4+X5+X81+X^4+X^5+X^8を16進数で表している?

    [IN]
    from machine import Pin, I2C
    import utime
    
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    
    def dht20_read_status(i2c):
        data = i2c.readfrom(0x38, 1, True) #指定アドレスからnbyte分(1Byte)を読み込む(Stop=Trueで通信終了)
        return data[0]
    
    def read_dht20(i2c):
        i2c.writeto(0x38, bytes([0xac,0x33,0x00]))
        utime.sleep_ms(80) #80ms待機
        cnt = 0
        while (dht20_read_status(i2c) & 0x80) == 0x80:
            utime.sleep_ms(1) #1ms待機
            cnt += 1
            if cnt >= 100:
                break
        data = i2c.readfrom(0x38, 7, True)
        n = []
        for i in data[:]:
            n.append(i)
        return n
    
    def format_bin(x: int, digits: int = 8):
        binary_string = bin(x)[2:]  # '0b' プレフィックスを削除
        formatted_binary = '{:0>{}}'.format(binary_string, digits)  # 指定された桁数で左を0で埋める
        return formatted_binary
    
    
    data = read_dht20(i2c)
    print(data)
    
    #CRC8計算(CRC:巡回冗長検査)
    def calc_crc8(data):
        crc = 0xff
        for i in data[:-1]:
            crc ^= i
            for j in range(8):
                if crc & 0x80:
                    crc = ((crc << 1) ^ 0x31) & 0xFF  # 追加: 結果をバイト範囲に制限
                else:
                    crc = (crc << 1) & 0xFF  # 追加: 結果をバイト範囲に制限
        return crc
    
    crc_data = data[-1]
    print(f'実データ(Bit[7]):{crc_data}')
    crc_calc = calc_crc8(data)
    
    print(f'計算結果:{crc_calc}')
    print(type(crc_calc))
    print(f'2進数:{format_bin(crc_calc)}')
    print(f'16進数:{hex(crc_calc)}')
    [OUT]
    [28, 136, 137, 133, 227, 6, 228]
    
    実データ(Bit[7]):228
    
    計算結果:228
    <class 'int'>
    2進数:11100100
    16進数:0xe4

    6-3.スクリプト実行

     スクリプトを作成し、実行しました。問題なく値を取得できました。

    [IN]
    from machine import Pin, I2C
    import utime
    
    class DHT20(object):
        def __init__(self, i2c):
            self.i2c = i2c
            if (self.dht20_read_status() & 0x80) == 0x80:
                self.dht20_init()  
    
        #運転状態確認:MSB=1▶Busy, MSB=0▶Idle    
        def dht20_read_status(self):
            data = self.i2c.readfrom(0x38, 1, True) #指定アドレスからnbyte分(1Byte)を読み込む(Stop=Trueで通信終了)
            return data[0]
        
        #初期化:実行前にセンサーがBusy状態の場合
        def dht20_init(self):
            i2c.writeto(0x38, bytes([0xa8,0x00,0x00]))
            utime.sleep_ms(10) #10ms待機
            i2c.writeto(0x38, bytes([0xbe,0x08,0x00]))
    
        #データ読み込み
        def read_dht20(self):
            self.i2c.writeto(0x38, bytes([0xac,0x33,0x00])) #計測開始
            utime.sleep_ms(80) #80ms待機
            cnt = 0
            #念のため100msの待機
            while (self.dht20_read_status() & 0x80) == 0x80:
                utime.sleep_ms(1) #1ms待機
                cnt += 1
                if cnt >= 100:
                    break
            data = self.i2c.readfrom(0x38, 7, True)
            #データをリストに格納
            n = []
            for i in data[:]:
                n.append(i)
            return n
    
        #CRC8計算(CRC:巡回冗長検査)
        def calc_crc8(self, data):
            crc = 0xff
            for i in data[:-1]: #list[-1]で最後の要素を除外(CRCデータは除外)
                crc ^= i #XOR演算
                for j in range(8):
                    if crc & 0x80: #MSBが1の場合
                        crc = ((crc << 1) ^ 0x31) & 0xFF  #1ビット左シフトし、0x31とXOR演算
                    else:
                        crc = (crc << 1) & 0xFF  #1ビット左シフトし、結果をバイト範囲に制限
            return crc
        
        #温度計算
        def dht20_temperature(self):
            data = self.read_dht20()
            Temper = 0
            
            #ビット演算による計算
            Temper = (Temper | data[3]) << 8
            Temper = (Temper | data[4]) << 8
            Temper = Temper | data[5]
            Temper = Temper & 0xfffff
            Temper = (Temper * 200 / 1024 / 1024) - 50
            return Temper
        
        #湿度計算
        def dht20_humidity(self):
            data = self.read_dht20()
            humidity = 0
            
            #ビット演算による計算
            humidity = (humidity | data[1]) << 8
            humidity = (humidity | data[2]) << 8
            humidity = humidity | data[3]
            humidity = humidity >> 4
            humidity = (humidity / 1024 / 1024)* 100
            return humidity
        
        
    # I2C設定
    i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=400000) 
    print([hex(i) for i in i2c.scan()])
    
    dht20 = DHT20(i2c)
    while True:
        temp = dht20.dht20_temperature()
        humid = dht20.dht20_humidity()
        print(f'Temperature: {temp:.2f}°C, Humidity: {humid:.2f}%')
        utime.sleep(1)  # 1秒待機
    
    [OUT]
    ['0x38']
    Temperature: 23.48°C, Humidity: 53.51%
    Temperature: 23.49°C, Humidity: 53.51%
    Temperature: 23.50°C, Humidity: 53.50%
    Temperature: 23.51°C, Humidity: 53.46%
    画像

    7.Pythonスクリプト(Raspberry Pi)

     Raspberry Piでも実行しました。コードは下記記事参照しました。

    7-1.任意:デバイス接続の確認

     I2C通信の確認は下記コマンドで実施可能です。

    [Terminal]
    sudo i2cdetect -y 1
    画像

    7-2.スクリプト実行

     Raspberry Piは「adafruit/Adafruit_CircuitPython_AHTx0」があるので、そのまま利用しました。

    [IN]
    import time
    import board
    import adafruit_ahtx0
    
    # Create sensor object, communicating over the board's default I2C bus
    i2c = board.I2C()  # uses board.SCL and board.SDA
    sensor = adafruit_ahtx0.AHTx0(i2c)
    
    while True:
        print("\nTemperature: %0.1f C" % sensor.temperature)
        print("Humidity: %0.1f %%" % sensor.relative_humidity)
        time.sleep(2)
    
    [OUT]
    Temperature: 23.5 C
    Humidity: 52.3 %
    
    Temperature: 23.5 C
    Humidity: 52.3 %
    
    Temperature: 23.5 C
    Humidity: 52.3 %
    画像

    7-3.参考:スクラッチでのスクリプト(SMBUS2)

     設計方法は前章のMicropythonと同じであり、smbus2でスクラッチで作成しました。
     同等の数値が出ているため、たぶん問題ないはずです。

    [IN]
    from smbus2 import SMBus
    import time
    
    class DHT20:
        def __init__(self, bus_number):
            self.bus = SMBus(bus_number)
            self.address = 0x38
            if (self.dht20_read_status() & 0x80) == 0x80:
                self.dht20_init()
    
        #運転状態の読み込み(確認)
        def dht20_read_status(self):
            return self.bus.read_byte_data(self.address, 0x71)
    
        #初期化※実行前にセンサーがBusy状態の場合
        def dht20_init(self):
            self.bus.write_i2c_block_data(self.address, 0xa8, [0x00, 0x00])
            time.sleep(0.01)
            self.bus.write_i2c_block_data(self.address, 0xbe, [0x08, 0x00])
    
        #データの読み込み
        def read_dht20(self):
            # 2バイトのデータを書き込むためにwrite_i2c_block_dataを使用
            self.bus.write_i2c_block_data(self.address, 0xac, [0x33, 0x00])
            time.sleep(0.08)  # 80ms待機
            cnt = 0
            while (self.dht20_read_status() & 0x80) == 0x80:
                time.sleep(0.001)  # 1ms待機
                cnt += 1
                if cnt >= 100:
                    break
            data = self.bus.read_i2c_block_data(self.address, 0x00, 7)  # 7バイト読み込み
            return data
    
        #CRC8計算(CRC:巡回冗長検査)
        def calc_crc8(self, data):
            crc = 0xff
            for i in data[:-1]:
                crc ^= i
                for _ in range(8):
                    if crc & 0x80:
                        crc = (crc << 1) ^ 0x31
                    else:
                        crc <<= 1
                    crc &= 0xff
            return crc
    
        #温度計算
        def dht20_temperature(self):
            data = self.read_dht20()
            #ビット演算でデータを結合
            Temper = 0
            Temper = (Temper | data[3]) << 8
            Temper = (Temper | data[4]) << 8
            Temper = Temper | data[5]
            Temper = Temper & 0xFFFFF  #ビットマスキングで下位20ビットを取得
            return (Temper * 200 / 1024 / 1024) - 50
    
        #湿度計算
        def dht20_humidity(self):
            data = self.read_dht20()
            #ビット演算でデータを結合
            humidity = 0
            humidity = (humidity | data[1]) << 8
            humidity = (humidity | data[2]) << 8
            humidity = humidity | data[3] 
            humidity = humidity >> 4 #下位4ビットを削除
            return humidity / 1024/ 1024 * 100
    
    # I2Cの設定
    bus_number = 1
    sensor = DHT20(bus_number)
    
    while True:
        data = sensor.read_dht20()
        print(f'Data: {data}')
        temperature = sensor.dht20_temperature()
        humidity = sensor.dht20_humidity()
        print(f'Temperature: {temperature:.2f}°C, Humidity: {humidity:.2f}%')
        time.sleep(1)
    [OUT]
    Data: [28, 131, 180, 149, 227, 13, 17]
    Temperature: 23.64°C, Humidity: 51.36%
    Data: [28, 131, 178, 133, 227, 90, 85]
    Temperature: 23.64°C, Humidity: 51.34%
    Data: [28, 131, 176, 197, 227, 89, 243]
    Temperature: 23.66°C, Humidity: 51.29%
    Data: [28, 131, 151, 181, 227, 104, 202]
    画像

    8.所感

     簡単な所感は下記の通り

    • なんとなくByte型やビット演算の必要性・計算方法が理解できた。

    • マイコンでメモリの割り当てめっちゃめんどくさい。制御盤設計する人の工数かかる理由がなんとなく分かった。

    • まだレジスタの指定とか、コマンドが無くても処理できるやつとか、CRCが微妙に理解できていない。


    参考資料

    別添1 Python関係

    別添2 技術関係

    あとがき

     やっとすんなりうまくいった。他のセンサが不調続きで全く進まなかったのでストレスから解放された。


     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ