全てのページ
GitBook提供
1 / 17

サンプルアクト

Sample Acts

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

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

サンプルアクトの紹介

以下は上から順に見ていただく

act0..4は、無線機能などを使わないごくシンプルな例です。Actの基本構造が理解できます。

Scratch は、UARTから1バイトコマンドを受けて、送信などを行うシンプルなコードです。

Slp_Wk_and_Txは、ステートマシンを用い、スリープを用いた間欠動作で、スリープ復帰→無線送信→スリープを繰り返します。

Parent_MONOSTICKは、専ら受信のみを行い、シリアルポートへ受信結果を出力します。このサンプルの無線パケットで、親機向け(0x00)や子機ブロードキャスト(0xFE)とアドレス設定しているものは受信できます。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。

PingPongは、一方から他方にパケットを送信し、受信した他方がパケットを送り返すサンプルです。送信と受信についての基本的な手続きが含まれます。

BRD_APPTWELITEはディジタル入力、アナログ入力、ディジタル出力、アナログ出力を用いた双方向通信を行っています。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。

PAL_AMB, PAL_MOT-single, PAL_MAGは、各種PAL基板用のサンプルです(TWELITE CUEでもサンプルの定義を変更することでPAL_MOT,PAL_MAGを実行できます)。PAL基板上のセンサー値を取得し、送信し、スリープします。

応用編のアクト

  • PAL_AMB_usenap は、数十msかかるセンサーの動作時間にTWELITEマイコンを短くスリープさせ、より省電力を目指すサンプルです。

  • PAL_AMB_behavior は、ビヘイビアを用いた例です。PAL_AMBでは温湿度センサーはライブラリ内部のコードが呼ばれますが、このサンプルでは温湿度センサーのアクセスのための独自の手続きも含まれます。

  • PAL_MOT_fifo は、加速度センサーのFIFOおよびFIFOの割り込みを用いて、サンプルを中断することなく、連続的に取得し無線送信するためのサンプルです。

  • PulseCounter は、パルスカウンター機能を用い、スリープ中も含め入力ポートで検出したパルス数を計数し、これを無線送信します。

  • WirelessUARTは、UART入力をserparserを用いアスキー形式を解釈し、これを送信します。

  • Setting は、インタラクティブモード<STG_STD>のより高度なカスタマイズを行います。

単体機能を紹介したAct

Unitから始まる名前のActは機能やAPIの紹介を目的としています。

最新版の入手

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

https://github.com/monowireless/Act_samples

共通の記述

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

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

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

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

  • チャネル 13

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

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

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

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

act0 .. 4

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

名前

内容

act0

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

act1

Lチカ(LEDの点滅)

act2

タイマーを用いたLチカ

act3

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

act4

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

Scratch

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

このアクトには以下が含まれます。

  • 無線パケットの送信 ('t' キー)

  • スリープ ('s'キー)

  • シリアルポートからの入力 - Serial

  • ディジタル(ボタン)入力 - Buttons

setup()

void setup() {
	/*** SETUP section */
	tx_busy = false;

	// 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>();
	nwk	<< NWK_SIMPLE::logical_id(0xFE); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.

	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- Scratch act ---" << mwx::crlf;
}

the_tweliteを設定してアプリケーションID APP_ID, 無線チャネルCHANNEL、受信有を設定します。

またnwkを生成し、子機アドレス0xFEを指定しています。このアドレスは子機でアドレスを指定していない名無しの子機という意味です。

設定できるアドレスは0x00: 親機,0x01~0xEF: 子機, 0xFE:子機アドレス未指定の範囲です。

送信先として指定するアドレスは0x00は親機宛、0x01~0xEFは指定の親機アドレス、0xFEは任意の子機アドレス、0xFFは親機を含む任意のアドレスです。

またButtonsオブジェクトを初期化します。連続参照によるチャタリング抑制アルゴリズムです。10msごとに5回連続同じ値になれば対象のポート(PIN_BTNのみ)のHIまたはLOWを確定します。pack_bits(N1, N2, ..)は1UL<<N1 | 1UL << N2 | ...を行いビットマップを生成します。

the_twelite.begin(); // start twelite!

the_tweliteを開始するための手続きです。act0..4では出てきませんでしたがthe_tweliteの設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。

begin()

void begin() {
	Serial << "..begin (run once at boot)" << mwx::crlf;
}

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

loop()

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

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;
}

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

シリアルからの入力

while(Serial.available()) {
  int c = Serial.read();

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

  switch(c) {
  case 'p': ... // millis() を表示
  case 't': ... // 無線パケットを送信 (vTransmit)
        if (!tx_busy) {
					tx_busy = Transmit();
					if (tx_busy) {
						Serial  << int(millis()) << ":tx request success! (" 
										<< int(tx_busy.get_value()) << ')' << mwx::crlf;
 					} else {
						Serial << int(millis()) << ":tx request failed" << mwx::crlf;;
					}
				}
  case 's': ... // スリープする
				Serial << int(millis()) << ":sleeping for " << 5000 << "ms" << mwx::crlf << mwx::flush;
				the_twelite.sleep(5000);
				break;
  }
}

Serial.available()がtrueの場合は、シリアルポートからの入力が保存されています。シリアルから1文字読み込んで、入力文字に応じた処理をします。

tを入力して無線送信

't'を入力したときは送信を行います。このサンプルではtx_busyフラグを用い連続的に入力は行わないようにしています。

送信要求は一定数までキューに保存されるため、キューの範囲(3パケット)で要求を積むことは可能です。

以下はif(!tx_busy)の判定をしないようにして 'tttt'と連続的に入力した場合の処理例です。4つ目の要求でキューが一杯になって要求は失敗しています。Transmit()の.prepare_tx_packet()で得られたpktオブジェクトがfalseになります。

送信タイミングはランダム化されるため、送信完了は送信要求順にはなりません。

--- Scratch act ---
..begin (run once at boot)
[t]11591:Transmit()
11592:tx request success! (1)
[t]11593:Transmit()
11593:tx request success! (2)
[t]11594:Transmit()
11595:tx request success! (3)
[t]11595:Transmit()
TX QUEUE is FULL
11596:tx request failed
11654:tx completed!(id=2, stat=1)
11719:tx completed!(id=3, stat=1)
11745:tx completed!(id=1, stat=1)

sを入力してスリープ

the_twelite.sleep(5000);

5000ms=5秒のスリープを実施します。復帰後はwakeup()が実行されます。

wakeup()

void wakeup() {
	Serial << int(millis()) << ":wake up!" << mwx::crlf;
}

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

Transmit()

MWX_APIRET Transmit() {
	Serial << int(millis()) << ":Transmit()" << 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(); 
	} else {
		// .prepare_tx_packet() 時点で失敗している(送信キューが一杯)
		Serial << "TX QUEUE is FULL" << mwx::crlf;
	  return MWX_APIRET(false, 0);
	}
}

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

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

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	Serial 	<< int(millis()) << ":tx completed!"
			<< format("(id=%d, stat=%d)", ev.u8CbId, ev.bStatus) << mwx::crlf;
	tx_busy = false; // clear tx busy flag.
}

送信完了時に呼び出されます。evには送信IDと完了ステータスが含まれます。

on_rx_packet()

void on_rx_packet(packet_rx& rx, bool_t &handled) {
	Serial << format("rx from %08x/%d", 
					rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;
}

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

Slp_Wk_and_Tx

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

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

このアクトには以下が含まれます。

  • 代表的な間欠動作(スリープ→起床→計測→無線送信→スリープ)の制御構造について

  • 送信パケットの生成と送信手続き、完了待ちについて

アクトの機能

  • 起動後、初期化処理を経て、一旦スリープする

    1. setup()初期化する

    2. begin() スリープ実行する

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

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

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

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

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

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

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

アクトの解説

宣言部

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include <SM_SIMPLE>

#include "Common.h"

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

状態定義

loop()内の順次処理を記述うするため、このサンプルではステートマシン(状態遷移)の考え方を用います。ごく単純な状態遷移の処理をまとめた<SM_SIMPLE>を用います。

Common.h に以下の状態に対応する列挙体 STATE が定義されています。

enum class 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)
};

状態を示す列挙体STATEを用いてSM_SIMPLEステートマシン(状態遷移)を宣言します。

SM_SIMPLE<STATE> step;

ここで宣言されたstepは、状態の管理、タイムアウト、処理待ちを行うための機能が含まれています。

センサーデータ

このサンプルではセンサーデーターの処理は行いませんが、ダミーデータを用意しておきます。

struct {
	uint16_t dummy_work_ct_now;
	uint16_t dummy_work_ct_max;  // counter for dummy work job. 
} sensor;

setup()

void setup() {
	/*** SETUP section */
	step.setup(); // init state machine
	
	// 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;
}

変数やクラスオブジェクトの初期化を行います。

  • stepステートマシンの初期化

  • the_tweliteクラスオブジェクトの初期化

  • ネットワーク <NWK_SIMPLE> の登録と初期化(DEVICE_IDの登録)を行います。

つづいてクラスオブジェクトやハードウェアなどの開始処理を行います。

the_twelite.begin(); // start twelite!

the_tweliteを開始するための手続きです。act0..4では出てきませんでしたがthe_tweliteの設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。

