PAL_MOT-oneshot

PAL_MOTアクトでは連続的に加速度データを取得して都度無線送信していました。このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。

このアクトの解説の前にPAL_MOTのアクトの解説をご覧ください。

本サンプルは、収録バージョンによって差が大きいため本ページでは2つの解説を記載します。

アクトの解説 (v2)

※ 最新のコードは「サンプルアクト>最新版の入手」を参照ください。

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。

状態変数

enum class E_STATE {
	INIT = 0,
	START_CAPTURE,
	WAIT_CAPTURE,
	REQUEST_TX,
	WAIT_TX,
	EXIT_NORMAL,
	EXIT_FATAL
} eState;

列挙体として eState 変数を宣言しています。

begin()

void begin() { 
	// sleep immediately, waiting for the first capture.
	sleepNow();
}

setup()を終了した後に呼ばれます。ここでは初回スリープを実行しています。

wakeup()

void wakeup() {
	Serial << crlf << "--- PAL_MOT(OneShot):" 
	       << FOURCHARS << " wake up ---" << crlf;
	eState = E_STATE::INIT;
}

起床後は状態変数eStateを初期状態INITにセットしています。この後loop()が実行されます。

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に遷移し、システムリセットを行います。

MWX_APIRET TxReq()

この関数では、センサーより得られたサンプル値の取得と、複数サンプルの平均値の計算、

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 packet
	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 (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 transmit
		ret = pkt.transmit();

		if (ret) {
			Serial << "..txreq(" << int(ret.get_value()) << ')';
		}

最期にパケットの生成と送信を要求を行います。パケットには X, Y, Z 軸の加速度、X軸の最小値,Y軸の最小値を含めています。

アクトの解説 (初版)

MWSDK2020_05 版の SDK 添付のサンプルコードです。

※ 最新のコードは「サンプルアクト>最新版の入手」を参照ください。

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。

wakeup()

起床後加速度センサーを稼働させます。

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サンプルの設定でも構いません。

loop()

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 packet
			if (auto&& pkt = nwk.the_twelite.network.use<NWK_SIMPLE>()) {
				auto&& pkt = nwk.prepare_tx_packet(); // get new packet instance.

				// 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 (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 transmit
				MWX_APIRET ret = pkt.transmit();
				
				if (ret) {
					Serial << "..txreq(" << int(ret.get_value()) << ')';
					txid = ret.get_value() & 0xFF;
				} else {
					sleepNow();
				}
				
				// finished tx request
				b_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()しています。

最終更新