LoginSignup
14
7

More than 3 years have passed since last update.

ラズパイで廉価だけどちょっと立派な温度センサDHT21(AM2302)をDHT11の気軽さで使いたい

Last updated at Posted at 2019-05-29

猛暑到来

5月では観測事情まれにみる猛暑。我が家もあっついです。家では猫が留守番しているのですが、高齢というのもあってちと心配。部屋が高温になったらお知らせするボットを作れないかなあと、とりあえずセンサの巻。

調達する

Amazonか秋月のような電子部品屋に売ってます。Amazonすごいな。

DHT11が定番だけど

とりあえずラズパイで温度といえばDHT11。安いしGPIOで繋げられるし、ネットにライブラリが転がってる。ですけど、、、測定誤差±2℃っていくらなんでも大きすぎじゃないですか?そんなに精度はいらないけど、もう一息ほしいところです。

ということでDHT22を購入

DHT22を購入。DHT11に比べると高いけど、まあ安い(800円位)。
買うときはセンサ単体と基盤に実装済のものがあります。
板に実装する予定がないときは実装済みのほうが楽だと思います。ジャンパピンまでついてるし。
スクリーンショット 2019-05-29 10.08.38.png

基盤実装品で買う場合

同じ外見をしていても制作業者によって質が様々のようです。レビューを見て慎重に買ってください。
僕はHiletgo制を買いました。
あとピンのアサインが業者やロットによって様々なので、配線は基盤の表記を見て行ってください。僕は何も考えずにネットのとおりに配線したらDHT11が焼け落ちました。

センサ単体で買う場合

こんな感じに抵抗を入れる必要があるそうです。詳しくはデータシートを。
スクリーンショット 2019-05-29 10.18.33.png

とりあえず使ってみる。

とりあえずDHT11のライブラリそのままで使えないかな、ということでやってみます。

配線

僕の買ったものはDAT/VCC/GNDの3つがあったので以下のように配線
DAT -> 7(GPIO4) | VCC -> 1 | GND -> 9

VCCはデータシートを見ると6.0Vまで動くようなので、3.3Vのピンが余ってなければ5Vピンでも行けると思います。

DHT11用ライブラリの導入

gitに転がっているDHT11ライブラリをcloneします。
gitをインストールしていない人はインストール

sudo apt-get install git

そんで

git clone https://github.com/szazo/DHT11_Python.git

そうするとカレントディレクトリに必要なファイルがダウンロードされます。

tree
 ├── DHT11_Python
    ├── dht11_example.py
    ├── dht11.py
    ├── __init__.py
    ├── LICENSE.md
    └── README.md

参考:Raspberry PiとDHT11で温度・湿度を測る
https://qiita.com/mininobu/items/1ba0223af84be153b850

使ってみる

ピン番号だけ書き換え。

[変更点]dht11_example.py
instance = dht11.DHT11(pin=4)

そんで実行

python dht11_example.py 

Last valid input: 2019-05-29 03:23:42.870835
Temperature: 1 C
Humidity: 2 %
Last valid input: 2019-05-29 03:23:46.116768
Temperature: 1 C
Humidity: 2 %

だめです!
我が家はそんなに寒くない。

dht11.pyを改造する

DHT22ってなんか情報が少ないんですよね、調べ不足かもしれませんが。dht11のライブラリ書き換えてなんとかなんないかなあ。

のぞいてみる

センサーからの読み出しの核になってるのはdht11.py、これをのぞいてみるとCRCまでチェックしてくれててエラー出てない。読み出しのフォーマットとかバイト数はDHT11と一緒みたい。
ソースのぞくとDHT11/22が送ってくるシリアルデータは5バイト。DHT11のビット配置は下のようになっています。DHT11は測定範囲が狭いから温度データは1バイトで十分なんですね。

スクリーンショット 2019-05-29 11.35.03.png
順当に考えると、精度の高いDHT22は湿度/温度2バイトづつなんですけど、2バイトの小数扱う変数ってありましたっけ?

ローデータを出してみる。

root@raspberrypi:/home/pi# python dht22_dump.py 
[1, 241, 1, 10, 253]

これを単純に2バイト整数として見ると
   湿度:497  |  温度:266

小数点型を使ってるわけではなくて、2バイト整数型で読んで、10で割るといいみたいです。

dht22.pyを作る

dht11.pyからの変更点はこちら。
もっとスマートな方法があるのかな、

[変更点]dht22.py
        #追加
        temp = (float(the_bytes[2]) * 256 + the_bytes[3]) / 10
        hum  = (float(the_bytes[0]) * 256 + the_bytes[1]) / 10

        #変更
        return DHT22Result(DHT22Result.ERR_NO_ERROR, temp, hum)

あとclass名をDHT22にした。

dht22.py
# -*- coding: utf-8 -*-

