#はじめに
Raspberry Pi 3 を使って、安価で入手可能な温湿度センサー DHT11 から温度および湿度の情報を読み取ります。プログラミング言語は Java を使用しています。
DHT11 |
---|
#準備
Raspberry Pi の GPIO を Java から制御するためには Pi4J を使用します。Pi4J の環境については、以下の記事の「Pi4J のインストール」をご参照ください。
今回 Raspberry Pi と DHT11 は以下のように接続しました。データシートには、VDD と Data パスの間に 5KΩ の抵抗が記載されていましたが、手持ちになかったので 10KΩ で代用しました(ここはあまり詳しくないので、とりあえず良しとします・・・)。
(参考)DHT11のデータシート
#プログラム
プログラムは以下のリンク先にある Python のプログラムを参照して、Java に書き換えてみたものです(少し変更を入れています)。
プログラムは、以下の3つのファイルから構成されています。
- DHT11Demo.java
- DHT11.java
- DHT11Result.java
メインとなるクラス DHT11 のインスタンス作成時に、コンストラクタに対して DHT11 の Data ピンが接続されている Raspberry Pi のピンの GPIO 番号を指定します。例えば Raspberry Pi のピン番号 8 に接続されている場合は、対応する GPIO 番号である 15 を指定します。
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);
}
}
}
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]);
}
}
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 を返す場合は、読み取りに失敗しています)。