begin()

void begin() {
	Serial << "..begin (run once at boot)" << crlf;
	SleepNow();
}

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

wakeup()

void wakeup() {
 memset(&sensor, 0, sizeof(sensor));
	Serial << crlf << int(millis()) << ":wake up!" << crlf;
}

起床直後に呼び出されます。ここではセンサーデータ領域の初期化と、起床時のメッセージを出力しています。

loop()

void loop() {
	do {
		switch(step.state()) {
		case STATE::INIT:
			sensor.dummy_work_ct_now = 0;
			sensor.dummy_work_ct_max = random(10,1000);
			step.next(STATE::WORK_JOB);
		break;

		...
		}
	} while (step.b_more_loop());
}

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

この制御構造はSM_SIMPLEステートマシンを利用しています。do..while() 構文のループになっています。ループの中はswitch case節となっていて、.state()で得られた状態により処理を分岐しています。状態の遷移は.next()を呼び出しステートマシン内の内部変数を新しい状態値に書き換えます。

step.b_more_loop()は、.next()により状態遷移があった場合trueに設定されます。これは状態遷移が発生したときloop()を脱出せずに次の状態のコード(case節)を実行する目的です。

以下に各状態の解説を行います。

STATE::INIT

sensor.dummy_work_ct_now = 0;
sensor.dummy_work_ct_max = random(10,1000);

step.next(STATE::WORK_JOB);

ダミーーのセンサー値を初期化します。一つは加算カウンタ、一つはカウンター停止値でランダムに決定しています。

STATE::WORK_JOB

if (TickTimer.available()) {
	Serial << '.';
	sensor.dummy_work_ct_now++;
	if (sensor.dummy_work_ct_now >= sensor.dummy_work_ct_max) {
		Serial << crlf;
		step.next(STATE::TX);
	}
}

WORK_JOB状態では1msごとのタイマー単位で処理します。TickタイマーごとにTickTimer.available()になります。Tickタイマーごとにカウンタを加算しdummy_work_ct_maxになったら、次の状態STATE::TXに遷移します。

STATE::TX

if (Transmit()) {
	Serial << int(millis()) << ":tx request success!" << crlf;
	step.set_timeout(100);
	step.clear_flag();
	step.next(STATE::WAIT_TX);
} else {
	// normall it should not be here.
	Serial << int(millis()) << "!FATAL: tx request failed." << crlf;
	step.next(STATE::EXIT_FATAL);
}

Transmit()関数を呼び出しパケット送信要求を行います。送信要求が成功した場合はSTATE::WAIT_TXEVENTに遷移して送信完了を待つことになります。ここでは完了待ちとしてSM_SIMPLEステートマシンのタイムアウトとフラッグ機能を用います(待ちループ中での変数値の変化により判定する単純なものです)。

単一の送信要求が失敗することは通常想定しませんが、失敗時はSTATE::EXIT_FATALとして例外処理する状態に遷移します。

この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。多くの場合、送信完了を待ってから、続く処理を行います。

Transmit()関数はMWX_APIRETオブジェクトを返しますが、このオブジェクトはbool型の成功の可否と、最大31ビットの値を保持しています。bool型として評価できますから、if文の判定は送信要求が成功したらtrue、失敗したらfalseを返します。

STATE::WAIT_TX

if (step.is_flag_ready()) {
	Serial << int(millis()) << ":tx completed!" << crlf;
	step.next(STATE::EXIT_NORMAL);
} else if (step.is_timeout()) {
	Serial << int(millis()) << "!FATAL: tx timeout." << crlf;
	step.next(STATE::EXIT_FATAL);
}

送信完了待ちは後述のon_tx_comp()によりステートマシン機能のフラッグをセットすることで判定しています。タイムアウトは.is_timeout()を呼び出すことで.set_timeout()を行ったときからの経過時間により判定します。

送信が成功しても失敗しても通常は完了通知がありますが、タイムアウトを設け例外処理のための状態STATE::EXIT_FATALに遷移します。

STATE::EXIT_NORMAL

SleepNow();

SleepNow()を呼び出して、スリープ処理に入ります。

STATE::EXIT_FATAL

Serial << crlf << "!FATAL: RESET THE SYSTEM.";
delay(1000); // wait a while.
the_twelite.reset_system();

重大なエラーとして、システムリセットを行います。

SleepNow()

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;
	Serial.flush();

	step.on_sleep(); // reset status of statemachine to INIT state.

	the_twelite.sleep(u16dur, false);
}

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

スリープ前にはSM_SIMPLEステートマシンの状態を.on_sleep()を呼び出してセットしておきます。

Transmit()

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.
			, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
		);
		
		// do transmit 
		//return nwksmpl.transmit(pkt);
		return pkt.transmit(); 
	}

	return MWX_APIRET(false, 0);
}

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

まず最初に送信パケットを格納するオブジェクトを取得します。このオブジェクトを操作し、送信データや条件を設定します。

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

mwx ライブラリでは、if文中でオブジェクトを取得し、そのオブジェクトのbool判定でtrueの場合に処理を行う記述を採用しています。ここではthe_twelite.network.use<NWK_SIMPLE>()によりボードオブジェクトを取得し、ボードオブジェクトの.prepare_tx_packet()によりパケットオブジェクトを取得しています。パケットオブジェクトの取得失敗は通常想定しませんが、失敗時は送信キューが一杯で送信要求が受け付けられない場合です。このサンプルは単一の送信のみですから、エラーは想定外の重大な問題に限られます。

pkt << tx_addr(0x00) // 宛先
		<< tx_retry(0x1) // 再送回数
		<< tx_packet_delay(0,0,2); // 送信遅延

得られたpktオブジェクトに対して、送信条件(宛先や再送など)を<<演算子を用いて設定します。tx_addrはパケットの宛先を指定します。tx_retryは再送回数、tx_packet_delayは送信遅延の指定です。

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.
	, uint16_t(sensor.dummy_work_ct_now) // put dummy sensor information.
);	

パケットのペイロード(データ部分)はpkt.get_payload()により得られるsmblbuf<uint8_t>派生の配列です。この配列に対して直接値を設定しても構いませんが、ここではpack_bytes()を用いた値の設定を行います。

ペイロードの最大長は上記の例では91バイトですが、詳しくはNWK_SIMPLEパケット構造と最大長を参照ください。

この関数は可変数引数により指定できます。一番最初のパラメータは.get_payload()より得られた配列オブジェクトです。

  • make_pair(FOURCC,4) : make_pairはC++標準ライブラリのもので、std::pairオブジェクトを生成します。文字列型に対して先頭から4バイト分を書き出すという意味になります。 (文字列型の配列は終端を含める、含めないといった話題が混乱を生むため、明示的に書き出すバイト数を指定するために、このような指定をします)

  • uint32_t型のデータを指定するとビッグエンディアン並びで4バイト分のデータを書き込みます。

  • uint16_t型のデータについても同様です。

uint8_t 型のポインタを用いてデータの書き込みを行うことも出来ます。

auto&& pay = pkt.get_payload(); // get buffer object.

// the following code will write data directly to internal buffer of `pay' object.
uint8_t *p = pay.begin(); // get the pointer of buffer head.

S_OCTET(p, FOURCC[0]); // store byte at pointer `p' and increment the pointer.
S_OCTET(p, FOURCC[1]);
S_OCTET(p, FOURCC[2]);
S_OCTET(p, FOURCC[3]);

S_DWORD(p, millis()); // store uint32_t data.
S_WORD(p, sensor.dummy_work_ct_now); // store uint16_t data.

pay.redim(p - pay.begin());

.get_payload()から得られた配列オブジェクトは、何も格納されていないサイズ0の配列ですが、この配列にデータを書き込むことでサイズが拡張され(実際は内部の固定長のバッファに対してデータを書き込み、内部管理のデータサイズを更新します)、最終的なサイズがペイロードのデータサイズです。

ここでは.begin()を用いてuint8_t*のポインタを得て、このポインタを用いてデータを書き込み、最後に書き込んだサイズを.redim()で設定します。

S_OCTET(), S_WORD(), S_DWORD()といった関数を書き込みに用いていますが、例えばS_OCTET(p, 'H')は *p = 'H'; p++;と同じ処理を行うポインタを用いたデータ書き込みです。

最後の.redim()は配列のサイズをバッファの初期化をせずに変更する手続きです。.resize()を呼び出すとすべて0クリアされます。

最後に.transmit()を呼び出して、送信要求を行います。戻り値はMWX_APIRET型です。要求後、実際の送信が行われますが、送信パラメータや送信サイズにもよりますが、完了まで数ms~数十ms程度はかかります。完了時にはon_tx_comp()が呼び出されます。

return pkt.transmit(); 

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

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()により完了としています。

Parent_MONOSTICK

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

MONOSTICKを親機として使用するアクトです。子機からのパケットのデータペイロードをシリアルポートに出力します。サンプルアクトの多くのサンプルでのパケットを表示することが出来ます。

このアクトには以下が含まれます。

  • 無線パケットの受信

  • 受信したパケットのデータ解釈

  • インタラクティブモードの設定 - <STG_STD>

  • バイト列のアスキー形式への変換 - serparser

