arrow-left

全てのページ
gitbookGitBook提供
1 / 16

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

act0 .. 4

act0 から始まるアクト(Act)は、actを始める - Opening actarrow-up-rightで紹介されたものを収録しています。LEDやボタンの動作のみの単純なものですが、最初にお試しいただくことをお勧めします。

名前

内容

act0

処理の記述がないテンプレート

act1

サンプルアクト

Sample Acts

アクトの動作を理解するため、いくつかのサンプルを用意しています。

circle-info

サンプルは MWSDK をインストールしたディレクトリにある Act_samples にあります。

最初にとの解説に目を通すようにしてください。

Lチカ(LEDの点滅)

act2

タイマーを用いたLチカ

act3

2つのタイマーを用いたLチカ

act4

ボタン(スイッチ)を用いたLED点灯

hashtag
最新版の入手
circle-info

最新版のコードや MWSDK バージョン間の修正履歴を確認する目的で Github にソース一式を置いています。以下のリンク先を参照してください。

https://github.com/monowireless/Act_samplesarrow-up-right

hashtag
共通の記述

アクトのサンプル中で以下の項目は共通の設定項目になり、以下で解説します。

circle-info

サンプルアクト共通として以下の設定をしています。

  • アプリケーションID 0x1234abcd

  • チャネル 13

アプリケーションIDとチャネルはともに他のネットワークと混在しないようにする仕組みです。

アプリケーションIDが異なる者同士は、チャネルが同じであっても混信することはありません。ただし、別のアプリケーションIDのシステムが頻繁に無線送信しているような場合はその無線送信が妨害となりますので影響は出ます。

チャネルは通信に使う周波数を決めます。TWELITE無線モジュールでは原則として16個のチャネルが利用でき、通常のシステムでは実施しないような極めて例外的な場合を除き、違うチャネルとは通信できません。

サンプルアクト共通の仕様として、パケットのペイロード(データ部)の先頭には4バイトの文字列(APP_FOURCHAR[])を格納しています。種別の識別性には1バイトで十分ですが、解説のための記述です。こういったシステム特有の識別子やデータ構造を含めるのも混信対策の一つです。

BRD_APPTWELITE
Parent_MONOSTICK
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const char APP_FOURCHAR[] = "BAT1";

PAL_AMB-behavior

の記述サンプルです。詳細はを参照ください。

ビヘイビア
こちら

PAL_AMB-usenap

PAL_AMB のサンプルを少し改良して、センサーデータ取得中の待ち時間(約50ms)を、スリープで待つようにします。

circle-check

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

hashtag
アクトの解説

hashtag
begin()

begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

setup()終了後に初回スリープを実行します。setup()中にセンサーデータ取得を開始していますが、この結果は評価せず、センサーを事前に一度は動かしておくという意味あいで、必ずしも必要な手続きではありません。

hashtag
wakeup()

起床後の手続きです。以下の処理を行います。

  • まだセンサーデータの取得開始をしていない場合、センサーデータ取得を行い、短いスリープに入る。

  • 直前にセンサーデータ取得開始を行ったので、データを確認して無線送信する。

上記の分岐をグローバル変数のb_sensor_startedにより制御しています。!b_sensor_startedの場合はセンサー取得開始(startSensorCapture())を行い、napNow()により短いスリープに入ります。時間は100msです。

napNow()によるスリープ復帰後、b_sensor_started==trueの節が実行されます。ここでは、2つのセンサーに対してE_EVENT_START_UPイベントを通知しています。このイベントは、センサーの取得が終了するのに十分な時間が経過したことを意味します。この通知をもとにsns_LTR308ALSとsns_SHTC3はavailableになります。この後loop()に移行し、無線パケットが送信されます。

circle-exclamation

センサーに通知するイベントは必要な時間待ちが終わったかどうかを判定するために使われます。実際時間が経過しているかどうかはnapNow()で正しい時間を設定したかどうかで決まります。短い時間で起床した場合は、必要とされる時間経過に足りないため、続く処理でセンサーデータが得られないなどのエラーが出ることが想定されます。

hashtag
napNow()

ごく短いスリープを実行する。

sleepのパラメータの2番目をtrueにすると前回のスリープ復帰時刻をもとに次の復帰時間を調整します。常に5秒おきに起床したいような場合設定します。

3番目をtrueにするとメモリーを保持しないスリープになります。復帰後はwakup()は呼び出されじ、電源再投入と同じ処理になります。

4番目はウェイクアップタイマーの2番目を使う指定です。ここでは1番目は通常のスリープに使用して、2番目を短いスリープに用いています。このアクトでは2番目を使う強い理由はありませんが、例えば上述の5秒おきに起床したいような場合、短いスリープに1番目のタイマーを用いてしまうとカウンター値がリセットされてしまい、経過時間の補正計算が煩雑になるため2番目のタイマーを使用します。

circle-info

あまり短いスリープ時間を設定してもスリープ復帰後のシステムの再初期化などのエネルギーコストと釣り合いません。目安として最小時間を30-50ms程度とお考え下さい。

void begin() {
	sleepNow(); // the first time is just sleeping.
}
void wakeup() {
	if (!b_senser_started) {
		// delete/make shorter this message if power requirement is harder.	
		Serial	<< mwx::crlf
				<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
				<< mwx::crlf
				<< "..start sensor capture again."
				<< mwx::crlf;

		startSensorCapture();
		b_senser_started = true;

		napNow(); // short period sleep.
	} else {
		Serial << "..wake up from short nap.." << mwx::crlf;

		auto&& brd = the_twelite.board.use<PAL_AMB>();

		b_senser_started = false;

		// tell sensors waking up.
		brd.sns_LTR308ALS.process_ev(E_EVENT_START_UP);
		brd.sns_SHTC3.process_ev(E_EVENT_START_UP);
	}
}
void napNow() {
	uint32_t u32ct = 100;
	Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
	the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}

PulseCounter

パルスカウンターを用いたアクト例です。

パルスカウンターは、マイコンを介在せず信号の立ち上がりまたは立ち下りの回数を計数するものです。不定期のパルスを計数し一定回数までカウントが進んだ時点で無線パケットで回数を送信するといった使用方法が考えられます。

circle-check

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

受信の確認のためParent_MONOSTICKの解説をご覧ください。

hashtag
アクトの機能

  • 子機側のDIO8に接続したパルスを計数し、一定時間経過後または一定数のカウントを検出した時点で無線送信する。

  • 子機側はスリープしながら動作する。

hashtag
アクトの使い方

hashtag
必要なTWELITE

hashtag
アクトの解説

hashtag
setup()

パルスカウンターの初期化を行います。

hashtag
begin()

パルスカウンターの動作を開始し、初回スリープを実行します。PulseCounter.begin()の最初のパラメータは、起床割り込みを発生させるためのカウント数100で、2番目は立ち下がり検出PIN_INT_MODE::FALLINGを指定しています。

hashtag
wakeup()

起床時にPulseCounter.available()を確認しています。availableつまりtrueになっていると、指定したカウント数以上のカウントになっていることを示します。ここではfalseの場合再スリープしています。

カウント数が指定以上の場合はloop()で送信処理と送信完了待ちを行います。

hashtag
loop()

パルスカウント値の読み出しを行います。読み出した後カウンタはリセットされます。

WirelessUART

WirelessUARTはシリアル通信を行います。

circle-check

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

hashtag
アクトの機能

役割

例

親機

MONOSTICK BLUEまたはREDarrow-up-right

アクトParent_MONOSTICKを動作させる。

子機

1.TWELITE DIParrow-up-right

2.BLUE PAL または RED PALarrow-up-right +環境センサーパル AMBIENT SENSE PALarrow-up-right

  • 2台のUART接続のTWELITE同士をアスキー書式で通信する。

hashtag
アクトの使い方

hashtag
必要なTWELITE

いずれかを2台。

  • MONOSTICK BLUE または REDarrow-up-right

  • TWELITE Rarrow-up-right でUART接続されているTWELITE DIParrow-up-rightなど

hashtag
アクトの解説

hashtag
setup()

論理IDはrandom(1,5)により1,2,3,4のいずれかの値に割り当てています。通常は、論理IDを設定保存したデータ、またはDIP SWといったハードウェアの情報から生成します。

シリアルパーサーを初期化します。

hashtag
loop()

シリアルからのデータ入力があった時点で、シリアルパーサーに1バイト入力します。アスキー形式が最後まで受け付けられた時点でSerialParser.parse()はtrueを戻します。

SerialParserは内部バッファに対してsmplbufでアクセスできます。上の例ではバッファの1バイト目を送信先のアドレスとして取り出し、2バイト目から末尾までをtransmit()関数に渡します。

パケットを受信したときには、送信元を先頭バイトにし続くペイロードを格納したバッファsmplbuf_u8<128> bufを生成し、出力用のシリアルパーサーserparser_attach poutからシリアルに出力しています。

hashtag
テスト用のコマンド

circle-exclamation

テストデータは必ずペースト機能を用いてターミナルに入力してください。入力にはタイムアウトがあるためです。

参考: TWE ProgrammerやTeraTermでのペーストはAlt+Vを用います。

circle-info

入力の末尾にCR LFが必要です。

最初はCR LFが省略できるXで終わる系列を試してください。終端文字列が入力されない場合は、その系列は無視されます。

hashtag
例

任意の子機宛に00112233を送付します。

hashtag
例

子機3番に対してAABBCC00112233を送付します。

circle-info

TWE Programmerのターミナル機能を用いて送付する場合は、Alt+Vキーを用いてペーストします。

