DHT11 という比較的価格の安い(ただし精度はいまいちな)デジタル温湿度センサーがあるので読んでみた。1-wire ライクな (1-wireではない) プロトコルで、データシートを読みながら頑張って読む感じ。
- 18ms 以上バスをローにすることでデータ送信を行わせる
- 18ms のローを送信したあと、入力状態に変えて 50us ロー + 50us ハイを待つ (初期メッセージ)
- そのあと 40回 50us ロー + (28us or 70us ハイ) のセットが送られてくる。70us のほうが「1」のビット
だいたいすぐできるんだけど、waiting for slope up と書いてあるところのディレイを入れないと、バスがハイになる前に loop_until_bit_is_clear してしまって状態がズレてしまうので、立ちあがる時間を適当に待つ必要があった。
湿度・温度それぞれ16bitが送られてくるんだけど、下位8bitは、このモジュールの場合、どちらも全部0しか返ってこない。のでコードでは無視している。下位 8bit はAM2302とか精度が良いがコストが高いというモジュールがあって、そちらの場合使われるっぽい。
あと、送られてくるデータは前回測定時のデータなので、2回読み出しを行って2回目を採用しないと、正しいデータにならない。連続して読むなら関係ない。
int main(void) {
logger_init(9600);
setup_io();
printf("initializing...\r\n");
sei();
printf("initialized\r\n");
uint8_t i, j, us;
int16_t humidity, temperature;
uint8_t res[5];
set_input(DDRD, PD7); clear_bit(PORTD, PD7); // hi-z
for (;;) {
set_output(DDRD, PD7); clear_bit(PORTD, PD7);
_delay_ms(20);
set_input(DDRD, PD7); // hi-z
// waiting for slope up
_delay_us(5);
// waiting for slave initial response
loop_until_bit_is_clear(PIND, PD7);
loop_until_bit_is_set(PIND, PD7);
loop_until_bit_is_clear(PIND, PD7);
for (i = 0; i < 5; i++) {
for (j = 0; j < 8; j++) {
us = 0;
// 50us
loop_until_bit_is_set(PIND, PD7);
while (bit_is_set(PIND, PD7)) {
us++;
_delay_us(1);
}
res[i] <<= 1;
if (us > 50) { // 0: <28us 1: <70us
res[i] |= 1;
}
}
}
printf("%02x %02x %02x %02x %02x\r\n", res[0], res[1], res[2], res[3], res[4]);
if (( (res[0] + res[1] + res[2] + res[3]) & 0xff) == res[4]) {
humidity = res[0];
temperature = res[2];
printf("%d%%RH %dC\r\n", humidity, temperature);
} else {
printf("invalid data\r\n");
}
_delay_ms(5000);
}
}
信号全体
最初の長いロー部分が、AVR 側からのローで 18ms ある。その後、ちょっとハイになってるところは、センサー待ちの部分、そして 50us ロー・50us ハイに続いてデータが流れてくる。目で見てビット読める感じでたのしい。
立ちあがり時間
これは 18ms 待ちあとで AVR 側を入力に切り替えてハイインピーダンス状態にした直後の立ちあがり。立ちあがりにちょっとかかっていて、その後11usぐらい待ち時間がある。
参考
DHT22 というプロトコル互換デバイスについてのものがあったのでコードほぼそのままです。