アクトの機能

  • サンプルアクトの子機からのパケットを受信して、シリアルポートへ出力する。

アクトの使い方

必要なTWELITEと配線

役割

例

親機

MONOSTICK BLUEまたはRED

子機

サンプルアクトの子機設定 (例: Slp_Wk_and_Tx, PAL_AMB, PAL_MAG, PAL_MOT???など)

最初は以下のデフォルトの設定にて確認してください。

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

  • チャネル: 13

アクトの解説

宣言部

インクルード

// use twelite mwx c++ template library
#include <TWELITE>
#include <MONOSTICK>
#include <NWK_SIMPLE>
#include <STG_STD>

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

  • <NWK_SIMPLE> 簡易中継ネットの定義を読み込みます

  • <STG_STD> インタラクティブモードの定義を読み込みます。

その他

// application ID
const uint32_t DEFAULT_APP_ID = 0x1234abcd;
// channel
const uint8_t DEFAULT_CHANNEL = 13;
// option bits
uint32_t OPT_BITS = 0;

/*** function prototype */
bool analyze_payload(packet_rx& rx);

デフォルト値や関数プロトタイプなどの宣言をしています。

setup()

auto&& brd = the_twelite.board.use<MONOSTICK>();
auto&& set = the_twelite.settings.use<STG_STD>();
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

setup()では、まず<MONOSTICK>ボードビヘイビア、<STG_STD>インタラクティブモード ビヘイビア、<NWK_SIMPLE>ビヘイビアをuse<>を用い読み込みます。この手続きは必ずsetup()内で行います。

set << SETTINGS::appname("PARENT"); // 設定画面中のタイトル
set << SETTINGS::appid_default(DEFAULT_APP_ID); // アプリケーションIDデフォルト
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // チャネルデフォルト
set << SETTINGS::lid_default(0x00); // LIDデフォルト
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
set.reload(); // 設定を不揮発性メモリから読み出す
OPT_BITS = set.u32opt1(); // 読み込み例(オプションビット)

続いてインタラクティブモードの設定と設定値の読み出しを行います。<STG_STD>インタラクティブモードでは、標準的な項目が用意されていますが、作成するアクトごとにいくつかのカスタマイズを行えるようになっています。

  • appname→ 設定画面中のタイトル行にでるアクト名称

  • appid_default→ デフォルトのアプリケーションID

  • ch_default→ デフォルトのチャネル

  • lid_default→ デバイスID(LID)のデフォルト値

  • .hide_items()→ 項目の非表示設定

設定値を読み出す前には必ず.reload()を呼び出します。設定値は.u32opt1()のように設定値ごとに読み出し用のメソッドが用意されています。

the_twelite
	<< set                    // インタラクティブモードの設定を反映
	<< TWENET::rx_when_idle() // 受信するように指定
	;

// Register Network
nwk << set;							// インタラクティブモードの設定を反映
nwk << NWK_SIMPLE::logical_id(0x00) // LIDだけは再設定
	;

いくつかの設定値は<STG_STD>オブジェクトを用いて直接反映することが出来ます。また、DIPスイッチの設定などにより特定の値を書き換えたいような場合などは、反映されたあとに別個に値を書き換えることも出来ます。上記の例ではthe_tweliteオブジェクトにアプリケーションID、チャネル、無線出力などを設定し、nwkオブジェクトに対してLIDと再送回数の設定をしてから、再度LIDを0に設定し直しています。

brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)

<MONOSTICK>ボードビヘイビアではLED点灯制御のための手続きを利用できます。

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

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

the_twelite.begin(); // start twelite!

the_tweliteを開始するための手続きです。act0..4では出てきませんでしたがthe_tweliteの設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。

loop()

このサンプルではloop()中の処理はありません。

void loop() {
}

on_rx_packet()

パケットを受信したときに呼び出されるコールバック関数です。この例では受信したパケットデータに対していくつかの出力を行っています。

void on_rx_packet(packet_rx& rx, bool_t &handled) {  
	Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;

  ...
  
	// packet analyze
	analyze_payload(rx);
}

analyze_payload()

関数の末尾で呼び出されるanalyze_payload()は、いくつかのサンプルアクトのパケットを解釈するコードが含まれています。サンプルアクト中のパケット生成部分と対応させてコードを参照してください。

bool b_handled = false;

uint8_t fourchars[4]{};
auto&& np = expand_bytes(
	    rx.get_payload().begin(), rx.get_payload().end()
		, fourchars
    );
    
if (np == nullptr) return;

// display fourchars at first
Serial
	<< fourchars 
	<< format("(ID=%d/LQ=%d)", rx.get_addr_src_lid(), rx.get_lqi())
	<< "-> ";
	

この関数では最初に4文字識別データをfourchars[5]配列に読み込みます。

読み込みはexpand_bytes()関数を用います。この関数の第1・第2パラメータはC++の標準ライブラリの作法に倣い、受信パケットのペイロード部の先頭ポインタ.begin()と末尾ポインタの次.end()を与えます。続くパラメータは可変引数として、読み込むデータ変数を与えます。戻り値はエラー時はnullptr、それ以外は次の解釈ポインタとなります。末尾まで解釈した場合は.end()が戻ります。ここでのパラメータはuint8_t fourchars[4]です。

この記述で対応するのは配列長さNが指定されるuint8_t[N]型のみで、uint8*型、char*型、char[]型などを用いる場合は、make_pair(char*,int)を用いた指定が必要になります。

char fourchars[5]{}; // 終端文字\0も含め5バイト確保する
auto&& np = expand_bytes(
	    rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((char *)fourchars, 4)
	);

つづいて4バイトヘッダに対応した処理を行います。ここではサンプルアクトSlp_Wk_and_Txのパケットを解釈し内容を表示します。

// Slp_Wk_and_Tx
if (!b_handled && !strncmp(fourchars, "TXSP", 4)) {
	b_handled = true;
	uint32_t tick_ms;
	uint16_t u16work_ct;

	np = expand_bytes(np, rx.get_payload().end()
		, tick_ms
		, u16work_ct
	);

	if (np != nullptr) {
		Serial << format("Tick=%d WkCt=%d", tick_ms, u16work_ct);
	} else {
		Serial << ".. error ..";
	}
}

他の解釈部の判定をスキップするようにb_handledをtrueに設定します。

"TXSP"のパケットではuint32_t型のシステムタイマーカウントと、uint16_t型のダミーカウンタの値が格納されています。各々変数を宣言してexpand_bytes()関数を用い読み込みます。上述と違うのは、読み出しの最初のポインタとして第一パラメータがnpとなっている点です。tick_msとu16work_ctをパラメータとして与え、ビッグエンディアン形式のバイト列としてペイロードに格納された値を読み出します。

読み出しに成功すれば内容を出力して終了です。

独自のアスキー書式を定義して出力する

ユーザが定義した並び順でアスキー形式により構成します。

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;

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

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

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

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

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

NWK_SIMPLEのヘッダを含めてダンプ出力する

最初の出力(if(0)により実行されないようになっています)は<NWK_SIMPLE>の制御データを含めたデータをすべて表示します。制御データは11バイトあります。通常は制御情報を直接参照することはありませんが、あくまでも参考です。

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  : 中継回数

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

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

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

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

PingPong

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

このアクトには以下が含まれます。

  • 無線パケットの受信からの速やかな応答送信

  • 相手のアドレスを直接指定した送信

  • シリアルポートからの入力 - Serial

  • ディジタル(ボタン)入力 - Buttons

  • アナログ入力 - Analogue

アクトの使い方

必要なTWELITE

いずれかを2台。

  • MONOSTICK BLUE または RED

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

アクトの解説

インクルード

// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>

全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク <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";
  • サンプルアクト共通宣言

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

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

セットアップ setup()

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

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

	// 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)

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

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

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

  • TWENET::rx_when_idle() 受信回路をオープンにする指定

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

// 以下の記述は 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);

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

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

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

the_twelite.begin(); // start twelite!

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

Analogue

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

Analogue.setup(true);

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

Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC), 50); 

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で開始されていて、

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

Buttons

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

Buttons.setup(5);

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

Buttons.begin(pack_bits(PIN_BTN),
					5, 		// history count
					10);  	// tick delta

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

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

Serial

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

Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;

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

ループ loop()

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

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

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

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);
		}
	}
}

Serial

		while(Serial.available())  {
				int c = Serial.read();
				Serial << mwx::crlf << char(c) << ':';
				switch(c) {
				    case 't':
				    	  vTransmit(MSG_PING, 0xFF);
				        break;
				    default:
							  break;
				}
		}

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

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

Buttons

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

	if (Buttons.available()) {
		uint32_t btn_state, change_mask;
		Buttons.read(btn_state, change_mask);

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

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

// 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);

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

transmit()

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

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()) {

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

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

パケットの送信設定

		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)

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

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

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

  • tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ100msから200msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから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()システム時間

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

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

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

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

// 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.
);

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.transmit();

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

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

on_rx_packet()

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

void on_rx_packet(packet_rx& rx, bool_t &handled) {
		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;
	}

まず受信パケットのデータはパラメータrxとして渡されます。rxから無線パケットのアドレス情報やデータペイロードにアクセスします。

while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();

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