BRD_APPTWELITEの解説
// Pulse Counter setup
PulseCounter.setup();
void begin() {
	// start the pulse counter capturing
	PulseCounter.begin(
		  100 // 100 count to wakeup
		, PIN_INT_MODE::FALLING // falling edge
		);

	sleepNow();
}
void wakeup() {
	Serial	<< mwx::crlf
			<< "--- Pulse Counter:" << FOURCHARS << " wake up ---"
			<< mwx::crlf;

	if (!PulseCounter.available()) {
		Serial << "..pulse counter does not reach the reference value." << mwx::crlf;
		sleepNow();
	}
}
uint16_t u16ct = PulseCounter.read();
void setup() {
	/*** SETUP section */
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	
	uid = random(1, 5); // set uid by random() (1..4)
	nwk	<< NWK_SIMPLE::logical_id(uid); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	SerialParser.begin(PARSER::ASCII, 128); // Initialize the serial parser
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- WirelessUart (id=" << int(uid) << ") ---" << mwx::crlf;
}
SerialParser.begin(PARSER::ASCII, 128); 
while(Serial.available())  {
	if (SerialParser.parse(Serial.read())) {
		Serial << ".." << SerialParser;
		const uint8_t* b = SerialParser.get_buf().begin();
		uint8_t addr = *b; ++b; // the first byte is destination address.
		transmit(addr, b, SerialParser.get_buf().end());
	}
}
if (the_twelite.receiver.available()) {
	auto&& rx = the_twelite.receiver.read();
	
	// check the packet header.
	const uint8_t* p = rx.get_payload().begin();
	if (rx.get_length() > 4 && !strncmp((const char*)p, (const char*)FOURCHARS, 4)) {
		Serial << format("..rx from %08x/%d", rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;

		smplbuf_u8<128> buf;
		mwx::pack_bytes(buf			
				, uint8_t(rx.get_addr_src_lid())            // src addr (LID)
				, make_pair(p+4, rx.get_payload().end()) );	// data body

		serparser_attach pout;
		pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
		Serial << pout;
	}
}
:FE00112233X

:FE001122339C
:03AABBCC00112233X

:03AABBCC0011223366

Parent_MONOSTICK

親機アプリケーション(MONOSTICK用)

MONOSTICKを親機として使用するアクトです。子機からのパケットのデータペイロードをシリアルポートに出力します。

circle-check

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

hashtag
アクトの機能

  • サンプルアクトの子機からのパケットを受信して、アスキー形式で出力する。

hashtag
アクトの使い方

hashtag
必要なTWELITEと配線

hashtag
アクトの解説

hashtag
インクルード

MONOSTICK用のボードビヘイビアをインクルードしています。このボードサポートには、LEDの制御、ウォッチドッグ対応が含まれます。

hashtag
setup()

ボードサポートの使用とLEDの動作を設定しています。

2行目では赤色のLEDを無線パケットを受信したら200ms点灯する設定をしています。最初のパラメータはLED_TIMER::ON_RXが無線パケット受信時を意味します。2番目は点灯時間をmsで指定します。

3行目はLEDの点滅指定です。1番目のパラメータはLED_TIMER::BLINKが点滅の指定で、2番目のパラメータは点滅のON/OFF切り替え時間です。500msごとにLEDが点灯、消灯(つまり1秒周期の点滅)を繰り返します。

ここではNWK_SIMPLE::logical_id(0x00)を設定し親機(論理ID=0x00)であることを指定します。

hashtag
loop()

パケットを受信したとき、その内容を表示します。ここでは2種類の表示を行っています。

最初の出力は<NWK_SIMPLE>の制御データを含めたデータをすべて表示します。制御データは11バイトあります。通常は制御情報を直接参照することはありませんが、あくまでも参考です。

1行目は出力用のシリアルパーサをローカルオブジェクトとして宣言しています。内部にバッファを持たず、外部のバッファを流用し、パーサーの出力機能を用いて、バッファ内のバイト列を書式出力します。

2行目はシリアルパーサーのバッファを設定します。すでにあるデータ配列、つまり受信パケットのペイロード部を指定します。serparser_attach poutは、既にあるバッファを用いたシリアルパーサーの宣言です。pout.begin()の1番目のパラメータは、パーサーの対応書式をPARSER::ASCIIつまりアスキー形式として指定しています。2番目はバッファの先頭アドレス。3番目はバッファ中の有効なデータ長、4番目はバッファの最大長を指定します。出力用で書式解釈に使わないため4番目のパラメータは3番目と同じ値を入れています。

6行目でシリアルポートへ>>演算子を用いて出力しています。

7行目のSerial << mwx::flushは、ここで出力が終わっていないデータの出力が終わるまで待ち処理を行う指定です。(Serial.flush()も同じ処理です)

2番目の出力はより実践的です。ユーザが定義した並び順で書式を構成します。

1行目はアスキー書式に変換する前のデータ列を格納するバッファをローカルオブジェクトとして宣言しています。

2行目はpack_bytes()を用いてデータ列を先ほどのbufに格納します。データ構造はソースコードのコメントを参照ください。pack_bytes()のパラメータにはsmplbuf_u8 (smplbuf<uint8_t, ???>)形式のコンテナを指定することもできます。

circle-info

パケットのシーケンス番号は、<NWK_SIMPLE>で自動設定され、送信パケット順に割り振られます。この値はパケットの重複検出に用いられます。

LQI (Link Quality Indicator)は受信時の電波強度に相当する値で、値が大きければ大きいほどより強い電界強度で受信できていることになります。ただしこの値と物理量との厳格な関連は定義されていませんし、環境のノイズと相対的なものでLQIがより大きな値であってもノイズも多ければ通信の成功率も低下することになります。

13,14,17行目は、シリアルパーサーの宣言と設定、出力です。

Scratch

テンプレートコードです。

act0は中身が空でしたがScratchには以下のコードが含まれます。

hashtag
setup()

アプリケーションID APP_ID, 無線チャネルCHANNEL、受信有、子機アドレス0xFEとして始動します。

hashtag
begin()

始動時setup()の後に1回だけ呼び出されます。メッセージの表示のみ。

hashtag
loop()

hashtag
ボタン(スイッチ)の入力検出

による連続参照により状態を確定します。ボタン状態が変化したらシリアルに出力します。

hashtag
シリアルからの入力

シリアルから1文字読み込んで、入力文字に応じた処理をします。

hashtag
パケットの受信

パケットを受信したら、送信元のアドレス情報を表示します。

hashtag
wakeup()

スリープ起床時に最初に呼び出されます。メッセージの表示のみ。

hashtag
vTransmit()

送信要求を行う最小限の手続きです。

この関数を抜けた時点では、まだ要求は実行されていません。しばらく待つ必要があります。この例では100-200msの送信開始の遅延があるため、送信が開始されるのは早くて100ms後です。

void setup() {
	/*** SETUP section */
	txreq_stat = MWX_APIRET(false, 0);

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // アプリケーションID
		<< TWENET::channel(CHANNEL) // チャネル
		<< TWENET::rx_when_idle();  // 受信有

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(0xFE); // 子機、特定アドレス指定なし(0xFE)

	/*** BEGIN section */
	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // ボタン処理初期化

	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- Scratch act ---" << mwx::crlf;
}
Buttons
void begin() {
	Serial << "..begin (run once at boot)" << mwx::crlf;
}
if (Buttons.available()) {
	uint32_t bm, cm;
	Buttons.read(bm, cm);

	if (cm & 0x80000000) {
		// the first capture.
	}

	Serial << int(millis()) << ":BTN" << format("%b") << mwx::crlf;
}
while(Serial.available())  {
  int c = Serial.read();

	Serial << '[' << char(c) << ']';

  switch(c) {
  case 'p': ... // millis() を表示
  case 't': ... // 無線パケットを送信 (vTransmit)
  case 's': ... // スリープする
  }
}
if (the_twelite.receiver.available()) {

		auto&& rx = the_twelite.receiver.read();

		// just dump a packet.
		Serial << format("rx from %08x/%d",
		   rx.get_addr_src_long(), rx.get_addr_src_lid()) << crlf;
}
void wakeup() {
	Serial << int(millis()) << ":wake up!" << mwx::crlf;
}
MWX_APIRET vTransmit() {
	Serial << int(millis()) << ":vTransmit()" << mwx::crlf;

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		// set tx packet behavior
		pkt << tx_addr(0xFF)  // 同報通信=ブロードキャスト
			<< tx_retry(0x1)    // 再送1回
			<< tx_packet_delay(100,200,20); // 送信時遅延100-200msの間に送信、再送間隔20ms

		// 送信データの指定(アプリケーションごとに決める)
		pack_bytes(pkt.get_payload()
			, make_pair("SCRT", 4) // 4文字識別子
			, uint32_t(millis())   // タイムスタンプ
		);
		
		// 送信要求を行う
		return pkt.transmit(); 
	}

  // .prepare_tx_packet() 時点で失敗している
	return MWX_APIRET(false, 0);
}

役割

例

親機

子機

サンプルアクトの子機設定 (例: +にを書き込んだもの)

<MONOSTICK>
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <MONOSTICK>
void setup() {
	/*** SETUP section */
	auto&& brd = the_twelite.board.use<MONOSTICK>();
	brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
	brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0x00); // set Logical ID. (0x00 means parent device)

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- MONOSTICK_Parent act ---" << mwx::crlf;
}
auto&& brd = the_twelite.board.use<MONOSTICK>();
brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0x00);
void loop() {
	// receive RF packet.
    while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();

		Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;

		// output type1 (raw packet)
		//   uint8_t  : 0x01
		//   uint8_t  : src addr (LID)
		//   uint32_t : src addr (long)
		//   uint32_t : dst addr (LID/long)
		//   uint8_t  : repeat count
		//     total 11 bytes of header.
		//     
		//   N        : payload
		{
		  serparser_attach pout;
			pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, 
			    rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);

			Serial << "RAW PACKET -> ";
			pout >> Serial;
			Serial << mwx::flush;
		}

		// output type2 
		{
			smplbuf_u8<128> buf;
			mwx::pack_bytes(buf
				, uint8_t(rx.get_addr_src_lid())		// src addr (LID)
				, uint8_t(0xCC)							        // cmd id (0xCC, fixed)
				, uint8_t(rx.get_psRxDataApp()->u8Seq)	// seqence number
				, uint32_t(rx.get_addr_src_long())	// src addr (long)
				, uint32_t(rx.get_addr_dst())			  // dst addr
				, uint8_t(rx.get_lqi())					    // LQI
				, uint16_t(rx.get_length())				  // payload length
				, rx.get_payload() 					      	// payload
			);

			serparser_attach pout;
			pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
			
			Serial << "FMT PACKET -> ";
			pout >> Serial;
			Serial << mwx::flush;
		}
	}
}
serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, 
    rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);

Serial << "RAW PACKET -> ";
pout >> Serial;
Serial << mwx::flush;

// 参考:制御部のパケット構造
// uint8_t  : 0x01 固定
// uint8_t  : 送信元のLID
// uint32_t : 送信元のロングアドレス(シリアル番号)
// uint32_t : 宛先アドレス
// uint8_t  : 中継回数
smplbuf_u8<128> buf;
mwx::pack_bytes(buf
	, uint8_t(rx.get_addr_src_lid())		   // 送信元の論理ID
	, uint8_t(0xCC)											   // 0xCC
	, uint8_t(rx.get_psRxDataApp()->u8Seq) // パケットのシーケンス番号
	, uint32_t(rx.get_addr_src_long())		 // 送信元のシリアル番号
	, uint32_t(rx.get_addr_dst())			     // 宛先アドレス
	, uint8_t(rx.get_lqi())					       // LQI:受信品質
	, uint16_t(rx.get_length())				     // 以降のバイト数
	, rx.get_payload() 						         // データペイロード
);

serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());

Serial << "FMT PACKET -> ";
pout >> Serial;
Serial << mwx::flush;
MONOSTICK BLUEまたはREDarrow-up-right
BLUE PAL または RED PALarrow-up-right
AMBIENT SENSE PALarrow-up-right
PAL_AMBアクト

Slp_Wk_and_Tx

Slp_Wk_and_Tx は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。

setup(), loop() の形式では、どうしても loop() 中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()中を switch構文による単純な状態遷移を用いることで、コードの見通しを良くしています。

circle-check

このアクトの解説の前にBRD_APPTWELITEの解説をご覧いただくことを推奨します。

circle-info

TWELITE STAGE 2020_05 には収録されていません。以下のリンク(GitHub)より入手ください。

