見出し画像

製品レビュー|電子機器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】

$$
\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】

$$
\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[%]=\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[℃]=\frac{デジタル値S_{T}}{2^{20}}\times 200 -50
$$

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

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

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

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

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

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

    • $${4bit=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+X^4+X^5+X^8
$$

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

  • 計算に使用した16進数

    • 0xff=11111111

    • 0x80=10000000

    • 0x31=00110001▶$${1+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 技術関係

あとがき

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


この記事が気に入ったらサポートをしてみませんか?