Serial << format("..receive(%08x/%d) : ",
   rx.get_addr_src_long(), rx.get_addr_src_lid());

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

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

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
    );

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

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

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

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

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

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

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;

数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数は最大8つまで(32bitパラメータの場合)に制限されています。(制限を超えるとコンパイルエラーが出ます。なおSerial.printfmt()には引数の数の制限がありません。)

mwx::crlfは改行文字(CR LF)を、mwx::flushは出力完了待ちを指定します。(mxw::flushはSerial.flush()と記述しても構いません)

BRD_APPTWELITE

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

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

このアクトには以下が含まれます。

  • 無線パケットの送受信

  • インタラクティブモードによる設定 - <STG_STD>

  • ディジタル(ボタン)入力 - Buttons

  • アナログ入力 - Analogue

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

アクトの機能

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

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

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

  • DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。

  • 受信したパケットの値に応じで DO1-4, PWM1-4 に設定する。

アクトの使い方

必要なTWELITEと配線例

役割

例

親機

TWELITE DIP

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

子機

TWELITE DIP

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

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

アクトの解説

インクルード

// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <BRD_APPTWELITE>
#include <STG_STD>

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

またインタラクティブモードを追加するために <STG_STD> をインクルードしています。

宣言部

/*** Config part */
// application ID
const uint32_t DEFAULT_APP_ID = 0x1234abcd;
// channel
const uint8_t DEFAULT_CHANNEL = 13;
// option bits
uint32_t OPT_BITS = 0;
// logical id
uint8_t LID = 0;

/*** function prototype */
MWX_APIRET transmit();
void receive();

/*** application defs */
const char APP_FOURCHAR[] = "BAT1";

// sensor values
uint16_t au16AI[5];
uint8_t u8DI_BM;
  • サンプルアクト共通宣言

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

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

セットアップ setup()

void setup() {
	/*** SETUP section */	
	// init vars
	for(auto&& x : au16AI) x = 0xFFFF;
	u8DI_BM = 0xFF;

	// load board and settings
	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

	// settings: configure items
	set << SETTINGS::appname("BRD_APPTWELITE");
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
	set.reload(); // load from EEPROM.
	OPT_BITS = set.u32opt1(); // this value is not used in this example.
	LID = set.u8devid(); // logical ID

	// the twelite main class
	the_twelite
		<< set                      // apply settings (appid, ch, power)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	if (brd.get_M1()) { LID = 0; }

	// Register Network
	nwk << set // apply settings (LID and retry)
			;

	// if M1 pin is set, force parent device (LID=0)
	nwk << NWK_SIMPLE::logical_id(LID); // write logical id again.
	
	/*** BEGIN section */
	// start ADC capture
	Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)
	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.setup(5); // init button manager with 5 history table.
	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 ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;
}

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

各種ビヘイビアオブジェクトの登録

	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

システムの振る舞いを決めるためのビヘイビアオブジェクトを登録します。インタラクティブモードの設定管理で合ったり、ボードサポート、また無線パケットのネットワーク記述です。

setup()内で登録しないと動作しません。

インタラクティブモードの設定

// インタラクティブモードの初期化
auto&& set = the_twelite.settings.use<STG_STD>();

set << SETTINGS::appname("BRD_APPTWELITE");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.
LID = set.u8devid(); // logical ID;

インタラクティブモードの初期化を行います。まずsetオブジェクトを取得しています。続いて以下の処理を行っています。

  • アプリケーション名を"BRD_APPTWELITE"に設定(メニューで利用される)

  • デフォルトのアプリケーションIDとチャネル値を書き換える

  • 不要な項目を削除する

  • set.reload()により保存された設定値を読み出す

  • OPT_BITSとLIDの値を変数にコピーする

読み出したインタラクティブモードの設定の反映については後述します。

以下は画面例です。+ + + と + を三回 0.2秒から 1 秒の間をあけて入力するとインタラクティブモード画面を出すことが出来ます。

[CONFIG/BRD_APPTWELITE:0/SID=8XXYYYYY]
a: (0x1234ABCD) Application ID [HEX:32bit]
i: (        13) Device ID [1-100,etc]
c: (        13) Channel [11-26]
x: (      0x03) RF Power/Retry [HEX:8bit]
o: (0x00000000) Option Bits [HEX:32bit]

 [ESC]:Back [!]:Reset System [M]:Extr Menu    

※ Option Bits は、メニューに表示はしていますが、このサンプリでは使用していません。

the_twelite

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

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

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

ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。以下ではボードオブジェクトを用い、M1ピンの状態を確認しています。M1ピンがLOであれば、LID=0、つまり親機アドレスと設定します。

	if (brd.get_M1()) { LID = 0; }

the_twelite を動作させるには初期設定が必要です。アプリケーションIDや無線チャネルの設定は必須といえます。

	// the twelite main class
	the_twelite
		<< set
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

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

  • setはインタラクティブモードから読み出した設定の一部(アプリケーションIDや無線チャネルなど)反映させます。反映される項目は<STG_STD>の解説を参照してください。

  • TWENET::rx_when_idle() 受信回路をオープンにする指定です。

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

// 以下の記述は MWX ライブラリでは利用できません。
#include <iostream>
std::cout << "hello world" << std::endl;

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

auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << set;
nwk << NWK_SIMPLE::logical_id(LID);

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

2,3行目は、<NWK_SIMPLE>の設定です。先にインタラクティブモードの設定値を反映させます。反映される項目はLIDと再送回数です。このアプリケーションではM1ピンの状態によってLID=0にする場合があるため、3行目で再度LIDを設定しています。

Analogue

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

Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0);

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

	Analogue.begin(pack_bits(
						BRD_APPTWELITE::PIN_AI1,
						BRD_APPTWELITE::PIN_AI2,
						BRD_APPTWELITE::PIN_AI3,
						BRD_APPTWELITE::PIN_AI4,
				   	PIN_ANALOGUE::VCC));

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で利用できるピンの一覧が定義されています。

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

Buttons

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

Buttons.setup(5);

初期化は 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

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

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

Timer0

Timer0.begin(32, true); // 32hz timer

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

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

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

the_tweliteの動作開始

the_twelite.begin(); // start twelite!

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

Serial

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

	Serial 	<< "--- BRD_APPTWELITE ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;

このサンプルでは始動時のメッセージとしていくつかのシステム設定値を表示しています。Serialオブジェクトには const char* 型の文字列や、int型(他の整数型はNG)、printfとほぼ同じ振る舞いをするformat()、改行文字を出力するcrlfなどを<<演算子に与えます。

サンプル中では名前空間mwx::を省略している場合もあります。上記ではmwx::crlfと記載していますがcrlfと記載しても構いません。mwx::名前空間は、一部を省略可能とするように設計しています。

ループ loop()

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

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

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

/*** 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();
			}
		}
	}
}

Buttons

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

	if (Buttons.available()) {
		uint32_t bp, bc;
		Buttons.read(bp, bc);

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

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

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

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;
*/

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

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

transmit();

Analogue

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

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);
}

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

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

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

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

Timer0

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

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();
		}
	}
}

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

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

transmit()

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

MWX_APIRET transmit() {
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	  auto&& set = the_twelite.settings.use<STG_STD>();
		if (!set.is_screen_opened()) {
			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()

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

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

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

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

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

インタラクティブモード画面表示中の表示抑制

auto&& set = the_twelite.settings.use<STG_STD>();
if (!set.is_screen_opened()) {
    //インタラクティブモード画面中ではない!
}

インタラクティブモードの画面が表示されているときは、画面出力を抑制します。

パケットの送信設定

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)

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

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

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

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

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

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

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

# 先頭バイトのインデックス: データ型 : バイト数 : 内容

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)

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

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

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

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

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);

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

// 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
}

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バイトですが、ビッグエンディアンの並びで書き込みます。

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

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

for(int i = 0; i < sizeof(au16AI)/sizeof(uint16_t)); i++) {
  pack_bytes(pkt.get_payload(), au16AI[i]);
}

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

return pkt.transmit();

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

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

on_rx_packet()

無線パケットが受信できたときは、受信イベントとしてon_rx_packet()が呼び出されます。

the_twelite.receiverによる手続きでは一旦受信パケットを内部キュー(2パケットまで格納)に格納してからの処理でしたが、on_rx_packet()ではTWENETライブラリからのコールバックから直接呼び出され、より取りこぼし等が発生しにくい手続きです。ただしloop()文中で長時間処理を止めてしまうような記述を行うと、同じように取りこぼしの原因となります。

ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。

void on_rx_packet(packet_rx& rx, bool_t &handled) {	
	auto&& set = the_twelite.settings.use<STG_STD>();

	Serial << format("..receive(%08x/%d) : ", rx.get_addr_src_long(), rx.get_addr_src_lid());

  // 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]);
}

まず受信パケットのデータrxをはパラメータとして渡されます。rxから無線パケットのアドレス情報やデータペイロードにアクセスします。パラメータhandledは通常利用しません。

void on_rx_packet(packet_rx& rx, bool_t &handled)

受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。インタラクティブモード画面が表示されているときは出力を抑制します。

if (!set.is_screen_opened()) {
   Serial << format("..receive(%08x/%d) : ",
      rx.get_addr_src_long(), rx.get_addr_src_lid());
}

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

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