hashtag
アクトの機能

  • 起動後、速やかにスリープする

    1. setup() : 初期化する

    2. begin() : スリープに遷移する

hashtag
アクトの解説

hashtag
インクルード

パケット送信を行うため <NWK_SIMPLE> をインクルードしています。また、アプリケーションIDなど基本的な定義は "Common.h" に記述しています。

Common.h には基本的な定義に加え、以下の列挙体が定義されています。こちらを状態変数として利用します。

hashtag
setup()

the_tweliteクラスオブジェクトの初期化とネットワーク <NWK_SIMPLE> の登録を行います。

hashtag
begin()

setup()の直後に一度だけ呼び出されます。SleepNow()関数夜を呼び出して初回のスリープ手続きを行います。

hashtag
wakeup()

起床直後に呼び出されます。ここでは状態変数eStateを初期状態E_STATE::INITにセットします。この後loop()が呼び出されます。

hashtag
loop()

上記のコードは、実際のコードを簡略化したものです。

このコードではloop_moreを条件としてdo..while() 構文のループになっています。これはloop()を脱出せずに次の状態のコード(case節)を実行する目的です。

wakeup()でINIT状態にセットされていますので初回のloop()ではINITが評価されます。ここは変数の初期化しているだけです。続く状態の処理を行うのにloop()を脱出する必要がないためloop_moreをtrueにしています。

WORK_JOB状態ではTichTimer.available()になるたびにカウンタを減算し0になったら次の状態TXに遷移します。

TX状態ではvTransmit()関数を呼び出しパケット送信要求を行います。この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。パケット送信要求が成功したらWAIT_TX状態に遷移します。タイムアウトの管理のため、この時点でのシステム時間u32millis_txを保存します。失敗したらEXIT_FATALに遷移します(モジュールのリセット実行)。

WAIT_TX状態では送信IDに対応する送信が完了したかを判定し、完了したらEXIT_NORMAL、100ms以上経過したらEXIT_FATAL状態に遷移します。

EXIT_NORMAL状態はSleepNow()を呼び出し、スリープします。

EXIT_FATAL状態ではシステムリセットを行います。

hashtag
void SleepNow()

周期スリープを行います。スリープ時間はrandom()関数を用いて、一定の時間ブレを作っています。これは複数のデバイスの送信周期が同期した場合、著しく失敗率が上がる場合があるためです。

hashtag
MWX_APIRET vTransmit()

ID=0x00の親機宛に無線パケットの送信要求を行います。格納されるデータはActサンプルで共通に使われている4文字識別子(FOURCC)に加え、システム時間[ms]が含まれます。

