環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
アクトの機能
環境センサーパル AMPIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
アクトの使い方
必要なTWELITE
アクトの解説
インクルード
コピー #include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_AMB> // include the board support of PAL_AMB
環境センサーパル <PAL_AMB>
のボードビヘイビアをインクルードします。
setup()
コピー void setup() {
/*** SETUP section */
// use PAL_AMB board support.
auto&& brd = the_twelite.board.use<PAL_AMB>();
// now it can read DIP sw status.
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
// the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)
/*** BEGIN section */
Wire.begin(); // start two wire serial bus.
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.
the_twelite.begin(); // start twelite!
startSensorCapture(); // start sensor capture!
/*** INIT message */
Serial << "--- PAL_AMB:" << FOURCHARS << " ---" << mwx::crlf;
}
最初にボードサポート <PAL_AMB>
を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。
コピー auto&& brd = the_twelite.board.use<PAL_AMB>();
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE
)とします。
LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
コピー brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle()
)は含めません。
コピー the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
ボード上のセンサーはI2Cバスを用いますので、バスを利用開始しておきます。
コピー Wire.begin(); // start two wire serial bus.
ボード上のセンサーの取得を開始します。startSensorCapture()
の解説を参照ください。
loop()
コピー void loop() {
auto&& brd = the_twelite.board.use<PAL_AMB>();
// mostly process every ms.
if (TickTimer.available()) {
// wait until sensor capture finish
if (!brd.sns_LTR308ALS.available()) {
// this will take around 50ms.
// note: to save battery life, perform sleeping to wait finish of sensor capture.
brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
}
if (!brd.sns_SHTC3.available()) {
brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
}
// now sensor data is ready.
if (brd.sns_LTR308ALS.available() && brd.sns_SHTC3.available() && !b_transmit) {
Serial << "..finish sensor capture." << mwx::crlf
<< " LTR308ALS: lumi=" << int(brd.sns_LTR308ALS.get_luminance()) << mwx::crlf
<< " SHTC3 : temp=" << brd.sns_SHTC3.get_temp() << 'C' << mwx::crlf
<< " humd=" << brd.sns_SHTC3.get_humid() << '%' << mwx::crlf
<< mwx::flush;
// get new packet instance.
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
, uint16_t(brd.sns_SHTC3.get_temp())
, uint16_t(brd.sns_SHTC3.get_humid())
);
// do transmit
MWX_APIRET ret = pkt.transmit();
Serial << "..transmit request by id = " << int(ret.get_value()) << '.' << mwx::crlf << mwx::flush;
if (ret) {
u8txid = ret.get_value() & 0xFF;
b_transmit = true;
}
else {
// fail to request
sleepNow();
}
}
}
}
// wait to complete transmission.
if (b_transmit) {
if (the_twelite.tx_status.is_complete(u8txid)) {
Serial << "..transmit complete." << mwx::crlf << mwx::flush;
// now sleeping
sleepNow();
}
}
}
このアクトでは、1ms周期で呼び出させる(アプリケーションループの呼び出しタイミングは不定期な遅延が発生します)TickTimerのイベントを起点としてセンサーの読み出しや送信要求を行います。TickTimer.available()
がtrue
になったときに処理を行います。これによりおよそ1msごとにif節内の処理を行います。
コピー if (TickTimer.available()) {
ボード上のセンサーは .sns_LTR308ALS
または .sns_SHTC3
という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()
がfalse
)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER)
)を送付します。
コピー if (!brd.sns_LTR308ALS.available()) {
brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
}
if (!brd.sns_SHTC3.available()) {
brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
}
上記2つのセンサーがavailableになった時点で、無線送信などの処理を行います。
コピー if (brd.sns_LTR308ALS.available()
&& brd.sns_SHTC3.available() && !b_transmit) {
送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。
コピー pkt << tx_addr(0x00) // 親機0x00宛
<< tx_retry(0x1) // リトライ1回
<< tx_packet_delay(0, 0, 2); // 遅延は最小限
パケットにセンサーデータを書き込み際に、センサーデータを取得しています。センサーの値を取得するメソッドはセンサーごとに違います。
コピー // prepare packet payload
pack_bytes(pkt.get_payload()
, make_pair(FOURCHARS, 4)
, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
, uint16_t(brd.sns_SHTC3.get_temp_cent())
, uint16_t(brd.sns_SHTC3.get_humid_per_dmil())
);
照度センサーは.get_luminance() : uint32_t
で得られます。
温湿度センサーは以下のように取得できます。
.get_temp_cent()
: int16_t
: 1℃を100とした温度 (25.6 ℃なら 2560)
.get_temp()
: float
: float値 (25.6 ℃なら 25.6)
.get_humid_dmil()
: int16_t
: 1%を100とした湿度 (56.8%なら 5680)
.get_temp()
: float
: float値 (56.8%なら 56.8)
得られた値のうち温度値は int16_t
ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_t
にキャストしています。
送信が成功すれば b_tansmit
を true
にし、u8txid
に送信パケットの識別IDを格納し、完了待ちします。送信が失敗すれば sleepNow()
によりスリープします。
コピー // do transmit
MWX_APIRET ret = pkt.transmit();
if (ret) {
u8txid = ret.get_value() & 0xFF;
b_transmit = true;
}
else {
// fail to request
sleepNow();
}
loop()
中 b_transmit
が true
になっている場合は、送信完了チェックを行い、完了すれば sleepNow()
によりスリープします。
コピー // wait to complete transmission.
if (b_transmit) {
if (the_twelite.tx_status.is_complete(u8txid)) {
Serial << "..transmit complete." << mwx::crlf << mwx::flush;
// now sleeping
sleepNow();
}
}
送信完了に確認は the_twelite.tx_status.is_complete(u8txid)
で行っています。u8txid
は送信時に戻り値として戻されたID値です。
startSensorCapture()
ボード上のセンサーの取得を開始します。多くのセンサーは、取得を開始してから完了するまで数msから数十msの時間を必要とします。
コピー void startSensorCapture() {
auto&& brd = the_twelite.board.use<PAL_AMB>();
// start sensor capture
brd.sns_SHTC3.begin();
brd.sns_LTR308ALS.begin();
b_transmit = false;
}
5行目で、温湿度センサー SHTC3 のデータ取得を開始します。
6行目で、照度センサー LTR308ALS のデータ取得を開始します。
7行目は、送信完了フラグをクリアします。
sleepNow()
スリープに入る手続きをまとめています。
コピー void sleepNow() {
uint32_t u32ct = 1750 + random(0,500);
Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
the_twelite.sleep(u32ct);
}
ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。
周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。
3行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。
スリープ前にflushを行うと、出力が不安定になる場合があります。
スリープに入るには the_twelite.sleep()
を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。
パラメータとしてスリープ時間をmsで指定しています。
TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000
を超えないように指定してください。
wakeup()
スリープから復帰し起床すると wakeup()
が呼び出されます。そのあとloop()
が都度呼び出されます。wakeup()
の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。
コピー void wakeup() {
Serial << mwx::crlf
<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
<< mwx::crlf
<< "..start sensor capture again."
<< mwx::crlf;
startSensorCapture();
}
ここではセンサーのデータ取得を開始しています。
応用編
より安全な実装
より安全な実装として、このアクトでは以下の例外処理を強化します。
センサー取得部分でavailableにならなかった場合の例外処理が記述されてません。
送信完了待ちで送信完了が通知されない場合の例外処理が記述されていません。
上記いずれもタイムアウト処理を行います。現在の時間は millis()
により取得できます。
コピー uint32_t t_start;
// 時間待ちの処理を開始した時点でタイムスタンプを保存
t_start = millis();
...
// loop() でタイムアウトのチェック
if (millis() - t_start > 100) {
sleepNow();
}
消費エネルギーの削減
アクト PAL_AMB-UseNap は、センサーのデータ取得待ちをスリープで行い、より低消費エネルギーで動作できます。