5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Raspberry Pi 3 & DHT11からJavaで温度・湿度を読み取る

Last updated at Posted at 2018-03-10

#はじめに
Raspberry Pi 3 を使って、安価で入手可能な温湿度センサー DHT11 から温度および湿度の情報を読み取ります。プログラミング言語は Java を使用しています。

DHT11
M-07003.jpg

#準備
Raspberry Pi の GPIO を Java から制御するためには Pi4J を使用します。Pi4J の環境については、以下の記事の「Pi4J のインストール」をご参照ください。

Raspberry Piのセンサー情報をグラフ化し、Webブラウザーで確認できる環境を準備する

今回 Raspberry Pi と DHT11 は以下のように接続しました。データシートには、VDD と Data パスの間に 5KΩ の抵抗が記載されていましたが、手持ちになかったので 10KΩ で代用しました(ここはあまり詳しくないので、とりあえず良しとします・・・)。

(参考)DHT11のデータシート

回路図
DHT11.png
実態配線図
DHT11_ブレッドボード.png

#プログラム
プログラムは以下のリンク先にある Python のプログラムを参照して、Java に書き換えてみたものです(少し変更を入れています)。

DHT11 Python library

プログラムは、以下の3つのファイルから構成されています。

  • DHT11Demo.java
  • DHT11.java
  • DHT11Result.java

メインとなるクラス DHT11 のインスタンス作成時に、コンストラクタに対して DHT11 の Data ピンが接続されている Raspberry Pi のピンの GPIO 番号を指定します。例えば Raspberry Pi のピン番号 8 に接続されている場合は、対応する GPIO 番号である 15 を指定します。

(参考)Raspberry Pi 3 のピン割り当て

DHT11Demo.java
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class DHT11Demo {
    public static void main(String[] args) throws InterruptedException {
        DHT11 dht11 = new DHT11(15); // use GPIO pin 15

        while (true) {
            DHT11Result result = dht11.read();

            if (result.isValid()) {
                System.out.println("Last valid input: " + new Date());
                System.out.printf("Temperature: %.1f C\n" , result.getTemperature());
                System.out.printf("Humidity:    %.1f %%\n", result.getHumidity());
            }

            TimeUnit.SECONDS.sleep(2);
        }
    }
}
DHT11.java
import java.util.ArrayList;
import java.util.List;

import com.pi4j.wiringpi.Gpio;
import com.pi4j.wiringpi.GpioUtil;

public class DHT11 {
    // DHT11 sensor reader class for Raspberry Pi

    private int pin; // GPIO pin number
    private List<Integer> data    = new ArrayList<>(10000);
    private List<Integer> lengths = new ArrayList<>(40); // will contain the lengths of data pull up periods

    public DHT11(int pin) {
        // setup wiringPi
        if (Gpio.wiringPiSetup() != 0) {
            throw new RuntimeException("Initialization of the GPIO has failed.");
        }
        this.pin = pin;
        GpioUtil.export(pin, GpioUtil.DIRECTION_OUT);
    }

    public DHT11Result read() {
        data.clear();
        lengths.clear();

        // change to output, then send start signal
        Gpio.pinMode(pin, Gpio.OUTPUT);

        Gpio.digitalWrite(pin, Gpio.LOW);
        Gpio.delay(18); // ms

        Gpio.digitalWrite(pin, Gpio.HIGH);

        // change to input
        Gpio.pinMode(pin, Gpio.INPUT);

        // collect data into a list
        collectInput();

        // parse lengths of all data pull up periods
        parseDataPullUpLengths();

        // if bit count mismatch, return error (4 byte data + 1 byte checksum)
        if (lengths.size() != 40)
            return new DHT11Result(DHT11Result.ERR_MISSING_DATA, 0.0, 0.0, data.size(), lengths.size());

        // calculate bits from lengths of the pull up periods
        boolean[] bits = calculateBits();

        // we have the bits, calculate bytes
        byte[] bytes = bitsToBytes(bits);

        // calculate checksum and check
        if (bytes[4] != calculateChecksum(bytes))
            return new DHT11Result(DHT11Result.ERR_CRC, 0.0, 0.0, data.size(), lengths.size());

        // ok, we have valid data, return it
        return new DHT11Result(DHT11Result.ERR_NO_ERROR,
                Double.parseDouble(bytes[2] + "." + bytes[3]),
                Double.parseDouble(bytes[0] + "." + bytes[1]),
                data.size(), lengths.size());
    }

    // this is used to determine where is the end of the data
    private final int MAX_UNCHANGED_COUNT = 500;

    private void collectInput() {
        // collect the data while unchanged found
        int unchangedCount = 0;

        int last = -1;
        while (true) {
            int current = Gpio.digitalRead(pin);
            data.add(current);

            if (last != current) {
                unchangedCount = 0;
                last = current;
            } else {
                if (++unchangedCount > MAX_UNCHANGED_COUNT) break;
            }
        }
    }

    protected enum SignalTransition {
        STATE_INIT_PULL_DOWN      ,
        STATE_INIT_PULL_UP        ,
        STATE_DATA_FIRST_PULL_DOWN,
        STATE_DATA_PULL_UP        ,
        STATE_DATA_PULL_DOWN
    };

