時刻サーバーからのデータをTWELITEで受信する

前回は、RTC-8564NBからI2Cでデータを読み取って、TWELITEで無線送信するところまでやりました。
alasixosaka.hatenablog.com

今回は、無線の受け側(親機)の話です。
まずは、簡単にプログラムするために、TWELITE STICKを使って受信することをやりました。
親機用のプログラムも、BME280のテストの時使ったプログラムをベースに少し改良するだけでできます。
BME280のデータを解析してシリアル出力していた部分を下記のように変更します。
今回はアプリの固有4文字が”RTCC”なので、RTCCが送られてきますので、if分で”RTCC”を判定して、RTCCだったら以下の処理を行います。
パケットが秒、分、時、日、月、年、曜日の順に送られてきますので、その順に各変数で受け取ります。
それを所定の形式で出力するだけです。

if 	(!b_handled && 
		(  !strncmp((char*)fourchars, "RTCC", 4) // BMx280		
		)
	)
{
	b_handled = true;

	uint8_t u8sec;
	uint8_t u8min;
	uint8_t u8hour;
	uint8_t u8day;
	uint8_t u8month;
	uint8_t u8year;
	uint8_t u8wday;

	np = expand_bytes(np, rx.get_payload().end()
		, u8sec
		, u8min
		, u8hour
		, u8day
		, u8month
		, u8year
		, u8wday
	);

	if (np != nullptr) {
		Serial 	<< format("%02x:%02x:%02x", u8hour, u8min, u8sec)
				//<< " Temp:" << div100(int16_t(u16temp))
				<< crlf
				<< format("%02x/%02x/%02x/%01x", u8year, u8month, u8day, u8wday)
				<< crlf
				;
	} else {
		Serial << ".. error ..";
	}
}

ただ今回はここからが問題でした。

TWELITEをスリープする

基本的にTWELITEの親機はスリープしないように使用するのが一般的ですが、今回は時刻合わせのためだけにTWELITEを使うので、1回/日程度通信できれば良い。そして、時計は電池で駆動するので不要な時はスリープさせておきたい。ということで、スリープをさせることにします。
TWELIETの親機用のサンプルアクトの説明には、ウォッチドッグタイマが1分でかかるというように書いてあります。つまり、無限ループなどに1分以上いるとウォッチドッグタイマーにかかってしまってリセットされてしまいます。ただ、時刻サーバーからはだいたい2秒間隔でデータが送られてくるので、5秒待ってデータが来なかったらスリープするというようにしようと考えました。TWELIETにはmilles()という関数があり、起動からの時間をmsで16ビットでカウントする関数があります。これを読んで、次のように、5秒間ループし、その間にデータが来なければ5秒間スリープするということを考えました。

int now = int(millis()&0xFFFF);
while(int(millis()&0xFFFF) < (now + 5000)){

}
the_twelite.sleep(5000);

ところが、これを実行すると、スリープが実行されず、リセットがかかってプログラムが最初から走ってしまいます。リセットがかかる理由はウォッチドッグタイマー以外に考えにくいので、とりあえずオフにしてみました。

ウォッチドッグタイマーをオフにする

Stick用のプログラムの説明にはウォッチドッグタイマーをオフにするやり方が書いてあります。通常は、

pinMode(PIN_WDT_EN, OUTPUT_INIT_LOW);

のように、PIN_WDT_ENをLOWにしておくところを、次のようにHIGHにしておくと、ウォッチドッグタイマーが働かなくなります。

pinMode(PIN_WDT_EN, OUTPUT_INIT_HIGH);

PIN_WDT_ENとは何ぞやというところですが、とりあえずマニュアルに従ってやってみます。
ところが、これでもリセットがかかってしまいます。

待ち時間を短くする。

待ち時間を5000としていたのを、2000(つまり2秒)に書き換えると、今度はスリープまでたどり着くことができました。色々試してみましたが3.5秒くらいが限界でした。この3.5秒にどういう意味があるのかさっぱりわかりませんが、例えば、つぎのように2秒待ちを繰り返すことで4秒間の待機をすることができます。

int now = int(millis()&0xFFFF);
while(int(millis()&0xFFFF) < (now + 2000)){

}
now = int(millis()&0xFFFF);
while(int(millis()&0xFFFF) < (now + 2000)){

}

これで解決万事めでたし。と思いきやそうは問屋が卸さないようで、

ウェイトループがあると受信ができない。

という、致命的なことがわかりました。ループ関数内は本来は何も書かれていないのですが、ここにウェイト関数なんかを入れてやるとうまく受信ができなくなります。delay(4000); とやっても同じです。ディレイの方はPICとかでも割り込みがうまくかからなくなるので、なんとなくそうだろうと思っていましたが、たんなるウェイトループでもうまくいきませんでした。
TWELITEの資料を読むと、loop関数は、「アプリケーションのメインループです。ループ終了後はCPUがDOZEモードに遷移し低消費電流で次の割り込みを待ちます。」と書いてあります。ということは、動作はArduinoのloop関数などと違って、ずっとループ内にいるのではなく、処理が終わったらループを抜け出して割り込み待ちになるということのようです。これが、loop関数内でウェイトを入れてうまく受信できなかった理由のようです。つまり、loop関数を抜けないと割り込み待ちにならないということです。ですので、loop関数内で時間待ちをするということはTWELITEの仕様上無理のようです。

ウェイトループは諦めて受信したらスリープするようにする

結局、結論はこれしかなさそうです。
TWELITEの資料の色々なところを読んでみましたが、解決策が見つけられず、ループ関数は元のまま、何もしないという形に戻して、受信処理の最後にスリープを入れることにしました。これはうまくいきました。受信したら、所定の時間スリープして、目覚めたらまたスリープするということを繰り返します。
ただ、こうすると、時刻サーバー側が何らかの理由で止まってしまっていた場合、受信するまで永遠に待ち続けるということになってしまいます。この辺りはPIC側で管理して、データ受信ができなければエラー表示をするという処理をするしかないと思います。それでもTWELITEは動き続けるのでもしそういう事態になったら結構電池を消費してしまうと思いますが現状では他に手がなさそうなので仕方がありません。ただ、資料によるとTWELITEはDOZEモードに入るようなので、めいっぱい電流を消費するわけではなさそうです。そのあたり、本当にどのくらい消費するかは、実際に消費電流を測ってみようと思っています。