Raspberry PiでRHT03を制御する

RHT03はMaxDetect社が製造している湿度計です。これをRaspberry Piで制御する方法を紹介します。ド素人だけに大変苦労しました。

RHT03は誤差がわずか±2%という精度の高さにもかかわらず、千石電商にて1,200円程で販売されています。メーカーの都合で型番が2種類(RHT03とDHT22)あるようですが、同一仕様のようです。RHT03をRaspberry Pi (以下ラズベリー)で制御するには、RHT03をGPIOへ接続し、GPIOの電圧を測定します。そして測定した電圧を湿度へ変換します。制御には大変苦労しました。RHT03を購入してから半年位かかったかと思います。最近は木工と金工ばかりでしたのも一因ですが。

2018/8/24 追記

RHT03以外にもいろいろなセンサーがあることがわかりました。RSコンポーネンツでは、湿度センサーが幅広く取り扱ってあります

まずRHT03の仕様書を元に、RHT03とラズベリーを接続しました。

RHT03とラズベリー

RHT03とラズベリー

これ以降、GPIO7を使い続けます。

動作確認

まずRHT03が動作しているのかどうか試してみたいと思い、プログラムを探しました。メーカーはサンプルプログラムを公開していますが、Word形式で公開しています。(^^; そして読んでもよくわかりませんでした。(^^;; 次にWiringPiにサンプルコードが含まれているのを発見しました。早速コンパイルして実行してみましたが、無限ループしてしまいます。ソースコード (wiringPi-da38443/examples/rht03.c) を開くと、すぐに怪しいコードが目につきました。

#define       RHT03_PIN     0

恐らくRHT03を接続したGPIO番号を意味するのでしょう。「0」を「7」に書き換えてコンパイルしましたが、それでもうまく動きません。さらに調べていると、ライブラリ (wiringPi-da38443/devLib/maxdetect.c) のmaxDetectLowHighWait関数で常にタイムアウトしている事がわかりました。この関数は電圧を無限ループで計測し続け、結果がLOWからHIGHに切り替わった時にループ抜けます。2秒間のタイムアウトがありますが、「2」という数自体に根拠はなさそうです。

結局自分でプログラムを作る事にしました。以前も書きましたが、私はC++でプログラムしています。まずはスタート信号を送り、GPIOで電圧を読みとることにしました。マニュアルの以下の部分に相当します。

When MCU send start signal, RHT03 change from standby-status to running-status. When MCU finishs sending the start signal, RHT03 will send response signal of 40-bit data that reflect the relative humidity and temperature to MCU. Without start signal from MCU, RHT03 will not give response signal to MCU.

日本語へ訳すとこのようなります。

MCUがスタート信号を送信すると、RHT03はスタンバイ状態から動作状態になります。MCUがスタート信号を送信し終わると、RHT03は相対湿度と気温を反映した40ビットの応答信号をMCUへ返します。MCUからのスタート信号がなければRHT03は応答信号をMCUへ返しません。

プログラムはこうなります。

setDirection(gpio_number, DIRECTION_OUTPUT);
setVoltage(gpio_number, VOLTAGE_LOW);
sleep_milliseconds(1);
setVoltage(gpio_number, VOLTAGE_HIGH);
sleep_microseconds(40);
setDirection(gpio_number, DIRECTION_INPUT);
while (ETERNAL)
cout << getVoltage(gpio_number) << endl;

結果を出力してExcelでグラフ化しました。

Excelでグラフ化

Excelでグラフ化

電圧が上下している事と、電圧が高い(低い)時間に幅がある事がわかります。RHT03は返答しているようです。 😉

後はこのグラフを数値に変換すればいいはずですが、その方法がよくわかりませんでした。一定時間電圧を測り、HIGHが長ければ1、LOWが長ければ0にするのだと最初は思いました。それを40回繰り返して得られたビット列を数値変換する訳です。例えば1ビットが1ミリ秒だとすると、

  1. 1ミリ秒電圧を測る
  2. HIGHが長ければ1、LOWが長ければ0とみなす
  3. 40回繰り返す
  4. 0と1を10進数へ変換

という手順かと思いました。結局これは間違っていましたが。

正確な時間測定

ラズベリーでこのような電圧計測を行う方法はあるのでしょうか。GPIOによって電圧を計測することは可能ですが、その結果は計測した時、すなわち「ある一瞬」の電圧にすぎません。私は「ある期間」の電圧を測定する必要があると思いました。最初に思いついたのはスリープ関数(usleep())を使う事です。

getVoltage(gpio_number);
usleep(1);

実際にやってみて、すぐに失敗に気付きました。ラズベリーではusleep(1)すると実際には155~164マイクロ秒スリープしてしまいます。しかも電圧を「ある一瞬」だけ計測してその後スリープするため、「ある期間」を測る事にはなりません。

次に考えたのは、「ある一瞬」の電圧を「ある期間」だけ計測し続ける作戦です。そしてHIGHが「多ければ」1、LOWが「多ければ」0とみなすことにしました。期間を正確に計測するにはシステムタイマーを使います。システムタイマーとは、ラズベリーが起動してからの時間の事で、単位はマイクロ秒です。システムタイマーについては、BCM2835 ARM Peripheralsを参考にしました。

「ある一瞬」のGPIOの電圧を計測するのも、「ある一瞬」のシステムタイマーを計測するのも同じ方法です。どちらもBCM2835のメモリから直接取得します。例えばGPIOのアドレスは以下の通りです。

// ペリフェラルベース開始アドレス
static const unsigned int BASE_ADDRESS_PERIPHERAL = 0x20000000;
// BASE_ADDRESS_PERIPHERAL から GPIOレジスタ へのオフセット
static const unsigned int OFFSET_GPIO = 0x200000;

GPIOのアドレス = BASE_ADDRESS_PERIPHERAL + OFFSET_GPIO = 0x20000000 + 0x200000 = 0x20200000です。

一方、システムタイマーのアドレスは

// BASE_ADDRESS_PERIPHERAL から システムタイマーレジスタ へのオフセット
static const unsigned int OFFSET_SYSTEM_TIMER = 0x3000;
// システムタイマーレジスタ から カウンター(の下位32ビット)レジスタ へのオフセット
static const unsigned int OFFSET_SYSTEM_TIMER_COUNTER_LOW = 0x1;

ですので、システムタイマーのアドレス = 0x20000000 + 0x3000 + 0x1 = 0x20003001です。

仕様書によると、他にもSystem Timer Compareというものがあり、あらかじめ指定したタイマーになるとSystem Timer Control/Statusを1にしてくれるようです。同時に複数の機器を計測する場合にはこちらを使う必要がありますが、今回は1つだけなので使いません。

電圧からバイナリへの変換

結果を出力してExcelでグラフ化しました。

Excelでグラフ化

Excelでグラフ化

これをどのようにバイナリ列へ変換するのか、わからなくなってきました。もし湿度が50%、気温が20℃だとすると、ビット列はこのようになるはずです。

50% = 0000 0001 1111 0100
20C = 0000 0000 1100 1000

0が連続するはずなのに、グラフではLOWが連続していません。HIGHとLOWを行ったり来たりしています。検索してみると、以下のページにオシロスコープによる測定結果がありました。

DHT22を使った温度計・湿度計の製作

これを見てようやく意味がわかりました。

  • 誤: 電圧HIGHなら1、LOWなら0
  • 正: 電圧はビットごとに最初は必ずLOWになり、その後必ずHIGHになる。HIGH が26~28マイクロ秒続けば0、70マイクロ秒続けば1

でした。試しにExcel上で計算してみるとぴったり40ビットとなり、値もそれらしくなりました。

Excel上で計算

Excel上で計算

チェックサム

RHT03にはチェックサムがあります。40ビットのうち最初の32ビットを8ビットごとに足していくと、結果がチェックサムと等しくなります。逆に言うと、結果がチェックサムと異なる場合は通信エラーを意味します。

実際にやってみます。なお、2進数なので1+1=10です。

0000001000011100000000101010110011011010の場合、最初の32ビットを8ビットごとに足していくと

00000010
00011100
00000010
10101100
--------
11001100

となります。

電卓

電卓

これはチェックサム(40ビットの最後の8ビット)の11011010とは一致しないため、通信エラーということになります・・・。

なお、このような方法でエラーを発見する方法を「誤り検出」と呼びます。数学的にはエラーを発見するだけでなく訂正する事も可能ですが、それを「誤り訂正」と呼びます。

誤り検出訂正 (Wikipedia)

RHT03のチェックサムは「誤り検出」用です。残念ながら「誤り訂正」用ではありません。

エラー検証

エラー原因を追究しました。まず正常時の電圧はこのようになります。

RHT03 voltage analysis 05

チェックサムエラー時の電圧をいくつかグラフ化すると、不自然な点がありました。

RHT03 voltage analysis 06

3,500マイクロ秒を経過したあたりでデータが抜けています。通常は1マイクロ秒に電圧を3回程測定できていますが、1回だけ173マイクロ秒もかかっています。ラズベリーがRHT03の信号を「聞き逃した」のです。恐らく原因はラズベリーが「忙しかったから」でしょう。OSのタスク管理の問題です。対症療法として電圧計測に26マイクロ秒以上かかった場合をエラー扱いする処理を追加しました。

次に1日連続計測してみました。cronで10分に1回計測し、結果をExcelでグラフ化しました。

RHT03 Result 01

計測値が異常な事がすぐにわかります。湿度が100%を超える事はありません。(^^;;; ラズベリーとRHT03のやりとりが間違っているはずですが、たまたまチェックサムが一致してしまったのです。湿度が100%を超えた場合をエラー扱いする処理を追加しました。

これらのエラーになった場合はただちに再計測するようプログラムしました。そしてようやくまともなグラフになりました。

RHT03 Result 02運用検証

Raspberry Piの真上にRHT03を置くと気温が高くなる事に気付きました。Raspberry Piが発熱するからです。そこで場所を離す事にしました。また、これまで使用してきたSTMicroelectronicsのSTTS751-0WB3FとFreescale SemiconductorのMPL115A2も一緒に稼働させてみました。これらは以前の記事で少し紹介したものです。

最終的にこの配線になりました。

最終的な配線

最終的な配線

Raspberry Pi上のブレッドボードは便利なので残し、もう1枚のブレッドボードへLANケーブルを使って接続しました。LANケーブルを使ったのは以下の理由です。

  • 通信を前提としたケーブルである
  • 手元に在庫がある
  • コンビニでも買えるほど普及している
  • そのため価格と品質が安定している
  • ケーブルが柔らかく配線が楽
  • 本数が8本もある

これらを全て満たすケーブルは他に見た事がありません。ただ私はノイズ、周波数、配線などを勉強中であり、間違っているかもしれません。

ブレッドボードにLANケーブルを接続するため、秋月電子のLANコネクタDIP化キットを使いました。

電源を接続している側です。

電源を接続している側

電源を接続している側

I2Cを接続している側です。

I2Cを接続している側

I2Cを接続している側

そして一度に計測してみました。

RHT03 Result 03STTS751とRHT03の気温は互いに近いため、問題なしと判断しました。一方、RHT03の湿度がたまに1, 2%ほど上昇している事に気付きました。このグラフに湿度計測回数を上書きするとこうなります。

RHT03 Result 04問題を整理しました。

  • 必ず2回計測に失敗している
  • 必ず奇数回計測している
  • 5回以上計測すると湿度が1, 2%ほど上昇する
  • 5回以上計測しても湿度上昇量は変わらない

最初の2つはプログラム改善で直しました。この怪奇現象とも思える問題は、前述のOSのタスク管理の問題だと思われます。計測前に必ず500ミリ秒待機すると解消しました。

RHT03 Result 05エラーになる理由も整理しました。理由は以下の5つに絞れました。

  • Raspberry Pi の電圧計測が遅れた
  • チェックサムが一致しない
  • 40ビット取得できない
  • 計測にタイムアウトした
  • 計測した湿度が異常 (100%を越える)

エラーにも原因と結果の組み合わせがあります。発生したエラーの組み合わせを真理値表に書き出して、文字通り真理を追究してみました。

真理値表

真理値表

チェックサムが一致しないのが根本的な原因です。1回だけチェックサムが一致しています。これはRaspberry Pi の電圧計測が遅れた時に偶然チェックサムが一致したためです。RHT03のチェックサムはただの「誤り検出」です。このような事があるので注意してください。

湿度が1, 2%ほど上昇する理由は、5ビット目が必ず1増加するからです。2進数で10000は16です。この値はRHT03の1.6%に相当するので、ちょうど上昇量と一致します。

なぜ問題が発生するのかはわかりません。計測と5ビット目の因果関係は興味があるので調査中です。しかし因果関係がわかったところでどのように直すのかはわからないでしょう。RHT03内部の話になるため私には検証しようがないからです。私はこれ以上の調査をやめました。もしかしたら、これが2%の誤差の理由なのかもしれません(笑)。

ダウンロード

プログラムを試したい方は、Tango RHT03用機能限定版をダウンロードしてください。

2016/9/28追記

レバテック様のページにて紹介いただきました。詳しくはこちら

また、この記事には続きがあります。「Raspberry Piで気象観測 その1 回路編」もどうぞ。

もし本記事に興味、ご関心があれば、ぜひ「ラズベリーパイと電子工作の記事一覧」を見てください。例えばこんな記事があります。

  • ド素人がRaspberry Piで電子工作を始める
    ラズベリーパイ財団が制作したたばこの箱サイズのパソコン「Raspberry Pi」は、私をパソコンから電子工学へ橋渡ししてくれる存在となりました。基盤やコネクタに群がるゾンビにしてしまったと言った方が正確かもしれませんが(笑)。
  • Raspberry Piで電子工作 その2
    電流、抵抗、電圧の意味がわからず、アキバのパーツ屋で買物すらできなかった私。勉強のためにお金をつぎ込んだ結果、だんだんと意味がわかってきました。
  • Raspberry Piでの電子工作にあたって購入した道具
    ラズベリーで電子工作を始めて4ヵ月が経ちました。勉強を進めるにあたり、本や電子パーツ、道具など、必要に応じて購入する必要がありました。今日は私がラズベリー以外に購入した道具を紹介します。

This post is also available in: 英語

コメントを残す

頂いたコメントは見逃さないようにメールでも送られるようになっています。