char fourchars[5]{};
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
	, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
  );

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

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

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

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

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

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

if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }

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

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

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

	// 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]
	);

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

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

確認のためシリアルポートへ出力します。インタラクティブモード画面が表示されているときは出力は抑制します。

auto&& set = the_twelite.settings.use<STG_STD>();
...
Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
for (auto&& x : au16AI_remote) {
	Serial << format("/%04d", x);
}
Serial << mwx::crlf;

数値のフォーマット出力が必要になるので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の値を変更します。

// 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]);

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

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

BRD_ARIA

TWELITE ARIA - トワイライトアリア を用い、センサー値の取得を行います。

このアクトには以下が含まれます。

  • 無線パケットの送信

  • インタラクティブモードによる設定 - <STG_STD>

  • ステートマシンによる状態遷移制御 - <SM_SIMPLE>

  • <ARIA>ボードビヘイビアによるボード操作

アクトの機能

  • TWELITE ARIA - トワイライトアリア を用い、センサー値の取得を行います。

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

アクトの使い方

必要なTWELITE

役割

例

親機

MONOSTICK BLUEまたはRED

アクトParent_MONOSTICKを動作させる。

子機

TWELITE ARIA BLUE または RED

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>// ネットワークサポート
#include <ARIA>   // TWELITE ARIA
#include <STG_STD>   // インタラクティブモード
#include <SM_SIMPLE> // 簡易ステートマシン

TWELITE ARIA<ARIA>のボードビヘイビアをインクルードします。

setup()

void setup(){
	/*** SETUP section */
	/// init vars or objects
	step.setup(); // initialize state machine
	
	/// load board and settings objects
	auto&& brd = the_twelite.board.use<ARIA>(); // load board support
	auto&& set = the_twelite.settings.use<STG_STD>(); // load save/load settings(interactive mode) support
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>(); // load network support

	/// configure settings
	// configure settings
	set << SETTINGS::appname("ARIA");
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);

	// if SET=LOW is detected, start with intaractive mode.
	if (digitalRead(brd.PIN_SET) == PIN_STATE::LOW) {
		set << SETTINGS::open_at_start();
		step.next(STATE::INTERACTIVE);
		return;
	}

	// load values
	set.reload(); // load from EEPROM.
	OPT_BITS = set.u32opt1(); // this value is not used in this example.
	
	LID = set.u8devid(); // set logical ID

	/// configure system basics
	the_twelite << set; // apply settings (from interactive mode)

	/// configure network
	nwk << set; // apply settings (from interactive mode)
	nwk << NWK_SIMPLE::logical_id(LID); // set LID again (LID can also be configured by DIP-SW.)	

	/// configure hardware
	// 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)
	
	// let the TWELITE begin!
	the_twelite.begin();

	/*** INIT message */
	Serial << "--- ARIA:" << FOURCHARS << " ---" << mwx::crlf;
	Serial	<< format("-- app:x%08x/ch:%d/lid:%d"
					, the_twelite.get_appid()
					, the_twelite.get_channel()
					, nwk.get_config().u8Lid
				)
			<< mwx::crlf;
	Serial 	<< format("-- pw:%d/retry:%d/opt:x%08x"
					, the_twelite.get_tx_power()
					, nwk.get_config().u8RetryDefault
					, OPT_BITS
			)
			<< mwx::crlf;
}

最初に変数などの初期化を行います。ここではステートマシンstepの初期化を行っています。

最初にボードサポート <ARIA> を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。

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

つづいて、インタラクティブモード関連の初期化と読出しを行います。

// configure settings
set << SETTINGS::appname("ARIA");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);

// if SET=LOW is detected, start with intaractive mode.
if (digitalRead(brd.PIN_SET) == PIN_STATE::LOW) {
	set << SETTINGS::open_at_start();
	step.next(STATE::INTERACTIVE);
	return;
}

// load values
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.

LID = set.u8devid(); // set logical ID

ここではsetオブジェクトの取得、アプリ名の反映、デフォルトのアプリケーションIDと通信チャネルの反映、設定メニューで不要項目の削除を行います。

次にSETピンの状態を読み出します。このサンプルはスリープによる間欠動作を行うため、+++入力によるインタラクティブモード遷移は出来ません。替わりに起動時のSETピン=LO状態でインタラクティブモードに遷移します。このときSETTINGS::open_at_start()を指定していますが、これはsetup()を終了後速やかにインタラクティブモード画面に遷移する指定です。

最後に.reload()を実行して設定値をEEPROMから読み出します。設定値を各変数にコピーしています。

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

the_twelite << set; // apply settings (from interactive mode)

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

brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

loop()

void loop() {
	auto&& brd = the_twelite.board.use<ARIA>();

	do {
		switch (step.state()) {
		 // 各状態の振る舞い
		case STATE::INIT:
		...
		break;
		...
		}
	while(step.b_more_loop());
}	

loop()は、SM_SIMPLEステートマシンstepを用いた制御を行っています。スリープ復帰からセンサー値取得、無線パケット送信、送信完了待ち、スリープといった一連の流れを簡潔に表現するためです。ループの戦闘ではbrdオブジェクトを取得しています。

case STATE::INTERACTIVE:

インタラクティブモード中にメインループが動作するのは都合が悪いため、この状態に固定します。

case STATE::INIT:

brd.sns_SHT4x.begin();

step.next(STATE::SENSOR);

センサーのデータ取得を開始します。

case STATE::SENSOR:

//  wait until sensor capture finish
if (!brd.sns_SHT4x.available()) {
	brd.sns_SHT4x.process_ev(E_EVENT_TICK_TIMER);
}else{ // now sensor data is ready.
	sensor.i16temp = brd.sns_SHT4x.get_temp_cent();
	sensor.i16humid = brd.sns_SHT4x.get_humid_per_dmil();

	// read magnet sensor
	sensor.b_north = digitalRead(ARIA::PIN_SNS_NORTH);
	sensor.b_south = digitalRead(ARIA::PIN_SNS_SOUTH);

	Serial << "..finish sensor capture." << mwx::crlf
		<< "  MAGnet   : north=" << int(sensor.b_north) << mwx::crlf
		<< "             south=" << int(sensor.b_south) << mwx::crlf
		<< "  SHT4x    : temp=" << div100(sensor.i16temp) << 'C' << mwx::crlf
		<< "             humd=" << div100(sensor.i16humid) << '%' << mwx::crlf
		;
	Serial.flush();

	step.next(STATE::TX);
}

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

センサーがavailableになった時点で、センサー値を取得し、STATE_TXに遷移します。

温湿度センサーは以下のように取得できます。

  • .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)

case STATE::TX:

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

pkt << tx_addr(0x00)  // 親機0x00宛
	<< tx_retry(0x1)    // リトライ1回
	<< tx_packet_delay(0, 0, 2); // 遅延は最小限

パケットのペイロード部に識別子のFOURCHARSとセンサーデータを格納します。得られた値のうち温度値は int16_t ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_tにキャストしています。

pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
	, uint8_t(sensor.b_north)
	, uint8_t(sensor.b_south)
	, uint16_t(sensor.i16temp)
	, uint16_t(sensor.i16humid)
);

送信要求を行います。送信要求が成功したら送信完了街の準備を行います。完了イベントを待つために.clear_flag()、万が一のときのタイムアウトをset_timeout(100)を指定します。パラメータの100の単位はミリ秒[ms]です。

// do transmit
MWX_APIRET ret = pkt.transmit();

if (ret) {
	step.clear_flag(); // waiting for flag is set.
	step.set_timeout(100); // set timeout
	step.next(STATE::TX_WAIT_COMP);
}

case STATE::TX_WAIT_COMP:

ここではタイムアウトの判定、送信完了イベントの判定を行います。

if (step.is_timeout()) { // maybe fatal error.
	the_twelite.reset_system();
}
if (step.is_flag_ready()) { // when tx is performed
	Serial << "..transmit complete." << mwx::crlf;
	Serial.flush();
	step.next(STATE::GO_SLEEP);
}

STATE::GO_SLEEP:

sleepNow()の処理を行います。

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()により完了としています。

sleepNow()

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