    private void parseDataPullUpLengths() {
        SignalTransition state = SignalTransition.STATE_INIT_PULL_DOWN;

        int currentLength = 0; // will contain the length of the previous period

        for (int current : data) {
            currentLength++;

            switch (state) {
            case STATE_INIT_PULL_DOWN:
                if (current == Gpio.LOW) {
                    // ok, we got the initial pull down
                    state = SignalTransition.STATE_INIT_PULL_UP;
                }
                break;

            case STATE_INIT_PULL_UP:
                if (current == Gpio.HIGH) {
                    // ok, we got the initial pull up
                    state = SignalTransition.STATE_DATA_FIRST_PULL_DOWN;
                }
                break;

            case STATE_DATA_FIRST_PULL_DOWN:
                if (current == Gpio.LOW) {
                    // we have the initial pull down, the next will be the data pull up
                    state = SignalTransition.STATE_DATA_PULL_UP;
                }
                break;

            case STATE_DATA_PULL_UP:
                if (current == Gpio.HIGH) {
                    // data pulled up, the length of this pull up will determine whether it is 0 or 1
                    currentLength = 0;
                    state = SignalTransition.STATE_DATA_PULL_DOWN;
                }
                break;

            case STATE_DATA_PULL_DOWN:
                if (current == Gpio.LOW) {
                    // pulled down, we store the length of the previous pull up period
                    lengths.add(currentLength);
                    state = SignalTransition.STATE_DATA_PULL_UP;
                }
                break;
            }
        }
    }

    private boolean[] calculateBits() {
        boolean[] bits = new boolean[40];

        int longestPullUp  = lengths.stream().mapToInt(Integer::intValue).max().getAsInt();
        int shortestPullUp = lengths.stream().mapToInt(Integer::intValue).min().getAsInt();

        // use the halfway to determine whether the period it is long or short
        int halfway = shortestPullUp + (longestPullUp - shortestPullUp) / 2;

        int i = 0;
        for (int length : lengths) bits[i++] = length > halfway;

        return bits;
    }

    private byte[] bitsToBytes(boolean[] bits) {
        byte[] bytes = new byte[5];
        byte   value = 0;

        for (int i = 0; i < bits.length; i ++) {
            value <<= 1;
            if (bits[i]) value |= 1;

            if (i % 8 == 7) {
                bytes[i / 8] = value;
                value = 0;
            }
        }
        return bytes;
    }

    private byte calculateChecksum(byte[] bytes) {
        return (byte)(bytes[0] + bytes[1] + bytes[2] + bytes[3]);
    }
}
DHT11Result.java
public class DHT11Result {
    // DHT11 sensor result returned by DHT11.read() method

    public static final int ERR_NO_ERROR     = 0;
    public static final int ERR_MISSING_DATA = 1;
    public static final int ERR_CRC          = 2;

    private int errorCode = ERR_NO_ERROR;
    private double temperature;
    private double humidity;
    private int listElements;                // for debug
    private int detectedBits;                // for debug

    DHT11Result(int errorCode, double temperature, double humidity, int listElements, int detectedBits) {
        this.errorCode   = errorCode;
        this.temperature = temperature;
        this.humidity    = humidity;

        this.listElements = listElements;
        this.detectedBits = detectedBits;
    }

    public boolean isValid() {
        return errorCode == ERR_NO_ERROR;
    }
    public int getErrorCode() {
        return errorCode;
    }
    public double getTemperature() {
        return temperature;
    }
    public double getHumidity() {
        return humidity;
    }
    double getListElements() {
        return listElements;
    }
    double getDetectedBits() {
        return detectedBits;
    }
}

#コンパイルと実行
上の3つのファイルを適当なディレクトリに置いてコンパイルします。

pi@raspberrypi:~ $ pi4j -c DHT11Demo.java
--------------------------------------------
Pi4J - Compiling: DHT11Demo.java
--------------------------------------------
+ javac -classpath '.:classes:*:classes:/opt/pi4j/lib/*' -d . DHT11Demo.java
pi@raspberrypi:~ $

コンパイルが終わったらルート権限で実行します。2秒ごとに温度・湿度のデータを読み出して表示します。終了する場合は Ctrl+C を入力してください。

pi@raspberrypi:~ $ sudo pi4j DHT11Demo
+ java -classpath '.:classes:*:classes:/opt/pi4j/lib/*' DHT11Demo
Last valid input: Sun May 13 11:02:56 JST 2018
Temperature: 26.6 C
Humidity:    52.0 %
Last valid input: Sun May 13 11:02:58 JST 2018
Temperature: 26.7 C
Humidity:    52.0 %
Last valid input: Sun May 13 11:03:00 JST 2018
Temperature: 26.7 C
Humidity:    52.0 %
Last valid input: Sun May 13 11:03:02 JST 2018
Temperature: 26.7 C
Humidity:    52.0 %
Last valid input: Sun May 13 11:03:06 JST 2018
Temperature: 26.8 C
Humidity:    51.0 %
^Cpi@raspberrypi:~ $

DHT11 はタイムクリティカルな通信を行うため、マルチタスク OS 上で動く Java からの読み取りは比較的失敗することが多く、特に起動時は数回続けて失敗することが多いです。実際に使用する場合はサンプルコード(DHT11Demo.java)のように DHT11Result クラスの isValid() メソッドで読み取り結果を確認し、成功するまでリトライしてください(メソッドが false を返す場合は、読み取りに失敗しています)。

5
4
0

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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?