import time
import RPi


class DHT22Result:
    'DHT22 sensor result returned by DHT22.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DHT22Result.ERR_NO_ERROR


class DHT22:
    'DHT22 sensor reader class for Raspberry'

    __pin = 0

    def __init__(self, pin):
        self.__pin = pin

    def read(self):
        RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)

        # send initial high
        self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)

        # pull down to low
        self.__send_and_sleep(RPi.GPIO.LOW, 0.02)

        # change to input using pull up
        RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)

        # collect data into an array
        data = self.__collect_input()

        # parse lengths of all data pull up periods
        pull_up_lengths = self.__parse_data_pull_up_lengths(data)

        # if bit count mismatch, return error (4 byte data + 1 byte checksum)
        if len(pull_up_lengths) != 40:
            return DHT22Result(DHT22Result.ERR_MISSING_DATA, 0, 0)

        # calculate bits from lengths of the pull up periods
        bits = self.__calculate_bits(pull_up_lengths)

        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)

        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DHT22Result(DHT22Result.ERR_CRC, 0, 0)

        #dump時に使用
        #print(str(the_bytes) )
        #追加
        temp = (float(the_bytes[2]) * 256 + the_bytes[3]) / 10
        hum  = (float(the_bytes[0]) * 256 + the_bytes[1]) / 10

        #変更
        return DHT22Result(DHT22Result.ERR_NO_ERROR, temp, hum)

    def __send_and_sleep(self, output, sleep):
        RPi.GPIO.output(self.__pin, output)
        time.sleep(sleep)

    def __collect_input(self):
        # collect the data while unchanged found
        unchanged_count = 0

        # this is used to determine where is the end of the data
        max_unchanged_count = 100

        last = -1
        data = []
        while True:
            current = RPi.GPIO.input(self.__pin)
            data.append(current)
            if last != current:
                unchanged_count = 0
                last = current
            else:
                unchanged_count += 1
                if unchanged_count > max_unchanged_count:
                    break

        return data

    def __parse_data_pull_up_lengths(self, data):
        STATE_INIT_PULL_DOWN = 1
        STATE_INIT_PULL_UP = 2
        STATE_DATA_FIRST_PULL_DOWN = 3
        STATE_DATA_PULL_UP = 4
        STATE_DATA_PULL_DOWN = 5

        state = STATE_INIT_PULL_DOWN

        lengths = [] # will contain the lengths of data pull up periods
        current_length = 0 # will contain the length of the previous period

        for i in range(len(data)):

            current = data[i]
            current_length += 1

            if state == STATE_INIT_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # ok, we got the initial pull down
                    state = STATE_INIT_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_INIT_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # ok, we got the initial pull up
                    state = STATE_DATA_FIRST_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_FIRST_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # we have the initial pull down, the next will be the data pull up
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # data pulled up, the length of this pull up will determine whether it is 0 or 1
                    current_length = 0
                    state = STATE_DATA_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # pulled down, we store the length of the previous pull up period
                    lengths.append(current_length)
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue

        return lengths

    def __calculate_bits(self, pull_up_lengths):
        # find shortest and longest period
        shortest_pull_up = 1000
        longest_pull_up = 0

        for i in range(0, len(pull_up_lengths)):
            length = pull_up_lengths[i]
            if length < shortest_pull_up:
                shortest_pull_up = length
            if length > longest_pull_up:
                longest_pull_up = length

        # use the halfway to determine whether the period it is long or short
        halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
        bits = []

        for i in range(0, len(pull_up_lengths)):
            bit = False
            if pull_up_lengths[i] > halfway:
                bit = True
            bits.append(bit)

        return bits

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0

        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255

クラス名をDHT22に変更したのでサンプルも少し変更。
あとGPIOは結構入れ替えるので、ピン番号を最初に出しました。

dht22_example.py
import RPi.GPIO as GPIO
import dht22
import time
import datetime

GPIO_PIN = 4

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin GPIO_PIN
instance = dht22.DHT22(pin=GPIO_PIN)

while True:
    result = instance.read()
    if result.is_valid():
        print("Last valid input: " + str(datetime.datetime.now()))
        print("Temperature: %s C" % result.temperature)
        print("Humidity: %s %%" % result.humidity)

    time.sleep(1)

動かしてみるよ

python dht22_example.py 
Last valid input: 2019-05-29 04:27:36.415902
Temperature: 28.5 C
Humidity: 53.7 %

温度は結構正確だけど、湿度は手元の温度湿度計と比べると4%くらい高め。
他のひとの結果を見てもそんな感じだからここは補正が必要かな。

これから

とりあえず自宅の温度は確認できるようになりました。ただこののままじゃ不便なので、敷居の温度を超えたらSlackに通知して、冷房のONを促す仕組みを作ろうと思います。

夏がくる前に!

14
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7