void sleepNow() {
	step.on_sleep(false); // reset state machine.

	// randomize sleep duration.
	uint32_t u32ct = 1750 + random(0,500);

	// set an interrupt for MAGnet sensor.
	pinMode(ARIA::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
	pinMode(ARIA::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

	// output message
	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
	Serial.flush(); // wait until all message printed.
	
	// do sleep.
	the_twelite.sleep(u32ct);
}

スリープ前に.on_sleep(false)によりステートマシンの状態を初期化します。パラメータのfalseはスリープ復帰後STATE::INIT(=0)から始めます。

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

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

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

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

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

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

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

wakeup()

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

void wakeup() {
    Serial	<< mwx::crlf
        	<< "--- ARIA:" << FOURCHARS << " wake up ";

    if (the_twelite.is_wokeup_by_wktimer()) {
        Serial << "(WakeTimer) ---";
    } else 
    if (the_twelite.is_wokeup_by_dio(ARIA::PIN_SNS_NORTH)) {
        Serial << "(MAGnet INT [N]) ---";
    } else 
    if (the_twelite.is_wokeup_by_dio(ARIA::PIN_SNS_SOUTH)) {
        Serial << "(MAGnet INT [S]) ---";
    } else {
        Serial << "(unknown source) ---";
    }

	Serial  << mwx::crlf
			<< "..start sensor capture again."
			<< mwx::crlf;
}

PAL_AMB

環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。

このアクトには以下が含まれます。

  • 無線パケットの送受信

  • インタラクティブモードによる設定 - <STG_STD>

  • ステートマシンによる状態遷移制御 - <SM_SIMPLE>

  • <PAL_AMB>ボードビヘイビアによるボード操作

アクトの機能

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

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

アクトの使い方

必要なTWELITE

役割

例

親機

MONOSTICK BLUEまたはRED

アクトParent_MONOSTICKを動作させる。

子機

BLUE PAL または RED PAL +環境センサーパル AMBIENT SENSE PAL

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>// ネットワークサポート
#include <PAL_AMB>   // PAL_AMB
#include <STG_STD>   // インタラクティブモード
#include <SM_SIMPLE> // 簡易ステートマシン

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

setup()

void setup() {
	/*** SETUP section */
	step.setup(); // ステートマシンの初期化
		
	// PAL_AMBのボードビヘイビアを読み込む
	auto&& brd = the_twelite.board.use<PAL_AMB>();
	
	// インタラクティブモードを読み込む
	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::appname(FOURCHARS);
	set << SETTINGS::appid_default(APP_ID); // set default appID
	set.hide_items(E_STGSTD_SETID::POWER_N_RETRY, E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);

	// SET ピンを検出した場合は、インタラクティブモードを起動する
	if (digitalRead(brd.PIN_BTN) == PIN_STATE::LOW) {
		set << SETTINGS::open_at_start();
		step.next(STATE::INTERACTIVE);
		return;
	}

	// インタラクティブモードのデータを読み出す
	set.reload();
	APP_ID = set.u32appid();
	CHANNEL = set.u8ch();
	OPT_BITS = set.u32opt1();

	// DIPスイッチとインタラクティブモードの設定からLIDを決める
	LID = (brd.get_DIPSW_BM() & 0x07); // 1st priority is DIP SW
	if (LID == 0) LID = set.u8devid(); // 2nd is setting.
	if (LID == 0) LID = 0xFE; // if still 0, set 0xFE (anonymous child)
	
	// LED初期化
	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;
}

最初に変数などの初期化を行います。ここではステートマシンstepの初期化を行っています。

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

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

つづいて、インタラクティブモード関連の初期化と読出しを行います。

// インタラクティブモードを読み込む
auto&& set = the_twelite.settings.use<STG_STD>();
set << SETTINGS::appname(FOURCHARS);
set << SETTINGS::appid_default(APP_ID); // set default appID
set.hide_items(E_STGSTD_SETID::POWER_N_RETRY, E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);

// SET ピンを検出した場合は、インタラクティブモードを起動する
if (digitalRead(brd.PIN_BTN) == PIN_STATE::LOW) {
	set << SETTINGS::open_at_start();
	step.next(STATE::INTERACTIVE);
	return;
}

// インタラクティブモードのデータを読み出す
set.reload();
APP_ID = set.u32appid();
CHANNEL = set.u8ch();
OPT_BITS = set.u32opt1();

// DIPスイッチとインタラクティブモードの設定からLIDを決める
LID = (brd.get_DIPSW_BM() & 0x07); // 1st priority is DIP SW
if (LID == 0) LID = set.u8devid(); // 2nd is setting.
if (LID == 0) LID = 0xFE; // if still 0, set 0xFE (anonymous child)

ここではsetオブジェクトの取得、アプリ名の反映、デフォルトのアプリケーションIDの反映、設定メニューで不要項目の削除を行います。

次にSETピンの状態を読み出します。このサンプルはスリープによる間欠動作を行うため、+++入力によるインタラクティブモード遷移は出来ません。替わりに起動時のSETピン=LO状態でインタラクティブモードに遷移します。このときSETTINGS::open_at_start()を指定していますが、これはsetup()を終了後速やかにインタラクティブモード画面に遷移する指定です。

最後に.reload()を実行して設定値をEEPROMから読み出します。設定値を各変数にコピーしています。

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

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

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

	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

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

Wire.begin(); // start two wire serial bus.

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

startSensorCapture();

loop()

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

	do {
		switch (step.state()) {
		 // 各状態の振る舞い
		case STATE::INIT:
		...
		break;
		...
		}
	while(step.b_more_loop());
}	

loop()は、SM_SIMPLEステートマシンstepを用いた制御を行っています。スリープ復帰からセンサー値取得、無線パケット送信、送信完了待ち、スリープといった一連の流れを簡潔に表現するためです。ループの戦闘ではbrdオブジェクトを取得しています。

case STATE::INTERACTIVE:

インタラクティブモード中にメインループが動作するのは都合が悪いため、この状態に固定します。

case STATE::INIT:

brd.sns_SHTC3.begin();
brd.sns_LTR308ALS.begin();

step.next(STATE::SENSOR);

センサーのデータ取得を開始します。

case STATE::SENSOR:

	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);
	}

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

上記2つのセンサーがavailableになった時点で、センサー値を取得し、STATE_TXに遷移します。

// now sensor data is ready.
if (brd.sns_LTR308ALS.available() && brd.sns_SHTC3.available()) {
	sensor.u32luminance = brd.sns_LTR308ALS.get_luminance();
	sensor.i16temp = brd.sns_SHTC3.get_temp_cent();
	sensor.i16humid = brd.sns_SHTC3.get_humid_per_dmil();

	Serial << "..finish sensor capture." << mwx::crlf
		<< "  LTR308ALS: lumi=" << int(sensor.u32luminance) << mwx::crlf
		<< "  SHTC3    : temp=" << div100(sensor.i16temp) << 'C' << mwx::crlf
		<< "             humd=" << div100(sensor.i16humid) << '%' << mwx::crlf
		;
	Serial.flush();

	step.next(STATE::TX);
}

照度センサーは.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)

case STATE::TX:

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

	pkt << tx_addr(0x00)  // 親機0x00宛
		<< tx_retry(0x1)    // リトライ1回
		<< tx_packet_delay(0, 0, 2); // 遅延は最小限

パケットのペイロード部に識別子のFOURCHARSとセンサーデータを格納します。得られた値のうち温度値は int16_t ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_tにキャストしています。

pack_bytes(pkt.get_payload() 
	, make_pair(FOURCHARS, 4)  
	, uint32_t(sensor.u32luminance)
	, uint16_t(sensor.i16temp)
	, uint16_t(sensor.i16humid)
);

送信要求を行います。送信要求が成功したら送信完了街の準備を行います。完了イベントを待つために.clear_flag()、万が一のときのタイムアウトをset_timeout(100)を指定します。パラメータの100の単位はミリ秒[ms]です。

// do transmit
MWX_APIRET ret = pkt.transmit();

if (ret) {
	step.clear_flag(); // waiting for flag is set.
	step.set_timeout(100); // set timeout
	step.next(STATE::TX_WAIT_COMP);
}

case STATE::TX_WAIT_COMP:

ここではタイムアウトの判定、送信完了イベントの判定を行います。

if (step.is_timeout()) { // maybe fatal error.
	the_twelite.reset_system();
}
if (step.is_flag_ready()) { // when tx is performed
	Serial << "..transmit complete." << mwx::crlf;
	Serial.flush();
	step.next(STATE::GO_SLEEP);
}

STATE::GO_SLEEP:

sleepNow()の処理を行います。

on_tx_comp()

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()により完了としています。

sleepNow()

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

void sleepNow() {
	step.on_sleep(false); // reset state machine.

	// randomize sleep duration.
	uint32_t u32ct = 1750 + random(0,500);

	// output message
	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
	Serial.flush(); // wait until all message printed.
	
	// do sleep.
	the_twelite.sleep(u32ct);
}

スリープ前に.on_sleep(false)によりステートマシンの状態を初期化します。パラメータのfalseはスリープ復帰後STATE::INIT(=0)から始めます。

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

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

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

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

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

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

wakeup()

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

void wakeup() {
	Serial	<< mwx::crlf
			<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
			<< mwx::crlf
			<< "..start sensor capture again."
			<< mwx::crlf;

応用編

消費エネルギーの削減

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

PAL_AMB-usenap

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

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

アクトの解説

begin()

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

void begin() {
	sleepNow(); // the first time is just sleeping.
}

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

wakeup()

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

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

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

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);
	}
}

上記の分岐をグローバル変数の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()に移行し、無線パケットが送信されます。

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

napNow()

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

void napNow() {
	uint32_t u32ct = 100;
	Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
	the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}

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

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

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

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

PAL_AMB-bhv

ビヘイビアの記述サンプルです。詳細はこちらを参照ください。

PAL_MAG

開閉センサーパル OPEN-CLOSE SENSE PAL を用い、センサー値の取得を行います。

このアクトには以下が含まれます。

  • 無線パケットの送受信

  • インタラクティブモードによる設定 - <STG_STD>

  • ステートマシンによる状態遷移制御 - <SM_SIMPLE>

  • <PAL_MAG>または<CUE>ボードビヘイビアによるボード操作

