前回、ようやく気温・湿度を測定するところまでたどり着きました。しかし、ちょっと逸ったのです。まだまだ、制御が完全ではありません。今回は、AM2302の制御を仕上げていきます。
まず、前回のプログラム(test_am2302.ino)で、Arduino起動時にいきなり、2sの待ち時間(delay(2000))が入っていたことにお気づきでしょうか?これ、AM2302のデータシートで、スペックとしては規定されていませんでしたが、使用方法の説明文中に、「電源投入時の不確定時間(unstable state) 2sの間、読み出しするな」と表記された時間を待っているのです。
一方、スペックとしてDC特性表に明示されたパラメータで、Sampling period=2sというものもあります。
どうも、データシートを読んでみてわかったことは、以下の様な動作になっているらしいことです。
なので、最後のステップまで到達してすべての準備が整う前に、連続して転送要求が来てもAM2302は応答できないわけです。そのためのスペックが「Sampling period=2s」であり、Arduino側は、一度転送要求を出したら次の転送要求までの間に2sを待たなくてはなりません。
もうひとつ、大事なことにお気づきでしょうか?そう、転送要求を出すと、AM2302は、「前回測定して、記憶しておいたデータを、転送してくる」のです。1分毎に測定する用途であれば、せいぜい1分前の状態が送られてくるだけで、許容できるかもしれません。しかし、1時間毎、あるいは、3時間毎に測定する用途ではどうでしょうか?なんと、3時間も前の測定値を送信してくることになります。これは許容できません。
AM2302のデータシートにも、このことは記載されていて、以下の通りです。
Note: the temperature and humidity data read by the host from the AM2302 is always the last measured value, such as the two measurement interval is very long, continuous read twice to the second value of real-time temperature and humidity values, while two read take minimum time interval be 2S.
重要なので、引用させていただきました。要約すると、「読み出した値は前回測定値」「測定間隔が長いときは連続で2回読み出すこと」「読み出し間隔は2s待つこと」と、なります。
よって、Arduinoのプログラム上では、「Sampling period=2s」の規定を守ってやる制御と、リアルタイムの気温・湿度を知るために2回読みだして2回目のデータを採用する制御、を実現する必要があります。
すでにここまでの記事で何度か登場していますが、Arduinoには、起動してからの経過時間を返すmillis()とmicros()という関数があります。
経過時間を測ろうとするときは、
ちょっと考えてみましょう。millis()もmicros()も、一定時間経過すると、オーバフロウして0に戻ってしまいます。もし、t1とt2の間で、オーバフロウして、0に戻ってしまっていた場合、t2≦t1となるケースが出現してしまいます。大丈夫でしょうか?
もし、タイマがオーバフロウしてt2に計測されたのだとすると、本来は、t2の最上位ビットのさらに上の桁に1が立っているべきところです。
t2=0x1 yy yy yy yy : 計、33ビット
t1=0xzz zz zz zz : 計、32ビット
残念ながら、t2はunsigned longであり、32ビットしか記憶できないため、最上位にあるべき1が無視されて、大小が逆転(t2≦t1)している、と考えることができます。
t2<t1の時にt2−t1を計算しようとすると、そのままでは減算できないので、最上位ビットのさらに上の桁から1借りてきて(ボロウ)、引き算を実行します。結果、無視されていた最上位(33ビット目)の1が復活して、以下の様になります。
t2−t1=0x1 yy yy yy yy−0xzz zz zz zz=<正しく計算される>
↑ボロウによって、無視されていた1が復活
タイマがオーバフロウしたとしても、オーバフロウした回数が1回かつt2<t1の範囲であれば、正しく経過時間を計算できることがわかりました。
オーバフロウした後でさらに時間が進んでしまって、t2≧t1のケースになってくると、さすがに不具合が起きる可能性がありますので、注意して使いましょう。
AM2302から気温・湿度データを読み出したとき、次の読み出しまでの間に2s空ける必要があります。そこで、読み出しを開始したときに、millis()で起動後経過時間を計測しておきます(msec_prev)。次回の読み出しを行おうとする前に、millis()で起動後経過時間を計測し(msec_now)、msec_now−msec_prevが2,000ms以上になるまで、待ちます。2,000ms以上になったら、msec_prevを現在の起動後経過時間で更新して、AM2302からの読み出しを実行します。
const int tcyc_am2302=2000; // read interval time[ms]
static unsigned long msec_prev;
void read_am2302 (void)
{
unsigned long msec_now;
// cycle time (2s)
msec_now=millis ();
if (msec_now - msec_prev<tcyc_am2302)
{
delay (tcyc_am2302 - (msec_now - msec_prev));
msec_now=millis ();
}
msec_prev=msec_now;
// 読み出し処理
:
}
read_am2302()を呼び出す側は、サイクルタイムを気にせず、呼び出すことができますが、最大で2s近く、結果が返らないことに留意してください。
AM2302から読み出したデータは、AM2302が前回読み出された後でセンサから読み取った値なので、リアルタイム(現在)の気温・湿度を知りたい場合には、2回連続で読み出す必要があります。
read_am2302()関数で、すでにサイクルタイム(2s)の処理が実施されているので、呼び出し側は、必要に応じ2回連続でread_am2302()を呼ぶだけでOKです。
read_am2302 ();
read_am2302 ();
// 1回目の読み出しデータは捨てて、2回目に読み出したデータを採用する。
短い間隔で複数回、気温・湿度データを読み出して、平均を取って使用する場合などは、毎回2回連続で読み出す必要はないと考えられるので、プログラムで使い分けます。
シリアルモニタからコマンドを受け付けるようにプログラムします。
コマンド | 動作 | レスポンス | エラー時 |
req |
AM2302からデータを読み出して、 結果を返します。 読み出しは1回のみ。 |
OK,<気温>,<湿度> | ERR,<エラーコード> |
rrt |
AM2302からリアルタイムのデータを 読み出して、結果を返します。 2回読み出して2回目のデータを返す。 |
OK,<気温>,<湿度> | ERR,<エラーコード> |
who | プログラムバージョンを返します。 | AM2302,<バージョン> | − |
コマンドは、大文字小文字を区別します。間違って大文字で送信すると、「ERR,-4」が返ります。コマンドは小文字、レスポンスは大文字で統一しています。
完成したプログラムをリンクしておきます。
プログラム:meas_am2302.ino
実行サンプル:【 】内はシリアルモニタからの送信データ
RDY
【who】
AM2302,0.90
【req】
OK,28.2,60.7
【rrt】
OK,27.6,55.1
【RRT】
ERR,-4
いよいよ、ゴールに近づいてきました。ちょっと簡単すぎて拍子抜けですが、それだけ、Arduinoの開発環境が優秀だと言うことでしょう。次回は、データのログを取ってみます。