PAL_MOTアクトでは連続的に加速度データを取得して都度無線送信していました。このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。
このアクトの解説の前にPAL_MOTのアクトの解説をご覧ください。
本サンプルは、収録バージョンによって差が大きいため本ページでは2つの解説を記載します。
※ 最新のコードは「サンプルアクト>最新版の入手」を参照ください。
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。
加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。
enum class E_STATE {INIT = 0,START_CAPTURE,WAIT_CAPTURE,REQUEST_TX,WAIT_TX,EXIT_NORMAL,EXIT_FATAL} eState;
列挙体として eState
変数を宣言しています。
void begin() {// sleep immediately, waiting for the first capture.sleepNow();}
setup()
を終了した後に呼ばれます。ここでは初回スリープを実行しています。
void wakeup() {Serial << crlf << "--- PAL_MOT(OneShot):"<< FOURCHARS << " wake up ---" << crlf;eState = E_STATE::INIT;}
起床後は状態変数eState
を初期状態INITにセットしています。この後loop()
が実行されます。
void loop() {auto&& brd = the_twelite.board.use<PAL_MOT>();bool loop_more;do {loop_more = false;switch(eState) {...}} while(loop_more);
loop()
の基本構造は状態変数eState
によるswitch ... case節です。eStateの初期状態はINITです。loop_more
は状態変数を書き換えた直後、loop()
を抜ける前にもう一度実行したいときにtrue
にセットします。
以下では各case節を解説します。eState
の初期状態はINITです。
case E_STATE::INIT:brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).loop_more = true;eState = E_STATE::START_CAPTURE;break;
状態INITでは、初期化(結果格納用のキューのクリア)を行います。
case E_STATE::START_CAPTURE:u32tick_capture = millis();brd.sns_MC3630.begin(// 400Hz, +/-4G range, get four samples (can be one sample)SnsMC3630::Settings(SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4));eState = E_STATE::WAIT_CAPTURE;break;
状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_capture
に格納します。
case E_STATE::WAIT_CAPTURE:if (brd.sns_MC3630.available()) {brd.sns_MC3630.end(); // stop now!eState = E_STATE::REQUEST_TX; loop_more = true;} else if ((millis() - u32tick_capture) > 100) {Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";eState = E_STATE::EXIT_FATAL;}break;
状態WAIT_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されるとsns_MC3630.available()
がtrue
になります。
タイムアウトした場合は状態EXIT_FATALに遷移します。
case E_STATE::REQUEST_TX:u32tick_tx = millis();txid = TxReq();if (txid) {eState = E_STATE::WAIT_TX;} else {Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";eState = E_STATE::EXIT_FATAL;}break;
状態REQUEST_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成、そうし尿級を行います。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_tx
に格納します。
case E_STATE::WAIT_TX:if(the_twelite.tx_status.is_complete(txid.get_value())) {eState = E_STATE::EXIT_NORMAL; loop_more = true;} else if (millis() - u32tick_tx > 100) {Serial << crlf << "!!!FATAL: TX TIMEOUT.";eState = E_STATE::EXIT_FATAL;}break;
状態WAIT_TXでは、無線パケットの送信完了を待ちます。
タイムアウト時には状態EXIT_FATALに遷移します。
case E_STATE::EXIT_NORMAL:sleepNow();break;case E_STATE::EXIT_FATAL:Serial << flush;the_twelite.reset_system();break;
一連の動作が完了したときは状態EXIT_NORMALに遷移しローカル定義の関数sleepNow()
を呼び出しスリープを実行します。またエラーを検出した場合は状態EXIT_FATALに遷移し、システムリセットを行います。
この関数では、センサーより得られたサンプル値の取得と、複数サンプルの平均値の計算、
int32_t x = 0, y = 0, z = 0;for (auto&& v: brd.sns_MC3630.get_que()) {x += v.x;y += v.y;z += v.z;}x /= brd.sns_MC3630.get_que().size();y /= brd.sns_MC3630.get_que().size();z /= brd.sns_MC3630.get_que().size();
取得サンプルの平均値を計算します。
ここでは除算を行っていますが、TWELITEマイコンには除算回路がないため、計算時間を要する演算となります。例えば以下のような改良が考えられます。
サンプル数を2のべき乗として、その数を変数に入れず直接指定した除算を行う(ビットシフトによる演算に最適化されます)。
平均値を求めず、合計値とサンプル数を送り、受信先で計算する。
auto&& x_minmax = std::minmax_element(get_axis_x_iter(brd.sns_MC3630.get_que().begin()),get_axis_x_iter(brd.sns_MC3630.get_que().end()));brd.sns_MC3630.get_que().clear(); // clean up the queue
X軸の最大値と最小値を計算します。
ここではイテレータとstd::minmax_element()
アルゴリズムを用いて計算します。get_axis_x_iter
はキューのイテレータをパラメータとして、axis_xyzt
構造体の.x
にアクセスするものです。
C++ Standard Template Library のアルゴリズムを使用する例としてstd::mimmax_element
紹介していますが、上述のforループ内で最大、最小を求めても構いません。
ここでキューをクリア.sns_MC3630.get_que().clear()
しています。
// prepare tx packetif (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {// set tx packet behaviorpkt << 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 (first)pack_bytes(pkt.get_payload() // set payload data objects., make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any., uint16_t(x), uint16_t(y), uint16_t(z), uint16_t(*x_minmax.first) // minimum of captured x, uint16_t(*x_minmax.second) // maximum of captured x);// perform transmitret = pkt.transmit();if (ret) {Serial << "..txreq(" << int(ret.get_value()) << ')';}
最期にパケットの生成と送信を要求を行います。パケットには X, Y, Z 軸の加速度、X軸の最小値,Y軸の最小値を含めています。
MWSDK2020_05 版の SDK 添付のサンプルコードです。
※ 最新のコードは「サンプルアクト>最新版の入手」を参照ください。
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。
加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。
起床後加速度センサーを稼働させます。
void wakeup() {Serial << mwx::crlf << "--- PAL_MOT(OneShot):" << FOURCHARS << " wake up ---" << mwx::crlf;auto&& brd = the_twelite.board.use<PAL_MOT>();brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).brd.sns_MC3630.begin(SnsMC3630::Settings(SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4));// 400Hz, +/-4G range, get four samples (can be one sample)b_transmit = false;txid = 0xFFFF;}
加速度センサーの結果を保存するキューの内容を抹消(.sns_MC3630.get_que().clear()
)しておきます。加速度センサーのサンプルの取得忘れがあったり、また停止させるまでに次のサンプルが取得したような場合を想定します。
ここで加速度センサーを都度開始します。設定は400Hz,±4G,FIFO割り込みは4サンプルとしています。4サンプルも必要ない場合は1サンプルの設定でも構いません。
void loop() {auto&& brd = the_twelite.board.use<PAL_MOT>();if (!b_transmit) {if (brd.sns_MC3630.available()) {brd.sns_MC3630.end(); // stop now!Serial << "..finish sensor capture." << mwx::crlf<< " ct=" << int(brd.sns_MC3630.get_que().size());// get all samples and average them.int32_t x = 0, y = 0, z = 0;for (auto&& v: brd.sns_MC3630.get_que()) {x += v.x;y += v.y;z += v.z;}x /= brd.sns_MC3630.get_que().size();y /= brd.sns_MC3630.get_que().size();z /= brd.sns_MC3630.get_que().size();Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;// just see X axis, min and max//auto&& x_axis = get_axis_x(brd.sns_MC3630.get_que());//auto&& x_minmax = std::minmax_element(x_axis.begin(), x_axis.end());auto&& x_minmax = std::minmax_element(get_axis_x_iter(brd.sns_MC3630.get_que().begin()),get_axis_x_iter(brd.sns_MC3630.get_que().end()));brd.sns_MC3630.get_que().clear(); // clean up the queue// prepare tx packetif (auto&& pkt = nwk.the_twelite.network.use<NWK_SIMPLE>()) {auto&& pkt = nwk.prepare_tx_packet(); // get new packet instance.// set tx packet behaviorpkt << 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 (first)pack_bytes(pkt.get_payload() // set payload data objects., make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any., uint16_t(x), uint16_t(y), uint16_t(z), uint16_t(*x_minmax.first) // minimum of captured x, uint16_t(*x_minmax.second) // maximum of captured x);// perform transmitMWX_APIRET ret = pkt.transmit();if (ret) {Serial << "..txreq(" << int(ret.get_value()) << ')';txid = ret.get_value() & 0xFF;} else {sleepNow();}// finished tx requestb_transmit = true;}}} else {// wait until transmit completion.if(the_twelite.tx_status.is_complete(txid)) {sleepNow();}}}
このアクトでは、サンプル取得後、すぐに加速度センサーの動作を停止します。
if (brd.sns_MC3630.available()) {brd.sns_MC3630.end(); // stop now!
取得サンプルの平均値を計算します。
int32_t x = 0, y = 0, z = 0;for (auto&& v: brd.sns_MC3630.get_que()) {x += v.x;y += v.y;z += v.z;}x /= brd.sns_MC3630.get_que().size();y /= brd.sns_MC3630.get_que().size();z /= brd.sns_MC3630.get_que().size();
X軸の最大値と最小値を計算します。
auto&& x_minmax = std::minmax_element(get_axis_x_iter(brd.sns_MC3630.get_que().begin()),get_axis_x_iter(brd.sns_MC3630.get_que().end()));brd.sns_MC3630.get_que().clear(); // clean up the queue
ここではイテレータとstd::minmax_element()
アルゴリズムを用いて計算します。get_axis_x_iter
はキューのイテレータをパラメータとして、axis_xyzt
構造体の.x
にアクセスするものです。
最後にキューをクリア.sns_MC3630.get_que().clear()
しています。