アクトの機能

  • 開閉センサーパル OPEN-CLOSE SENSE PAL を用い、磁気センサーの検出時に割り込み起床し、無線送信します。

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

アクトの使い方

必要なTWELITE

役割

例

親機

MONOSTICK BLUEまたはRED

アクトParent_MONOSTICKを動作させる。

子機

BLUE PAL または RED PAL +開閉センサーパル OPEN-CLOSE SENSE PAL

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_MAG>

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

setup()

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;
}

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

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

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

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

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

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

begin()

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

void begin() {
	sleepNow(); // the first time is just sleeping.
}

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

sleepNow()

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);
}

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

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

wakeup()

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

void wakeup() {
	if (the_twelite.is_wokeup_by_wktimer()) {
		sleepNow();
	}
}

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

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

loop()

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

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();
		}
	}
}

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

	if (!b_transmit) {

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

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);

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

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

// do transmit
MWX_APIRET ret = pkt.transmit();

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

if (the_twelite.tx_status.is_complete(u8txid)) {		
	b_transmit = 0;
	sleepNow();
}

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

PAL_MOT-single

このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。

のアクトには以下が含まれます。

  • 無線パケットの送受信

  • インタラクティブモードによる設定 - <STG_STD>

  • ステートマシンによる状態遷移制御 - <SM_SIMPLE>

  • <PAL_MOT>または<CUE>ボードビヘイビアによるボード操作

アクトの解説

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

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

宣言部

インクルード

#include <TWELITE>    // MWXライブラリ基本
#include <NWK_SIMPLE> // ネットワーク
#include <SM_SIMPLE>  // ステートマシン(状態遷移)
#include <STG_STD>    // インタラクティブモード

/*** board selection (choose one) */
#define USE_PAL_MOT
//#define USE_CUE
// board dependend definitions.
#if defined(USE_PAL_MOT)
#define BRDN PAL_MOT
#define BRDC <PAL_MOT>
#elif defined(USE_CUE)
#define BRDN CUE
#define BRDC <CUE>
#endif
// include board support
#include BRDC

MOT PALまたはTWELITE CUEに対応するため、インクルード部分はマクロになっています。USE_PAL_MOTまたは、USE_CUEのいずれかを定義します。

USE_PAL_MOT が定義されている場合は動作センサーパルのボードビヘイビア<PAL_MOT>をインクルードしています。

状態定義

enum class E_STATE : uint8_t {
	INTERACTIVE = 255,
	INIT = 0,
	START_CAPTURE,
	WAIT_CAPTURE,
	REQUEST_TX,
	WAIT_TX,
	EXIT_NORMAL,
	EXIT_FATAL
};
SM_SIMPLE<E_STATE> step;

loop()中の順次処理を行うために状態を定義し、またステートマシンstepを宣言します。

センサーデータ格納

struct {
	int32_t x_ave, y_ave, z_ave;
	int32_t x_min, y_min, z_min;
	int32_t x_max, y_max, z_max;
	uint16_t n_seq;
	uint8_t n_samples;
} sensor;

センサーデータを格納するためのデータ構造です。

setup()

/// load board and settings objects
auto&& brd = the_twelite.board.use BRDC (); // load board support
auto&& set = the_twelite.settings.use<STG_STD>(); // load save/load settings(interactive mode) support
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>(); // load network support

ボード、設定、ネットワークの各ビヘイビアオブジェクトの登録を行います。

インタラクティブモード

// settings: configure items
set << SETTINGS::appname("MOT");
set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
set << SETTINGS::lid_default(0x1); // set default LID
set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);

// if SET=LOW is detected, start with intaractive mode.
if (digitalRead(brd.PIN_SET) == PIN_STATE::LOW) {
	set << SETTINGS::open_at_start();
	brd.set_led(LED_TIMER::BLINK, 300); // slower blink
	step.next(STATE::INTERACTIVE);
	return;
}

// load settings
set.reload(); // load from EEPROM.
OPT_BITS = set.u32opt1(); // this value is not used in this example.

インタラクティブモードの初期化を行います。

まず、設定項目の調整を行います。ここでは、メニュー項目で表示されるタイトル名SETTINGS::appname、アプリケーションIDのデフォルト値の設定SETTINGS::appid_default、チャネルのデフォルトSETTINGS::ch_default、論理デバイスIDのデフォルトSETTINGS::lid_default、非表示項目の設定.hide_items()を行います。

このサンプルでは起動時にSETピンがLOである場合にインタラクティブモードに遷移します。digitalRead(brd.PIN_SET)によりピンがLOであることを確認できた場合は、SETTINGS::open_at_start()を指定します。この指定によりsetup()を抜けた後に速やかにインタラクティブモード画面が表示されます。画面が表示されてもbegin()やloop()が実行されます。このサンプルでは状態STATE::INTERACTIVEとしてloop()中ではスリープなどの動作はせず何もしないようにします。

続いて設定値を読み出します。設定値を読むには必ず.reload()を実行します。このサンプルではオプションビット設定.u32opt1()を読み出します。

the_twelite

the_twelite << set;

the_tweliteは、システムの基本的な振る舞いを管理するクラスオブジェクトです。このオブジェクトは、setup()内でアプリケーションIDやチャネルなど様々な初期化を行います。

ここではインタラクティブモードの設定値の一部を反映しています。

インタラクティブモード設定で反映した項目を別の設定に変更したい場合は、続いて上書きしたい設定を行います。

the_twelite << set;// インタラクティブモード
the_twelite << twenet::channel(19); // chを19に上書き設定

NWK_SIMPLEオブジェクト

nwk << set;

ネットワークビヘイビアオブジェクトに対しても設定を行います。インタラクティブモードの論理デバイスID(LID)と再送設定が反映されます。

その他、ハードウェアの初期化など

brd.set_led(LED_TIMER::BLINK, 100);

LEDのブリンク設定などを行います。

begin()

void begin() { 
	auto&& set = the_twelite.settings.use<STG_STD>();
	if (!set.is_screen_opened()) {
		// sleep immediately, waiting for the first capture.
		sleepNow();
	}
}

setup()を終了した後に呼ばれます。ここでは初回スリープを実行しています。ただしインタラクティブモードの画面が表示される場合はスリープしません。

wakeup()

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

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

loop()

void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	do {
		switch(step.state()) {
			case STATE::INTERACTIVE:
			break;
		...
	} while(step.b_more_loop());
}

loop() の基本構造は<SM_STATE>ステートマシンstateを用いswitch ... case節での制御です。初期状態はSTATE::INITまたはSTATE::INTERACTIVEです。

STATE::INTERACTIVE

インタラクティブモード画面が表示されているときの状態です。何もしません。この画面ではSerialの入出力はインタラクティブモードが利用します。

STATE::INIT

初期状態のINITです。

case STATE::INIT:
	brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
	memset(&sensor, 0, sizeof(sensor)); // clear sensor data
	step.next(STATE::START_CAPTURE);
break;

状態INITでは、初期化(結果格納用のキューのクリア)や結果格納用のデータ構造の初期化を行います。STATE::START_CAPTUREに遷移します。この遷移設定後、もう一度whileループが実行されます。

STATE::CAPTURE

case STATE::START_CAPTURE:
	brd.sns_MC3630.begin(
		// 400Hz, +/-4G range, get four samples and will average them.
		SnsMC3630::Settings(
			SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, N_SAMPLES)); 

	step.set_timeout(100);
	step.next(STATE::WAIT_CAPTURE);
break;

状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。

例外処理のためのタイムアウトの設定と、次の状態STATE::WAIT_CAPTUREに遷移します。

STATE::WAIT_CAPTURE