MWX_APIRETはuint32_t型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()の戻り型になっており、送信要求の成功と失敗ならびに送信IDをデータ部に格納します。

  • スリープ起床後、状態変数を初期化し、以下の順に動作を行う

    1. wakeup(): スリープからの起床、各初期化を行う

    2. loop()状態INIT->WORK_JOBに遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し 100 カウント後にTX状態に進む)

    3. loop() 状態TX: 送信要求を行う

    4. loop() 状態WAIT_TX: 送信完了待ちを行う

    5. loop() 状態EXIT_NORMAL: スリープする (1. に戻る)

  • loop() 状態EXIT_FATAL: エラーが発生した場合は、モジュールリセットする

  • https://github.com/monowireless/Act_samplesarrow-up-right
    #include <TWELITE>
    #include <NWK_SIMPLE>
    
    #include "Common.h"
    enum class E_STATE {
        INIT = 0,    // INIT STATE
        WORK_JOB,    // do some job (e.g sensor capture)
        TX,          // reuest transmit
        WAIT_TX,     // wait its completion
        EXIT_NORMAL, // normal exiting.
        EXIT_FATAL   // has a fatal error (will do system reset)
    };
    void setup() {
    	/*** SETUP section */
    	txreq_stat = MWX_APIRET(false, 0);
    
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
    		<< TWENET::rx_when_idle(false);  // open receive circuit (if not set, it can't listen packts from others)
    
    	// Register Network
    	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
    	nwk	<< NWK_SIMPLE::logical_id(DEVICE_ID); // set Logical ID. 
    
    	/*** BEGIN section */
    	the_twelite.begin(); // start twelite!
    
    	/*** INIT message */
    	Serial << "--- Sleep an Tx Act ---" << crlf;
    }
    void begin() {
    	Serial << "..begin (run once at boot)" << crlf;
    	SleepNow();
    }
    void wakeup() {
    	Serial << crlf << int(millis()) << ":wake up!" << crlf;
    	eState = E_STATE::INIT;
    }
    void loop() {
    	bool loop_more; // if set, one more loop on state machine.
    	do {
    		loop_more = false; // set no-loop at the initial.
    		switch(eState) {
    			case E_STATE::INIT: 
    				eState = E_STATE::WORK_JOB; loop_more = true;
    				break;
    			case E_STATE::WORK_JOB:
    			  if (TickTimer.available())
    			    if(--dummy_work_count == 0)
    						eState = E_STATE::TX: loop_more = true;
    				break;
    			case E_STATE::TX:
    			  txreq_stat = vTransmit();
    				if(txreq_stat) {
    				  u32millis_tx = millis();
    				  eState = E_STATE::WAIT_TX;
    				} else {
    				  eState = E_STATE::EXIT_FATAL; loop_more = true;
    				}
    			  break;
    			case E_STATE::WAIT_TX:
    			  if (the_twelite.tx_status.is_complete(txreq_stat.get_value())) {
    			    eState = E_STATE::EXIT_NORMAL; loop_more = true;
    			  } else if (millis() - u32millis_tx > 100) {
    			    eState = E_STATE::EXIT_FATAL; loop_more = true;
    			  }
    			  break;
    			case E_STATE::EXIT_NORMAL:
    				SleepNow();
    				break;
    			case E_STATE::EXIT_FATAL:
    				the_twelite.reset_system();
    				break;
    		}
    	} while (loop_more);
    void SleepNow() {
    	uint16_t u16dur = SLEEP_DUR;
    	u16dur = random(SLEEP_DUR - SLEEP_DUR_TERMOR, SLEEP_DUR + SLEEP_DUR_TERMOR);
    
    	Serial << int(millis()) << ":sleeping for " << int(u16dur) << "ms" << crlf << mwx::flush;
    	the_twelite.sleep(u16dur, false);
    }
    MWX_APIRET vTransmit() {
    	Serial << int(millis()) << ":vTransmit()" << crlf;
    
    	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 (0x3 send four times in total)
    			<< tx_packet_delay(0,0,2); // send packet w/ delay (send first packet with randomized delay from 0 to 0ms, repeat every 2ms)
    
    		// prepare packet payload
    		pack_bytes(pkt.get_payload() // set payload data objects.
    			, make_pair(FOURCC, 4) // string should be paired with length explicitly.
    			, uint32_t(millis()) // put timestamp here.
    		);
    		
    		// do transmit 
    		//return nwksmpl.transmit(pkt);
    		return pkt.transmit(); 
    	}
    
    	return MWX_APIRET(false, 0);
    }

    Unit_???

    Unit_で始まるアクト(Act)は、ごく単機能の記述や動作を確認するためのものです。

    名前

    内容

    Unit_I2Cprobe

    I2Cバスをスキャンして、応答のあるデバイス番号を表示します(この手順で応答しないデバイスもあります)。

    PAL_AMB

    を用い、センサー値の取得を行います。

    circle-check

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

    受信の確認のための解説をご覧ください。

    PAL_MAG

    を用い、センサー値の取得を行います。

    circle-check

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

    受信の確認のための解説をご覧ください。

    Unit_delayMicoroseconds

    delayMicroseconds()の動作を確認します。16MhzのTickTimerのカウントとの比較をします。

    Unit_using_PAL_NOTICE

    通知パル(NOTICE PAL)のLED点灯を試します。起動時に全灯フラッシュ、その後はシリアル入力で操作します。

    • r,g,b,w : 各色の点灯モードをトグルする

    • R,G,B,W : 各色の明るさを変更する(消灯・全灯時は無効)

    • c: 周期を変化させる(点滅時)

    • C: 点滅時のデューティを変化させる(点滅時)

    Unit_div100

    10,100,1000の割り算と商を求めるdiv10(),div100(),div1000()の動作確認を行います。-99999~99999まで計算を行い通常の/,%による除算との経過時間の比較をします。

    Unit_div_format

    div10(),div100(),div1000()の結果を文字列出力します。

    Unit_using_UART1

    UART1 (Serial1) の使用サンプルです。UART0(Serial)からの入力をUART1に出力し、UART1からの入力をUART0に出力します。

    hashtag
    アクトの機能
    • 環境センサーパル AMPIENT SENSE PAL を用い、センサー値の取得を行います。

    • コイン電池で動作させるための、スリープ機能を利用します。

    hashtag
    アクトの使い方

    hashtag
    必要なTWELITE

    役割

    例

    親機

    アクトを動作させる。

    子機

    +

    hashtag
    アクトの解説

    hashtag
    インクルード

    環境センサーパル <PAL_AMB> のボードビヘイビアをインクルードします。

    hashtag
    setup()

    最初にボードサポート <PAL_AMB> を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

    ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

    LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

    このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle())は含めません。

    ボード上のセンサーはI2Cバスを用いますので、バスを利用開始しておきます。

    ボード上のセンサーの取得を開始します。startSensorCapture()の解説を参照ください。

    hashtag
    loop()

    このアクトでは、1ms周期で呼び出させる(アプリケーションループの呼び出しタイミングは不定期な遅延が発生します)TickTimerのイベントを起点としてセンサーの読み出しや送信要求を行います。TickTimer.available()がtrueになったときに処理を行います。これによりおよそ1msごとにif節内の処理を行います。

    ボード上のセンサーは .sns_LTR308ALS または .sns_SHTC3 という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()がfalse)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER))を送付します。

    上記2つのセンサーがavailableになった時点で、無線送信などの処理を行います。

    送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。

    パケットにセンサーデータを書き込み際に、センサーデータを取得しています。センサーの値を取得するメソッドはセンサーごとに違います。

    照度センサーは.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() によりスリープします。

    loop() 中 b_transmit が true になっている場合は、送信完了チェックを行い、完了すれば sleepNow() によりスリープします。

    送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。

    hashtag

    hashtag
    startSensorCapture()

    ボード上のセンサーの取得を開始します。多くのセンサーは、取得を開始してから完了するまで数msから数十msの時間を必要とします。

    5行目で、温湿度センサー SHTC3 のデータ取得を開始します。

    6行目で、照度センサー LTR308ALS のデータ取得を開始します。

    7行目は、送信完了フラグをクリアします。

    hashtag
    sleepNow()

    スリープに入る手続きをまとめています。

    ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。

    circle-info

    周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。

    3行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。

    circle-exclamation

    スリープ前にflushを行うと、出力が不安定になる場合があります。

    スリープに入るには the_twelite.sleep() を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。

    パラメータとしてスリープ時間をmsで指定しています。

    triangle-exclamation

    TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000を超えないように指定してください。

    hashtag
    wakeup()

    スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。

    ここではセンサーのデータ取得を開始しています。

    hashtag
    応用編

    hashtag
    より安全な実装

    より安全な実装として、このアクトでは以下の例外処理を強化します。

    • センサー取得部分でavailableにならなかった場合の例外処理が記述されてません。

    • 送信完了待ちで送信完了が通知されない場合の例外処理が記述されていません。

    上記いずれもタイムアウト処理を行います。現在の時間は millis() により取得できます。

    hashtag
    消費エネルギーの削減

    アクト PAL_AMB-UseNap は、センサーのデータ取得待ちをスリープで行い、より低消費エネルギーで動作できます。

    環境センサーパル AMBIENT SENSE PALarrow-up-right
    BRD_APPTWELITEの解説
    Parent_MONOSTICK
    hashtag
    アクトの機能
    • 開閉センサーパル OPEN-CLOSE SENSE PAL を用い、磁気センサーの検出時に割り込み起床し、無線送信します。

    • コイン電池で動作させるための、スリープ機能を利用します。

    hashtag
    アクトの使い方

    hashtag
    必要なTWELITE

    役割

    例

    親機

    アクトを動作させる。

    子機

    +

    hashtag
    アクトの解説

    hashtag
    インクルード

    開閉センサーパルのボード ビヘイビア<PAL_MAG>をインクルードします。

    hashtag
    setup()

    最初にボードビヘイビア<PAL_MAG>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

    ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

    LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

    hashtag
    begin()

    begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

    setup()終了後にsleepNow()を呼び出し初回スリープを実行します。

    hashtag
    sleepNow()

    スリープに入るまえに磁気センサーのDIOピンの割り込み設定をします。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。

    7行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。

    hashtag

    hashtag
    wakeup()

    スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。

    ここではウェイクアップタイマーからの起床の場合(the_twelite.is_wokeup_by_wktimer())は再びスリープを実行します。これは上述のウォッチドッグタイマーのリセットを行う目的のみの起床です。

    磁気センサーの検出時の起床の場合は、このままloop()処理に移行します。

    hashtag
    loop()

    ここでは、検出された磁気センサーのDIOの確認を行い、パケットの送信を行い、パケット送信完了後に再びスリープを実行します。

    b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。

    磁気センサーの検出DIOピンの確認を行います。検出ピンは二種類あります。N極検知とS極検知です。単に磁石が近づいたことだけを知りたいならいずれかのピンの検出されたことが条件となります。

    起床要因のピンを確認するにはthe_twelite.is_wokeup_by_dio()を用います。パラメータはピン番号です。戻り値をuint8_tに格納しているのはパケットのペイロードに格納するためです。

    通信条件の設定やペイロードにデータを格納後、送信を行います。

    その後、loop() 中 b_transmit が true になっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。

    送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。

    開閉センサーパル OPEN-CLOSE SENSE PALarrow-up-right
    BRD_APPTWELITEの解説
    Parent_MONOSTICK

    BRD_APPTWELITE

    IO通信(標準アプリケーションApp_Tweliteの基本機能)

    App_TweLite で必要な配線と同じ配線想定したボードサポート <BRD_APPTWELITE> を用いたサンプルです。

    circle-exclamation

    このサンプルは App_TweLite と通信できません。

    hashtag
    アクトの機能

    • M1を読み取り、親機か子機を決める。

    • DI1-DI4 の値を読み取ります。Buttons クラスにより、チャタリングの影響を小さくするため、連続で同じ値になったときにはじめて変化が通知されます。変化があったときには通信を行います。

    • AI1-AI4 の値を読み取ります。

    hashtag
    アクトの使い方

    hashtag
    必要なTWELITEと配線例

    hashtag
    アクトの解説

    hashtag
    インクルード

    全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク とボードサポート <BRD_APPTWELITE>をインクルードしておきます。

    hashtag
    宣言部

    • サンプルアクト共通宣言

    • 長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)

    • アプリケーション中のデータ保持するための変数

    hashtag
    セットアップ setup()

    大まかな流れは、各部の初期設定、各部の開始となっています。

    hashtag
    the_twelite

    このオブジェクトはTWENETの中核としてふるまいます。

    ボードの登録(このアクトでは<BRD_APPTWELITE>を登録しています)。以下のように use の後に <> で登録したいボードの名前を指定します。

    ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。

    ここではボードオブジェクトを用いbrd.get_M1()を呼び出すことでM1ピンの設定を読み出します。1がスイッチをセットしGND側になっていることを示します。M1 がセットされた場合にu8devidを0x00に、そうでなければ0xFEを指定しています。これはネットワーク上での役割が親機(0x00)であるか子機(0xFE)あるかを決定します。この値は <NWK_SIMPLE>の設定に使用します。

    the_twelite に設定を反映するには << を用います。

    • TWENET::appid(APP_ID) アプリケーションIDの指定

    • TWENET::channel(CHANNEL) チャネルの指定

    • TWENET::rx_when_idle()

    circle-info

    <<, >>演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。

    次にネットワークを登録します。

    1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。

    2行目は、<NWK_SIMPLE>の設定です。先ほどボードから得たM1ピンの状態によって親機アドレス(0x00)か子機アドレス(0xFE)を格納したu8devidを指定します。

    setup() 関数の末尾で the_twelite.begin() を実行しています。

    hashtag
    Analogue

    ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。

    初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。2番目のパラメータは、ADCの開始をTimer0に同期して行う指定です。

    ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。

    ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。

    パラメータと指定されるBRD_APPTWELITE::にはPIN_AI1..4が定義されています。App_Tweliteで用いるAI1..AI4に対応します。AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 と割り当てられています。PIN_ANALOGUE::にはADCで利用できるピンの一覧が定義されています。

    circle-info

    初回を除き ADC の開始は、割り込みハンドラ内で行います。

    hashtag
    Buttons

    DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。

    初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。

    開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_DI1-4 (DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。

    circle-info

    ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。

    hashtag
    Timer0

    App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。

    上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。

    Timer0.begin()を呼び出したあと、タイマーが稼働します。

    hashtag
    Serial

    Serial オブジェクトは、初期化や開始手続きなく利用できます。

    上記の例では "--- BRD_APPTWELITE(254) ---\r\n" といった文字列を表示します。ここでは10進数の数値文字列として表示するためにint(u8devid)としています。int型でない場合は出力の意味合いが変わりバイトの書き出しになるので注意してください。

    hashtag
    ループ loop()

    ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。

    circle-exclamation

    TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。

    したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。

    hashtag
    Buttons

    DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。

    1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば bp & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。

    circle-info

    初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。

    次にビットマップから値を取り出してu8DI_BMに格納しています。ここではMWXライブラリで用意したcollect_bits()関数を用いています。

    collect_bits() は、上述のpack_bits()と同様のビット位置の整数値を引数とします。可変数引数の関数で、必要な数だけパラメータを並べます。上記の処理では bit0 は DI1、bit1 は DI2、bit2 は DI3、bit3 は DI4の値としてu8DI_BMに格納しています。

    App_Twelite では、DI1から4に変化があった場合に無線送信しますので、Buttons.available()を起点に送信処理を行います。transmit()処理の内容は後述します。

    hashtag
    Analogue

    ADCのアナログディジタル変換が終了した直後のloop()で available になります。次の ADC が開始するまでは、データは直前に取得されたものとして読み出すことが出来ます。

    ADC値を読むには Analogue.read() または Analogue.read_raw() メソッドを用います。read()はmVに変換した値、read_raw()は 0..1023 のADC値となります。パラメータにはADCのピン番号を指定します。ADCのピン番号はPIN_ANALOGUE::やBRD_APPTWELITE::に定義されているので、こちらを利用しています。

    circle-exclamation

    周期的に実行されるADCの値は、タイミングによってはavailable通知前のより新しい値が読み出されることがあります。

    このアクトでは32Hzと比較的ゆっくりの周期で処理しているため、available判定直後に処理すれば問題にはなりませんが、変換周期が短い場合、loop()中で比較的長い時間のかかる処理をしている場合は注意が必要です。

    Analogueには、変換終了後に割り込みハンドラ内から呼び出されるコールバック関数を指定することが出来ます。例えば、このコールバック関数からFIFOキューに値を格納する処理を行い、アプリケーションループ内ではキューの値を逐次読み出すといった非同期処理を行います。

    hashtag
    Timer0

    Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。

    AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。

    u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。

    hashtag
    the_twelite.receiver

    受信パケットがある場合の処理です。

    無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。

    circle-exclamation

    TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。

    内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。

    hashtag
    transmit()

    無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。

    hashtag

    hashtag
    関数プロトタイプ

    MWX_APIRETはuint32_t型のデータメンバを持つ戻り値を取り扱うクラスです。MSB(bit31)が成功失敗、それ以外が戻り値として利用するものです。

    hashtag
    ネットワークオブジェクトとパケットオブジェクトの取得

    ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。

    ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。

    hashtag
    パケットの送信設定

    パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。

    • tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。

    • tx_retry() パラメータに再送回数を指定します。例の1は再送回数が1回、つまり合計2回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。

    hashtag
    パケットのデータペイロード

    ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。

    送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。

    circle-info

    データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。

    IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。

    上記の例は、解説のためある程度の妥協をしています。節約を考える場合 00: の識別子は1バイトの簡単なものにすべきですし、Vccの電圧値は8ビットに丸めてもかまわないでしょう。また各AI1..AI4の値は10bitで、合計40bit=5バイトに対して6バイト使用しています。

    上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。

    上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。

    pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。

    そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。

    3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。

    4行目はuint8_t型でDI1..DI4のビットマップを書き込みます。

    7-9行目ではau16AI配列の値を順に書き込んでいます。この値はuint16_t型で2バイトですが、ビッグエンディアンの並びで書き込みます。

    circle-info

    7行目のfor文はC++で導入された範囲for文です。サイズのわかっている配列やbegin(), end()によるイテレータによるアクセスが可能なコンテナクラスなどは、この構文が使用できます。au16AIの型もコンパイル時に判定できるため auto&& (ユニバーサル参照)で型の指定も省略してます。

    通常のfor文に書き換えると以下のようになります。

    これでパケットの準備は終わりです。あとは、送信要求を行います。

    パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。戻り値としてMWX_APIRET型を返していますが、このアクトでは使っていません。

    circle-info

    戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。

    hashtag
    receive()

    パケットの受信が確認できた(つまりthe_twelite.receiver.available()がtrueになった)ときの処理です。ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。

    まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。

    次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。

    circle-info

    <NWK_SIMPLE>では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。

    MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。

    1行目ではデータ格納のためのchar型の配列を宣言しています。サイズが5バイトなのは末尾にヌル文字を含め、文字出力などでの利便性を挙げるためです。末尾の{}は初期化の指定で、5バイト目を0にすれば良いのですが、ここでは配列全体をデフォルトの方法で初期化、つまり0にしています。

    2行目でexpand_bytes()により4バイト文字列を取り出しています。パラメータにコンテナ型を指定しない理由は、この続きを読み出すための読み出し位置を把握する必要があるためです。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。

    3番目に読み出す変数を指定しますが、ここでもmake_pairによって文字列配列とサイズのペアを指定します。

    circle-info

    このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()の戻り値により判定してください。

    expand_bytes()の戻り値は uint8_t* ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)を戻します。

    読み出した4バイト文字列の識別子が、このアクトで指定した識別子と異なる場合は、このパケットを処理しません。

    circle-info

    TWENETではアプリケーションIDと物理的な無線チャネルが合致する場合は、どのアプリケーションもたとえ種別が違ったとしても、受信することが出来ます。他のアプリケーションで作成したパケットを意図しない形で受信しない目的で、このような識別子やデータペイロードの構造などのチェックを行い、偶然の一致が起きないように対処することを推奨します。

    シンプルネットワーク<NWK_SIMPLE>でのパケット構造の要件も満足する必要があるため、シンプルネットワークを使用しない他のアプリケーションが同じ構造のパケットを定義しない限り(非常にまれと思われます)、パケットの混在受信は発生しません。

    続いて、データ部分の取得です。DI1..DI4の値とAI1..AI4の値を別の変数に格納します。

    先ほどのexpand_bytes()の戻り値npを1番目のパラメータにしています。先に読み取った4バイト文字列識別子の次から読み出す指定です。2番目のパラメータは同様です。

    3番目以降のパラメータはデータペイロードの並びに一致した型の変数を、送り側のデータ構造と同じ順番で並べています。この処理が終われば、指定した変数にペイロードから読み出した値が格納されます。

    確認のためシリアルポートへ出力します。

    数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)

    1行目の "DI:%04b" は"DI:0010"のようにDI1..DI4のビットマップを4桁で表示します。3行目の"/%04d"は"/3280/0010/0512/1023/1023"のように Vcc/AI1..AI4の値を順に整数で出力します。5行目のmwx::crlfは改行文字列を出力します。

    これで必要なデータの展開が終わったので、あとはボード上のDO1..DO4とPWM1..PWM4の値を変更します。

    digitalWrite()はディジタル出力の値を変更します。1番目のパラメータはピン番号で、2番目はHIGH(Vccレベル)かLOW(GNDレベル)を指定します。

    Timer?.change_duty()はPWM出力のデューティ比を変更します。パラメータにデューティ比 0..1024 を指定します。最大値が1023でないことに注意してください(ライブラリ内で実行される割り算のコストが大きいため2のべき乗である1024を最大値としています)。0にするとGNDレベル、1024にするとVccレベル相当の出力になります。

    #include <TWELITE>
    #include <NWK_SIMPLE>
    #include <PAL_AMB> // include the board support of PAL_AMB
    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;
    }
    auto&& brd = the_twelite.board.use<PAL_AMB>();
    
    u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
    if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
    	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
    	the_twelite
    		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL); // set channel (pysical channel)
    Wire.begin(); // start two wire serial bus.
    startSensorCapture();
    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();
    		}
    	}
    }
    	if (TickTimer.available()) {
    		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);
    		}
    		if (brd.sns_LTR308ALS.available() 
    						&& brd.sns_SHTC3.available() && !b_transmit) {
    	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())
    	);
    	// do transmit
    	MWX_APIRET ret = pkt.transmit();
    
    	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();
        }
    }
    void startSensorCapture() {
    	auto&& brd = the_twelite.board.use<PAL_AMB>();
    
    	// start sensor capture
    	brd.sns_SHTC3.begin();
    	brd.sns_LTR308ALS.begin();
    	b_transmit = false;
    }
    void sleepNow() {
    	uint32_t u32ct = 1750 + random(0,500);
    	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
    
    	the_twelite.sleep(u32ct);
    }
    void wakeup() {
    	Serial	<< mwx::crlf
    			<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
    			<< mwx::crlf
    			<< "..start sensor capture again."
    			<< mwx::crlf;
    	startSensorCapture();
    }
    uint32_t t_start;
    
      // 時間待ちの処理を開始した時点でタイムスタンプを保存
      t_start = millis();
    
    ...
    
      // loop() でタイムアウトのチェック
      if (millis() - t_start > 100) {
        sleepNow();
      }
    #include <TWELITE>
    #include <NWK_SIMPLE>
    #include <PAL_>
    void setup() {
    	/*** SETUP section */
    	// use PAL_AMB board support.
    	auto&& brd = the_twelite.board.use<PAL_MAG>();
    	// 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 */
    	the_twelite.begin(); // start twelite!
    
    	/*** INIT message */
    	Serial << "--- PAL_MAG:" << FOURCHARS << " ---" << mwx::crlf;
    }
    auto&& brd = the_twelite.board.use<PAL_MAG>();
    
    u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
    if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
    	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
    void begin() {
    	sleepNow(); // the first time is just sleeping.
    }
    void sleepNow() {
    	uint32_t u32ct = 60000;
    	
    	pinMode(PAL_MAG::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
    	pinMode(PAL_MAG::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);
    
    	the_twelite.sleep(u32ct);
    }
    void wakeup() {
    	if (the_twelite.is_wokeup_by_wktimer()) {
    		sleepNow();
    	}
    }
    void loop() {
    	if (!b_transmit) {
    		if (auto&& pkt = 
          the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
    
    			uint8_t b_north = 
    			  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
    			uint8_t b_south = 
    			  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);
    	
    			Serial << "..sensor north=" << int(b_north) 
    			       << " south=" << int(b_south) << mwx::crlf;
    	
    			// set tx packet behavior
    			pkt << tx_addr(0x00)
    				<< tx_retry(0x1)
    				<< tx_packet_delay(0, 0, 2);
    	
    			// prepare packet payload
    			pack_bytes(pkt.get_payload()
    				, make_pair(FOURCHARS, 4) 
    				, b_north
    				, b_south
    			);
    	
    			// do transmit
    			MWX_APIRET ret = pkt.transmit();
    	
    			if (ret) {
    				u8txid = ret.get_value() & 0xFF;
    				b_transmit = true;
    			}
    			else {
    				// fail to request
    				sleepNow();
    			}
    		} else {
    		  sleepNow();
    		}
    	} else { 
    		if (the_twelite.tx_status.is_complete(u8txid)) {		
    			b_transmit = 0;
    			sleepNow();
    		}
    	}
    }
    	if (!b_transmit) {
    uint8_t b_north = 
      the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
    uint8_t b_south = 
      the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);
    // do transmit
    MWX_APIRET ret = pkt.transmit();
    if (the_twelite.tx_status.is_complete(u8txid)) {		
    	b_transmit = 0;
    	sleepNow();
    }
    MONOSTICK BLUEまたはREDarrow-up-right
    Parent_MONOSTICK
    BLUE PAL または RED PALarrow-up-right
    環境センサーパル AMBIENT SENSE PALarrow-up-right
    MONOSTICK BLUEまたはREDarrow-up-right
    Parent_MONOSTICK
    BLUE PAL または RED PALarrow-up-right
    開閉センサーパル OPEN-CLOSE SENSE PALarrow-up-right
    DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。
  • 受信したパケットの値に応じで DO1-4, PWM1-4 に設定する。

  • 受信回路をオープンにする指定

    tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ0msから50msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから10ms置きに再送を行うという意味です。

    役割

    例

    親機

    TWELITE DIParrow-up-right

    最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。

    子機

    TWELITE DIParrow-up-right

    最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。

    <NWK_SIMPLE>
    配線例 (AI1-AI4は省略可)

    PAL_MOT-oneshot

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

    circle-check

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

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

    hashtag
    アクトの解説 (v2)

    circle-exclamation

    ※ 最新のコードは「を参照ください。

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

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

    circle-info

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

    hashtag
    状態変数

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

    hashtag
    begin()

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

    hashtag
    wakeup()

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

    hashtag
    loop()

    loop() の基本構造は状態変数eStateによるswitch ... case節です。eStateの初期状態はINITです。loop_moreは状態変数を書き換えた直後、loop()を抜ける前にもう一度実行したいときにtrueにセットします。

    以下では各case節を解説します。eStateの初期状態はINITです。

    状態INITでは、初期化(結果格納用のキューのクリア)を行います。

    状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_captureに格納します。

    状態WAIT_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されるとsns_MC3630.available()がtrueになります。

    タイムアウトした場合は状態EXIT_FATALに遷移します。

    状態REQUEST_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成、そうし尿級を行います。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_txに格納します。

    状態WAIT_TXでは、無線パケットの送信完了を待ちます。

    タイムアウト時には状態EXIT_FATALに遷移します。

    一連の動作が完了したときは状態EXIT_NORMALに遷移しローカル定義の関数sleepNow()を呼び出しスリープを実行します。またエラーを検出した場合は状態EXIT_FATALに遷移し、システムリセットを行います。

    hashtag
    MWX_APIRET TxReq()

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

    取得サンプルの平均値を計算します。

    circle-info

    ここでは除算を行っていますが、TWELITEマイコンには除算回路がないため、計算時間を要する演算となります。例えば以下のような改良が考えられます。

    • サンプル数を2のべき乗として、その数を変数に入れず直接指定した除算を行う(ビットシフトによる演算に最適化されます)。

    • 平均値を求めず、合計値とサンプル数を送り、受信先で計算する。

    X軸の最大値と最小値を計算します。

    ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。

    circle-info

    C++ Standard Template Library のアルゴリズムを使用する例としてstd::mimmax_element紹介していますが、上述のforループ内で最大、最小を求めても構いません。

    ここでキューをクリア.sns_MC3630.get_que().clear()しています。

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

    hashtag
    アクトの解説 (初版)

    circle-exclamation

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

    ※ 最新のコードは「を参照ください。

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

    circle-info

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

    hashtag
    wakeup()

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

    加速度センサーの結果を保存するキューの内容を抹消(.sns_MC3630.get_que().clear())しておきます。加速度センサーのサンプルの取得忘れがあったり、また停止させるまでに次のサンプルが取得したような場合を想定します。

    ここで加速度センサーを都度開始します。設定は400Hz,±4G,FIFO割り込みは4サンプルとしています。4サンプルも必要ない場合は1サンプルの設定でも構いません。

    hashtag
    loop()

    このアクトでは、サンプル取得後、すぐに加速度センサーの動作を停止します。

    取得サンプルの平均値を計算します。

    X軸の最大値と最小値を計算します。

    ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。

    最後にキューをクリア.sns_MC3630.get_que().clear()しています。

    // use twelite mwx c++ template library
    #include <TWELITE>
    #include <NWK_SIMPLE>
    #include <BRD_APPTWELITE>
    /*** Config part */
    // application ID
    const uint32_t APP_ID = 0x1234abcd;
    
    // channel
    const uint8_t CHANNEL = 13;
    
    /*** function prototype */
    MWX_APIRET transmit();
    void receive();
    
    /*** application defs */
    const char APP_FOURCHAR[] = "BAT1";
    uint8_t u8devid = 0;
    
    uint16_t au16AI[5];
    uint8_t u8DI_BM;
    void setup() {
    	// 変数の初期化
    	for(auto&& x : au16AI) x = 0xFFFF;
    	u8DI_BM = 0xFF;
    
    	/*** SETUP section */
    	// App_Twelite仕様のボード定義をシステムに登録します。
    	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
    
    	// check DIP sw settings
    	u8devid = (brd.get_M1()) ? 0x00 : 0xFE;
    
    	// setup analogue
    	Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)
    
    	// setup buttons
    	Buttons.setup(5); // init button manager with 5 history table.
    
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
    		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
    
    	// Register Network
    	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    	nwksmpl << NWK_SIMPLE::logical_id(u8devid); // set Logical ID. (0x00 means parent device)
    
    	/*** BEGIN section */
    	// start ADC capture
    	Analogue.begin(pack_bits(
    						BRD_APPTWELITE::PIN_AI1,
    						BRD_APPTWELITE::PIN_AI2,
    						BRD_APPTWELITE::PIN_AI3,
    						BRD_APPTWELITE::PIN_AI4,
    				   	PIN_ANALOGUE::VCC)); // _start continuous adc capture.
    
    	// Timer setup
    	Timer0.begin(32, true); // 32hz timer
    
    	// start button check
    	Buttons.begin(pack_bits(
    						BRD_APPTWELITE::PIN_DI1,
    						BRD_APPTWELITE::PIN_DI2,
    						BRD_APPTWELITE::PIN_DI3,
    						BRD_APPTWELITE::PIN_DI4),
    					5, 		// history count
    					4);  	// tick delta (change is detected by 5*4=20ms consequtive same values)	
    
    
    	the_twelite.begin(); // start twelite!
    
    	/*** INIT message */
    	Serial << "--- BRD_APPTWELITE(" << int(u8devid) << ") ---" << mwx::crlf;
    }
    auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
    u8devid = (brd.get_M1()) ? 0x00 : 0xFE;
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
    		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
    // 以下の記述は MWX ライブラリでは利用できません。
    #include <iostream>
    std::cout << "hello world" << std::endl;
    auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    nwksmpl << NWK_SIMPLE::logical_id(u8devid);
    the_twelite.begin(); // start twelite!
    Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0);
    	Analogue.begin(pack_bits(
    						BRD_APPTWELITE::PIN_AI1,
    						BRD_APPTWELITE::PIN_AI2,
    						BRD_APPTWELITE::PIN_AI3,
    						BRD_APPTWELITE::PIN_AI4,
    				   	PIN_ANALOGUE::VCC));
    Buttons.setup(5);
    Buttons.begin(pack_bits(
    						BRD_APPTWELITE::PIN_DI1,
    						BRD_APPTWELITE::PIN_DI2,
    						BRD_APPTWELITE::PIN_DI3,
    						BRD_APPTWELITE::PIN_DI4),
    					5, 		// history count
    					4);  	// tick delta
    Timer0.begin(32, true); // 32hz timer
    Serial << "--- BRD_APPTWELITE("
           << int(u8devid) 
           << ") ---" << mwx::crlf;
    /*** loop procedure (called every event) */
    void loop() {
    	if (Buttons.available()) {
    		uint32_t bp, bc;
    		Buttons.read(bp, bc);
    
    		u8DI_BM = uint8_t(collect_bits(bp, 
    							BRD_APPTWELITE::PIN_DI4,   // bit3
    							BRD_APPTWELITE::PIN_DI3,   // bit2
    							BRD_APPTWELITE::PIN_DI2,   // bit1
    							BRD_APPTWELITE::PIN_DI1)); // bit0
    
    		transmit();
    	}
    
    	if (Analogue.available()) {
    		au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
    		au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
    		au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
    		au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
    		au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
    	}
    
    	if (Timer0.available()) {
    		static uint8_t u16ct;
    		u16ct++;
    
    		if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
    			if ((u16ct % 32) == 0) { // every 32ticks of Timer0
    				transmit();
    			}
    		}
    	}
    
    	// receive RF packet.
      if (the_twelite.receiver.available()) {
    		receive();
    	}
    }
    	if (Buttons.available()) {
    		uint32_t bp, bc;
    		Buttons.read(bp, bc);
    u8DI_BM = uint8_t(collect_bits(bp, 
    		BRD_APPTWELITE::PIN_DI4,   // bit3
    		BRD_APPTWELITE::PIN_DI3,   // bit2
    		BRD_APPTWELITE::PIN_DI2,   // bit1
    		BRD_APPTWELITE::PIN_DI1)); // bit0
    
    /* collect_bits は以下の処理を行います。
    u8DI_BM = 0;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI1)) u8DI_BM |= 1;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI2)) u8DI_BM |= 2;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI3)) u8DI_BM |= 4;
    if (bp & (1UL << BRD_APPTWELITE::PIN_DI4)) u8DI_BM |= 8;
    */
    transmit();
    if (Analogue.available()) {
    	au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
    	au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
    	au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
    	au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
    	au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
    }
    if (Timer0.available()) {
    	static uint8_t u16ct;
    	u16ct++;
    
    	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
    		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
    			transmit();
    		}
    	}
    }
    if (the_twelite.receiver.available()) {
    	receive();
    }
    MWX_APIRET transmit() {
    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
    		Serial << "..DI=" << format("%04b ", u8DI_BM);
    		Serial << format("ADC=%04d/%04d/%04d/%04d ", au16AI[1], au16AI[2], au16AI[3], au16AI[4]);
    		Serial << "Vcc=" << format("%04d ", au16AI[0]);
    		Serial << " --> transmit" << mwx::crlf;
    
    		// set tx packet behavior
    		pkt << tx_addr(u8devid == 0 ? 0xFE : 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,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    
    		// prepare packet payload
    		pack_bytes(pkt.get_payload() // set payload data objects.
    			, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
    			, uint8_t(u8DI_BM)
    		);
    
    		for (auto&& x : au16AI) {
    			pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
    		}
    		
    		// do transmit 
    		return pkt.transmit();
    	}
    	return MWX_APIRET(false, 0);
    }
    MWX_APIRET transmit()
    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
    pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
    		<< tx_retry(0x1) // set retry (0x3 send four times in total)
    		<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    # 先頭バイトのインデックス: データ型 : バイト数 : 内容
    
    00: uint8_t[4] : 4 : 4文字識別子
    04: uint8_t    : 1 : DI1..4のビットマップ
    06: uint16_t   : 2 : Vccの電圧値
    08: uint16_t   : 2 : AI1のADC値 (0..1023)
    10: uint16_t   : 2 : AI2のADC値 (0..1023)
    12: uint16_t   : 2 : AI3のADC値 (0..1023)
    14: uint16_t   : 2 : AI4のADC値 (0..1023)
    auto&& payl = pkt.get_payload();
    payl.reserve(16); // 16バイトにリサイズ
    payl[00] = APP_FOURCHAR[0];
    payl[01] = APP_FOURCHAR[1];
    ...
    payl[08] = (au16AI[0] & 0xFF00) >> 8; //Vcc
    payl[09] = (au16AI[0] & 0xFF);
    ...
    payl[14] = (au16AI[4] & 0xFF00) >> 8; // AI4
    payl[15] = (au16AI[4] & 0xFF);
    // prepare packet payload
    pack_bytes(pkt.get_payload() // set payload data objects.
    	, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
    	, uint8_t(u8DI_BM)
    );
    
    for (auto&& x : au16AI) {
    	pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
    }
    for(int i = 0; i < sizeof(au16AI)/sizeof(uint16_t)); i++) {
      pack_bytes(pkt.get_payload(), au16AI[i]);
    }
    return pkt.transmit();
    void receive() {	
    	auto&& rx = the_twelite.receiver.read();
    
      // expand the packet payload
    	char fourchars[5]{};
    	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    		, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
        );
    
    	// check header
    	if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
    
    	// read rest of payload
    	uint8_t u8DI_BM_remote = 0xff;
    	uint16_t au16AI_remote[5];
    	expand_bytes(np, rx.get_payload().end()
    		, u8DI_BM_remote
    		, au16AI_remote[0]
    		, au16AI_remote[1]
    		, au16AI_remote[2]
    		, au16AI_remote[3]
    		, au16AI_remote[4]
    	);
    
    	Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
    	for (auto&& x : au16AI_remote) {
    		Serial << format("/%04d", x);
    	}
    	Serial << mwx::crlf;
    
    	// set local DO
    	digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
    	digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
    
    	// set local PWM : duty is set 0..1024, so 1023 is set 1024.
    	Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
    	Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
    	Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
    	Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
    }
    
    auto&& rx = the_twelite.receiver.read();
    Serial << format("..receive(%08x/%d) : ",
       rx.get_addr_src_long(), rx.get_addr_src_lid());
    char fourchars[5]{};
    auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    	, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
      );
    if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
    	// read rest of payload
    	uint8_t u8DI_BM_remote = 0xff;
    	uint16_t au16AI_remote[5];
    	expand_bytes(np, rx.get_payload().end()
    		, u8DI_BM_remote
    		, au16AI_remote[0]
    		, au16AI_remote[1]
    		, au16AI_remote[2]
    		, au16AI_remote[3]
    		, au16AI_remote[4]
    	);
    Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
    for (auto&& x : au16AI_remote) {
    	Serial << format("/%04d", x);
    }
    Serial << mwx::crlf;
    // set local DO
    digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
    digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
    
    // set local PWM : duty is set 0..1024, so 1023 is set 1024.
    Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
    Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
    Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
    Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
    v2 ... 状態変数を用いた loop() 実装に書き換え
    初版 ... MWSDK2020_05 版の SDK 添付
    サンプルアクト>最新版の入手」
    サンプルアクト>最新版の入手」
    enum class E_STATE {
    	INIT = 0,
    	START_CAPTURE,
    	WAIT_CAPTURE,
    	REQUEST_TX,
    	WAIT_TX,
    	EXIT_NORMAL,
    	EXIT_FATAL
    } eState;
    void begin() { 
    	// sleep immediately, waiting for the first capture.
    	sleepNow();
    }
    void wakeup() {
    	Serial << crlf << "--- PAL_MOT(OneShot):" 
    	       << FOURCHARS << " wake up ---" << crlf;
    	eState = E_STATE::INIT;
    }
    void loop() {
    	auto&& brd = the_twelite.board.use<PAL_MOT>();
    	bool loop_more;
    	do {
    	  loop_more = false;
    	  switch(eState) {
     	    ...
    	  }
    	} while(loop_more);
    			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;
    			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;
    			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;
    			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;
    			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;
    			case E_STATE::EXIT_NORMAL:
    				sleepNow();
    			break;
    
    			case E_STATE::EXIT_FATAL:
    				Serial << flush;
    				the_twelite.reset_system();
    			break;
    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();
    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 = 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()) << ')';
    		}
    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;
    }
    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();
    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

    PingPong

    2台のシリアル接続しているTWELITEの片方からPING(ピン)の無線パケットを送信すると、他方からPONG(ポン)の無線パケットが返ってきます。

    hashtag
    アクトの使い方

    hashtag
    必要なTWELITE

    いずれかを2台。

    • でUART接続されているなど

    hashtag
    アクトの解説

    hashtag
    インクルード

    全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク をインクルードしておきます。

    hashtag
    宣言部

    • サンプルアクト共通宣言

    • 長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)

    • アプリケーション中のデータ保持するための変数

    hashtag
    セットアップ setup()

    大まかな流れは、各部の初期設定、各部の開始となっています。

    hashtag
    the_twelite

    このオブジェクトはTWENETを操作するための中核クラスオブジェクトです。

    the_twelite に設定を反映するには << を用います。

    • TWENET::appid(APP_ID) アプリケーションIDの指定

    • TWENET::channel(CHANNEL) チャネルの指定

    • TWENET::rx_when_idle()

    circle-info

    <<, >>演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。

    次にネットワークを登録します。

    1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。

    2行目は、<NWK_SIMPLE>の設定で、0xFE(ID未設定の子機)という指定を行います。

    3行目は、中継回数の最大値を指定しています。この解説では中継には触れませんが、複数台で動作させたときにパケットの中継が行われます。

    setup() 関数の末尾で the_twelite.begin() を実行しています。

    hashtag
    Analogue

    ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。

    初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。

    ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。

    ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。

    パラメータにはPIN_ANALOGUE::A1(ADC0)とPIN_ANALOGUE::VCC(モジュール電源電圧)が指定されています。

    2番目のパラメータには50が指定されています。ADCの動作はデフォルトではTickTimerで開始されていて、

    circle-info

    初回を除き ADC の開始は、割り込みハンドラ内で行います。

    hashtag
    Buttons

    DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。

    初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。

    開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_BTN (12) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。10を指定しているので10msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。

    circle-info

    ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。

    hashtag
    Serial

    Serial オブジェクトは、初期化や開始手続きなく利用できます。

    シリアルポートへの文字列出力を行います。mwx::crlfは改行文字です。

    hashtag
    ループ loop()

    ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。

    circle-exclamation

    TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。

    したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。

    hashtag
    Serial

    Serial.available()がtrueの間はシリアルポートからの入力があります。内部のFIFOキューに格納されるためある程度の余裕はありますが、速やかに読み出すようにします。データの読み出しはSerial.read()を呼びます。

    ここでは't'キーの入力に対応してvTransmit()関数を呼び出しPINGパケットを送信します。

    hashtag
    Buttons

    DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。

    1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば btn_state & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。

    circle-info

    初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。

    初回確定以外の場合かつPIN_BTNのボタンが離されたタイミングでvTransmit()を呼び出しています。押したタイミングにするには(!(btn_state && (1UL << PIN_BTN)))のように条件を論理反転します。

    hashtag
    Timer0

    Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。

    AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。

    u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。

    hashtag
    the_twelite.receiver

    受信パケットがある場合の処理です。

    無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。

    circle-exclamation

    TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。

    内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。

    まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。

    次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。

    circle-info

    <NWK_SIMPLE>では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。

    MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。

    1行目から3行目までは、データを格納する変数を指定しています。

    6行目でexpand_bytes()によりパケットのペイロードのデータを変数に格納します。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。

    3番目以降のパラメータに変数を列挙します。列挙した順番にペイロードの読み出しとデータ格納が行われます。

    circle-info

    このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()の戻り値により判定してください。

    expand_bytes()の戻り値は uint8_t* ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)を戻します。

    msgに読み出した4バイト文字列の識別子が"PING"の場合はPONGメッセージを送信する処理です。

    続いて到着したパケット情報を表示します。

    数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)

    mwx::crlfは改行文字(CR LF)を、mwx::flushは出力完了待ちを指定します。

    hashtag
    transmit()

    無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。

    hashtag

    hashtag
    ネットワークオブジェクトとパケットオブジェクトの取得

    ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。

    ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。

    hashtag
    パケットの送信設定

    パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。

    • tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。

    • tx_retry() パラメータに再送回数を指定します。例の3は再送回数が3回、つまり合計4回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。

    hashtag
    パケットのデータペイロード

    ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。

    送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。

    circle-info

    データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。

    IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。

    上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。

    上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。

    pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。

    そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。

    3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。

    4,5,6行目は、数値型の値 (uint8_t, uint16_t, uint32_t)を格納します。符号付などの数値型、char型など同じ数値型であっても左記の3つの型にキャストして投入します。

    analogRead()とanalogRead_mv()は、ADCの結果を取得するものです。前者はADC値(0..1023)、後者は電圧[mv](0..2470)となります。モジュールの電源電圧は内部的に分圧抵抗の値を読んでいるためその変換を行うadalogRead_mv()を利用しています。

    これでパケットの準備は終わりです。あとは、送信要求を行います。

    パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。

    circle-info

    このアクトでは使用しませんが、戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。

    受信回路をオープンにする指定

    tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ100msから200msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから20ms置きに再送を行うという意味です。

    MONOSTICK BLUE または REDarrow-up-right
    TWELITE Rarrow-up-right
    TWELITE DIParrow-up-right
    <NWK_SIMPLE>
    // use twelite mwx c++ template library
    #include <TWELITE>
    #include <NWK_SIMPLE>
    // application ID
    const uint32_t APP_ID = 0x1234abcd;
    
    // channel
    const uint8_t CHANNEL = 13;
    
    // DIO pins
    const uint8_t PIN_BTN = 12;
    
    /*** function prototype */
    void vTransmit(const char* msg, uint32_t addr);
    
    /*** application defs */
    // packet message
    const int MSG_LEN = 4;
    const char MSG_PING[] = "PING";
    const char MSG_PONG[] = "PONG";
    void setup() {
    	/*** SETUP section */
    	Buttons.setup(5); // init button manager with 5 history table.
    	Analogue.setup(true, 50); // setup analogue read (check every 50ms)
    
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
    		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
    
    	// Register Network
    	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    	nwksmpl << NWK_SIMPLE::logical_id(0xFE) // set Logical ID. (0xFE means a child device with no ID)
    	        << NWK_SIMPLE::repeat_max(3);   // can repeat a packet up to three times. (being kind of a router)
    
    	/*** BEGIN section */
    	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.
    	Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.
    
    	the_twelite.begin(); // start twelite!
    
    	/*** INIT message */
    	Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
    }
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
    		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
    		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
    // 以下の記述は MWX ライブラリでは利用できません。
    #include <iostream>
    std::cout << "hello world" << std::endl;
    auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    nwksmpl << NWK_SIMPLE::logical_id(0xFE);
            << NWK_SIMPLE::repeat_max(3);
    the_twelite.begin(); // start twelite!
    Analogue.setup(true);
    Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC), 50); 
    Buttons.setup(5);
    Buttons.begin(pack_bits(PIN_BTN),
    					5, 		// history count
    					10);  	// tick delta
    Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
    void loop() {
    	  // read from serial
    		while(Serial.available())  {
    				int c = Serial.read();
    				Serial << mwx::crlf << char(c) << ':';
    				switch(c) {
    				    case 't':
    				    	  vTransmit(MSG_PING, 0xFF);
    				        break;
    				    default:
    							  break;
    				}
    		}
    
    
    	// Button press
    	if (Buttons.available()) {
    		uint32_t btn_state, change_mask;
    		Buttons.read(btn_state, change_mask);
    
    		// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
    		if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
    			// PIN_BTN pressed
    			vTransmit(MSG_PING, 0xFF);
    		}
    	}
    
    	// receive RF packet.
        while (the_twelite.receiver.available()) {
    		auto&& rx = the_twelite.receiver.read();
    
    		// rx >> Serial; // debugging (display longer packet information)
    
    		uint8_t msg[MSG_LEN];
    		uint16_t adcval, volt;
    		uint32_t timestamp;
    
    		// expand packet payload (shall match with sent packet data structure, see pack_bytes())
    		expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    					, msg       // 4bytes of msg
    								//   also can be -> std::make_pair(&msg[0], MSG_LEN)
    					, adcval    // 2bytes, A1 value [0..1023]
    				    , volt      // 2bytes, Module VCC[mV]
    					, timestamp // 4bytes of timestamp
            );
    		
    		// if PING packet, respond pong!
            if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
    			// transmit a PONG packet with specifying the address.
                vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
            }
    
    		// display the packet
    		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                        , rx.get_psRxDataApp()->u32SrcAddr
                        , rx.get_lqi()
                        , rx.get_length()
    					, rx.get_psRxDataApp()->u8Seq
                        )
    				<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
    					, msg
    					, adcval
    					, volt
    					, timestamp
    					)
                   << mwx::crlf
    			   << mwx::flush;
    	}
    }
    		while(Serial.available())  {
    				int c = Serial.read();
    				Serial << mwx::crlf << char(c) << ':';
    				switch(c) {
    				    case 't':
    				    	  vTransmit(MSG_PING, 0xFF);
    				        break;
    				    default:
    							  break;
    				}
    		}
    	if (Buttons.available()) {
    		uint32_t btn_state, change_mask;
    		Buttons.read(btn_state, change_mask);
    // Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
    if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
    	// PIN_BTN pressed
    	vTransmit(MSG_PING, 0xFF);
    if (Timer0.available()) {
    	static uint8_t u16ct;
    	u16ct++;
    
    	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
    		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
    			transmit();
    		}
    	}
    }
    while (the_twelite.receiver.available()) {
    		auto&& rx = the_twelite.receiver.read();
    
    		uint8_t msg[MSG_LEN];
    		uint16_t adcval, volt;
    		uint32_t timestamp;
    
    		// expand packet payload (shall match with sent packet data structure, see pack_bytes())
    		expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    					, msg       // 4bytes of msg
    											//   also can be -> std::make_pair(&msg[0], MSG_LEN)
    					, adcval    // 2bytes, A1 value [0..1023]
    				  , volt      // 2bytes, Module VCC[mV]
    					, timestamp // 4bytes of timestamp
            );
    		
    		// if PING packet, respond pong!
        if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
    				// transmit a PONG packet with specifying the address.
            vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
        }
    
    		// display the packet
    		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                        , rx.get_psRxDataApp()->u32SrcAddr
                        , rx.get_lqi()
                        , rx.get_length()
    					, rx.get_psRxDataApp()->u8Seq
                        )
    				<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
    					, msg
    					, adcval
    					, volt
    					, timestamp
    					)
                   << mwx::crlf
    			   << mwx::flush;
    	}
    while (the_twelite.receiver.available()) {
    		auto&& rx = the_twelite.receiver.read();
    Serial << format("..receive(%08x/%d) : ",
       rx.get_addr_src_long(), rx.get_addr_src_lid());
    uint8_t msg[MSG_LEN];
    uint16_t adcval, volt;
    uint32_t timestamp;
    
    // expand packet payload (shall match with sent packet data structure, see pack_bytes())
    expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
    		, msg       // 4bytes of msg
    								//   also can be -> std::make_pair(&msg[0], MSG_LEN)
    		, adcval    // 2bytes, A1 value [0..1023]
    	  , volt      // 2bytes, Module VCC[mV]
    		, timestamp // 4bytes of timestamp
        );
    if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
        vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
    }
    		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                        , rx.get_psRxDataApp()->u32SrcAddr
                        , rx.get_lqi()
                        , rx.get_length()
    										, rx.get_psRxDataApp()->u8Seq
                        )
               << format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
                        , msg
                        , adcval
                        , volt
                        , timestamp
                        )
             << mwx::crlf
    			   << mwx::flush;
    void vTransmit(const char* msg, uint32_t addr) {
    	Serial << "vTransmit()" << mwx::crlf;
    
    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
    		// set tx packet behavior
    		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
    			<< tx_retry(0x3) // set retry (0x3 send four times in total)
    			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    
    		// prepare packet payload
    		pack_bytes(pkt.get_payload() // set payload data objects.
    			, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
    			, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
    			, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
    			, uint32_t(millis()) // put timestamp here.
    		);
    	
    		// do transmit 
    		pkt.transmit();
    	}
    }
    	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
    		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
    			<< tx_retry(0x3) // set retry (0x3 send four times in total)
    			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
    # 先頭バイトのインデックス: データ型 : バイト数 : 内容
    
    00: uint8_t[4] : 4 : 4文字識別子
    08: uint16_t   : 2 : AI1のADC値 (0..1023)
    06: uint16_t   : 2 : Vccの電圧値 (2000..3600)
    10: uint32_t   : 4 : millis()システム時間
    // prepare packet payload
    pack_bytes(pkt.get_payload() // set payload data objects.
    	, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
    	, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
    	, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
    	, uint32_t(millis()) // put timestamp here.
    );
    pkt.transmit();

    PAL_MOT

    動作センサーパル MOTION SENSE PALarrow-up-right を用い、センサー値の取得を行います。

    circle-check

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

    受信の確認のためParent_MONOSTICKの解説をご覧ください。

    hashtag
    アクトの機能

    • 動作センサーパル MOTION SENSE PAL を用い、加速度センサーの加速度を連続的に計測し、無線送信します。

    • コイン電池で動作させるための、スリープ機能を利用します。

    hashtag
    アクトの使い方

    hashtag
    必要なTWELITE

    hashtag
    アクトの解説

    hashtag
    インクルード

     動作センサーパルのボードビヘイビアをインクルードします。

    hashtag
    setup()

    最初にボードビヘイビア<PAL_MOT>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

    ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

    LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

    hashtag
    加速度センサーの初期化

    加速度センサーの計測を開始します。加速度センサーの設定(SnsMC3630::Settings)には計測周波数と測定レンジを指定します。ここでは14HZの計測(SnsMC3630::MODE_LP_14HZ)で、±4Gのレンジ(SnsMC3630::RANGE_PLUS_MINUS_4G)で計測します。

    開始後は加速度センサーの計測が秒14回行われ、その値はセンサー内部のFIFOキューに保存されます。センサーに28回分の計測が終わった時点で通知されます。

    hashtag
    begin()

    begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

    setup()終了後にsleepNow()を呼び出し初回スリープを実行します。

    hashtag
    sleepNow()

    スリープに入るまえに加速度センサーのDIOピンの割り込み設定をします。FIFOキューが一定数まで到達したときに発生する割り込みです。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。

    3行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。

    hashtag

    hashtag
    wakeup()

    加速度センサーのFIFO割り込みにより、スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。

    ここではloop()で使用する変数の初期化を行っています。

    hashtag
    loop()

    ここでは、加速度センサー内のFIFOキューに格納された加速度情報を取り出し、これをもとにパケット送信を行います。パケット送信完了後に再びスリープを実行します。

    b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。

    最初にセンサーがavailableかどうかを確認します。割り込み起床後であるため、availableでないのは通常ではなく、そのままスリープします。

    無線送信パケットでは使用しないのですが、取り出した加速度の情報を確認してみます。

    加速度センサーの計測結果はbrd.sns_MC3630.get_que()で得られるFIFOキューに格納されます。

    加速度センサーの計測結果を格納している構造体 axis_xyzt は x, y, z の三軸の情報に加え、続き番号 t が格納されています。

    格納されているサンプル数はキューのサイズ(brd.sns_MC3630.get_que().size())を読み出すことで確認できます。通常は28サンプルですが処理の遅延等によりもう少し進む場合もあります。最初のサンプルはfront()で取得することができます。その続き番号はfront().tになります。

    ここでは、サンプルをキューから取り出す前にサンプルの平均をとってみます。キューの各要素にはfor文(for (auto&& v: brd.sns_MC3630.get_que()) { ... }) でアクセスできます。for文内の v.x, v.y, v.z が各要素になります。ここでは各要素の合計を計算しています。for文終了後は要素数で割ることで平均を計算しています。

    次にパケットを生成して送信要求を行いますが、データ量が大きいため2回に分けて送信します。そのため送信処理がfor文で2回行われます。

    送信するパケットに含めるサンプル数とサンプル最初の続き番号をパケットのペイロードの先頭部分に格納します。

    最後に加速度データを格納します。先程は平均値の計算のためにキューの各要素を参照のみしましたが、ここではキューから1サンプルずつ読み出してパケットのペイロードに格納します。

    加速度センサーからのデータキューの先頭を読み出すのは.front()を用います。読みだした後.pop()を用いて先頭キューを開放します。

    加速度センサーから取得されるデータは1Gを1000としたミリGの単位です。レンジを±4Gとしているため、12bitの範囲に入るように2で割って格納します。データ数を節約するため最初の4バイトにX,Y軸とZ軸の上位8bitを格納し、次の1バイトにZ軸の下位4bitの合計5バイトを生成します。

    2回分の送信待ちを行うため送信IDはtxid[]配列に格納します。

    その後、loop() 中 b_transmit が trueになっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。

    送信完了に確認は the_twelite.tx_status.is_complete() で行っています。txid[]は送信時に戻り値として戻されたID値です。

    役割

    例

    親機

    アクトを動作させる。

    子機

    +

    <PAL_MOT>
    #include <TWELITE>
    #include <NWK_SIMPLE>
    #include <PAL_>
    void setup() {
    	/*** SETUP section */
    	// board
    	auto&& brd = the_twelite.board.use<PAL_MOT>();
    	brd.set_led(LED_TIMER::BLINK, 100);
    
    	// the twelite main class
    	the_twelite
    		<< TWENET::appid(APP_ID)     
    		<< TWENET::channel(CHANNEL);
    
    	// Register Network
    	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
    	nwk	<< NWK_SIMPLE::logical_id(0xFE); 
    	
    	/*** BEGIN section */
    	the_twelite.begin(); // start twelite!
    	brd.sns_MC3630.begin(SnsMC3630::Settings(
    		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));
    
    	/*** INIT message */
    	Serial << "--- PAL_MOT(Cont):" << FOURCHARS 
    				 << " ---" << mwx::crlf;
    }
    
    auto&& brd = the_twelite.board.use<PAL_MOT>();
    
    u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
    if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
    	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
    	brd.sns_MC3630.begin(SnsMC3630::Settings(
    		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));
    void begin() {
    	sleepNow(); // the first time is just sleeping.
    }
    void sleepNow() {
    	pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);
    	the_twelite.sleep(60000, false);
    }
    void wakeup() {
    	Serial << "--- PAL_MOT(Cont):" << FOURCHARS
    	       << " wake up ---" << mwx::crlf;
    
    	b_transmit = false;
    	txid[0] = 0xFFFF;
    	txid[1] = 0xFFFF;
    }
    void loop() {
    	auto&& brd = the_twelite.board.use<PAL_MOT>();
    
    	if (!b_transmit) {
    		if (!brd.sns_MC3630.available()) {
    			Serial << "..sensor is not available." 
    					<< mwx::crlf << mwx::flush;
    			sleepNow();
    		}
    
    		// send a packet
    		Serial << "..finish sensor capture." << mwx::crlf
    			<< "  seq=" << int(brd.sns_MC3630.get_que().back().t) 
    			<< "/ct=" << int(brd.sns_MC3630.get_que().size());
    
    		// calc average in the queue.
    		{
    			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;
    		}
    
    		for (int ip = 0; ip < 2; ip++) {
    			if(auto&& pkt = 
    				the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
    				
    				// set tx packet behavior
    				pkt << tx_addr(0x00)
    					<< tx_retry(0x1)
    					<< tx_packet_delay(0, 0, 2);
    			
    				// prepare packet (first)
    				uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
    									? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
    				uint16_t seq = brd.sns_MC3630.get_que().front().t;
    			
    				pack_bytes(pkt.get_payload()
    					, make_pair(FOURCHARS, 4)
    					, seq 
    					, siz
    				);
    
    				// store sensor data (36bits into 5byts, alas 4bits are not used...)
    				for (int i = 0; i < siz; i++) {
    					auto&& v = brd.sns_MC3630.get_que().front();
    					uint32_t v1;
    
    					v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
    						| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
    						| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
    					uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
    					pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
    					brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
    				}
    
    				// perform transmit
    				MWX_APIRET ret = pkt.transmit();
    
    				if (ret) {
    					Serial << "..txreq(" << int(ret.get_value()) << ')';
    					txid[ip] = ret.get_value() & 0xFF;
    				} else {
    					sleepNow();
    				}
    			}
    		}
    
    		// finished tx request
    		b_transmit = true;
    	} else {
    		if(		the_twelite.tx_status.is_complete(txid[0])
    			 && the_twelite.tx_status.is_complete(txid[1]) ) {
    
    			sleepNow();
    		}
    	}
    }
    
    	if (!b_transmit) {
    if (!brd.sns_MC3630.available()) {
    	Serial << "..sensor is not available." 
    			<< mwx::crlf << mwx::flush;
    	sleepNow();
    }
    Serial << "..finish sensor capture." << mwx::crlf
    	<< "  seq=" << int(brd.sns_MC3630.get_que().front().t) 
    	<< "/ct=" << int(brd.sns_MC3630.get_que().size());
    
    // calc average in the queue.
    {
    	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;
    }
    		for (int ip = 0; ip < 2; ip++) {
    // prepare packet (first)
    uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
    ? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
    uint16_t seq = brd.sns_MC3630.get_que().front().t;
    
    pack_bytes(pkt.get_payload()
    	, make_pair(FOURCHARS, 4)
    	, seq 
    	, siz
    );
    for (int i = 0; i < siz; i++) {
    	auto&& v = brd.sns_MC3630.get_que().front();
    	uint32_t v1;
    
    	v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
    		| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
    		| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
    	uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
    	pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
    	brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
    }
    MWX_APIRET ret = pkt.transmit();
    
    if (ret) {
    	Serial << "..txreq(" << int(ret.get_value()) << ')';
    	txid[ip] = ret.get_value() & 0xFF;
    } else {
    	sleepNow();
    }
    } else {
    	if(		the_twelite.tx_status.is_complete(txid[0])
    		 && the_twelite.tx_status.is_complete(txid[1]) ) {
    
    		sleepNow();
    	}
    }
    MONOSTICK BLUEまたはREDarrow-up-right
    Parent_MONOSTICK
    BLUE PAL または RED PALarrow-up-right
    動作センサーパル MOTION SENSE PALarrow-up-right