しまねソフト研究開発センター(略称 ITOC)にいます、東です。
Grove Beginner Kit for Arduino を使ってみる記事の第6回、今回は I2C 接続の温湿度センサーを題材にします。
このレポートでは、
「メーカーのデータシートを見て、センサICを直接コントロールをすること」
を、方針とします。
もちろん、プログラムを書く上で既存のライブラリ等の実装を参考にすることは良い事です。しかしながらこのレポートは、I2C バスやセンサ IC の扱い方、データシートの読み方を示す事も目的としているため、できるだけ低レイヤーでの説明を行います。
ターゲットは、以下の通り。
- Arduino - 付属の Arduino Uno 互換機
- Ruby - Raspberry Pi + Grove Base HAT for Raspberry Pi
- mruby/c - RBoard
温湿度センサー (Temperature&Humidity Sensor(DHT20))
外観 | 回路図 |
---|---|
(センサーがGrove端子に接続されてるだけなので省略) |
(メーカーWiki: Grove - Temperature&Humidity Sensor(DHT20) より引用)
搭載されているセンサーICは、ASAIR製 DHT20(ケース色が黒色) です。
2022年頃、Grove Beginner Kit に仕様変更があったようで、従来は DHT11(ケース色が水色)が使用されていました。Grove Beginner Kit 公式ドキュメントもまだ DHT11 のままです。この両者には、制御方法に互換性がないので、注意が必要です。
このレポートでは、DHT20 の方を説明しています。
データシートの確認
ICメーカー製品ページ で、データシートを確認します。
このデータシートは、情報が断片的で、なかなかに理解が難しいものでしたので、一緒に提供されているサンプルプログラムと併せて読み解きます。
また、このICは、よくある内部レジスタを読み書きする方法ではなく、まるでストリーム通信のようにI2Cバスを使っています。もちろんこういった使い方は規格違反ではありませんが、たまに見かける「内部レジスタを1バイトで指定してデータ送受信を行うことしか想定していないI2Cライブラリ」では扱うことができないと思われます。
ICの初期化
ICの初期化は以下の通りです。
- 電源ON後、500ms 待つ。(データシートでは、最低100msと記載されているが、サンプルプログラムでは 500msを推奨と書いてあった)
- 0x71 を送信し 1バイトをステータスワードとして読み取る
- (ステータスワード & 0x18) == 0x18 でなければ、IC自体の初期化が必要。だが、初期化方法はデータシートには無くウェブページを参照するように記載してある
データの読み込み
プログラムからの指示で測定を開始し、測定完了を待って測定データを取得する方式です。またデータ化けなどが起こっていないかの確認のため、1バイトのCRCが付与されています。読み取りシーケンスは以下の通りです。
- トリガーコマンド
0xAC,0x33,0x00
を送信する - 80ms 待つ
- 0x71 を送信し 1バイトをステータスワードとして読み取る
- ステータスワードの bit7 が 1 ならば、もう少し待つ
- ステータスワードの bit7 が 0 ならば、続いて 6 バイト読み取る
- データは、温度、湿度とも、20bit長
データを読み込んだ後、必要に応じて CRC を確認後、データシート記載の計算式に従ってデータを換算します。
RH = (\frac{S_{RH}}{2^{20}}) * 100
T = (\frac{S_T}{2^{20}}) * 200 - 50
サンプルプログラム
プログラムを簡単にするため、ステータスワード確認から引き続き6バイトの読み込みは、合わせて7バイトの読み込みをしてから確認することにします。また、起動時に確認している (ステータスワード & 0x18) != 0x18
時の IC 自体の初期化は、こちらで試す限りでは一度も必要になりませんでしたので、コードには反映していません。
Arduino
#include <Wire.h>
const int I2C_ADRS = 0x38;
void setup() {
Wire.begin();
Serial.begin(9600);
// IC初期化
delay( 500 );
Wire.beginTransmission( I2C_ADRS );
Wire.write( 0x71 );
Wire.endTransmission( false );
Wire.requestFrom( I2C_ADRS, 1 );
int sts = Wire.read();
if( (sts & 0x18) != 0x18 ) {
Serial.println("Sensor needs initialize.");
while(1)
;
}
}
void loop() {
byte data[7];
// Trigger
Wire.beginTransmission( I2C_ADRS );
Wire.write( 0xac );
Wire.write( 0x33 );
Wire.write( 0x00 );
Wire.endTransmission();
// Read status & data
int i;
for(i = 0; i < 10; i++) {
delay(80);
Wire.requestFrom( I2C_ADRS, 7 );
for( int i = 0; i < 7; i++ ) {
data[i] = Wire.read();
}
if( (data[0] & 0x80) == 0 ) break;
}
if( i == 10 ) {
Serial.println("Sensor read error.");
return;
}
// check CRC
int crc = 0xff;
for( int i = 0; i < 6; i++ ) {
crc ^= data[i];
for( int i = 0; i < 8; i++ ) {
crc <<= 1;
if( crc > 0xff ) {
crc ^= 0x31;
}
crc &= 0xff;
}
}
if( crc != data[6] ) {
Serial.println("CRC error.");
return;
}
// convert
double humidity = (double)((uint32_t)data[1] << 12 | (uint32_t)data[2 << 4] | data[3] >> 4) / 1048576 * 100;
double temperature = (double)((uint32_t)(data[3] & 0x0f) << 16 | (uint32_t)data[4] << 8 | data[5]) / 1048576 * 200 - 50;
// display
Serial.print("Temp: ");
Serial.print(temperature);
Serial.print(" Humi: ");
Serial.println(humidity);
delay( 1000 );
}
Raspberry Pi (CRuby)
require "mruby/i2c"
class DHT20
I2C_ADRS = 0x38
#
# init instance
#
def initialize( i2c_bus, address = I2C_ADRS )
@bus = i2c_bus
@address = address
end
#
# init sensor
#
def init()
sleep 0.5
data = @bus.read( @address, 1, 0x71 ).bytes
if (data[0] & 0x18) != 0x18
raise "Sensor needs initialize."
end
end
#
# meas
#
def meas()
@bus.write( @address, 0xac, 0x33, 0x00 ) # Trigger
retry_count = 0
while true
sleep 0.08
d = @bus.read( @address, 7 ).bytes
break if d.size > 1 && d[0][7] == 0
retry_count += 1
raise "Retry error." if retry_count > 10
end
# check CRC
crc = 0xff
6.times {|i|
crc ^= d[i]
8.times {
crc <<= 1
crc ^= 0x31 if crc > 0xff
crc &= 0xff
}
}
raise "CRC error." if crc != d[6]
return {
:humidity => (d[1] << 12 | d[2] << 4 | d[3] >> 4).to_f / 2**20 * 100,
:temperature => ((d[3] & 0x0f) << 16 | d[4] << 8 | d[5]).to_f / 2**20 * 200 - 50,
}
end
end
sensor = DHT20.new( I2C.new )
sensor.init
while true
data = sensor.meas
printf("Temp:%5.2f Humi:%3.0f\n", data[:temperature], data[:humidity] )
sleep 1
end
RBoard (mruby/c)
RaspberryPiと同じプログラムから、require をコメントアウトしたプログラムがそのまま動きます。
おわりに
今回は、温湿度センサーを使ってみました。
このセンサーの公称性能「確度、温度 ±0.5℃、湿度 ±3%」が正確に測れるとしたら驚異的な性能ですね。
一般的に、温度や湿度は測定誤差が大きくなりがちです。特にセンサ周辺に熱源があったり輻射熱をうけたり、また、値が逐次変化する場合はセンサー自体の時定数の影響もあります。身近なのに、意外と正しく測定するのは難しいのが、温度、湿度です。
次回は最終回、OLED ビットマップディスプレイを題材にしようと思います。