case STATE::WAIT_CAPTURE:
	if (brd.sns_MC3630.available()) {
		brd.sns_MC3630.end(); // stop now!

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

sensor.n_samples = brd.sns_MC3630.get_que().size();
if (sensor.n_samples) sensor.n_seq = brd.sns_MC3630.get_que()[0].get_t();
...

サンプル数とサンプルのシーケンス番号を取得します。

// get all samples and average them.
for (auto&& v: brd.sns_MC3630.get_que()) {
	sensor.x_ave  += v.x;
	sensor.y_ave  += v.y;
	sensor.z_ave  += v.z;
}

if (sensor.n_samples == N_SAMPLES) {
	// if N_SAMPLES == 2^n, division is much faster.
	sensor.x_ave /= N_SAMPLES;
	sensor.y_ave /= N_SAMPLES;
	sensor.z_ave /= N_SAMPLES;
}
...

すべてのサンプルデータに対して読み出し、平均値をとる処理をします。

// can also be:
//	int32_t x_max = -999999, x_min = 999999;
//	for (auto&& v: brd.sns_MC3630.get_que()) {
//		if (v.x >= x_max) x_max = v.x;
//		if (v.y <= x_min) x_min = v.x;
//		...
//	}	
auto&& x_minmax = std::minmax_element(
	get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
	get_axis_x_iter(brd.sns_MC3630.get_que().end()));
sensor.x_min = *x_minmax.first;
sensor.x_max = *x_minmax.second;
...

ここでは取得されたサンプルに対して、各軸に対応するイテレータを用い最大・最小を得ています。

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

if (brd.sns_MC3630.available()) {
  ...
  brd.sns_MC3630.get_que().clear(); // clean up the queue
  step.next(STATE::REQUEST_TX); // next state
} else if (step.is_timeout()) {
  Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";
  step.next(STATE::EXIT_FATAL);
}
break;

.sns_MC3630.get_que().clear()を呼び出し、キューにあるデータをクリアします。これを呼び出さないと続くサンプル取得ができません。その後STATE::REQUEST_TX状態に遷移します。

.is_timeout()はタイムアウトをチェックします。タイムアウト時は異常としてSTATE::EXIT_FATALに遷移します。

STATE::REQUEST_TX

case STATE::REQUEST_TX:
	if (TxReq()) {
		step.set_timeout(100);
		step.clear_flag();
		step.next(STATE::WAIT_TX);
	} else {
		Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";
		step.next(STATE::EXIT_FATAL);
	}
break;

状態REQUEST_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成・送信を行います。送信要求は送信キューの状態などで失敗することがあります。送信要求が成功した場合、TxReq()はtrueとして戻りますが、まだ送信は行われません。送信完了はon_tx_comp()コールバックが呼び出されます。

また.clear_flag()により送信完了を知らせるためのフラグをクリアしておきます。同時にタイムアウトも設定します。

E_SATE::WAIT_TX

case STATE::WAIT_TX:
	if (step.is_flag_ready()) {
		step.next(STATE::EXIT_NORMAL);
	}
	if (step.is_timeout()) {
		Serial << crlf << "!!!FATAL: TX TIMEOUT.";
		step.next(STATE::EXIT_FATAL);
	}
break;

状態STATE::WAIT_TXでは、無線パケットの送信完了を待ちます。フラグはon_tx_comp()コールバック関数でセットされ、セット後に.is_flag_ready()がtrueになります。

E_SATE::EXIT_NORMAL, FATAL

case STATE::EXIT_NORMAL:
	sleepNow();
break;

case STATE::EXIT_FATAL:
	Serial << flush;
	the_twelite.reset_system();
break;

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

MWX_APIRET TxReq()

MWX_APIRET TxReq() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();
	MWX_APIRET ret = false;

	// 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(sensor.n_seq)
				, uint8_t(sensor.n_samples)
				, uint16_t(sensor.x_ave)
				, uint16_t(sensor.y_ave)
				, uint16_t(sensor.z_ave)
				, uint16_t(sensor.x_min)
				, uint16_t(sensor.y_min)
				, uint16_t(sensor.z_min)
				, uint16_t(sensor.x_max)
				, uint16_t(sensor.y_max)
				, uint16_t(sensor.z_max)
			);

		// perform transmit
		ret = pkt.transmit();

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

	return ret;
}

最期にパケットの生成と送信を要求を行います。パケットには続き番号、サンプル数、XYZの平均値、XYZの最小サンプル値、XYZの最大サンプル値を含めます。

sleepNow()

void sleepNow() {
	Serial << crlf << "..sleeping now.." << crlf;
	Serial.flush();
	step.on_sleep(false); // reset state machine.
	the_twelite.sleep(3000, false); // set longer sleep (PAL must wakeup less than 60sec.)
}

スリープの手続きです。

  • シリアルポートはスリープ前にSerial.flush()を呼び出してすべて出力しておきます。

  • <SM_SIMPLE>ステートマシンはon_sleep()を行う必要があります。

PAL_MOT-fifo

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

のアクトには以下が含まれます。

  • 無線パケットの送受信

  • インタラクティブモードによる設定 - <STG_STD>

  • ステートマシンによる状態遷移制御 - <SM_SIMPLE>

  • <PAL_MOT>または<CUE>ボードビヘイビアによるボード操作

アクトの機能

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

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

アクトの使い方

必要なTWELITE

役割

例

親機

MONOSTICK BLUEまたはRED

アクトParent_MONOSTICKを動作させる。

子機

BLUE PAL または RED PAL +動作センサーパル MOTION SENSE PAL

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>

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

setup()

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;
}

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

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

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

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

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

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

加速度センサーの初期化

	brd.sns_MC3630.begin(SnsMC3630::Settings(
		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));

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

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

begin()

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

void begin() {
	sleepNow(); // the first time is just sleeping.
}

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

sleepNow()

void sleepNow() {
	pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);
	the_twelite.sleep(60000, false);
}

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

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

wakeup()

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

void wakeup() {
	Serial << "--- PAL_MOT(Cont):" << FOURCHARS
	       << " wake up ---" << mwx::crlf;

	b_transmit = false;
	txid[0] = 0xFFFF;
	txid[1] = 0xFFFF;
}

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

loop()

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

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();
		}
	}
}

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

	if (!b_transmit) {

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

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;
}

加速度センサーの計測結果は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回行われます。

		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
);

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

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.
}

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

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

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

MWX_APIRET ret = pkt.transmit();

if (ret) {
	Serial << "..txreq(" << int(ret.get_value()) << ')';
	txid[ip] = ret.get_value() & 0xFF;
} else {
	sleepNow();
}

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

} else {
	if(		the_twelite.tx_status.is_complete(txid[0])
		 && the_twelite.tx_status.is_complete(txid[1]) ) {

		sleepNow();
	}
}

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

PulseCounter

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

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

アクトの機能

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

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

アクトの使い方

必要なTWELITE

役割

例

親機

MONOSTICK BLUEまたはRED

アクトParent_MONOSTICKを動作させる。

子機

1.TWELITE DIP

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

アクトの解説

setup()

// Pulse Counter setup
PulseCounter.setup();

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

begin()

void begin() {
	// start the pulse counter capturing
	PulseCounter.begin(
		  100 // 100 count to wakeup
		, PIN_INT_MODE::FALLING // falling edge
		);

	sleepNow();
}

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

wakeup()

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();
	}
}

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

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

loop()

uint16_t u16ct = PulseCounter.read();

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

WirelessUART

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

アクトの機能

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

アクトの使い方

必要なTWELITE

PCにシリアル接続されている以下のデバイスを2台。

  • MONOSTICK BLUE または RED

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

親機宛のパケットであればParent_MONOSTICKでも受信できます。

アクトの解説

setup()

void setup() {
	auto&& set = the_twelite.settings.use<STG_STD>();
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

	/*** INTERACTIE MODE */
	// settings: configure items
	set << SETTINGS::appname("WirelessUART");
	set << SETTINGS::appid_default(DEFAULT_APP_ID); // set default appID
	set << SETTINGS::ch_default(DEFAULT_CHANNEL); // set default channel
	set << SETTINGS::lid_default(DEFAULT_LID); // set default lid
	set.hide_items(E_STGSTD_SETID::OPT_DWORD2, E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4, E_STGSTD_SETID::ENC_KEY_STRING, E_STGSTD_SETID::ENC_MODE);
	set.reload(); // load from EEPROM.
	
	/*** SETUP section */
	// the twelite main class
	the_twelite
		<< set                      // from iteractive mode (APPID/CH/POWER)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	nwk	<< set;						// from interactive mode (LID/REPEAT)

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

	/*** INIT message */
	Serial << "--- WirelessUart (id=" << int(nwk.get_config().u8Lid) << ") ---" << mwx::crlf;
}

インタラクティブモードを初期化しています。このサンプルでは互いに論理デバイスID(LID)が異なるデバイスを2台以上用意します。

SerialParser.begin(PARSER::ASCII, 128); 

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

loop()

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());
	}
}

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

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

on_rx_packet()

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

void on_rx_packet(packet_rx& rx, bool_t &handled) {
	// 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;
	}
}

テスト用のコマンド

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

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

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

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

例

:FE00112233X

:FE001122339C

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

例

:03AABBCC00112233X

:03AABBCC0011223366

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

例

:FF00112233X

:00112233X

任意の親機または子機宛(0xFF)、親機宛(0x00)に送付します。

Unit_???

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

名前

内容

Unit_ADC

ADCを動作させるサンプルです。100msごとにADCを連続実行しつつ約1秒おきに読み出し表示します。[s]キーでスリープします。

Unit_I2Cprobe

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

Unit_delayMicoroseconds

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

Unit_brd_CUE

TWELITE CUEの加速度センサー,磁気センサー,LEDの動作確認を行います。ターミナルから[a],[s],[l]キーを入力します。

Unit_brd_ARIA

TWELITE ARIAの温湿度センサー、磁気センサー、LEDの動作確認を行います。ターミナルから[t],[s],[l]キーを入力します。

Unit_brd_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_UART1

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

Unit_Pkt_Parser

パケット情報のパーサーpktparserの使用サンプルです。App_Wingsの出力を解釈することが出来ます。 ※ TWELITE無線モジュール同士をシリアル接続して、一方をApp_Wingsとして他方でその出力を解釈したいような場合です。他方をTWELITE無線モジュール以外に接続したい場合は「他のプラットフォーム」を参照ください。

Uint_EEPROM

EEPROMの読み書きテストコードです。

Unit_Cue_MagBuz

TWELITE CUEの磁石センサーとSETピン(圧電ブザーを接続)を用いた、磁石を離すとブザーが鳴るプログラムです。

Unit_doint-bhv

IO割り込みを処理するビヘイビア記述例です。