Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Install and Build
MWXライブラリを用いてアプリケーションを記述(本書ではアクトと呼びます)し、実行するために開発環境のセットアップが必要です。
シリアルポート向き書式入力 (mwx::serial_parser)
この組み込みクラスはシリアルポートでの書式入力に利用することを想定して組み込みオブジェクトとして定義しています。
初期化時(begin())にヒープから内部で使用するバッファ領域を確保するmwx::serial_parser<mwx::alloc_heap<uint8_t>>型として定義されています。
詳細はクラス を参照してください。
DIO(汎用ディジタルIO)ピンの設定を行います。
void pinMode(uint8_t u8pin, E_PIN_MODE mode)この関数では DIO0..19 と、DO0,1のピンの状態を変更できます。設定内容は E_PIN_MODE の列挙値のDIOの解説とDOの解説を参照してください。
DO0,1は特殊なピンで、原則として他の目的で利用されるものですが、出力としても設定可能です。ただしハード的な制約があるピンですので、利用には注意が必要です。
両方のピンは、電源投入時にHIGHレベルが担保される必要があります。不安定な電圧をとったりするような回路構成の場合、モジュールが起動しないなどの問題が出ます。
settingsパラメータを与えて呼び出した場合は、バスの設定を行います。バスの利用を終了します。SPIのセレクトピンを解除します。
バスの読み書きを行います。trasnfer()は8bit、transfer16()は16bit、transfer32()は32bitの転送を行います。
void beginTransaction()
void beginTransaction(SPISettings settings)void endTransaction()inline uint8_t transfer(uint8_t data)
inline uint16_t transfer16(uint16_t data)
inline uint32_t transfer32(uint32_t data)システムタイマー (mwx::periph_ticktimer)
TickTimerはTWENETの内部制御用に利用され、暗黙に実行されています。タイマーの周期は1msです。loop()中でTickTimerイベントにより1msごとの処理を記述する目的でavailable()メソッドのみを定義しています。
必ず1ms刻みでavailableになる訳ではない点に注意してください。
ユーザプログラムの記述内容や、システム内部の割り込み処理などが要因で、大きな遅延が発生することもあり、イベントが飛ばされるような場合もあります。
void loop() {
if (TickTimer.available()) {
if ((millis() & 0x3FF) == 0) { // これは処理されない場合がある
Serial << '*';
}
}
}available()
TickTimer割り込み発生後にセットされ、その直後のloop()でtrueになります。loop()終了後にクリアされます。
乱数を生成します。
1行目は0..(maxval-1)の値を戻します。maxvalの値が最大値ではないことに注意してください。
2行目はminval..maxval-1の値を戻します。
act0 から始まるアクト(Act)は、で紹介されたものを収録しています。LEDやボタンの動作のみの単純なものですが、最初にお試しいただくことをお勧めします。
mwxライブラリ 0.1.4 以降に収録
入力設定のポートの値を一括読み出しします。
LSB側から順にDIO0 ... DIO19 の順に値が格納されます。
HIGH側のピンには 1 が、LOW側のピンには 0 が設定されます。
通常は使用しません。
スリープ復帰後、ペリフェラルAPIが初期化されない再初期に呼び出されます。
act4
ボタン(スイッチ)を用いたLED点灯
名前
内容
act0
処理の記述がないテンプレート
act1
Lチカ(LEDの点滅)
act2
タイマーを用いたLチカ
act3
2つのタイマーを用いたLチカ
inline bool available()uint32_t random(uint32_t maxval)
uint32_t random(uint32_t minval, uint32_t maxval)uint32_t digitalReadBitmap()入力設定のポートの値を読み出す。
static inline E_PIN_STATE digitalRead(uint8_t u8pin)事前に入力に設定したピンの入力値をLOWまたはHIGHで得ます。
割り込みハンドラの登録を解除します。
void detachIntDio(uint8_t u8pin)ディジタル出力ピンの設定を変更します。
static inline void digitalWrite(uint8_t u8pin, E_PIN_STATE ulVal)事前にpinMode()にて設定対象のピンを出力用に設定しておきます。1番目のパラメータは、設定対象のピン番号を指定します。2番目のパラメータはHIGHかLOWのいずれかを指定します。
コード実行の初期に呼び出され、初期化コードを記述します。
TWENETの初期化は setup() 関数が終了した後にも実行されます。多くの処理はTWENETが終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。
注意すべき事項を以下に列挙します。
スリープthe_twenet.sleep()の実行はできません。初期化後速やかにスリープしたいときはbegin()関数内に最初のスリープ処理を記述してください。
delay()関数は後述の処理*に置き換えられます。この場合、パラメータのmsはミリ秒を指定するものではありません。
* delay()の代替処理
uint8_t型のsmplbuf_strm_u8???はインタフェースも有しているため、いくつかのストリーム用のメソッドを使用することができます。
例
twe::stream へのバッファ出力をフラッシュする。
mwx::stream の出力バッファをフラッシュする。flush() メソッドを呼び出すヘルパークラスへのインスタンス。
シリアルポートの場合は出力完了までポーリング待ちを行う
mwx::simpbuf バッファの場合は 0x00 を末尾に出力する(サイズは変更しない)
チェックサムの計算で良く用いられる値です。
CRC8, XOR, LRC(で使用)の計算を行います。
CRC8_u8CalcU16(), CRC8_u8CalcU32()はu16c, u32cをビッグエンディアン並びとして、CRC8を計算します。
のボードビヘイビアです。
に加えボード上のセンサーを取り扱えるようになっています。
温湿度センサー SHTC3
照度センサー LTR308ALS
mwxライブラリでは、書式出力をC言語で常用されるprintfの実装を用いています。いくつかの関数の関数定義が利用できます。
mwx_printf()はSerialオブジェクトに対してprintf出力を行います。Serial.printfmt()と同じ処理になります。
mwx_snprintf()はバッファに対してsnprintfを行います。
static inline void delay(uint32_t ms) {
volatile uint32_t ct = ms * 4096;
while (ct > 0) {
--ct;
}
}解説
v
uint16_t または uint32_t の型の値
Serial << mwx::bigendian(0x1234abcdUL);
// output binary -> 0x12 0x34 0xab 0xcdtemplate <typename T>
bigendian::bigendian(T v)パラメータ
// smplbuf_strm_u8<N> : ローカル確保
template <int N> using smplbuf_strm_u8
= _smplbuf_stream<uint8_t, mwx::alloc_local<uint8_t, N>>;
// smplbuf_strm_u8_attach : 既存バッファへのアタッチ版
using smplbuf_strm_u8_attach
= mwx::_smplbuf_stream<uint8_t, mwx::alloc_attach<uint8_t>>;
// smplbuf_strm_u8_heap : HEAP確保
using smplbuf_strm_u8_heap
= mwx::_smplbuf_stream<uint8_t, mwx::alloc_heap<uint8_t>>;
// << 演算子の定義
template <class L_STRM, class ALOC>
mwx::stream<L_STRM>& operator << (
mwx::stream<L_STRM>& lhs,
mwx::_smplbuf_stream<uint8_t, ALOC>& rhs)
{
lhs << rhs.to_stream();
return lhs;
}smplbuf_strm_u8<128> sb1;
sb1 << "hello";
sb1 << uint32_t(0x30313233);
sb1 << format("world%d",99);
sb1.printfmt("Z!");
Serial << sb1;
// hello0123world99Z!for (int i = 0; i < 127; ++i) {
Serial << "hello world! (" << i << ")" << twe::endl << twe::flush;
}XORは各要素の排他的論理和 XOR をとったものです。
無線パケットのデータ列、アスキー形式のチェックサム(LRC)、各種センサーのデータチェック用に利用されるため、ライブラリ手続きとして追加した。
uint8_t CRC8_u8Calc(uint8_t *pu8Data, uint8_t size, uint8_t init=0)
uint8_t CRC8_u8CalcU32(uint32_t u32c, uint8_t init=0)
uint8_t CRC8_u8CalcU16(uint16_t u16c, uint8_t init=0)
uint8_t XOR_u8Calc(uint8_t *pu8Data, uint8_t size)
uint8_t LRC_u8Calc(uint8_t* pu8Data, uint8_t size)SHTC3センサーのオブジェクトです。
LTR308ALSセンサーのオブジェクトです。
void setup() {
auto&& brd = the_twelite.board.use<PAL_AMB>();
}int mwx_printf(const char* format, ...)
int mwx_snprintf(char* buffer, size_t count, const char* format, ...)8bit値(0..25など)の値とユーザが取り扱いやすい0..1000(千分率, ‰)値でスケール(拡縮)する。低い演算コストで実施するため、除算(x*1000/255)の替わりに乗算とビットシフトによって近似計算します。
0..1000 を 0..127 にスケールします。(16646*x+65000)>>17を用いて近似計算します。
0..127 を 0..1000 にスケールします。(2064000UL*x+131072)>>18を用いて近似計算します。
0..1000 を 0..255 にスケールします。(33423*x+65000)>>17を用いて近似計算します。
0..255 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18を用いて近似計算します。
0..1000 を 0..256 にスケールします。(33554*x+66000) >> 17を用いて近似計算します。
注: x=999,1000は計算値が256になりますがuint8_tの範囲として255を返します。
0..256 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18を用いて近似計算します。
ハードウェアに設定すべき値は0..255といった2進数が前提になることが多く、ユーザアプリケーションで取り扱う数は0..1000といった10進数基準のほうが扱いやすい。これらのスケール変換に除算を行わない式を定義した。
始動時setup()の後に1回だけ呼び出されます。メッセージの表示のみ。
Buttonsによる連続参照により状態を確定します。ボタン状態が変化したらシリアルに出力します。
シリアルから1文字読み込んで、入力文字に応じた処理をします。
パケットを受信したら、送信元のアドレス情報を表示します。
スリープ起床時に最初に呼び出されます。メッセージの表示のみ。
送信要求を行う最小限の手続きです。
この関数を抜けた時点では、まだ要求は実行されていません。しばらく待つ必要があります。この例では100-200msの送信開始の遅延があるため、送信が開始されるのは早くて100ms後です。
static inline uint8_t scale_1000_to_127u8(uint16_t x)
static inline uint16_t scale_127u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_255u8(uint16_t x)
static inline uint16_t scale_255u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_256u8(uint16_t x)
static inline uint16_t scale_256u16_to_1000(uint16_t x)void setup() {
/*** SETUP section */
txreq_stat = MWX_APIRET(false, 0);
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // アプリケーションID
<< TWENET::channel(CHANNEL) // チャネル
<< TWENET::rx_when_idle(); // 受信有
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(0xFE); // 子機、特定アドレス指定なし(0xFE)
/*** BEGIN section */
Buttons.begin(pack_bits(PIN_BTN), 5, 10); // ボタン処理初期化
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- Scratch act ---" << mwx::crlf;
}void begin() {
Serial << "..begin (run once at boot)" << mwx::crlf;
}if (Buttons.available()) {
uint32_t bm, cm;
Buttons.read(bm, cm);
if (cm & 0x80000000) {
// the first capture.
}
Serial << int(millis()) << ":BTN" << format("%b") << mwx::crlf;
}while(Serial.available()) {
int c = Serial.read();
Serial << '[' << char(c) << ']';
switch(c) {
case 'p': ... // millis() を表示
case 't': ... // 無線パケットを送信 (vTransmit)
case 's': ... // スリープする
}
}if (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
// just dump a packet.
Serial << format("rx from %08x/%d",
rx.get_addr_src_long(), rx.get_addr_src_lid()) << crlf;
}void wakeup() {
Serial << int(millis()) << ":wake up!" << mwx::crlf;
}MWX_APIRET vTransmit() {
Serial << int(millis()) << ":vTransmit()" << mwx::crlf;
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0xFF) // 同報通信=ブロードキャスト
<< tx_retry(0x1) // 再送1回
<< tx_packet_delay(100,200,20); // 送信時遅延100-200msの間に送信、再送間隔20ms
// 送信データの指定(アプリケーションごとに決める)
pack_bytes(pkt.get_payload()
, make_pair("SCRT", 4) // 4文字識別子
, uint32_t(millis()) // タイムスタンプ
);
// 送信要求を行う
return pkt.transmit();
}
// .prepare_tx_packet() 時点で失敗している
return MWX_APIRET(false, 0);
}PAL_AMB のサンプルを少し改良して、センサーデータ取得中の待ち時間(約50ms)を、スリープで待つようにします。
このアクトの解説の前にPAL_AMBのアクトの解説をご覧ください。
begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。
setup()終了後に初回スリープを実行します。setup()中にセンサーデータ取得を開始していますが、この結果は評価せず、センサーを事前に一度は動かしておくという意味あいで、必ずしも必要な手続きではありません。
起床後の手続きです。以下の処理を行います。
まだセンサーデータの取得開始をしていない場合、センサーデータ取得を行い、短いスリープに入る。
直前にセンサーデータ取得開始を行ったので、データを確認して無線送信する。
上記の分岐をグローバル変数の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()で正しい時間を設定したかどうかで決まります。短い時間で起床した場合は、必要とされる時間経過に足りないため、続く処理でセンサーデータが得られないなどのエラーが出ることが想定されます。
ごく短いスリープを実行する。
sleepのパラメータの2番目をtrueにすると前回のスリープ復帰時刻をもとに次の復帰時間を調整します。常に5秒おきに起床したいような場合設定します。
3番目をtrueにするとメモリーを保持しないスリープになります。復帰後はwakup()は呼び出されじ、電源再投入と同じ処理になります。
4番目はウェイクアップタイマーの2番目を使う指定です。ここでは1番目は通常のスリープに使用して、2番目を短いスリープに用いています。このアクトでは2番目を使う強い理由はありませんが、例えば上述の5秒おきに起床したいような場合、短いスリープに1番目のタイマーを用いてしまうとカウンター値がリセットされてしまい、経過時間の補正計算が煩雑になるため2番目のタイマーを使用します。
MWSDK 2020-04 以降は TWELITE STAGE アーカイブに含まれます。
TWELITE STAGEアプリからビルドする場合は、MWSDK_ROOT 環境変数の設定が不要になります。
MWSDK 2019-12 と同じの方法でビルドする場合は、TWELITE STAGE アーカイブ中の MWSDK ディレクトリの構成は大きく変わっておりませんので、読み替えて利用ください。
TWELITE STAGE については以下をご覧ください。
TWELITE SDK (MWSDK) をインストールします。本書ではMWSDKと記載します。
TWELITE SDKはTWENET MWX C++ ライブラリ対応版をダウンロードしてください。
MWX 対応 SDK の圧縮アーカイブファイルをしてください。
圧縮アーカイブファイルを展開してください。
展開先のディレクトリ名にはスペース、日本語名が含まれてはいけません。
MWSDK_ROOT, MWSDK_ROOT_WINNAME(Windows10のみ) の設定が必要です。
ここでは展開後のディレクトリ名を C:\MWSDK とします。別のディレクトリにインストールした場合は、読み替えてください。
C:\MWSDK\SET_ENV.CMD を実行してください。以下の環境変数を設定します。
MWSDK_ROOT
MWSDK_ROOT_WINNAME
SDKに収録された時点から、ライブラリソースコードに修正がある場合があります。を参照の上、必要に応じでライブラリソースコードを差し替えてください。
このクラスはTWENETのtsRxDataApp構造体のラッパークラスです。
このクラスオブジェクトは、ビヘイビアのコールバック関数またはthe_twelite.receiver.read()により取得できます。
packet_rxでは、特にパケットのデータペイロードをsmplbufコンテナで取り扱えるようにし、expand_bytes()などのユーティリティ関数によりペイロードの解釈記述を簡素化しています。
パケットのデータペイロードを取得する。
TWENET Cライブラリの受信構造体を得る。
ペイロードのデータ長を返す。.get_payload().size()と同じ値になる。
LQI値 (Link Quality Indicator)を得る。
送信元のアドレスを得る。
get_addr_src_long()は送信元のシリアル番号で、MSB(bit31)が必ず1になります。
get_addr_src_lid()は送信元の論理IDで0x00-0xFEまでの値をとります(<NWK_SIMPLE>で指定する論理IDです)。
宛先アドレスを得ます。
宛先アドレスは、送信元で指定され、宛先の種別によって値の範囲が変わります。
暗号化パケットの場合は true を返し、平文の時はfalse を返します。
mwx::stream に printf の書式を入力
mwx::stream の << 演算子に対してフォーマット書式を書き出すヘルパークラスです。ライブラリ内では Using format=mwx::mwx_format; として別名定義しています。
Serial << format("formatted print: %.2f", (double)3123 / 100.) << mwx::crlf;
// formatted print: 31.23[改行]可変数引数リストに登録できる引数は最大8つです。doubleやuint64_t型など64bitのパラメータが含まれる場合は引数の数が制限されます。制限を超えた場合はstatic_assertによるコンパイルエラーになります。
コンストラクタで受け取った引数リストを、パラメータパックの展開機能を用いてクラス内部変数に格納する
operator << が呼び出された時点で、fctprintf() を呼び出し、ストリームにデータを書き出す
コンストラクタでは、書式のポインタとパラメータを保存します。続く <<演算子による呼び出しでフォーマットを解釈して出力処理を行います。
fmtは本オブジェクトが破棄されるまで、アクセス可能であることが必要です。
DIO割り込みを有効にします。
void attachIntDio(uint8_t u8pin, E_PIN_INT_MODE mode)事前に入力設定したピンに対して、1番目のパラメータは割り込みを有効にしたいピン番号で、2番目は割り込み方向(立ち上がり、立ち下がり)を指定します。
DIO5のピンがHIGHからLOWに変化したときに割り込みが発生する設定を行う。
アプリケーションビヘイビアmyAppClassの基本定義。詳細は省略している。
アプリケーションビヘイビアmyAppClassの割り込みハンドラの記述。DIO5の割り込み発生時にDIO12の出力設定を反転させ、割り込みハンドラが終了してから発生するイベントではシリアルポートSerialに*を表示する。
標準アプリケーションApp_Tweliteと同じ配線を想定したボードビヘイビアです。定数定義と、M1-M3,BPSピンの読み出し機能があります。
以下の定数を定義しています。BRD_APPTWELITE::PIN_DI1のようにアクセスできます。
static const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO12;
static const uint8_t PIN_DI2 = mwx::PIN_DIGITAL::DIO13;
static const uint8_t PIN_DI3 = mwx::PIN_DIGITAL::DIO11;
static const uint8_t PIN_DI4 = mwx::PIN_DIGITAL::DIO16;
static const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18;
static const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19;
static const uint8_t PIN_DO3 = mwx::PIN_DIGITAL::DIO4;
static const uint8_t PIN_DO4 = mwx::PIN_DIGITAL::DIO9;
static const uint8_t PIN_M1 = mwx::PIN_DIGITAL::DIO10;
static const uint8_t PIN_M2 = mwx::PIN_DIGITAL::DIO2;
static const uint8_t PIN_M3 = mwx::PIN_DIGITAL::DIO3;
static const uint8_t PIN_BPS = mwx::PIN_DIGITAL::DIO17;
static const uint8_t PIN_AI1 = mwx::PIN_ANALOGUE::A1;
static const uint8_t PIN_AI2 = mwx::PIN_ANALOGUE::A3;
static const uint8_t PIN_AI3 = mwx::PIN_ANALOGUE::A2;
static const uint8_t PIN_AI4 = mwx::PIN_ANALOGUE::A4;DIP SW (M1 M2 M3 BPS) ピンの値を取得するためのメソッドが用意されています。
戻り値はHIGH, LOWではなく、0がセットされていない(HIGH側)、1がスイッチがセットされる(LOW側)という意味です。
get_DIPSW_BM()は、bit0から順にM1,M2,M3,BPSピンの値を返します。
この値はシステム起動時に確認されて以降は、スイッチを操作しても更新されません。
twe::stream に改行コードを出力する
mwx::stream の << 演算子に対して改行コード (CR LF) を出力するためのヘルパークラスのインスタンスです。
値
解説
MSB(bit31)がセットされている
宛先としてシリアル番号を指定しています。
0x00-0xFF
宛先として論理ID(8bit)が指定されています。
パラメータ
解説
fmt
フォーマット書式。
TWESDK/TWENET/current/src/printf/README.md 参照
...
フォーマット書式に応じたパラメータ。 ※ 最大数は4で、5つ以上のパラメータではコンパイルエラーとなる。
※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。
void setup() {
the_twelite.app.use<myAppClass>();
pinMode(PIN_DIGITAL::DIO5, PIN_MODE::INPUT_PULLUP);
attachIntDio(PIN_DIGITAL::DIO5, PIN_INT_MODE::FALLING);
}
void loop() {
;
}inline uint8_t get_M1()
inline uint8_t get_M2()
inline uint8_t get_M3()
inline uint8_t get_BPS()
inline uint8_t get_DIPSW_BM()Serial << "hello world!" << mwx::crlf;インストールしたPC上からMWSDKをアンインストールするには以下を行ってください。
UNSET_ENV.cmdを実行してください。環境変数の設定を解除します。
MWSDKディレクトリを削除してください。
開発環境やシェルに MWX_ROOT環境変数を反映されるように設定してください。
方法はいくつかありますが、ホームディレクトリの.profile(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。
MWSDK_ROOT=/foo/bar/MWSDK/
export MWSDK_ROOT
エディタを使用せずに追加するには以下のようにコマンド入力します。$はプロンプトで環境によって表示が違います。/foo/bar/MSWSDKの部分はインストールしたディレクトリに応じて書き換えてください。
開発環境やシェルに MWX_ROOT環境変数を反映されるように設定してください。
方法はいくつかありますが、ホームディレクトリの.profile(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。
MWSDK_ROOT=/foo/bar/MWSDK/
export MWSDK_ROOT
エディタを使用せずに追加するには以下のようにコマンド入力します。$はプロンプトで環境によって表示が違います。/foo/bar/MSWSDKの部分はインストールしたディレクトリに応じて書き換えてください。
ポインタqで指定するバイト配列にuint8_t,ビッグエンディアンでuint16_t,uint32_tの値を書き込む。
q は書き込んだバイト数分だけインクリメントされる。
無線パケットのデータペイロードの生成・分解時の操作を簡略化するため。
より簡略化したpack_bytes(), expand_bytes()も利用できます。
inline uint8_t G_OCTET(const uint8_t*& p) {
return *(p)++;
}
inline uint16_t G_WORD(const uint8_t*& p) {
uint32_t r = *p++;
r = (r << 8) + *p++;
return r;
}
inline uint32_t G_DWORD(const uint8_t*& p) {
uint32_t r = *p++;
r = (r << 8) + *p++;
r = (r << 8) + *p++;
r = (r << 8) + *p++;
return r;
}void begin() {
sleepNow(); // the first time is just sleeping.
}void wakeup() {
if (!b_senser_started) {
// delete/make shorter this message if power requirement is harder.
Serial << mwx::crlf
<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
<< mwx::crlf
<< "..start sensor capture again."
<< mwx::crlf;
startSensorCapture();
b_senser_started = true;
napNow(); // short period sleep.
} else {
Serial << "..wake up from short nap.." << mwx::crlf;
auto&& brd = the_twelite.board.use<PAL_AMB>();
b_senser_started = false;
// tell sensors waking up.
brd.sns_LTR308ALS.process_ev(E_EVENT_START_UP);
brd.sns_SHTC3.process_ev(E_EVENT_START_UP);
}
}void napNow() {
uint32_t u32ct = 100;
Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}smplbuf_u8_attach& get_payload()const tsRxDataApp* get_psRxDataApp() uint8_t get_length()uint8_t get_lqi()uint32_t get_addr_src_long()
uint8_t get_addr_src_lid()uint32_t get_addr_dst()bool is_secure_pkt()format(const char *fmt, ...)class myAppClass: public mwx::BrdPal, MWX_APPDEFS_CRTP(myAppClasslMot)
{
};/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
#define __MWX_APP_CLASS_NAME myAppClass
#include "_mwx_cbs_cpphead.hpp"
/*****************************************************************/
MWX_DIO_INT(PIN_DIGITAL::DIO5, uint32_t arg, uint8_t& handled) {
static uint8_t ct;
digitalWrite(PIN_DIGITAL::DIO12, (++ct & 1) ? HIGH : LOW);
handled = false; // if true, no further event.
}
MWX_DIO_EVENT(PIN_DIGITAL::DIO5, uint32_t arg) {
Serial << '*';
}
/*****************************************************************/
// common procedure (DO NOT REMOVE)
#include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
#undef __MWX_APP_CLASS_NAME
} // mwx
/*****************************************************************/
$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profile$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profileMWSDK_ROOT=C:/MWSDK/
MW_ROOT_WINNAME=C:\MWSDK\ inline uint8_t& S_OCTET(uint8_t*& q, uint8_t c) {
*q++ = c;
return *q;
}
inline uint8_t& S_WORD(uint8_t*& q, uint16_t c) {
*(q) = ((c) >> 8) & 0xff; (q)++;
*(q) = ((c) & 0xff); (q)++;
return *q;
}
inline uint8_t& S_DWORD(uint8_t*& q, uint32_t c) {
*(q) = ((c) >> 24) & 0xff; (q)++;
*(q) = ((c) >> 16) & 0xff; (q)++;
*(q) = ((c) >> 8) & 0xff; (q)++;
*(q) = ((c) & 0xff); (q)++;
return *q;
}TWELITE の UART0 ポート (mwx::serial_jen)
mwx::stream を実装し TWELITE の UART0 で入出力する。
Serialオブジェクトはシステム起動時に UART0, 115200 bps で初期化され、ライブラリ内で初期化処理が行われます。ユーザコード上は、setup()から利用できます。
Serial1オブジェクトは、ライブラリ内で用意されていますが、初期化処理は行っていません。UART1を有効化するためには、必要な初期化手続き Serial1.setup(), Serial1.begin() を行ってください。
起動直後の setup(), wakeup() やスリープ直前の flush 処理で、出力が不安定になる場合があります。
オブジェクトの初期化を行う。
TX/RX用のFIFOバッファのメモリ確保
TWE_tsFILE 構造体のメモリ確保
Serial(UART0) は ライブラリ内で setup() の呼び出しが自動で行われます。ユーザによる呼び出しを行う必要はありません。
また、Serial (UART0) のバッファサイズは、コンパイル時に決定されます。マクロ MWX_SER_TX_BUFF (未指定時は 768), MWX_SER_RX_BUFF(未指定時 256) により変更できます。
ハードウェアの初期化を行う。
Serial (UART0) は ライブラリ内で begin() の呼び出しが自動で行われます。ユーザによる呼び出しを行う必要はありません。
(未実装)ハードウェアの使用を停止する。
Cライブラリで利用する TWE_tsFILE* 形式での構造体を得る。
ヘルパークラス版はより抽象度が高い実装です。読み書きを行うオブジェクト transceiver を生成することが、バスの利用開始となり、オブジェクトを破棄するとバス利用の終了手続きを行います。
if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。
また、読み書きオブジェクトは、mwx::streamインタフェースを実装しているため<<演算子などを利用することができます。
バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぎます
クラスオブジェクトは、MWXライブラリであらかじめ定義されたオブジェクトで、TWENETを取り扱うthe_twelite、ペリフェラルの利用のためのオブジェクトが定義されています。
各オブジェクトは.setup(), .begin()メソッドの呼び出しを行って初期化する必要があります。(UART0を利用するSerialオブジェクトのみ初期化は必要ありません)
パラメータ
解説
buf_tx
TX用のFIFOバッファサイズ
buf_rx
RX用のFIFOバッファサイズ
パラメータ
解説
speed
UART のボーレートを指定する。
config
serial_jen::E_CONF::PORT_ALTビットを指定したときは、UART1をDIO14,15で初期化します。指定しない場合はDIO11(TxD),9(RxD)で初期化します。
.prepare_tx_packet()pktpacket_txこのpktオブジェクトは、まず、()内の条件判定にてtrueかfalseを返します。falseが返ってくるのは、送信用のキューが一杯でこれ以上要求が追加できない時です。
無線パケットには宛先情報など相手に届けるための様々な設定を行います。設定には設定内容を含むオブジェクトを<<演算子の右辺値に与えます。
以下に設定に用いるオブジェクトについて記載します。
各設定の利用可否や意味合いは、ネットワーク ビヘイビアの仕様によります。
宛先アドレスaddrを指定します。宛先アドレスの値については、ネットワークビヘイビアの仕様を参照してください。
MSB(bit31=0x80000000)がセットされるアドレスは、無線モジュールのシリアル番号宛という意味になります。
0x00..0xEFは、8bitの論理IDを意味します。0xFEは子機宛(0x01..0xEF)の同報通信(ブロードキャスト)、0xFFは親機子機関係なく同報通信(ブロードキャスト)します。
再送回数の指定を行います。再送回数はu8countで指定します。force_retryは、送信が成功しようがしまいが、指定回数の再送を行う設定です。
ネットワークビヘイビア<NWK_SIMPLE>では、同じ内容のパケットをu8count+1回送信します。
force_retryの設定は無視されます。
パケットを送信するまでの遅延と再送間隔を設定します。u16DelayMinとu16DelayMaxの2つの値をミリ秒[ms]で指定します。送信要求をしてからこの間のどこかのタイミングで送信を開始します。再送間隔をu16RetryDurの値[ms]で指定します。再送間隔は一定です。
内部処理の都合で指定通りのタイミングで送信処理が始まらない場合もあります。また、IEEE802.15.4の処理でもパケット創出までの時間ブレが発生します。これらのタイミングのブレは、多くのシステムではパケットの衝突回避を行う上で有効な手立てとなります。
厳格なタイミングでのパケット送信は、IEEE802.15.4の規格の性質上、例外的な使用方法とお考え下さい。
この指定は有効です。
最初の送信から1秒を超えて再送され到達した同一パケットについては、新たなパケットが到達したとして重複除外が為されません。再送間隔を長く設定したり、中継でのパケット到達遅延により1秒を超えて同じパケットを受信する場合があります。
重複パケットの処理の設定は<NWK_SIMPLE>ビヘイビアの初期化で設定できます。
パケット送信を「できるだけ速やかに」実行するように要求する設定です。TWENETでのパケット送信処理は、1msごとに動作するTickTimer起点で行われています。この設定をすることで、要求後速やかにパケット送信要求が処理されます。もちろん、tx_packet_delay(0,0,0)以外の設定では意味がない指定になります。
他のパケット送信処理が行われている場合は、通常の処理になります。
この指定は有効です。
無線パケット通信では、送信完了後、送信相手先からACK(アック)という短い無線電文を得て、送信成功とする送信方法があります。このオプションを設定することで、ACK付き送信を行います。
<NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。
<NWK_SIMPLE>は、シンプルに動作する中継ネットワークの実装を目的としており、ACK付きの通信は行いません。
ブロードキャストの指定を行います。
<NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。
替わりに宛先アドレスtx_addr(0xFF) (ブロードキャスト)またはtx_addr(0xFE)(子機宛のブロードキャスト)を指定します。
0..7の指定ができるTWENET無線パケットのタイプIDを指定します。
<NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。
<NWK_SIMPLE>ではタイプIDを内部的に使用しています。ユーザは使用できません。
mwx::streamインタフェースによる読み書き手続きを統一します
読み込み処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法。
上記では get_rwer() メソッドにより生成された x オブジェクトを用いて1バイトずつ読み書きを行っています。
if(...) 内で x オブジェクトを生成します。同時にSPIバスのセレクトピンをセットします。(型は、型推論によるユニバーサル参照 auto&& で解決しています。)
生成した x オブジェクトには operator bool () が定義されており、判定式の評価として利用される。SPIバスの場合は常に true となる。
x オブジェクトには uint8_t transfer(uint8_t) メソッドが定義されていて、これを呼び出すことでSPIに対して8bitの読み書き転送を行。
if() { ... } スコープの末尾で x のデストラクタが呼び出され、SPIバスのセレクトピンを解除します。
SPIバスの読み書きに用いるワーカーオブジェクトを取得します。
それぞれ8bit,16bit,32bitの転送を行い、読み取り結果を書き込んだデータ幅と同じデータ幅で返す。
int型,uint8_t型は8bitの転送を行います。
uint16_t型、uint32_t型は、それぞれ16bitの転送、32bitの転送を行います。
転送結果は最大16バイトの内部FIFOキューに格納され >> 演算子により読み出します。バッファが大きくないので、転送都度読み出すことを想定します。
直前の転送と同じデータ幅の変数を指定します。
読み出した結果が不要の場合はnull_stream()オブジェクトを使用します。iで指定したデータバイト分だけ読み飛ばします。
void setup(uint16_t buf_tx, uint16_t buf_rx)void begin(unsigned long speed = 115200, uint8_t config = 0x06)TWE_tsFILE* get_tsFile();if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
pkt << tx_addr(0x00)
<< tx_retry(0x1)
<< tx_packet_delay(0,50,10);
pack_bytes(pkt.get_payload()
, make_pair("APP1", 4)
, uint8_t(u8DI_BM)
);
pkt.transmit();
}if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
...
}pkt << tx_addr(0x00)
<< tx_retry(0x1)
<< tx_packet_delay(0,50,10);tx_addr(uint32_t addr)tx_retry(uint8_t u8count, bool force_retry = false)tx_packet_delay(uint16_t u16DelayMin,
uint16_t u16DelayMax,
uint16_t u16RetryDur)tx_process_immediate()tx_ack_required()tx_addr_broadcast()tx_packet_type_id(uint8_t)uint8_t c;
if (auto&& trs = SPI.get_rwer()) { // オブジェクトの生成とデバイスの通信判定
// このスコープ(波かっこ)内が trs の有効期間。
trs << 0x00; // 0x00 を mwx::stream インタフェースで書き出し
trs >> c; // 読み出したデータをcに格納。
}
// if 節を抜けるところで wrt は破棄され、バスの利用終了inline uint8_t _spi_single_op(uint8_t cmd, uint8_t arg) {
uint8_t d0, d1;
if (auto&& x = SPI.get_rwer()) {
d0 = x.transfer(cmd); (void)d0;
d1 = x.transfer(arg);
// (x << (cmd)) >> d0;
// (x << (arg)) >> d1;
}
return d1;
}periph_spi::transceiver get_rwer()uint8_t transfer(uint8_t val)
uint16_t transfer16(uint16_t val)
uint32_t transfer32(uint32_t val)operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)
null_stream(size_t i = 1)
operator >> (null_stream&& p)TWELITE STAGE の配布パッケージリリース後の修正・追加分などはGitHubレポジトリに格納しております。必要に応じて配布パッケージの位置を差し替えて利用いただくようお願いいたします。
MWSDKの他の更新が必要になる場合があります。更新時のリリース記述を参照してください。MWSDKの更新についてはを参照ください。
ライブラリのソースコードは GitHub ()にて公開しています。ライブラリのソースコードの差し替えは、以下の手順で行ってください。
各リリースのリンクよりGitのクローンを行うか zip 形式でソースコードをダウンロードします。
以下のディレクトリの内容を差し替えます。
リリース前の更新については上記に掲載する場合があります。
商・余を計算する div100() をSerial等へ出力できるようにした
smplbuf<> 配列クラスの実装変更。消費メモリの削減などを目的としてmwx::streamの継承をやめ、別途継承クラスとヘルパークラス定義した
mwx_printf() mwx_snprintf()
()
を追加
チャネルマネージャ の実装
()
delayMilliseconds() の追加
digitalReadBitmap() の追加
delay() の精度向上
MWSDK2020_05 に対応
重複チェッカ duplicate_checker の初期化等に不備があり期待通りの除去を行っていなかった
format() の実装を機種依存の少ないものとした。また、引数を最大8までとした。64bit引数が含まれる場合は引数の数は制限される。
修正は MWSDK2020_05 を前提としています。
本修正については、更新を推奨します。
MWSDK2020_04 に対応
Timer0..4の初期化の問題を修正
mwx::format() の内部処理を変更
インタラクティブモード対応のための実験的なコードの追加
本修正は MWSDK2020_04 を前提としています。
本修正については、更新を推奨します。
パケット内の中継フラグの扱いについての問題を修正
本修正については、更新を推奨します。
初版リリース (SDL 2019/12月号収録)
ADC (mwx::periph_analogue.hpp)
Analogueは、ADCの実行と値の取得を行います。一度に複数のチャネルを連続取得でき、またこれをタイマーなどの周期に合わせて逐次実行可能です。
標準アプリ(App_Twelite)では、半導体データシート中のピン名ADC2/ADC3が、TWELITE DIPの並びにあわせてAI3/AI2 となっています。ご注意ください。
*1 ディジタル、アナログ共用のADC2/ADC3ピンは利用手続きと利用制限があります。
ADC開始前に利用するピンをプルアップ無しとします。これを実行しないと常にプルアップ電圧をADCで観察することになります。
通常の回路構成では、スリープ時には電流リークが発生します。 ソフトウェアの記述のみで回避することは出来ません。
ADCの初期化を行います。setup()では、半導体内部のレギュレータの始動、周期実行するためのタイマーデバイスの指定、指定チャネルすべてのADCが終了したときに呼び出されるコールバック関数の指定します。
1番目のパラメータにはADCを行いたいポートを指定します。ポートの指定はピンの定義で述べたポート番号に対応するビットをセットしたビットマップになります。例えば PIN_ANALOGUE::A2とPIN_ANALOGUE::VCCの2つのピンの値を得たい場合は (1 <<PIN_ANALOGUE::A1 | 1<<PIN_ANALOGUE::VCC )を指定します。を用いpack_bits(PIN_ANALOGUE::A1,PIN_ANALOGUE::VCC)のように記述することもできます。
begin()の呼び出し後、速やかに最初のADC処理が開始され、その終了割り込から次のピンの処理を開始します。すべての処理が終われば(指定されている場合)コールバック関数が呼び出されます。次のタイマー割り込みが発生まで待ってから新たなADC処理を開始します。
2番目のパラメータは、ACを開始するまでのタイマー割り込みの回数を指定します。例えばTickTimerは1msごとに呼び出されますが、パラメータに16を指定すれば 16msごとの処理になります。
デフォルトのADCピン(PIN_ANALOGUE::A1,PIN_ANALOGUE::A2)を指定してADC処理を開始します。end()では中断したADC処理を再開します。
ADC処理を終了し、半導体内部のレギュレータを停止します。
ADCの値が取得後にtrueになります。本関数により確認した後は次のADC完了まではfalseです。
ADC値を読み出します。パラメータには読み出したいADCピンを指定します。read()はmVに変換した読み値、read_raw()はADCの値(0..1023)を戻します。
ADC完了(available)後、次のADC処理が実行するタイミング付近まで遅れて値を読み出すと、次のADC値が戻される場合があります。ADCの処理は割り込みハンドラで実施されているためloop()の処理中であっても値が更新されるためです。
ADCの割り込みハンドラはsetup()の呼び出し時にperiph_analogue::ADC_handler()に設定されます。
半導体のペリフェラルライブラリで別途ハンドラを指定すると正常に動作しなくなります。
ADCがbegin()により周期実行状態であれば、スリープ復帰後もADC処理を再開します。
Makefileはbuild/Makefileに格納されています。makeコマンドを実行することで、アクトをビルドするよう予め定義されています。
プロジェクトディレクトリを他の環境からコピーしたあとには、必ずbuild/objs_???ディレクトリを削除してください。他の環境での中間ファイルが残っているとmakeがエラーになります。
(MWSDK 2020-04) USE_APPDEPS=0 を付加して clean してから、改めて make コマンドを実行することでエラーを回避できます。
$ make USE_APPDEPS=0 TWELITE=BLUE clean
...
$ make TWELITE=BLUE
ビルド対象をBLUEまたはREDで指定します。TWELITE BLUEならmake TWELITE=BLUEと指定します。
ビルドを実行します。通常は省略してmake TWELITE=BLUEのように実行します。
ビルドの中間ファイルを削除します。make TWELITE=BLUE cleanのように実行します。
すべての中間ファイルを削除します。make cleanallのように実行します。buildディレクトリのobjs_???ディレクトリをすべて削除するのと同じです。
1 (デフォルト) を設定すると、ファイルの依存関係をもとに、ビルドファイルを決定します。例えば、ヘッダファイルに変更があった場合に関連するソースファイルが再コンパイル対象となります。
0 では依存関係を評価しません。0 に設定した場合、矛盾ある中間ファイルが残っていても makefile がエラーになりません。
アクトの規模に応じて、また、ビヘイビアの定義をする場合には、通常はソースファイルを分割してビルドします。
ビルドファイルの一つは{プロジェクトフォルダ名.cpp}です。
他にファイルを定義する場合は、プロジェクトフォルダのbuild/Makefileを編集します。
上記はでのMakefileの例です。
バージョン番号を指定します。ビルド結果ファイル名に反映されます。
コンパイル中は -DVERSION_MAIN=0 -DVERSION_SUB=1 -DVERSION_VAR=0のように定義として渡されます。
(MWSDK 2020-04) サブディレクトリにファイルを配置しない場合は、追加指定は不要になりました。プロジェクトファイルにある .c .cpp ファイルがすべて追加されます。
ソースファイルを追加する際に必要なのはAPPSRC_CXXとAPP_COMMON_SRC_DIR_ADD?です。
ソースファイル名をAPPSRC_CXXに追記します。このファイル名にはディレクトリ名が含まれてはいけません。サブディレクトリにあるものもディレクトリなしで指定します(つまり同じファイル名がサブディレクトリにある場合は、ビルドが失敗します)
次にソースファイルがプロジェクトディレクトリ以外の場所に格納されている場合の検索パスを指定します。最大4つまで設定できます。
ディレクトリの指定はMakefileからの相対パスになります。
その他にもいくつかのオプションをコンパイラ・リンカに渡すことができます。
ここではアクト(BINファイル)の書き込みと実行について解説します。
この節はWindows10環境のユーティリティTWE-Programmerを用いた解説です。
Linux/macOSではtweterm.pyを用います。本ページでの書き込み・実行の流れを参照いただいた上、tweterm.pyを利用ください。
出来上がったBINファイルをTWELITEに書き込むにはTWE-Programmerを使用します。詳しい使い方はを参照ください。
BINファイルが出来上がれば、実機で動作させることになります。繊細な電子部品ですので、取り扱いには十分注意してください。以下に代表的な注意事項を記載します。
特にTWELITE Rを用いている場合は、多くの場合電子基板がケースなどを介さず直接外部に触れる状態で使用するため、意図しないショートやノイズなどUSBデバイスが正常に動作しない状態になる場合があります。
この場合は、アプリケーションを終了させUSBデバイスを抜き差しすることで通常は回復します。最悪、USBデバイスの破損やPCの破損も考えられます。
電子基板の取り扱いには十分注意してください。
デバイスが認識されれば、新たにCOMポートが追加されます。複数あってわかりにくい場合は、抜いた状態でのCOMポートと刺した状態でのCOMポートの様子を観察してみてください。
デバイスマネージャでデバイスの存在が確認できない場合は、導入を行ってみてください。FT232R用のドライバを選択します。
{MWSDKインストールディレクトリ}/Tools/TWE-Programmerディレクトリを開き、TWE-Programmer.exe をダブルクリックします。
TWE-Programmer.exe を起動すると以下のような画面になります。画面例では、起動前に MONOSTICK を差し込んでいて COM6 に割り当てられています。
MONOSTICK や TWELITE R が表示されない場合は以下を試してください。
TWE-Programmer を終了する。
MONOSTICK や TWELITE RをPCから抜く(取り外す)。
ファイルダイアログから BIN ファイルを指定します。
BINファイルの書き込みは、BINファイルを指定すると自動で行われます。
書き込みが成功するとTWELITEは自動でリセットして、ターミナルが表示されます(ターミナル接続するにチェックが入っている場合)。
MWSDK添付のアクトファイルは始動時にメッセージを表示します。何か表示されれば書き込みは無事終了し、TWELITE 無線モジュール上のアクトが無事に動作していることが確認できます。
書き込みに失敗したときは、ターミナルは開かず、TWE-Programmerのウインドウ背景がピンク色になります。
書き込みが失敗したときは (再)書き込みボタンを押して、もう一度書き込みを実行してください。
TWE-Programmer が反応しないなど、うまくいかない場合は、以下を実行した上、USBへの接続からやり直してください。
TWE-Programmer を終了する
TWELITE PAL での書き込み中に、ハードウェア・ウォッチドッグタイマーが作動し書き込みが中断する場合があります。再度、書き込みを行ってください。
WirelessUARTはシリアル通信を行います。
このアクトの解説の前にBRD_APPTWELITEの解説をご覧ください。
2台のUART接続のTWELITE同士をアスキー書式で通信する。
いずれかを2台。
でUART接続されているなど
論理IDはrandom(1,5)により1,2,3,4のいずれかの値に割り当てています。通常は、論理IDを設定保存したデータ、またはDIP SWといったハードウェアの情報から生成します。
を初期化します。
シリアルからのデータ入力があった時点で、シリアルパーサーに1バイト入力します。アスキー形式が最後まで受け付けられた時点でSerialParser.parse()はtrueを戻します。
SerialParserは内部バッファに対してsmplbufでアクセスできます。上の例ではバッファの1バイト目を送信先のアドレスとして取り出し、2バイト目から末尾までをtransmit()関数に渡します。
パケットを受信したときには、送信元を先頭バイトにし続くペイロードを格納したバッファsmplbuf_u8<128> bufを生成し、出力用のシリアルパーサーserparser_attach poutからシリアルに出力しています。
テストデータは必ずペースト機能を用いてターミナルに入力してください。入力にはタイムアウトがあるためです。
参考: TWE ProgrammerやTeraTermでのペーストはAlt+Vを用います。
任意の子機宛に00112233を送付します。
子機3番に対してAABBCC00112233を送付します。
TWELITE PALのハードには共通部分があり、ボードビヘイビアも共通ハードについては、共通のインタフェースを定義しています。
以下の定義が利用可能になります。
PAL_AMB::PIN_BTNのようにアクセスできます。
上記のコードのように各ピンが初期化されます。
起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。
ウォッチドッグタイマーをタイムアウトしないためにTWELITEを60秒以内の設定(キャリブレーション済み内部CRタイマー使用時)で起床してください。
LED(D1)の制御を行います。
ボードビヘイビアでの制御を行わない場合は、このメソッドを呼び出さないでください。
modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。
指定期間だけLEDを点灯します。set_led()の機能と同時には使えません。
get_D1() .. get_D4()はDIP SWがHIGH(スイッチが上)の時0、LOW(スイッチが下)のとき1を返します。
get_DIPSW_BM()はDIP SWの設定値を0..15で返します。SW1==LOW を1, SW2 == LOWを2, SW3 == LOWを4, SW4 == LOWを8とした和を返します。
D1..D4のHIGH(1),LOW(0)の値とは反対になります。
DIP SWのLOW(0)側がセットされた、つまり、1の値を持つと意味付けしているためです。
この値はシステム起動時に確認されて以降は、スイッチを操作しても更新されません。
バイト列を分解し変数に格納します。
expand_bytes()は、パラメータにuint8_t*型のイテレータの組み合わせを指定します。これは解析対象の先頭と末尾の次のイテレータの指定となります。eの位置まで解析が進んだ場合はエラーとなりnullptrを返します。
展開にエラーがない場合は、次の読み出しを行うイテレータを戻します。
可変数パラメータには以下を指定します。
バイト数
データ長
解説
この例では、まず4バイトの文字列を読み出しています。ここではmake_pair()を用いて明示的に4バイト分のデータを読み出します。
戻されたイテレータnpをもとに、次のデータを読み出します。次のデータはuint8_t型、あとはuint16_t型が5つ続いています。
無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t型のバイト配列の記述を簡素化するため。
上記はもっとも単純な記述だが、以下のようにを用いてバイト配列から読み出せる。
MWSDK2020_05 には含まれません。対応パッケージはMWSDK_2020_07_UNOFFICIAL以降となります。
ポーリングによる時間待ちを行います(μ秒指定)。
microsecにて与えられた期間待ち処理を行います。
時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。
static const uint8_t PIN_BTN = 12; // button (as SET)
static const uint8_t PIN_LED = 5; // LED
static const uint8_t PIN_WDT = 13; // WDT (shall tick every 60sec)
static const uint8_t PIN_D1 = 1; // DIP SW1
static const uint8_t PIN_D2 = 2; // DIP SW2
static const uint8_t PIN_D3 = 3; // DIP SW3
static const uint8_t PIN_D4 = 4; // DIP SW4
static const uint8_t PIN_SNS_EN = 16;
static const uint8_t PIN_SNS_INT = 17;const uint8_t* expand_bytes(
const uint8_t* b, const uint8_t* e, ...)Act/behavior Programming Interface
MWXライブラリのAPIは、今後、改善を目的として仕様の変更を行う場合があります。
loop()関数の初回コールの手前で一度だけ呼び出されます。TWENET の初期化は終了しているのでsetup()のような制約を考慮する必要はありません。
主な使い方は、
始動メッセージの表示
テスト用のコードを記述
始動直後のスリープ遷移
setup()で不都合がある処理(無線パケット処理・タイマー動作など)
通常は使用しません。
ペリフェラルAPIも初期化もされていない、コード実行の再初期に呼び出されます。
the_twelite.stop_watchdog(), the_twelite.restart_watchdog() を追加した
mwx::stream のメンテナンス: operator bool() の廃止。読み出しタイムアウトの設定で 0xff を指定した場合(.set_timeout(0xff))タイムアウトを無効に。その他 << 演算子の定義を追加。
NOTICE PAL / PCA9632 のサポートを追加 (解説 https://mwx.twelite.info/v/latest/boards/pal/pal_notice, サンプル https://github.com/monowireless/Act_samples/tree/master/Unit_using_PAL_NOTICE)
除算を行わない 8bit と 0..1000 間のスケール関数を追加。
10,100,1000による除算(商と余を同時に計算) div10(), div100(), div1000() を追加。値域を制限し乗算とビットシフトを中心に構成。
暗号化パケットの対応メソッドを追加
packet_rx::is_secure_pkt() : 受信パケットが暗号化されているかどうかの判定
STG_STD::u8encmode() : インタラクティブモードでの暗号化設定を取得
STG_STD::pu8enckeystr() : インタラクティブモードでの暗号化鍵バイト列の取得
Serial1: デフォルトのポートは半導体の仕様では I2C と重複する DIO14,15 だが、通常 I2C に割り当てられるため DIO11(TxD), DIO9(RxD) とした。
Serial: ボーレートの指定で /100 が発生するが、主要なボーレートについてこの計算を省略するようにした。
Serial: available(), read() を外部で実施するための代理関数の保持を void* のみとし、仕様メモリを 8bytes 削減。
typedef boolean の追加
ネットワーク: 暗号化の対応を追加。
暗号化を有効にするには NWK_SIMPLE::secure_pkt(const uint8_t*, bool = false) を設定追加する。1番目のパラメータは暗号キー、2番目を true にすると、平文のパケットも受信する。
SHT3xとBME280のセンサーサポート追加
センサー: レガシーコード(Cライブラリのラッパクラス)で、設定パラメータや状態をやり取りするための仕掛けを追加した。
センサー: SHT3x, BME280では I2C アドレスを指定可能とした。
設定: hide_items() を追加。不要な設定項目を削除可能。
設定: H/W UTIL メニューを追加。DIの状態表示、I2Cのプローブ、PAL EEPROM内容の表示。
設定: 暗号化関連のメニューの追加
I2C関連の修正(TwoWireクラスを用いて実装されたコードとの親和性を向上するための修正)
requestFrom(false) の処理時に NO_STOP メッセージの送信コードが無かったため処理が正常に行われなかった。
TwoWire のクラス名エリアスを追加した。
begin() 処理で、多重初期化しないようにした。
setClock() メソッドを追加(ただしダミー関数で何もしない)
WIRE_CONF::WIRE_???KHZ を追加。バスクロックの主要な設定値を追加した。
Serial1 インスタンスが定義されていない問題を修正
Analogueの割り込みハンドラが呼び出されない問題を修正
ライブラリ名
依存バージョン
mwx
twesettings
TWENET C
1.3.4
ライブラリ名
依存バージョン
mwx
twesettings
TWENET C
1.3.4
ライブラリ名
依存バージョン
mwx
twesettings
TWENET C
1.3.3
uint8_t PIN_ANALOGUE::VCC = 4
Vcc 電源電圧
定数
種別
標準アプリでのピン名
uint8_t PIN_ANALOGUE::A1 = 0
ADC1ピン
AI1
uint8_t PIN_ANALOGUE::A2 = 1
ADC2ピン
AI3
uint8_t PIN_ANALOGUE::A3 = 2
uint8_t PIN_ANALOGUE::D0 = 2
ADC3ピン (DIO0) *1
AI2
uint8_t PIN_ANALOGUE::A4 = 3
uint8_t PIN_ANALOGUE::D1 = 3
ADC4ピン (DIO1) *1
パラメータ
解説
bWaitInit
trueを指定すると、半導体内部のレギュレータの初期化を待つ。
kick_ev
周期実行に指定するタイマーデバイスを指定する。指定可能なデバイスは、以下の5種類で、初回以外は割り込みハンドラ内でADが開始される。
E_AHI_DEVICE_TICK_TIMER (TickTimer)
E_AHI_DEVICE_TIMER0 .. 4 (Timer0 .. 4)
fp_on_finish
指定されたポートすべてのADCが終了後に、割り込みハンドラ内から呼び出されるコールバック関数。ADC計測値をFIFOキューなどに別途格納したい場合に利用する。
AI4
指定
内容
CXXFLAGS
C++ソースファイルに対してコンパイルオプションを指定します。
CFLAGS
C/C++ソースファイルに対してコンパイルオプションを指定します。
INCFLAGS
ヘッダファイルのインクルードファイル指定をします。
OPTFLAGS
特別な理由があって-Os以外のコンパイルオプションを適用したい場合に定義します。
LDFLAGS
リンカオプションを指定します。(上記Makefileのコメントには記述はありませんが指定は可能です)
指定
意味
LED_TIMER::BLINK
LEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RX
パケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP
送信完了時にtickに与える時間[ms]だけ点灯します。
uint8_t
1
uint16_t
2
ビッグエンディアン並びとして展開する
uint32_t
4
ビッグエンディアン並びとして展開する
uint8_t[N]
N
uint8_t 型の固定長配列
std::pair<char*,N>
N
char*,uint8_t*型の配列と配列長のペアmake_pair()で生成できる
setup(), wakeup()関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。パラメータに10以下といった短い時間を指定した場合は、誤差が大きくなる場合があります。
void delayMicroseconds(uint32_t microsec)ポーリングによる時間待ちを行います。
void delay(uint32_t ms)msにて与えられた期間待ち処理を行います。
時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。
setup(), wakeup()関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。
パラメータに1,2といった短い時間を指定した場合は、誤差が大きくなる場合があります。
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)
<< NWK_SIMPLE::secure_pkt((const uint8_t*)"0123456789ABCDEF");
;.../MWSTAGE/ --- TWELITE STAGE 配布ディレクトリ
.../MWSDK --- MWSDKディレクトリ
.../TWENET/current/src/mwx <-- このディレクトリを差し替えるpinMode(PIN_DIGITAL::DIO0, PIN_MODE::INPUT);
pinMode(PIN_DIGITAL::DIO1, PIN_MODE::INPUT);void setup(
bool bWaitInit = false,
uint8_t kick_ev = E_AHI_DEVICE_TICK_TIMER,
void (*fp_on_finish)() = nullptr) void begin(uint8_t bmPorts, uint8_t capt_tick = 1)void begin()void end()inline bool available()inline int16_t read(uint8_t s)
inline int16_t read_raw(uint8_t s)##############################################################################
# Copyright (C) 2019 Mono Wireless Inc. All Rights Reserved.
# Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
# AGREEMENT).
##############################################################################
# USER PROJECT BUILD DEFINITION.
##############################################################################
#####################################################################
### set TWELITE model
TWELITE ?= BLUE
#TWELITE = RED
#####################################################################
### set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB = 1
VERSION_VAR = 0
#####################################################################
### set an additional source file
### the default file name is dirname.
### for C++ files compiled with g++ (must have .cpp suffix)
APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cpp
### for C files compiled with gcc (must have .c suffix)
#APPSRC += my_c_file.c
### Additional Src/Include Path
# if set, find source files from given dirs.
#
APP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Child
#APP_COMMON_SRC_DIR_ADD3 =
#APP_COMMON_SRC_DIR_ADD4 =
#####################################################################
### set misc option for compiler
### C++ flags passed to g++
# e.g. CXXFLAGS += -DMY_DEFS
#CXXFLAGS +=
### C++/C flags passed to g++/gcc
# e.g. CFLAGS += -DMY_DEFS
#CFLAGS +=
### include opts
# e.g. INCFLAGS += -I../my_common_src/
#INCFLAGS +=
### optimize flag (default is -Os, normally no need to change)
#OPTFLAG=-O2
#####################################################################
### must include mwx.mk (the makefile body part.)
MWSDK_PATH?=$(realpath $(MWSDK_ROOT))
include $(MWSDK_PATH)/MkFiles/mwx.mk
######################################################################## set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB = 1
VERSION_VAR = 0APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cppAPP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Childvoid setup() {
/*** SETUP section */
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
uid = random(1, 5); // set uid by random() (1..4)
nwk << NWK_SIMPLE::logical_id(uid); // set Logical ID. (0xFE means a child device with no ID)
/*** BEGIN section */
SerialParser.begin(PARSER::ASCII, 128); // Initialize the serial parser
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- WirelessUart (id=" << int(uid) << ") ---" << mwx::crlf;
}SerialParser.begin(PARSER::ASCII, 128); while(Serial.available()) {
if (SerialParser.parse(Serial.read())) {
Serial << ".." << SerialParser;
const uint8_t* b = SerialParser.get_buf().begin();
uint8_t addr = *b; ++b; // the first byte is destination address.
transmit(addr, b, SerialParser.get_buf().end());
}
}if (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
// check the packet header.
const uint8_t* p = rx.get_payload().begin();
if (rx.get_length() > 4 && !strncmp((const char*)p, (const char*)FOURCHARS, 4)) {
Serial << format("..rx from %08x/%d", rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;
smplbuf_u8<128> buf;
mwx::pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // src addr (LID)
, make_pair(p+4, rx.get_payload().end()) ); // data body
serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
Serial << pout;
}
}:FE00112233X
:FE001122339C
:03AABBCC00112233X
:03AABBCC0011223366
pinMode(PIN_BTN, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT_INIT_HIGH);
pinMode(PIN_WDT, OUTPUT_INIT_HIGH);
pinMode(PIN_D1, INPUT_PULLUP);
pinMode(PIN_D2, INPUT_PULLUP);
pinMode(PIN_D3, INPUT_PULLUP);
pinMode(PIN_D4, INPUT_PULLUP);void set_led(uint8_t mode, uint16_t tick)void led_one_shot(uint16_t tick)inline uint8_t get_D1()
inline uint8_t get_D2()
inline uint8_t get_D3()
inline uint8_t get_D4()
inline uint8_t get_DIPSW_BM()auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
auto&& np =
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, make_pair((uint8_t*)fourchars, 4)
);
// 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]
);auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
uint8_t *p = rx.get_payload().begin();
fourchars[0] = p[0];
fourchars[1] = p[1];
fourchars[2] = p[2];
fourchars[3] = p[3];
fourchars[4] = 0;
p += 4;
u8DI_BM_remote = (p[0] << 8) + p[1]; p+=2;
au16AI_remote[0] = (p[0] << 8) + p[1]; p+=2;
...auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
uint8_t *p = rx.get_payload().begin();
fourchars[0] = G_OCTET(p);
fourchars[1] = G_OCTET(p);
fourchars[2] = G_OCTET(p);
fourchars[3] = G_OCTET(p);
fourchars[4] = 0;
u8DI_BM_remote = G_WORD(p);
au16AI_remote[0] = G_WORD(p);
...回路の間違い
電源を入れる前にはもう一度回路を確認してください。
電池の逆差しや過大電圧には注意してください。
静電気
人感がない電圧であっても半導体の故障になりえます。大掛かりな対応をしなくとも、金属部に触れてから作業する・リストバンド・専用マットなど簡易にできる対応でも相応の効果はあります。
金属物などが触れることでのショート
電子基板の近くには金属物がないようにしてください。クリップなどが散らかっているとこれが原因でショートすることもありますし、電池が大放電して加熱する危険な状況も考えられます。
もう一度MONOSTICK や TWELITE Rを差し込む。
TWE-Programmer を起動する。
これでもうまくいかない場合は以下も試してください。
PCから可能な限り他のUSBデバイスを取り外す。
PCを再起動してからMONOSTICK や TWELITE Rを差し込んでみる。
FTDI社のドライバを再導入してみる。
MONOSTICK や TWELITE Rを取り外す
場合によってはPCの再起動を行ってください。
アプリケーションの記述を行うコールバック関数です。コールバックはシステム(ライブラリ)から呼び出されるという意味です。ユーザがいくつかのコールバック関数を定義することでシステムの振る舞いを記述します。
以下のコールバック関数は必須定義です。
setup()
loop()
それ以外の関数は定義しない場合は、何も実行しない空の関数が替わりにリンクされます。
正確なふるまいを参照したい方はソースコードmwx_appcore.cppを参照してください。
正確なふるまいを参照したい方はソースコードmwx_appcore.cppを参照してください。
内部が配列構造のコンテナクラスです。初期化時にバッファの最大サイズを決定しますが、その最大サイズまでの範囲で可変長の配列として振る舞います。
smplbufは要素の型Tとで指定したメモリ領域に対して配列の操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。
オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、サイズは0です。必要に応じてサイズを拡張しながら使用します。
上記のuint8_t型に限り別名定義があります。
要素データを並べてバイト列を生成します。
pack_bytesはコンテナクラスのbegin(),end()イテレータをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。
可変引数パラメータに与えるデータは以下に示すとおりです。
Unit_で始まるアクト(Act)は、ごく単機能の記述や動作を確認するためのものです。
アプリケーションのメインループです。ループ終了後はCPUがDOZEモードに遷移し低消費電流で次の割り込みを待ちます。
アクトの記述では、ほとんどの処理がこのループ内に記述されます。
Board behaviors
ボードビヘイビアは、TWELITE無線マイコンに接続したハードウェアを取り扱うための手続きが含まれます。
定数の定義(ピン番号など)
ハードウェアの初期化
センサー等の取り扱い
スリープから起床したときにloop()に移行する前に呼ばれ、スリープ復帰後の初期化処理や復帰状態によって処理を分岐するための手続きを含めます。
init_coldboot()
↓ (TWENET内部処理:初期化1)
setup()
↓(TWENET内部処理:初期化2)
begin() --- 初回のみ
↓
loop() <--+
↓ |イベント処理、ビヘイビア処理
CPU DOZE -+整数から指定したビット位置の値を取得し、指定した順番のビットマップを作成します。
constexpr uint32_t collect_bits(uint32_t bm, ...)パラメータbmに指定する値から、その後の可変数パラメータで指定する0..31のビット位置に対応する値を取り出します。取り出した値はパラメータ順に並べビットマップとして戻り値になります。
ビットマップの並び順は、最初のパラメータを上位ビットとし末尾のパラメータがbit0になります。
uint32_t b1 = 0x12; // (b00010010)
uint32_t b2 = collect_bits(b1, 4, 2, 1, 0);
// bit4->1, bit2->0, bit1->1, bit0->0
// b2=0x10 (b1010)例ではb1のビット4,2,1,0を取り出すと (1,0,1,0) になります。これをb1010として0x10のように計算されます。
IOポート(DI,DO)の状態など各種ビットマップに値を参照・設定する場面があり、その記述を簡素化するため。
push_back()メソッドを定義しています。末尾にデータを追記するタイプのアルゴリズムが使用可能になります。
型TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。
smplbuf_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。
smplbuf_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。
smplbuf_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。
グローバルオブジェクトを生成する場合は、コンストラクタによる初期化が行なえません。実行初期(setup()を推奨)に初期化関数init_local(),attach(),init_heap()を呼び出すようにしてください。
初期化子リスト(イニシャライザリスト){ ... } によるメンバーの初期化をできます。smplbuf_localのローカル宣言でのコンストラクタでの利用を除き、初期化のメソッドを呼び出した後に有効です。
代入演算子の右辺値 (smplbuf_local, smplbuf_attach, smplbuf_heap)
コンストラクタ(smplbuf_localのローカル宣言、グローバル宣言は不可)
末尾にメンバーcを追加します。append()の戻り値はboolで、バッファが一杯で追加できないときにfalseが返ります。
pop_back()は末尾のエントリを抹消します。ただしエントリのクリアはしません。
empty()は配列に要素が格納されていない場合にtrueを戻します。is_end()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。
size()は配列の要素数を返します。
capacity()は配列の最大格納数を返します。
reserve()は配列のサイズを拡張します。配列が格納されていない領域はデフォルトで初期化されます。
reserve_hear()は配列の先頭部に指定したサイズの領域を確保します。コンテナオブジェクトからは参照できない領域となります。例えばパケットペイロードのヘッダ部分を読み飛ばした部分配列にアクセスするようなコンテナとして利用できるようにします。確保した領域を戻しすべてアクセスできるようにコンテナを戻すには確保時と同じ負の値を与えます。
redim()は利用領域のサイズを変更します。reserve()と違い、未使用領域の初期化を行いません。
要素にアクセスします。
iに負の値を与えるとバッファー末尾からの要素となります。-1の場合は末尾の要素、-2は末尾から一つ手前となります。
uint8_t型の配列オブジェクト(smplbuf<uint8_t, *>)は、mwx::streamの派生オブジェクトに対して、そのまま出力できます。
Serialなどmwx::streamの派生オブジェクトに対して、バイト列を出力します。
ストリームへの出力目的で利用します。<<演算子の実装に用いられています。
mwx::streamでは<<演算子やprintfmt()メソッドと行ったストリームに対してバイト列を出力するための関数・演算子が定義されています。uint8_t型のsmplbufの配列を出力先と見立ててストリーム出力手続きを行えます。
mwx::streamを継承したsmplbufクラスを利用する。
v1.6リリースでの .get_stream_helper() には不具合がありました。より新しいコードを利用ください。
the_twelite.sleep()
↓ sleeping...
init_warmboot()
↓ (TWENET内部処理:初期化3)
wakeup()
↓(TWENET内部処理:初期化4)
loop() <--+
↓ |イベント処理、ビヘイビア処理
CPU DOZE -+template <typename T, int N> smplbuf_local
template <typename T> smplbuf_attach
template <typename T> smplbuf_heap// 配列領域は、クラスメンバー変数の固定配列
smplbuf_local<uint8_t, 128> b1;
// 配列領域は、すでにある領域を参照
uint8_t buf[128];
smplbuf_attach<uint8_t> b2(;
// 配列領域は、ヒープに確保
smplbuf_heap<uint8_t> b3;
// 初期化(グローバル定義の場合はsetup()で行う)
void setup() {
b1.init_local();
b2.attach(buf, 0, 128);
b3.init_heap(128);
}
// 処理関数内
void some_func() {
smplbuf_local<uint8_t, 128> bl;
// bl.init_local(); // smplbuf_localがローカル定義の場合は省略可能
bl.push_back('a');
}template <int N>
smplbuf_u8
// smplbuf<uint8_t, alloc_local<uint8_t, N>>
smplbuf_u8_attach
// smplbuf<uint8_t, alloc_attach<uint8_t>>
smplbuf_u8_heap
// smplbuf<uint8_t, alloc_heap<uint8_t>>void begin() { // begin()は起動時1回だけ動作する
smplbuf_u8<32> b1;
b1.reserve(5); // 5バイト分利用領域に初期化(b1[0..5]にアクセスできる)
b1[0] = 1;
b1[1] = 4;
b1[2] = 9;
b1[3] = 16;
b1[4] = 25;
for(uint8_t x : b1) { // 暗黙に .begin() .end() を用いたループ
Serial << int(x) << ",";
}
}smplbuf_local<T,N>()
smplbuf_local<T,N>::init_local()
smplbuf_attach<T>(T* buf, uint16_t size, uint16_t N)
smplbuf_attach<T>::attach(T* buf, uint16_t size, uint16_t N)
smplbuf_heap<T>()
smplbuf_heap<T>::init_heap(uint16_t N)
// 例
// 内部に固定配列
smplbuf_local<uint8_t, 128> b1;
b1.init_local();
// すでにある配列を利用する
uint8_t buf[128];
smplbuf_attach<uint8_t> b2;
b2.attach(buf, 0, 128);
// ヒープに確保する
smplbuf_heap<uint8_t> b3;
b3.init_heap(128); void in_some_func() {
smplbuf_local<uint8_t, 5> b1;
b1.init_local();
b1 = { 0, 1, 2, 3, 4 };
smplbuf_local<uint8_t, 5> b2{0, 1, 2, 3, 4};
}inline bool append(T&& c)
inline bool append(const T& c)
inline void push_back(T&& c)
inline void push_back(const T& c)
inline void pop_back()inline bool empty()
inline bool is_end()
inline uint16_t size()
inline uint16_t capacity()inline bool reserve(uint16_t len)
inline void reserve_head(uint16_t len)
inline void redim(uint16_t len)inline T& operator [] (int i)
inline T operator [] (int i) consttemplate <class L_STRM, class AL>
mwx::stream<L_STRM>& operator << (
mwx::stream<L_STRM>& lhs, mwx::_smplbuf<uint8_t, AL>& rhs)
//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');
Serial << buf;
// 出力: abc
inline std::pair<T*, T*> to_stream()
//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');
Serial << buf.to_stream();
// 出力:0123uint16_t
2
ビッグエンディアン並びで格納される
uint32_t
4
ビッグエンディアン並びで格納される
uint8_t[N]
N
uint8_t 型の固定長配列
std::pair<char*,N>
N
char*,uint8_t*型の配列と配列長のペア。make_pair()で生成できる。
pack_bytesはコンテナオブジェクトをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。コンテナの.push_back()メソッドで末尾に追加します。
可変引数パラメータに与えるデータは以下に示すとおりです。
データ型
バイト数
解説
uint8_t
1
uint16_t
2
ビッグエンディアン並びで格納される
uint32_t
4
ビッグエンディアン並びで格納される
uint8_t[N]
この例では受信パケットの各属性やペイロードを別のバッファbufに再格納しています。
無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t型のバイト配列の記述を簡素化するため。
上記はもっとも単純な記述だが、以下のようにByte array utilsを用いてバイト配列を生成できる。
データ型
バイト数
解説
uint8_t
1
当サポートでは VSCode のインストール方法、使用方法のお問い合わせについては対応いたしません。一般で得られる情報を参照ください。
環境によっては、インストールのためにセキュリティ設定などが必要になる場合があります。インストールの可否はシステム管理者にご確認の上、手順は配布元や一般の情報を参考にしてください。
VSCode では、以下のことができます。
ソースコードの編集
GIT への接続(お客様が独自にソース管理を GIT 上で行う場合)
ソースコード解釈に基づく intellisense(*全ての定義が正確に解釈されることを保証するわけではありません)
VSCode はリンク先より入手してください。
Visual Studio Code が C/C++ 言語の記述を解釈できるようにするために、プラグインをインストールします。
C/C++ for Visual Studio Code
MWXライブラリのサンプルには .vscode の定義を含めています。この定義は MWSDK_ROOT 環境変数を用い、ライブラリのソースコード({MWSDK_ROOT}/TWENET/current以下)を特定しています。
VS Code のソースコードの解釈はコンパイラでの解釈とは完全には一致しません。またソースコードの編集状況によっては解釈がより不完全な状態になる場合もあります。
10,100,1000の割り算と商を求めるの動作確認を行います。-99999~99999まで計算を行い通常の/,%による除算との経過時間の比較をします。
Unit_div_format
の結果を文字列出力します。
Unit_using_UART1
UART1 () の使用サンプルです。UART0()からの入力をUART1に出力し、UART1からの入力をUART0に出力します。
名前
内容
Unit_I2Cprobe
I2Cバスをスキャンして、応答のあるデバイス番号を表示します(この手順で応答しないデバイスもあります)。
Unit_delayMicoroseconds
delayMicroseconds()の動作を確認します。16MhzのTickTimerのカウントとの比較をします。
Unit_using_PAL_NOTICE
通知パル(NOTICE PAL)のLED点灯を試します。起動時に全灯フラッシュ、その後はシリアル入力で操作します。
r,g,b,w : 各色の点灯モードをトグルする
R,G,B,W : 各色の明るさを変更する(消灯・全灯時は無効)
c: 周期を変化させる(点滅時)
C: 点滅時のデューティを変化させる(点滅時)
Unit_div100
本プログラムは pyftdi (https://github.com/eblot/pyftdi) ライブラリサンプルスクリプト pyterm.py に TWELITE 用のファームウェア書き込みスクリプトを組み込んだものです。以下の機能があります。
TWELITE 用ファームウェアの書き込み (TWELITE R/MONOSTICK)
シリアルポートでの動作振る舞いの確認
本スクリプトの OS X での実行には Python3 インタプリタが必要です。コマンドライン環境ならびに Python インタプリタの取り扱いに慣れた方を対象とします。
本スクリプトを Linux で動作させるには同等のパッケージ (libusb-dev, pyserial, pyftdi) を用意します。コマンドライン環境ならびに Python インタプリタの取り扱いに慣れた方を対象とします。
参考環境:Ubuntu 18.04 (x86-64 64bit), Python 3.6.5
Mac OS X または Linux
python3.5 以降
libusb
pyserial
以下の環境で開発、動作確認を実施しました。ただし、これら環境で動作を保証するものではありません。
以下の手順はパッケージ管理システムのバージョンアップや仕様変更で、そのまま適用できない場合があります。一般の情報などを参考にしてください。
TWELITE SDK をインストールしたディレクトリを ${TWELITESDK} と記載します。
スクリプトは以下になります。
スクリプトに実行許可を与えます。
必要に応じて環境変数 PATH に追加しておきます。
libusb と OS のドライバが競合するため、ドライバをアンロード(無効に)しておきます。
FTDI 関連のドライバをアンロードします。
ドライバのアンロードは不要です。
エラーが出る場合は、ドライバをアンロードを試してみてください。
Ctrl+C キーを入力することで制御プロンプトを表示し、いくつかの特別な操作が可能です。それ以外の場合は、直接 TWELITE 無線モジュールに入力文字列が送付されます。
実行例中では、適宜改行を挟んでいます。
エラーが発生する場合は、シリアルポートの権限の問題かもしれません。root権限(sudo等)で実行します。
以下の例では App_UART (UART 通信アプリ) を書き込み、起動メッセージを確認しています。
通常通り + + + と3回入力しても同じ結果になります。
インタラクティブモードで m B Enter S と順に入力します。
以下の例ではApp_UARTがバイナリ形式で入出力を行ないます。Ctrl+C B を入力します。
この状態では入出力は書式形式となります。キーボードの入力はアスキー形式、 TWELITE からの電文はバイナリ形式として解釈します。
TWELITE からの電文を受け取る例を示します。一番簡単な方法は TWELITE をリセットします。Ctrl+C r を入力します。
出力された [dbf...] がTWELITEからの電文で実際は0xdb 0xf1 0x67... と続くバイナリ列になります。
反対に TWELITE に電文を送る場合はアスキー書式で入力します。:7800112233AABBCCDDX と入力します。 ここでは ペイロードが 0x7800112233AABBCCDD のデータをバイナリ形式で TWELITE に送付しています。直後に応答として [dba18001] が戻ってきています。
Ctrl+C Ctrl+C を入力します。
ヘルパークラス版はより抽象度が高い実装です。読み書きに対応するオブジェクト reader, writer を生成することがバスの利用開始となり、オブジェクトを破棄するとバス利用の終了手続きを行います。
if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。
また読み書きオブジェクトはmwx::streamインタフェースを実装しているため<<演算子などを利用することができます。
バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぐ
mwx::streamインタフェースによる読み書き手続きの統一
読み込み処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法です。
上記では get_readr() メソッドにより生成された rdr オブジェクトを用いて1バイトずつ読み出しします。 メソッドのパラメータには読み込みたい二線シリアル ID を指定します。
if(...) 内で rdr オブジェクトを生成。(型は、型推論によるユニバーサル参照 auto&& で解決しています。)
生成した rdr オブジェクトには operator bool () が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true となる。
I2C 読み出しに用いるワーカーオブジェクトを取得します。
書き出し処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法です。
上記では get_writer() メソッドにより生成された wrt オブジェクトを用いて1バイトずつ書き出す。 メソッドのパラメータには読み出したい二線シリアル ID を指定します。
if(...) 内で wrt オブジェクトを生成する。(型名は長くなるため auto で解決)
生成した wrt オブジェクトには operator bool () が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true となる。
I2C書き出しに用いるワーカーオブジェクトを取得します。
int型,uint8_t型は8bitの転送を行います。
1バイト書き出す。
それぞれのデータ型のサイズ分だけ読み出します。
1バイト読み出します。エラーがある場合は-1を戻し、正常時は読み出したバイト値を戻します。
b_stopをtrueにすると、その読み出しにおいてSTOPビットを発行します。
以下の例は、環境センサーパルの温湿度センサーSHTC3の計測例です。
ディジタル入力管理クラス (mwx::periph_buttons)
ディジタル入力の変化を検出します。このクラスは、同じ検出値が複数回得られたときに変化を検出します。メカ式のボタンのチャタリングの影響を小さくするのに有効です。
パラメータのmax_historyは、begin()で設定可能な参照回数の最大値です。ここではメモリーの確保と初期化を行います。
Buttonsの動作を開始します。1番目のパラメータbmPortMaskは監視対象のディジタル入力のビットマップを指定します。bit 0がDIO 0, ... , bit N がDIO Nに対応します。複数指定することができます。2番目のu8HistoryCountは値の確定をするのに必要な回数です。3番目のtick_deltaは値の確認を行う間隔をmsで指定します。
値の確定にはu8HistoryCount*tick_delta[ms]かかることになります。例えばu8HistoryCount=5, tick_delta=4の場合は、状態の確定に最低約20msかかります。
確認はTickTimerのイベントハンドラで行っています。割り込みハンドラではないので、処理等の遅延の影響を受けますが、メカ式ボタン等のチャタリング抑制には十分です。
Buttonsの動作を終了します。
変化が検出されたときにtrueを返します。read()を実行するとクリアされます。
availableになったとき呼び出します。u32portは現在の入力DIOのビットマップ、u32changedは変化が検出されたDIOのビットマップです。
Buttonsが動作していない場合はfalseを返します。
Buttonsが動作を開始した時点では、DIOの入力状態は未確定です。値が確定した時点でavailableになります。このときread()で読み出すビットマップのMSB(bit31)が1にセットされます。
動作確定を要するため、入力値が定常的に変化するポートを監視する目的では利用できません。
スリープ前にButtonsが稼働状態であれば、復帰後に再開します。再開後、初回確定を行います。
32bit型をラップしたAPI戻り値クラス。MSB(bit31)は失敗、成功のフラグ。bit0..30は戻り値を格納するために使用します。
MWX_APIRET()
MWX_APIRET(bool b)
MWX_APIRET(bool b, uint32_t val)デフォルトコンストラクタはfalse,0の組み合わせで構築します。
またbool型とuint32_t型をパラメータとする明示的な構築も可能です。
bool型のコンストラクタを実装しているため、以下のようにtrue/falseを用いることができます。
MSBに1がセットされていればtrueを返す。
MSBが0の場合にtrueを返す。
bit0..30の値部を取得する。
新しいプロジェクトの作成は、すでにあるサンプルアクトのディレクトリを別の名前でコピーし、ファイル名の編集を行います。
コピー先のディレクトリは MWSDK 配下のディレクトリでなくても構いません。ただし、ディレクトリ名に空白文字や日本語名が含まれてはいけません。
MWSDK以外のディレクトリにコピーした場合 VS Code のワークスペース定義の一部が機能しなくなります。ワークスペースにmwxライブラリのソースコードディレクトリが追加されている場合は、新たに設定してください。
ライブラリソース設定をしなくてもビルドには影響はありません。より深いコード解釈をVSCode上で行い、編集効率を上げるための設定です。
プロジェクトのファイル構造は以下のようになっています(ここでは PingPong を例に挙げます)。
この PingPong ディレクトリを別の場所(ただしディレクトリ名に日本語や空白が含まない)にコピーします。
編集の必要があるのは、PingPong.cpp のファイル名です。これをディレクトリ名と同じAlphaBravo.cppに変更します。
build\build-BLUE.cmd を実行してBINファイルが生成されれば完了です(Windows10)。
Linux/WSL/macOS ではmake TWELITE=BLUEを実行して、ビルドが成功するか確認します。
パルスカウンタ (mwx::periph_pulse_counter)
パルスカウンタは、マイコンのCPUが稼働していない時もパルスを読み取り計数する回路です。パルスカウンターは2系統あります。PC0はPulseCounter0, PC1はPulseCounter1に割り当てられます。
またPulseCounterはPulseCounter1の別名です。
オブジェクトを初期化し、計数を開始します。1番目のパラメータrefctは割り込みやavailable判定の基準となるカウント数です。この数を超えたときにアプリケーションに報告されます。またrefctには0を指定することもできます。この場合は、スリープ起床要因にはなりません。
2番目のパラメータedgeは割り込みが立ち会がり(PIN_INT_MODE::RISING)か立下り(PIN_INT_MODE::FALLING)を指定します。
3番目のdebounceは、0,1,2,3の値をとります。1,2,3の設定はノイズの影響を小さくするため値の変化の検出に連続した同じ値を要する設定です。
検出を中止します。
指定カウント数(begin()のrefct)が0の場合は、カウントが1以上でtrueを返します。
指定カウント数(begin()のrefct)が1以上の場合は、検出回数が指定カウント数を超えた場合にtrueとなります。
カウント値を読み出します。読み出し後にカウント値を0にリセットします。
uint8_t* pack_bytes(uint8_t* b, uint8_t* e, ...)smplbuf_u8& pack_bytes(smplbuf_u8& c, ...)auto&& rx = the_twelite.receiver.read();
smplbuf<uint8_t, 128> buf;
mwx::pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // src addr (LID)
, uint8_t(0xCC) // cmd id (0xCC, fixed)
, uint8_t(rx.get_psRxDataApp()->u8Seq) // seqence number
, uint32_t(rx.get_addr_src_long()) // src addr (long)
, uint32_t(rx.get_addr_dst()) // dst addr
, uint8_t(rx.get_lqi()) // LQI
, uint16_t(rx.get_length()) // payload length
, rx.get_payload() // payload
);auto&& rx = the_twelite.receiver.read();
uint8_t data[128];
data[0] = rx.get_addr_src_lid();
data[1] = 0xCC;
data[2] = rx.get_psRxDataApp()->u8Seq;
data[4] = rx.get_addr_src_long() & 0x000000FF;
data[5] = (rx.get_addr_src_long() & 0x0000FF00) >> 8;
data[6] = (rx.get_addr_src_long() & 0x00FF0000) >> 16;
data[7] = (rx.get_addr_src_long() & 0xFF000000) >> 24;
...auto&& rx = the_twelite.receiver.read();
uint8_t data[128], *q = data;
S_OCTET(q, rx.get_addr_src_lid());
S_OCTET(q, 0xCC);
S_OCTET(q, rx.get_psRxDataApp()->u8Seq);
S_DWORD(q, rx.get_addr_src_long());
S_DWORD(q, rx.get_addr_dst());
S_OCTET(q, rx.get_lqi());
S_WORD(q, rx.get_length());
for (auto x : rx.get_payload()) {
S_OCTET(q, x);
}if (auto&& wrt = Wire.get_writer(...)) { // オブジェクトの生成とデバイスの通信判定
// このスコープ(波かっこ)内が wrt の有効期間。
wrt << 0x00; // 0x00 を mwx::stream インタフェースで書き出し
}
// if 節を抜けるところで wrt は破棄され、バスの利用終了class MWX_APIRET {
uint32_t _code;
public:
MWX_APIRET() : _code(0) {}
MWX_APIRET(bool b) {
_code = b ? 0x80000000 : 0;
}
MWX_APIRET(bool b, uint32_t val) {
_code = (b ? 0x80000000 : 0) + (val & 0x7fffffff);
}
inline bool is_success() const { return ((_code & 0x80000000) != 0); }
inline bool is_fail() const { return ((_code & 0x80000000) == 0); }
inline uint32_t get_value() const { return _code & 0x7fffffff; }
inline operator uint32_t() const { return get_value(); }
inline operator bool() const { return is_success(); }
};N
uint8_t 型の固定長配列
std::pair<char*,N>
N
char*,uint8_t*型の配列と配列長のペア。make_pair()で生成できる。
smplbuf_u8?
.size()
uint8_t型のsmplbuf<>コンテナ。コンテナ長(.size())のデータを格納する。
rdr オブジェクトには int operator () (void) 演算子が定義されていて、これを呼び出すことで2線シリアルバスから1バイトのデータを読み出す。読み込みに失敗したときは -1 が戻り、成功した場合は読み込んだバイト値が戻る。
if() { ... } スコープの末尾で rdr のデストラクタが呼び出され、二線シリアルバスの STOP を行う。
wrt オブジェクトには int operator () (void) 演算子が定義されていて、これを呼び出すことで2線シリアルバスに1バイトのデータを書き出しす。失敗したときは -1 が戻り、成功した場合は書き込んだバイト値が戻る。if() { ... } スコープの末尾で wrt のデストラクタが呼び出され、二線シリアルバスの STOP を行う。
パラメータ
解説
addr
読み込み用のI2Cアドレス
read_count
読み出しバイト数(この値を指定すると最後の転送で STOP ビットを発行する)。0を指定した場合は STOP ビットなしとなる(デバイスによっては動作するものもあります)
パラメータ
解説
addr
書き出し用のI2Cアドレス
MWX_APIRET myfunc() {
if (...) return true;
else false;
}1.2Khz
設定
連続サンプル数
最大検出周波数
0
-
100Khz
1
2
3.7Khz
2
4
2.2Khz
3
8
本パッケージ内で、ライセンス上特別な記述のないものは、モノワイヤレスソフトウェア使用許諾契約書(MW-SLA)を適用します。
本ドキュメントについても、本ライブラリパッケージ部の一部としてMW-SLA下の取り扱いとします。
本ソフトウェアについては、モノワイヤレス株式会社が正式にサポートを行うものではありません。お問い合わせにはご回答できない場合もございます。予めご了承ください。
不具合などのご報告に対してモノワイヤレス株式会社は、修正や改善をお約束するものではありません。
また導入パッケージなどお客様の環境に依存して動作しない場合もございます。
# Copyright (C) 2019 Mono Wireless Inc. All Rights Reserved.
# Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
# AGREEMENT)タイマーを初期化します。この呼び出しにより必要なメモリ領域の確保を行います。
タイマーを開始します。1番目のパラメータは、タイマーの周期でHzで指定します。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。3番目のパラメータをtrueにするとPWM出力を有効にします。
タイマーの動作を停止します。
タイマー割り込みが発生した直後のloop()でtrueになり、loop()が終了すればfalseになります。
デューティー比の設定を行う。1番目のパラメータにデューティ比を指定します(小さい値を指定すると波形の平均はGNDレベルに近づき、大きい値を指定するとVccレベルに近づく)。2番目のパラメータはデューティ比の最大値を指定します。
duty_maxは1024,4096,16384のいずれかの指定を推奨します。
内部でのカウント値の計算に除算が発生しますが、これら3つに限りビットシフトによる演算を行っていますが、これ以外の値では計算量の大きい除算処理が実行されます。
タイマーの周波数を設定します。2番目のパラメータは周波数の小数点3桁分の値を整数で指定します。例えば 10.4 Hz としたい場合は hz=10, mil=400 と指定します。
const uint8_t DEV_ADDR = 0x70;
uint8_t au8data[6];
if (auto&& rdr = Wire.get_reader(DEV_ADDR, 6)) {
for (auto&& c: au8data) {
c = rdr();
}
}
// same above
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(SHTC3_ADDRESS, 6)) {
rdr >> u16temp;
rdr >> u8temp_csum;
rdr >> u16humd;
rdr >> u8humd_csum;
}periphe_wire::reader
get_reader(uint8_t addr, uint8_t read_count = 0)const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
wrt(SHTC3_TRIG_H);
wrt(SHTC3_TRIG_L);
}
// same above
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
wrt << SHTC3_TRIG_H; // int type is handled as uint8_t
wrt << SHTC3_TRIG_L;
}
periph_wire::writer
get_writer(uint8_t addr)operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)operator() (uint8_t val)
operator() (int val)operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)
operator >> (uint8_t(&c)[N]) // Nバイトの固定配列int operator() (bool b_stop = false)
//例
uint8_t dat[6];
if (auto&& rdr = Wire.get_reader(0x70)) {
for(uint8_t& x : dat) {
x = rdr();
}
}Wire.begin();
// reset (may not necessary...)
if (auto&& wrt = Wire.get_writer(0x70)) {
wrt << 0x80; // SHTC3_SOFT_RST_H
wrt << 0x05; // SHTC3_SOFT_RST_L
}
delay(5); // wait some
// start read
if (auto&& wrt = Wire.get_writer(0x70)) {
wrt << 0x60; // SHTC3_TRIG_H
wrt << 0x9C; // SHTC3_TRIG_L
}
delay(10); // wait some
// read result
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(0x70, 6)) {
rdr >> u16temp;
rdr >> u8temp_csum;
rdr >> u16humd;
rdr >> u8humd_csum;
}
// checksum 0x31, init=0xFF
if (CRC8_u8CalcU16(u16temp, 0xff) != u8temp_csum) {
Serial << format("{SHTC3 T CKSUM %x}", u8temp_csum); }
if (CRC8_u8CalcU16(u16humd, 0xff) != u8humd_csum) {
Serial << format("{SHTC3 H CKSUM %x}", u8humd_csum); }
// calc temp/humid (degC x 100, % x 100)
int16_t i16Temp = (int16_t)(-4500 + ((17500 * int32_t(u16temp)) >> 16));
int16_t i16Humd = (int16_t)((int32_t(u16humd) * 10000) >> 16);
Serial << "temp=" << int(i16Temp)
<< ",humid=" << int(i16Humd) << mwx::crlf;void setup(uint8_t max_history);void begin(uint32_t bmPortMask,
uint8_t u8HistoryCount,
uint16_t tick_delta);void end()inline bool available()bool read(uint32_t& u32port, uint32_t& u32changed)inline bool is_success()
inline operator bool()inline bool is_fail()inline uint32_t get_value()
inline operator uint32_t()Act_samples
+-PingPong
+-PingPong.cpp : アクトファイル
+-build : ビルドディレクトリ
+-.vscode : VS Code 用の設定ファイル SomeDir
+-AlphaBravo
+-PingPong.cpp -> AplhaBravo.cpp ※ファイル名を変更
+-build : ビルドディレクトリ
+-.vscode : VS Code 用の設定ファイルvoid begin(uint16_t refct = 0,
E_PIN_INT_MODE edge = PIN_INT_MODE::FALLING,
uint8_t debounce = 0)void end()inline bool available()uint16_t read()void setup()void begin(uint16_t u16Hz, bool b_sw_int = true, bool b_pwm_out = false)void end()inline bool available()void change_duty(uint16_t duty, uint16_t duty_max = 1024)void change_hz(uint16_t hz, uint16_t mil = 0) pyftdi
お使いのディストリビューションのパッケージ導入法を調べてください。以下のパッケージが必要です。
python3.5 以降 (多くの場合導入されています)
libusb-dev
pyserial
pyftdi
パッケージ導入コマンド例。
-b 115200 115200bps を指定。
-F [ファームウェア]
ファームウェアを書き込みます。
-F App_Twelite.bin ファイル名が App_Twelite.binを書き込みます。
--no-color
文字のカラー出力を抑制します。
--no-term
ファームウェアの書き込みのみを実施しターミナルを開きません。
書式の解釈を停止します。
環境
Mac OS X 10.11.6, Python3.5.1 (2018/05)
Ubuntu 18.04, Python3.6.7 (2018/05)
Mac OS X 10.14.2, Python3.7.2 (2019/01)
パラメータ
解説
-p ftdi:///? または -p
デバイス一覧を表示します。
-p [デバイス名]
デバイスを指定します。
ftdi:///1 他にデバイスがない時。
ftdi://::MW19ZZUB/1 シリアル番号による指定。`
-b [ボーレート]
ボーレートを指定します。
入力キー
解説
Ctrl+C Ctrl+C
ターミナルを終了します。
Ctrl+C Ctrl+R または Ctrl+C r
TWELITE 無線マイコンをリセットします。
Ctrl+C Ctrl+I または Ctrl+C i
インタラクティブモードへ遷移する + + + コマンドを入力します。インタラクティブモード対応ファームウェアのみ有効です。
Ctrl+C A
書式の解釈を開始します。TWELITE無線マイコンからの出力とキー入力に対して、アスキー形式の解釈を行います。
Ctrl+C B
書式の解釈を開始します。TWELITE無線マイコンからの出力に対してバイナリ形式の解釈を行います。キー入力はアスキー形式で解釈します。
Ctrl+C N





Mono Wireless C++ Library for TWELITE.
資料の取り扱いについてをご参照ください。 お気付きの点がありましたら、当サポート窓口にご連絡いただければ幸いです。
MWX ライブラリは、TWELITE 無線モジュールのコード表記を簡素化することを目的としています。MWXで作成されたプログラムをアクト act と呼びます。アクトにはループによる記述と、イベントによる記述(ビヘイビア behavior と呼びます)の二種類があります。
本ページではアクトの主な特徴を紹介します。
ループによる記述 (setup(), loop())。小規模な機能を記述するのに向いています。
イベントドリブンのアプリケーション記述。各種イベント・割り込みハンドラの定義、アプリケーションの複雑な振る舞いを記述するのに向いたステートマシンをクラス内に定義して見通しの良いコードを記述できます。この記述をビヘイビアと呼びます。
ペリフェラルの手続きを簡素化。よく使われる UART, I2C, SPI, ADC, DIO, タイマー, パルスカウンタを取り扱うクラスオブジェクトを定義しています。
シンプルな中継ネットワークを定義。この中継ネットワークは TWELITE 標準アプリケーションと同等の実装で、個体アドレスの管理は 8bit の論理IDで行うこと、ネットワーク構築のための通信を行わないため電源投入後すぐにネットワーク宛に無線パケットを送ることができる点が特徴です。
PAL や MONOSTICK 向けのボード定義。ボード上のセンサーなどを容易に取り扱えます。
MONOSTICK用のボードビヘイビアです。内蔵ウォッチドッグタイマーの制御とLED点灯用の手続きが含まれます。
以下の定義が利用可能になります。
MONOSTICK::PIN_LEDのようにアクセスできます。
上記のコードのように各ピンが初期化されます。
起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。
ウォッチドッグタイマーのタイムアウトは1秒です。
MONOSTICKでは通常スリープするアプリケーションを実行しませんが、その場合はMONOSTICK::PIN_WDT_ENをHIGHにしてからスリープします。
LED(赤、黄)の制御を行います。
ボードビヘイビアでの制御を行わない場合は、このメソッドを呼び出さないでください。
黄色のLED(MONOSTICK::PIN_LED_YELLOW)はSPIMISOピン(半導体のピン名DO1)です。本ボードビヘイビアではPWM制御による点灯用のメソッドや手続きは含まれません。必要に応じて以下の記述を行います。
set_led_yellow()は呼び出さないようにして下さい。
黄色のLED(MONOSTICK::PIN_LED_YELLOW)は、スリープ中に点灯させることはできません。
modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。
パルスカウンターを用いたアクト例です。
パルスカウンターは、マイコンを介在せず信号の立ち上がりまたは立ち下りの回数を計数するものです。不定期のパルスを計数し一定回数までカウントが進んだ時点で無線パケットで回数を送信するといった使用方法が考えられます。
このアクトの解説の前にBRD_APPTWELITEの解説をご覧ください。
受信の確認のためParent_MONOSTICKの解説をご覧ください。
子機側のDIO8に接続したパルスを計数し、一定時間経過後または一定数のカウントを検出した時点で無線送信する。
子機側はスリープしながら動作する。
パルスカウンターの初期化を行います。
パルスカウンターの動作を開始し、初回スリープを実行します。PulseCounter.begin()の最初のパラメータは、起床割り込みを発生させるためのカウント数100で、2番目は立ち下がり検出PIN_INT_MODE::FALLINGを指定しています。
起床時にPulseCounter.available()を確認しています。availableつまりtrueになっていると、指定したカウント数以上のカウントになっていることを示します。ここではfalseの場合再スリープしています。
カウント数が指定以上の場合はloop()で送信処理と送信完了待ちを行います。
パルスカウント値の読み出しを行います。読み出した後カウンタはリセットされます。
二線シリアル(I2C) master の読み書き (mwx::periph_wire)
二線シリアル(I2C) master の読み書きを行います。
mwx::periph_wire<MWX_TWOWIRE_RCVBUFF>はTwoWireとして参照可能です。
以下の定義型で引数や戻り値の型を記載します。
API 中に STOP ビットの扱いが厳格でない呼び出しを行うものもあります。
ライブラリ内でインスタンスの生成と必要な初期化は行われます。ユーザコードでは Wire.begin() を呼び出すことで利用可能となります。
requestFrom() メソッドを用いる場合、データを一時保管するための FIFO キューのサイズを指定できます。コンパイル時にマクロMWX_TWOWIRE_BUFF に必要なバイト数を指定してコンパイルする。デフォルトは 32 バイトです。
例:
-DMWX_TWOWIRE_BUFF=16
ハードウェアの初期化を行います。
初期化せずにWireの操作を行うとTWELITE無線モジュールがハングアップします。
スリープからの起床時は、スリープ直前で動作していた場合、直前の状態に復帰します。
読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。
requestFrom(), beginTransmission(), endTransmission(), write()
reader, writer
address で指定したデバイスが応答するかを確認します。デバイスが存在する場合は true が戻ります。
本来はバス周波数を変更するための手続きですが、何も処理をしません。
MWX ライブラリは、TWELITE モジュールのプログラムをより容易にかつ拡張性を高めるために設計されています。これまでMWSDKで利用していた TWENET C ライブラリを基本とし、MWXライブラリはアプリケーション開発層のライブラリとして開発しております。
MWX ライブラリの名称は Mono Wireless C++ Library for TWELITE です。MW は MonoWireless から、また C++ -> CXX -> double X -> WX。この MW と WX を重ねて MWX になりました。
このライブラリを用いて記述したコードを「アクト(act)」と呼びます。
本解説での表記について記載します。
ユニバーサル参照と呼ばれ、標準ライブラリなどで良く用いられます。当ライブラリでもほとんどの場合auto&& と記載します。
namespace, inline namespace, using を用いて、名前の再定義などを行っています。解説中でも一部省略して記載しています。
MWXライブラリは、下層に位置する各ライブラリ・機能(TWNET Cライブラリでの機能、また半導体ベンダが提供するマイコン・ペリフェラル機能、IEEE802.15.4の機能)について、その全てに対応する目的では開発しておりません。
MWXライブラリはC++言語で記述されておりアクトの記述においても C++ での記述を行うことになります。しかしながらC++言語であってもすべての機能が使えるわけではありません。特に以下の点に注意してください。
new, new[] 演算子でのメモリ確保は行えますが、確保したメモリを破棄することはできません。C++ライブラリで動的メモリ確保をするものは殆どが事実上利用不可能です。
グローバルオブジェクトのコンストラクタが呼び出されません。
参考:必要な場合は、初期化関数(setup()) で new ((void*)&obj_global) class_foo(); のように初期化することでコンストラクタの呼び出しを含めた初期化を行えます。
例外 exception
※ 当社で把握しているものについての記載です。
標準ライブラリについては利用可否、また利用できそうなものについての包括的な検証は行っておりません。動作の不都合が確認できた場合は、別の方法で実装するようにしてください。
ソースコードは以下から参照できます。
{MWSDKインストールディレクトリ}/TWENET/current/src/mwx
開発環境を構築するためには、ソフトウェア群のインストール、またこれらの利用許諾に同意する必要があります。また、PC、ワークステーション上でセキュリティ設定等が必要になる場合があります。
配布時には十分注意しておりますが、ウィルスなどの確認はお客様のほうでも留意いただくようお願いいたします。
お客様のセキュリティの考え方や運用(例:外部アプリケーションのインストールの可否)については、お客様の環境の管理者にご確認ください。
また、開発環境をインストールまた動作するにあたり、OSが介在し設定等必要になる場合があります(例:開発元が不明なアプリケーションの実行。開発環境または紹介するツール群の多くは、アプリケーションは開発元を証明する仕組みが組み込まれせん)。設定方法については、一般の情報を参考いただくようお願いいたします。
MWXライブラリを用いてアプリケーションを記述するには以下が必要です。
MWSDK(ソフトウェア開発環境)
開発用エディタ(Microsoft社のVisualStudio Codeを紹介します)
コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、サポート中のWindows10バージョンを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
Windows10 #1809, #1903
.NET CLR v4.0.30319
FTDI社のドライバが動作していること (MONOSTICK, TWELITE Rを動作させるため)
開発環境を動作させるための環境や使用方法については、その開発元やコミュニティの情報を参照ください。
Linux/WSL環境下/macOSのビルド結果はWindows10の結果と異なります。通常系の動作で差異が見られることは当社が把握する限りありませんが、特にgccのLTOを無効にしているためバイナリサイズが数%程度大きくなる傾向にあります。
動作等に疑問を感じた際は、必ず Windows10 上のビルドを実施し再現することを確認してから、お問い合わせください。
Sample Acts
アクトの動作を理解するため、いくつかのサンプルを用意しています。
最初にとの解説に目を通すようにしてください。
コンテナクラス(smplbuf, smplque)のテンプレート引数として指定し、内部で利用するメモリの確保または領域指定します。
このクラスはユーザコードから直接呼び出すものではありませんが、内部的にコンテナの宣言に用いられています。
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"$ brew install python3${TWELITESDK}/Tools/tweterm/tweterm.py$ chmod +x ${TWELITESDK}/Tools/tweterm/tweterm.py$ PATH=${TWELITESDK}/Tools/tweterm:$PATH$ sudo kextunload -b com.apple.driver.AppleUSBFTDI$ sudo rmmod ftdi_sio
$ sudo rmmod usbserial$ tweterm.py -p ftdi:///?
Available interfaces:
ftdi://ftdi:232:MW19ZZUB/1 (MONOSTICK)
Please specify the USB device$ tweterm.py -p ftdi://ftdi:232:MW19ZZUB/1 -b 115200 -F ../App_Uart_Master_RED_L1101_V1-2-15.bin
*** TWE Wrting firmware ... ../App_Uart_Master_RED_L1101_V1-2-15.bin
MODEL: TWEModel.TWELite
SER: 102eebd
file info: 0f 03 000b
erasing sect #0..#1..#2..
0%..10%..20%..30%..40%..50%..60%..70%..80%..90%..done - 10.24 kb/s
Entering minicom mode
!INF TWE UART APP V1-02-15, SID=0x8102EEBD, LID=0x78
8102EEBD:0> *** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[+ + +]
--- CONFIG/TWE UART APP V1-02-15/SID=0x8102eebd/LID=0x00 -E ---
a: set Application ID (0x67720103)
i: set Device ID (121=0x79)
... <省略> --- CONFIG/TWE UART APP V1-02-15/SID=0x8102eebd/LID=0x00 -E ---
a: set Application ID (0x67720103)
i: set Device ID (121=0x79)
c: set Channels (18)
x: set RF Conf (3)
r: set Role (0x0)
l: set Layer (0x1)
b: set UART baud (38400)
B: set UART option (8N1)
m: set UART mode (B)*
h: set handle name [sadkasldja]
C: set crypt mode (0)
o: set option bits (0x00000000)
---
S: save Configuration
R: reset to Defaults
!INF Write config Success
!INF RESET SYSTEM...*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>
[FMT: console ASCII, serial BINARY]*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[RESET TWE]
[dbf1677201030001020f008102eebd0000]:7800112233AABBCCDDX[dba18001]*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[
[EXIT]
Bye$ brew install libusb$ pip3 install pyserial$ pip3 install pyftdiconst uint8_t PIN_LED = mwx::PIN_DIGITAL::DIO16; // LED
const uint8_t PIN_WDT = mwx::PIN_DIGITAL::DIO9; // WDT (shall tick < 1sec)
const uint8_t PIN_WDT_EN = mwx::PIN_DIGITAL::DIO11; // WDT (LO as WDT enabled)
const uint8_t PIN_LED_YELLOW = mwx::PIN_DIGITAL::DO1; // YELLOW LEDusing TwoWire = mwx::periph_twowire<MWX_TWOWIRE_RCVBUFF>; Act (USER APPs)...
+-----------------------+
| MWX C++ LIB |
+---------------+ |
| TWENET C LIB | |
+------------+----------+
| MAC LAYER | AHI APIs |
+-----------------------+
| TWELITE HARDWARE |
+-----------------------+コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
Ubuntu 16.04 LTS 64bit
Ubuntu 18.04 LTS 64bit
*32bitのシステムはサポートしません。
コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
macOS 14.06
始動後にPWM出力の初期化を別途行います。SPIMISOピンはApp_Twelite標準アプリケーションではPWM3に対応し、Timer3クラスオブジェクトにより制御できます。
スリープ復帰後にPWM出力の初期化を別途行います。その際、DO1の出力設定を解除します。
PWM設定前にDO1の出力設定を解除してください。
pinMode(PIN_LED_YELLOW, DISABLE_OUTPUT);
指定
意味
LED_TIMER::BLINK
LEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RX
パケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP
送信完了時にtickに与える時間[ms]だけ点灯します。
役割
例
親機
アクトParent_MONOSTICKを動作させる。
子機
Wire.write({11,12})パラメータ
解説
u8mode
バス周波数を指定する。デフォルトは100Khz(WIRE_CONF::WIRE_100KHZ)
周波数はWIRE_CONF::WIRE_??KHZで指定し??には50,66,80,100,133,160,200,266,320,400を指定できる。
b_portalt
ハードウェアのピン割り当てを変更する。
&&仮想関数 virtual が使用できません。
上記の制約があるためSTLなどC++標準ライブラリの一部のみの利用となります。
上記の例ではMY_APP_CLASSという名前でビヘイビアクラスを定義しています。いくつかの箇所にMY_APP_CLASSの記述が必要です。
クラス名の定義と、ベース(親)クラスの定義をします。MWX_APPDEFS_CRTP()はマクロです。
ここでは必要な定義を #include により取り込んでいます。
コンストラクタの定義です。
メインループで、グローバル定義のloop()と同じ役割の関数です。
on_create()はオブジェクト生成時(use<>()メソッド)に呼び出されます。valは将来の拡張のためのパラメータです。
on_begin()はsetup()終了後に呼び出されます。valは将来の拡張のためのパラメータです。
スリープ前に呼び出されます。valは将来の拡張のためのパラメータです。
スリープ復帰時の初期段階で呼び出されます。valは将来の拡張のためのパラメータです。
この時点でまだペリフェラルが初期化されていません。スリープ起床要因の確認ができます。
スリープ復帰時に呼び出されます。valは将来の拡張のためのパラメータです。
パケットが受信されたとき、受信したパケット情報をrxとして呼び出されます。
パケット送信完了時に送信情報をevTxとして呼び出されます。evTx.u8CbIdが送信時のIDでevTx.bStatusが送信の成功(1)失敗(0)を示すフラグです。
ビヘイビアのハンドラ(割り込み、イベント、状態定義)はcppファイルに定義します。ファイルは分割できず、全てのハンドラ定義を一つのファイル中に記述します。
ハンドラの定義をしないビヘイビアの場合でも、必ず、下記のcppファイルを作成します。
cppファイルの冒頭と末尾にはMWXライブラリの必要な定義(#include "_mwx_cbs_cpphead.hpp")をインクルードする必要があります。
ファイルの冒頭には、上記のようにビヘイビア定義の.hppファイルをインクルードします。__MWX_APP_CLASS_NAMEにはビヘイビアのクラス名を指定します。上記ではMY_APP_CLASSです。
ファイルの末尾では必要な定義(#include "_mwx_cbs_cpptail.cpp")をインクルードします。
ハンドラ定義は以下の例のように記述します。定義の種別については後述します。定義用のマクロを用いて利用したいハンドラの定義を記述します。利用しないハンドラは記述しないようにしてください。
MWX_???_INT()は割り込みハンドラの定義、MWX_???_EVENT()はイベントハンドラの定義、MWX_STATE()はステートマシンの状態定義です。
割り込みハンドラは、マイコンの割り込みが発生したときに現在実行中のコードを中断して実行されます。このため、可能な限り短い処理を記述することが望ましく、また、変数等の操作に対しても細心の注意が必要です。
割り込みハンドラのパラメータにはuint8_t& handledがあり、この値をtrueにセットすることで、続くイベント呼び出しを行いません。
handledがfalseのまま割り込みハンドラを終了した場合、アプリケーションループ(通常コード)の中でイベントハンドラが呼び出されます。イベントハンドラにはhandledのパラメータはありません。イベントハンドラは通常コードですので、比較的大きな処理を行うことが出来ます。イベントハンドラのオーバーヘッドも発生するため、頻繁な割り込み都度呼び出されるような処理の場合、処理しきれなくなる可能性があります。また、イベントの発生はシステム内部のFIFOキューにより処理されるため、一定時間内に処理できない場合はイベントが消失する場合もあります。
以下にハンドラ関数定義用のマクロの解説を行います。
DIO(ディジタルIO)割り込み・イベントです。Nは対象DIOの番号を指定します。argは将来の拡張のための定義です。
割り込みを発生させるためにはpinMode()による適切な入力設定, attachDioInt()による割り込み開始の設定が必要です。
TickTimer割り込み・イベントです。argは将来の拡張のための定義です。
TickTimerのhandledフラグをtrueにセットしてはいけません。TWENETが動作しなくなります。
タイマー割り込み・イベントです。Nは対象タイマーの番号を指定します。argは将来の拡張のための定義です。
割り込みを発生させるためには、Timerオブジェクトをソフトウェア割り込みを有効にして開始します。
MWXライブラリで標準的に定義しない、その他の割り込み・イベントの定義です。AHIペリフェラルマニュアルの理解が必要です。
その他の割り込み・イベントは以下のハンドラ関数で受けることが出来ます。これらは将来的に専用のハンドラが定義された場合、利用できなくなります。
ペリフェラル (AHI) の割り込みハンドラのu32DeviceIdがarg、u32ItemBitmapがarg2に対応します。
状態マシン(ステートマシン)は、メッセージを受け取り、そのメッセージに応じて状態を遷移させながら動作させるアプリケーションの記述方法です。
PAL_AMB-behaviorサンプルでは、センサーの動作開始からセンサーの値取得、無線パケット送信から送信完了まで、スリープ遷移といったアプリケーションの動作の流れを記述しています。実例として参考にしてください。
受け取るイベントは以下となります。
イベント名
内容
E_EVENT_START_UP
システム始動時に呼び出される。電源投入直後はパラメータが0で呼び出されます。実行初期であるため、通常処理を行う状態に遷移する場合は一旦begin()メソッドからPEV_Process()を呼び出し動作を開始させます。
スリープ復帰後も呼び出されるがパラメータは0以外です。この状態からは通常処理を行えます。
E_EVENT_NEW_STATE
状態遷移直後に新しい状態で呼び出されます。ある状態に遷移したときに最初に実行される処理を記述します。
E_EVENT_TICK_TIMER
1msごとのTickTimerで呼び出されます。
E_EVENT_TICK_SECOND
1秒毎に呼び出されます。
状態をsに設定します。
状態ハンドラを抜けると次の状態に遷移し、続けてE_EVENTS_NEW_STATEイベントで状態ハンドラが呼び出されます。
状態遷移してからの経過時間[ms]を返します。タイムアウトを管理するような目的で使用します。
上記の例では100ms経過した時点でシステムリセットを行います。
状態ハンドラ外から呼び出します。状態ハンドラをイベントevパラメータu32evargで実行します。
送信完了イベントを状態マシンに伝えます。つまり状態ハンドラの呼び出しを行います。
直接状態ハンドラを呼び出すことは行わないでください。E_EVENT_NEW_STATEが実行されないなどの問題が発生します。
スリープ直前に設定します。スリープ復帰後に、直前の状態を維持します。つまり、スリープを開始した状態でE_EVENT_START_UPで状態ハンドラが呼び出されます。
イベントが起床時のE_EVENT_START_UPかどうか判定します。
イベントがスリープ復帰時のE_EVENT_START_UPかどうか判定します。
アクトのサンプル中で以下の項目は共通の設定項目になり、以下で解説します。
すでにあるバッファを指定する
alloc_local<T, int N>
Nバイトのバッファを内部に静的確保する
alloc_heap<T>
指定したサイズをヒープに確保する
alloc_attachやalloc_heapではメモリ確保クラスに応じた初期化メソッド (init_???())を実行する必要があります。
バッファーp・サイズnで初期化します。
バッファのサイズを返す。
想定したallocクラスと違うメソッド呼び出し記述に対して、static_assertのように、コンパイルエラーを発生させるためのメソッドです。
クラス名
内容
alloc_attach<T>
$ sudo apt-get install libusb-dev
$ sudo apt-get install python3-pip
$ pip3 install pyserial
$ pip3 install pyftdi#include <TWELITE>
const uint8_t PIN_LED = 5;
void setup() {
pinMode(PIN_LED, OUTPUT);
}
void loop() {
if (TickTimer.available()) {
uint32 t_now = millis();
// blink LED every 1024ms
digitalWrite(PIN_LED, (t_now >> 10) & 1 ? HIGH : LOW);
}
} // myApp.hpp
...
class myApp : MWX_APPDEFS_CRTP(myApp) {
...
void loop() {
// main loop
}
void receive(mwx::packet_rx& rx) {
// on receive
}
};
// myApp.cpp
...
MWX_DIO_EVENT(12, uint32_t arg) {
// on event from DIO12
}void loop() {
while(Serial.available() {
auto x = Serial.read(); ... } // serial message
if (Analogue.available() {
auto x = Analogue.read(...); } // adc values
if (Buttons.available() {
Buttons.read(...); } // DIO changes
if (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read(); } // on rx packet
}#include <TWELITE>
#include <NWK_SIMPLE>
void setup() {
...
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.
}
void loop() {
...
vTransmit();
...
}
void vTransmit() {
if (auto&& pkt =
the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet();
pkt << tx_addr(0x00) // to parent
<< tx_retry(0x3); // set retry
pack_bytes(pkt.get_payload() // prepare payload data
, uint8_t(0x01)
, uint16_t(analogRead(PIN_ANALOGUE::A1))
, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)));
pkt.transmit(); // transmit!
}#include <TWELITE>
#include <PAL_AMB> // include the board support of PAL_AMB
void setup() {
auto&& brd = the_twelite.board.use<PAL_AMB>(); // use PAL AMB
uint8_t u8dip = brd.get_DIP_SW(); // check DIP s/w status
brd.set_led(LED_TIMER::BLINK, 100); // LED switchs on/off every 100ms
...
// start capture of sensors
brd.sns_SHTC3.begin();
}
void loop() {
if (TickTime.available()) { // check every ms
auto&& brd = the_twelite.board.use<PAL_AMB>();
if (brd.sns_LTR308ALS.available()) {
Serial << brd.sns_SHTC3..get_temp();
} else {
// notify sensor that 1ms passed.
brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
}
}
}pinMode(PIN_LED, OUTPUT_INIT_HIGH);
pinMode(PIN_WDT, OUTPUT_INIT_LOW);
pinMode(PIN_WDT_EN, OUTPUT_INIT_LOW);
pinMode(PIN_LED_YELLOW, OUTPUT);void set_led_red(uint8_t mode, uint16_t tick)
void set_led_yellow(uint8_t mode, uint16_t tick)
// Pulse Counter setup
PulseCounter.setup();void begin() {
// start the pulse counter capturing
PulseCounter.begin(
100 // 100 count to wakeup
, PIN_INT_MODE::FALLING // falling edge
);
sleepNow();
}void wakeup() {
Serial << mwx::crlf
<< "--- Pulse Counter:" << FOURCHARS << " wake up ---"
<< mwx::crlf;
if (!PulseCounter.available()) {
Serial << "..pulse counter does not reach the reference value." << mwx::crlf;
sleepNow();
}
}uint16_t u16ct = PulseCounter.read();typedef uint8_t size_type;
typedef uint8_t value_type;void begin(
const size_type u8mode = WIRE_100KHZ,
bool b_portalt = false)void setup() {
...
Wire.begin();
...
}
void wakeup() {
...
Wire.begin();
...
}bool probe(uint8_t address)void setClock(uint32_t speed)#include <algorithm>
int v[] = { 1, 3, -1, 5, 10 };
auto&& result = std::minmax_element(std::begin(v), std::end(v));
Serial << "min=" << int(result.first)
<< ",max=" << int(result.second);class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
{
public:
static const uint8_t TYPE_ID = 0x01;
// load common definition for handlers
#define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_hpphead.hpp"
#undef __MWX_APP_CLASS_NAME
public:
// constructor
MY_APP_CLASS() {}
void _setup() {}
void _begin() {}
public:
// TWENET callback handler (mandate)
void loop() {}
void on_sleep(uint32_t & val) {}
void warmboot(uint32_t & val) {}
void wakeup(uint32_t & val) {}
void on_create(uint32_t& val) { _setup(); }
void on_begin(uint32_t& val) { _begin(); }
void on_message(uint32_t& val) { }
public:
void network_event(mwx::packet_ev_nwk& pEvNwk) {}
void receive(mwx::packet_rx& rx) {}
void transmit_complete(mwx::packet_ev_tx& evTx) {}
};class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS) #define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_hpphead.hpp"
#undef __MWX_APP_CLASS_NAMEMY_APP_CLASS() {}void receive(mwx::packet_rx& rx)void transmit_complete(mwx::packet_ev_tx& evTx)#include <TWELITE>
#include "myAppClass.hpp" // ビヘイビア定義ファイル
/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
#define __MWX_APP_CLASS_NAME MY_APP_CLASS
#include "_mwx_cbs_cpphead.hpp" // 冒頭の定義
/*****************************************************************//*****************************************************************/
// common procedure (DO NOT REMOVE)
#include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
#undef __MWX_APP_CLASS_NAME
/*****************************************************************/// TickTimer割り込み
MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
// blink LED
digitalWrite(PAL_AMB::PIN_LED,
((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}
// PAL_AMB::PIN_BIN(12)のイベント
MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
Serial << "Button Pressed" << mwx::crlf;
static uint32_t u32tick_last;
uint32_t tick = millis();
if (tick - u32tick_last > 100) {
PEV_Process(E_ORDER_KICK, 0UL);
}
u32tick_last = tick;
}
// 状態 STATE_0 の動作定義
MWX_STATE(E_MWX::STATE_0, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_START_UP) {
Serial << "[STATE_0:START_UP]" << mwx::crlf;
} else
if (ev == E_ORDER_KICK) {
PEV_SetState(E_MWX::STATE_1);
}
}
// 状態 STATE_1 の動作定義
MWX_STATE(E_MWX::STATE_1, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_NEW_STATE) {
Serial << "[STATE_1]" << mwx::crlf;
} else
if (ev == E_ORDER_KICK) {
PEV_SetState(E_MWX::STATE_2);
} else
if (ev == E_EVENT_TICK_SECOND) {
Serial << "<1>";
}
}MWX_DIO_INT(N, uint32_t arg, uint8_t& handled)
MWX_DIO_EVENT(N, arg)MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled)
MWX_TICKTIMER_EVENT(uint32_t arg)MWX_TIMER_INT(N, uint32_t arg, uint8_t& handled)
MWX_TIMER_EVENT(N, uint32_t arg)MWX_MISC_INT(uint32_t arg, uint32_t arg2, handled)
MWX_MISC_EVENT(auint32_t rg, uint32_t arg2)void PEV_SetState(uint32_t s)uint32_t PEV_u32Elaspsed_ms()MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg) {
...
if (PEV_u32Elaspsed_ms() > 100) {
// does not finish TX!
Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
the_twelite.reset_system();
}
}void PEV_Process(uint32_t ev, uint32_t u32evarg) {void transmit_complete(mwx::packet_ev_tx& txev) {
Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}void PEV_KeepStateOnWakeup()bool PEV_is_coldboot(uint32_t ev, uint32_t u32evarg)bool PEV_is_warmboot(uint32_t ev, uint32_t u32evarg)const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const char APP_FOURCHAR[] = "BAT1";void attach(T* p, int n) // alloc_attach
void init_local() // alloc_local
void init_heap(int n) // alloc_heapuint16_t alloc_size()定数
意味
const uint8_t
SPI_CONF::MSBFIRST
MSB を先頭ビットにする
const uint8_t
SPI_CONF::LSBFIRST
LSB を先頭ビットにする
const uint8_t
SPI_CONF::SPI_MODE0
SPI MODE 0 に設定する
const uint8_t
SPI_CONF::SPI_MODE1
SPI MODE 1 に設定する
const uint8_t
SPI_CONF::SPI_MODE2
SPI MODE 2 に設定する
const uint8_t
SPI_CONF::SPI_MODE3
SPIバスの利用手続きはbegin()メソッドによります。
ハードウェアの初期化を行います。
スリープ復帰後にも本処理が必須です。
パラメータ
解説
slave_select
使用するSPIスレーブのセレクトピンを指定する。
0 : DIO19
1 : DIO0 (DIO 19 は予約されます)
2 : DIO1 (DIO 0,19 は予約されます)
settings
SPIのバス設定を指定します。
clock[hz]でSPIバスの周波数を指定します。指定した周波数に近いディバイザが選択されます。16Mhzまたは16Mhzを偶数で割った値になります。
bitOrderはSPI_CONF::MSBFIRSTかSPI_CONF::LSBFIRSTを指定します。
dataModeはSPI_CONF::SPIMODE0..3を指定します。
SPIのハードウェアの利用を終了します。
読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。
メンバ関数版 (以下のメンバ関数を用いた入出力)
beginTransaction(), endTransaction(), transfer(), transfer16(), transfer32()
ヘルパークラス版(stream機能が使用可能)
transceiver

PAL_MOTアクトでは連続的に加速度データを取得して都度無線送信していました。このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。
このアクトの解説の前にPAL_MOTのアクトの解説をご覧ください。
本サンプルは、収録バージョンによって差が大きいため本ページでは2つの解説を記載します。
※ 最新のコードは「を参照ください。
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。
列挙体として eState 変数を宣言しています。
setup()を終了した後に呼ばれます。ここでは初回スリープを実行しています。
起床後は状態変数eStateを初期状態INITにセットしています。この後loop()が実行されます。
loop() の基本構造は状態変数eStateによるswitch ... case節です。eStateの初期状態はINITです。loop_moreは状態変数を書き換えた直後、loop()を抜ける前にもう一度実行したいときにtrueにセットします。
以下では各case節を解説します。eStateの初期状態はINITです。
状態INITでは、初期化(結果格納用のキューのクリア)を行います。
状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_captureに格納します。
状態WAIT_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されるとsns_MC3630.available()がtrueになります。
タイムアウトした場合は状態EXIT_FATALに遷移します。
状態REQUEST_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成、そうし尿級を行います。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_txに格納します。
状態WAIT_TXでは、無線パケットの送信完了を待ちます。
タイムアウト時には状態EXIT_FATALに遷移します。
一連の動作が完了したときは状態EXIT_NORMALに遷移しローカル定義の関数sleepNow()を呼び出しスリープを実行します。またエラーを検出した場合は状態EXIT_FATALに遷移し、システムリセットを行います。
この関数では、センサーより得られたサンプル値の取得と、複数サンプルの平均値の計算、
取得サンプルの平均値を計算します。
X軸の最大値と最小値を計算します。
ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。
ここでキューをクリア.sns_MC3630.get_que().clear()しています。
最期にパケットの生成と送信を要求を行います。パケットには X, Y, Z 軸の加速度、X軸の最小値,Y軸の最小値を含めています。
MWSDK2020_05 版の SDK 添付のサンプルコードです。
※ 最新のコードは「を参照ください。
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。
起床後加速度センサーを稼働させます。
加速度センサーの結果を保存するキューの内容を抹消(.sns_MC3630.get_que().clear())しておきます。加速度センサーのサンプルの取得忘れがあったり、また停止させるまでに次のサンプルが取得したような場合を想定します。
ここで加速度センサーを都度開始します。設定は400Hz,±4G,FIFO割り込みは4サンプルとしています。4サンプルも必要ない場合は1サンプルの設定でも構いません。
このアクトでは、サンプル取得後、すぐに加速度センサーの動作を停止します。
取得サンプルの平均値を計算します。
X軸の最大値と最小値を計算します。
ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。
最後にキューをクリア.sns_MC3630.get_que().clear()しています。
メンバ関数を利用した方法は、抽象度が比較的低く、C言語ライブラリで提供されるような一般的なAPI体系に倣っています。二線シリアルバスの操作手続きがより直感的です。
ただしバスの利用の開始と終了を明示的に意識して記述する必要があります。
指定バイト数分を一括で読み出します。読みだした結果はキューに保存されるため、直後にキューが空になるまで .read() メソッドを呼び出すようにしてください。
書き出し処理は、beginTransmission() を実行後、write() メソッドにより行います。一連の書き出しが終了したら endTranmission() を呼びます。
書き出しの転送を初期化する。書き出し処理終了後、速やかに endTransmission() を呼び出す。
1バイトの書き出しを行う。
バイト列の書き出しを行います。
書き出しの終了処理を行います。
3軸の加速度センサーの値を格納するための構造体ですが、コンテナクラスに格納したときの利便性を上げるための手続きを追加しています。
struct axis_xyzt {
int16_t x;
int16_t y;
int16_t z;
uint16_t t;
};/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x_iter(Iter p)
auto&& get_axis_y_iter(Iter p)
auto&& get_axis_z_iter(Iter p)axis_xyztを格納したコンテナクラスのイテレータをパラメータとして、X, Y, Z 軸のいずれかの要素にアクセスするイテレータを生成します。
以下の例では、buf.begin(), buf.end()をX軸用のイテレータとしてアルゴリズムstd::minmax_elementに用いています。
axis_xyztを格納したコンテナクラスのXYZ軸のいずれかの軸を取り出した仮想的なコンテナクラスを生成する関数です。この生成したクラスにはbegin()とend()メソッドのみ実装されています。このbegin()とend()メソッドで取得できるイテレータは前節のイテレータと同じものになります。
void begin(uint8_t slave_select, SPISettings settings)
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)void setup() {
...
SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
...
}
void wakeip() {
...
SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
...
}void end()SPI MODE 3 に設定する
パラメータ
解説
u8address
読み出し対象のI2Cアドレス
length
読み出しバイト数
b_send_stop=true
true の時、読み込み終了時にSTOPビットを設定する。
戻り値型 size_type
読み出したバイト数。 0 は読み出しの失敗。
パラメータ
解説
u8address
書き出し対象のI2Cアドレス
パラメータ
解説
value
書き込むバイト
戻り値 size_type
書き込んだバイト数。0はエラー。
パラメータ
解説
*value
書き込むバイト列
size_type
バイト数
戻り値 size_type
書き込んだバイト数。0はエラー。
パラメータ
解説
sendStop = true
STOPビットを発行します。
戻り値 uint8_t
0: 成功 4: 失敗
#include <algorithm>
void myfunc() {
// コンテナクラス
smplbuf_local<axis_xyzt, 10> buf;
// テスト用にデータを投入
buf[0] = { 1, 2, 3, 4 };
buf[1] = { 2, 3, 4, 5 };
...
// 最大、最小値を得るアルゴリズム
auto&& minmax = std::minmax_element(
get_axis_x_iter(buf.begin()),
get_axis_x_iter(buf.end()));
Serial << "min=" << int(*minmax.first)
<< ",max=" << int(*minmax.second) << mwx::crlf;
}enum class E_STATE {
INIT = 0,
START_CAPTURE,
WAIT_CAPTURE,
REQUEST_TX,
WAIT_TX,
EXIT_NORMAL,
EXIT_FATAL
} eState;void begin() {
// sleep immediately, waiting for the first capture.
sleepNow();
}void wakeup() {
Serial << crlf << "--- PAL_MOT(OneShot):"
<< FOURCHARS << " wake up ---" << crlf;
eState = E_STATE::INIT;
}void loop() {
auto&& brd = the_twelite.board.use<PAL_MOT>();
bool loop_more;
do {
loop_more = false;
switch(eState) {
...
}
} while(loop_more); case E_STATE::INIT:
brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
loop_more = true;
eState = E_STATE::START_CAPTURE;
break; case E_STATE::START_CAPTURE:
u32tick_capture = millis();
brd.sns_MC3630.begin(
// 400Hz, +/-4G range, get four samples (can be one sample)
SnsMC3630::Settings(
SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4));
eState = E_STATE::WAIT_CAPTURE;
break; case E_STATE::WAIT_CAPTURE:
if (brd.sns_MC3630.available()) {
brd.sns_MC3630.end(); // stop now!
eState = E_STATE::REQUEST_TX; loop_more = true;
} else if ((millis() - u32tick_capture) > 100) {
Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";
eState = E_STATE::EXIT_FATAL;
}
break; case E_STATE::REQUEST_TX:
u32tick_tx = millis();
txid = TxReq();
if (txid) {
eState = E_STATE::WAIT_TX;
} else {
Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";
eState = E_STATE::EXIT_FATAL;
}
break; case E_STATE::WAIT_TX:
if(the_twelite.tx_status.is_complete(txid.get_value())) {
eState = E_STATE::EXIT_NORMAL; loop_more = true;
} else if (millis() - u32tick_tx > 100) {
Serial << crlf << "!!!FATAL: TX TIMEOUT.";
eState = E_STATE::EXIT_FATAL;
}
break; case E_STATE::EXIT_NORMAL:
sleepNow();
break;
case E_STATE::EXIT_FATAL:
Serial << flush;
the_twelite.reset_system();
break;int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
x += v.x;
y += v.y;
z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();auto&& x_minmax = std::minmax_element(
get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
get_axis_x_iter(brd.sns_MC3630.get_que().end()));
brd.sns_MC3630.get_que().clear(); // clean up the queue // prepare tx packet
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet (first)
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint16_t(x)
, uint16_t(y)
, uint16_t(z)
, uint16_t(*x_minmax.first) // minimum of captured x
, uint16_t(*x_minmax.second) // maximum of captured x
);
// perform transmit
ret = pkt.transmit();
if (ret) {
Serial << "..txreq(" << int(ret.get_value()) << ')';
}void wakeup() {
Serial << mwx::crlf << "--- PAL_MOT(OneShot):" << FOURCHARS << " wake up ---" << mwx::crlf;
auto&& brd = the_twelite.board.use<PAL_MOT>();
brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
brd.sns_MC3630.begin(SnsMC3630::Settings(
SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4));
// 400Hz, +/-4G range, get four samples (can be one sample)
b_transmit = false;
txid = 0xFFFF;
}void loop() {
auto&& brd = the_twelite.board.use<PAL_MOT>();
if (!b_transmit) {
if (brd.sns_MC3630.available()) {
brd.sns_MC3630.end(); // stop now!
Serial << "..finish sensor capture." << mwx::crlf
<< " ct=" << int(brd.sns_MC3630.get_que().size());
// get all samples and average them.
int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
x += v.x;
y += v.y;
z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();
Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
// just see X axis, min and max
//auto&& x_axis = get_axis_x(brd.sns_MC3630.get_que());
//auto&& x_minmax = std::minmax_element(x_axis.begin(), x_axis.end());
auto&& x_minmax = std::minmax_element(
get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
get_axis_x_iter(brd.sns_MC3630.get_que().end()));
brd.sns_MC3630.get_que().clear(); // clean up the queue
// prepare tx packet
if (auto&& pkt = nwk.the_twelite.network.use<NWK_SIMPLE>()) {
auto&& pkt = nwk.prepare_tx_packet(); // get new packet instance.
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet (first)
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint16_t(x)
, uint16_t(y)
, uint16_t(z)
, uint16_t(*x_minmax.first) // minimum of captured x
, uint16_t(*x_minmax.second) // maximum of captured x
);
// perform transmit
MWX_APIRET ret = pkt.transmit();
if (ret) {
Serial << "..txreq(" << int(ret.get_value()) << ')';
txid = ret.get_value() & 0xFF;
} else {
sleepNow();
}
// finished tx request
b_transmit = true;
}
}
} else {
// wait until transmit completion.
if(the_twelite.tx_status.is_complete(txid)) {
sleepNow();
}
}
}
if (brd.sns_MC3630.available()) {
brd.sns_MC3630.end(); // stop now!int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
x += v.x;
y += v.y;
z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();auto&& x_minmax = std::minmax_element(
get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
get_axis_x_iter(brd.sns_MC3630.get_que().end()));
brd.sns_MC3630.get_que().clear(); // clean up the queuesize_type requestFrom(
uint8_t u8address,
size_type length,
bool b_send_stop = true)int len = Wire.requestFrom(0x70, 6);
for (int i = 0; i < 6; i++) {
if (Wire.available()) {
au8data[i] = Wire.read();
Serial.print(buff[i], HEX);
}
}
// skip the rest (just in case)
// while (Wire.available()) Wire.read(); // normally, not necessary.
#define DEV_ADDR (0x70)
const uint8_t msg[2] =
{SHTC3_SOFT_RST_H, SHTC3_SOFT_RST_L};
Wire.beginTransmission(DEV_ADDR);
Wire.write(msg, sizeof(msg));
Wire.endTransmission();void beginTransmission(uint8_t address)size_type write(const value_type value)size_type write(
const value_type* value,
size_type quantity)uint8_t endTransmission(bool sendStop = true)/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x(T& c)
auto&& get_axis_y(T& c)
auto&& get_axis_z(T& c)#include <algorithm>
void myfunc() {
// コンテナクラス
smplbuf_local<axis_xyzt, 10> buf;
// テスト用にデータを投入
buf[0] = { 1, 2, 3, 4 };
buf[1] = { 2, 3, 4, 5 };
...
// キューの中の X 軸を取り出す
auto&& vx = get_axis_x(que);
// 範囲for文の利用
for (auto&& e : vx) { Serial << int(e) << ','; }
// 最大、最小値を得るアルゴリズム
auto&& minmax = std::minmax_element(
vx.begin(), vx.end());
Serial << "min=" << int(*minmax.first)
<< ",max=" << int(*minmax.second) << mwx::crlf;
}動作センサーパル MOTION SENSE PAL を用い、加速度センサーの加速度を連続的に計測し、無線送信します。
コイン電池で動作させるための、スリープ機能を利用します。
役割
例
親機
アクトを動作させる。
子機
+
動作センサーパルのボードビヘイビア<PAL_MOT>をインクルードします。
最初にボードビヘイビア<PAL_MOT>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。
ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。
LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
加速度センサーの計測を開始します。加速度センサーの設定(SnsMC3630::Settings)には計測周波数と測定レンジを指定します。ここでは14HZの計測(SnsMC3630::MODE_LP_14HZ)で、±4Gのレンジ(SnsMC3630::RANGE_PLUS_MINUS_4G)で計測します。
開始後は加速度センサーの計測が秒14回行われ、その値はセンサー内部のFIFOキューに保存されます。センサーに28回分の計測が終わった時点で通知されます。
begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。
setup()終了後にsleepNow()を呼び出し初回スリープを実行します。
スリープに入るまえに加速度センサーのDIOピンの割り込み設定をします。FIFOキューが一定数まで到達したときに発生する割り込みです。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。
3行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。
加速度センサーのFIFO割り込みにより、スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。
ここではloop()で使用する変数の初期化を行っています。
ここでは、加速度センサー内のFIFOキューに格納された加速度情報を取り出し、これをもとにパケット送信を行います。パケット送信完了後に再びスリープを実行します。
b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。
最初にセンサーがavailableかどうかを確認します。割り込み起床後であるため、availableでないのは通常ではなく、そのままスリープします。
無線送信パケットでは使用しないのですが、取り出した加速度の情報を確認してみます。
加速度センサーの計測結果はbrd.sns_MC3630.get_que()で得られるFIFOキューに格納されます。
加速度センサーの計測結果を格納している構造体 axis_xyzt は x, y, z の三軸の情報に加え、続き番号 t が格納されています。
格納されているサンプル数はキューのサイズ(brd.sns_MC3630.get_que().size())を読み出すことで確認できます。通常は28サンプルですが処理の遅延等によりもう少し進む場合もあります。最初のサンプルはfront()で取得することができます。その続き番号はfront().tになります。
ここでは、サンプルをキューから取り出す前にサンプルの平均をとってみます。キューの各要素にはfor文(for (auto&& v: brd.sns_MC3630.get_que()) { ... }) でアクセスできます。for文内の v.x, v.y, v.z が各要素になります。ここでは各要素の合計を計算しています。for文終了後は要素数で割ることで平均を計算しています。
次にパケットを生成して送信要求を行いますが、データ量が大きいため2回に分けて送信します。そのため送信処理がfor文で2回行われます。
送信するパケットに含めるサンプル数とサンプル最初の続き番号をパケットのペイロードの先頭部分に格納します。
最後に加速度データを格納します。先程は平均値の計算のためにキューの各要素を参照のみしましたが、ここではキューから1サンプルずつ読み出してパケットのペイロードに格納します。
加速度センサーからのデータキューの先頭を読み出すのは.front()を用います。読みだした後.pop()を用いて先頭キューを開放します。
加速度センサーから取得されるデータは1Gを1000としたミリGの単位です。レンジを±4Gとしているため、12bitの範囲に入るように2で割って格納します。データ数を節約するため最初の4バイトにX,Y軸とZ軸の上位8bitを格納し、次の1バイトにZ軸の下位4bitの合計5バイトを生成します。
2回分の送信待ちを行うため送信IDはtxid[]配列に格納します。
その後、loop() 中 b_transmit が trueになっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。
送信完了に確認は the_twelite.tx_status.is_complete() で行っています。txid[]は送信時に戻り値として戻されたID値です。
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
このアクトの解説の前に、、を参照してください。またについても参照ください。
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
親機にPALを使用する場合は、コイン電池での動作はできません。目安として50mA以上の電流を安定して得られるような電源環境を用意してください。
PAL_AMB-behavior.hpp : setup()のみの定義です。DIP-SWを読み出し、D1..D3が上位置の場合は親機として動作し、それ以外は子機としてDIP SWに対応するIDをセットします。
Parent/myAppBhvParent.hpp : 親機用のビヘイビアクラス定義
Parent/myAppBhvParent.cpp : 実装
親機のビヘイビア名は<MY_APP_PARENT>、子機は<MY_APP_CHILD>です。
DIP SWの読み値が0の場合は親機用のビヘイビア<MY_APP_PARENT>を、それ以外の場合は子機用のビヘイビア<MY_APP_CHILD>を登録します。
親機がMONOSTICKの場合は、PAL用のDIP SWの読み値は0となり、親機としてふるまいます。ただしこの動作はMONOSTICKの仕様として定義されているものではありません。
親機はスリープをしない受信機としてふるまい、子機からのパケットを受信したときにシリアルポートにパケットの情報を出力します。
親機用がパケットを受信したときは、パケットの先頭4文字が照合(FOURCHARS)できれば、そのパケット内容を表示します。
親機の割り込みハンドラはLEDの点滅を行います。
PAL上のボタン(5)が押されたときには、状態マシンに対してE_ORDER_KICKイベントを発行します。
状態マシンは、状態遷移の参考として記述したもので、アプリケーションの動作上意味のあるものではありません。ボタンから送付されるE_ORDER_KICKイベントによる状態遷移や、タイムアウトなどを実行しています。
子機の動作の流れはPAL_AMB-usenapと同じです。初回スリープから「起床→センサー動作開始→短いスリープ→起床→センサー値取得→無線送信→無線送信完了待ち→スリープ」を繰り返します。
on_begin()から呼び出される_begin()関数では、初回スリープを実行しています。
(※_begin()関数で本処理を記述せずon_begin()に直接記述してもかまいません)
スリープからの起床処理を記述しています。
ここで初回のWire.begin()を実行しています。2回目以降のスリープ起床時では冗長な記述です。この処理はon_begin()に移動してもかまいません。
送信完了時に状態マシンに対してE_ORDER_KICKメッセージを処理します。
状態名を定義しています。
SHTC3用のセンサー取得実装例です。送付コマンド等の詳細はSHTC3のデータシートなどを参考にしてください。
LTR308ALSのセンサー取得実装例です。送付コマンド等の詳細はLTR308ALSのデータシートなどを参考にしてください。
WireWriteAndGet()はaddrのデバイスに対してcmdを1バイト送信してから、1バイト受信して値を返します。
0番の状態は特別な意味を持ちます。起動直後またはスリープ復帰後の状態です。
起動直後PEV_is_coldboot(ev,evarg)判定がtrueになって呼び出されます。on_begin()から、そのままスリープしてしまうため、状態遷移するようなコードも含まれません。この時点では主要な初期化がまだ終わっていませんので、無線パケットの送信など複雑な処理を行うことが出来ません。そのような処理を行うための最初の状態遷移を行うためにはon_begin()からイベントを送り、そのイベントに従って状態遷移を行います。
スリープ復帰後はPEV_is_warmboot(ev,evarg)がtrueになる呼び出しが最初にあります。PEV_SetState()を呼び出しSTATE_SENSOR状態に遷移します。
スリープ復帰後STATE_IDLEから遷移したとき、STATE_SENSORの状態ハンドラが続けて呼び出されます。この時のイベントevはE_EVENT_NEW_STATEです。
ここではSHTC3, LTR308ALSの2センサーの動作開始を行います。一定時間経過すれば、センサーはデータ取得可能な状態になります。この時間待ちを66ms設定のスリープで行います。スリープ前にPEV_KeepStateOnWakeup()が呼ばれている点に注意してください。この呼び出しを行うと、スリープ復帰後の状態はSTATE_IDLEではなく、スリープしたときの状態、つまりSTATE_SENSORとなります。
短いスリープから復帰するとPEV_is_warmboot(ev,evarg)判定がtrueとなる呼び出しが最初に発生します。この呼び出し時点で、無線パケットの送信などを行うことが出来ます。STATE_TXに遷移します。
ここではE_EVENT_NEW_STATEイベントの時に、センサーデータ読み出し、無線パケットの送信手続きに入ります。送信手続きの詳細は他のアクトサンプル例を参考にしてください。
送信完了まちの処理はループでのアクト記述と違い、transmit_complete()からのPEV_Process()によるメッセージを待つことで完了確認としています。メッセージを受け取った時点でスリープします。スリープ処理はSTATE_SLEEPに遷移することで行っています。
最後にタイムアウト処理を行っています。万が一送信パケットの完了メッセージが戻ってこなかった場合を想定します。PEV_u32Elaspsed_ms()はその状態に遷移してからの経過時間を[ms]で返します。時間経過した場合は、上記では(このタイムアウトは余程のことだとして)システムリセットthe_twelite.reset_system()を行います。
スリープを行います。前の状態から遷移した直後のE_EVENT_NEW_STATEに記述します。スリープ直前に他のイベントが呼ばれる可能性がありますので、必ず1回だけ実行される判定式の中でthe_twelite.sleep()を実行してください。
TWENET 利用の中核クラス (mwx::twenet)
the_tweliteオブジェクトは、TWENETの利用手続きをまとめたもので、無線の基本設定やスリープ等の手続きなど無線マイコンを操作するための手続きが含まれます。
the_tweliteはsetup()関数内で設定と開始the_twelite.begin()を行います。setup()以外では設定は行えません。
上記の例では、アプリケーションIDの設定、通信チャネルの設定、受信回路の設定を行っています。
様々な手続きが含まれます。
また無線ネットワークを取り扱うクラスやボード対応をまとめたクラス、ユーザ記述のイベントドリブン処理を行うクラスを登録できるようになっています。このクラスを登録することにより、専用化した機能を手軽に利用できるようになります。これらのクラスを本解説中では「」と呼称します。
上記の例では環境センサーパル<PAL_AMB>と、シンプル中継ネットワーク<NWK_SIMPLE>の2種類を登録しています。これらを登録することにより環境センサーパル上のセンサーなどハードウェアを簡易に取り扱うことが出来ます。また煩雑な無線パケットの取り扱いについて中継の処理や重複で届いたパケットの自動破棄などの機能を暗黙に持たせることが出来ます。
MWXライブラリには、ここで紹介したメソッド以外にも定義されています。
アクト記述には直接関係ないもの、設定しても有効に機能しないもの、内部的に使用されているものが含まれます。
<<演算子 (設定)オブジェクトthe_tweliteの初期設定を行うために<<演算子を用います。
以下に挙げる設定用のクラスオブジェクトを入力とし、設定をしなければデフォルト値が適用されます。
パラメータidに指定したアプリケーションIDを設定します。これは必須指定です。
設定の読み出しは uint32_t the_twelite.get_appid() で行います。
パラメータchに指定したチャネル番号(11..26)を設定します。
設定の読み出しはuint8_t the_twelite.get_channel()で行います。
パラメータpwに指定した出力設定を(0..3)を設定します。デフォルトは(3:出力減衰無し)です。
設定値の読み出しはuint8_t the_twelite.get_tx_power()で行います。
パラメータbEnableが1であれば常に受信回路を動作させ、他からの無線パケットを受信できるようになります。デフォルトは0で、もっぱら送信専用となります。
設定値の読み出しはuint8_t the_twelite.get_rx_when_idle()で行います。
チャネルマネージャを有効にします。チャネルを複数指定すると複数チャネルでの送受信を行います。ch2,ch3に0を指定すると、その指定は無効になります。
MWXライブラリコード中には他にも設定項目がありますが、現時点ではライブラリの機能に無関係な設定であったり、設定を行うと矛盾を起こす可能性があるものです。
事前に設定(<<演算子参照)や、ビヘイビアの登録を済ませた後に実行します。通常はsetup()関数内の一番最後に記述します。
the_twelite 設定完了
ビヘイビアの初期化
TWENETの初期化は setup() 関数が終了した後にも実行されます。多くの処理はTWENETが終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。
チャネル設定を変更します。失敗時にはチャネルは変更されずfalseを戻します。
現在設定中のチャネル番号(11..26)を取得する。MAC層のAPIより取得します。
モジュールのシリアル番号を取得します。
モジュールをスリープさせる。
スリープからの復帰要因が指定したディジタルピンである場合にtrueを返します。
スリープからの復帰要因がウェイクアップタイマーである場合にtrueを返します。
システムをリセットします。リセット後はsetup()からの処理となります。
ウォッチドッグタイマーを停止します。長時間のポーリング待ちを行うような場合はタイマーを停止します。
ウォッチドッグタイマーを再開します。
twe_tweliteには3つのビヘイビアを登録でき、これらを格納する以下のクラスオブジェクトを定義されています。
network : ネットワークを実装するビヘイビアです。通常はを登録します。
board: ボード対応のビヘイビアです。ボード上の各デバイス利用手続きが付加されます。
app: ユーザアプリケーションを記述したビヘイビアです。割り込みやイベント記述、ステートマシンによる状態遷移によるふるまいの記述が可能です。また複数のアプリケーション記述を定義しておいて、起動時に全く振る舞いの違うアプリケーションを選択する記述が容易に行えます。
ビヘイビア<B>を登録します。登録は内で行います。戻り値は登録したビヘイビアに対応するオブジェクトの参照です。
登録後は登録時と同じ書式でオブジェクトの取得を行います。
誤ったビヘイビアを指定した場合は、パニック動作(無限ループ)となりプログラムの動作が停止します。
the_tweliteには上述のboard, network, appの3つのクラスオブジェクトが定義されていますが他に以下が定義されています。
送信完了状態を通知する。
指定したIDのパケットが送信完了したときにtrueを返す。
指定したIDのパケットが送信完了し、かつ送信成功したときにtrueを返す。
受信パケットを取得する。
read()メソッドで得られる受信パケットデータは、続くパケットが受信処理時に上書きされる設計となっています。available直後に読み出してなにか短い処理をする場合は問題になることはありませんが、原則として読み出し→アプリケーションが使うため必要なデータのコピー→loop()の終了を速やかに行います。例えばloop()中で長いdelay()を行うと受信パケットの取りこぼしなどが発生します。
まだ読み出していない受信パケットが存在する場合にtrueを返す。
パケットを読み出します。
FIFOキューを構造のコンテナクラスです。
smplqueは要素の型Tとメモリの確保方法allocで指定したメモリ領域に対してFIFOキューの操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。
要素型は原則として数値や数値などを格納する構造体を想定しています。デストラクタによる破棄手続きが必要なオブジェクトを格納することを想定していません(キューから要素を抹消する際にオブジェクトを抹消する処理をしていないため)。
宣言時に割り込み禁止設定を行うクラスIntrを登録することが出来ます。このクラスは指定しない場合は、割り込み禁止制御を行わない通常の動作となります。
オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、初期サイズは0で何も格納されていません。最大サイズは変更できません。
FIFOキューですのでpush(),pop(),front()といったメソッドを用いて操作します。
イテレータによるアクセスも可能です。
型TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。
smplque_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。
smplque_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。
smplque_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。
グローバルオブジェクトを生成する場合は、コンストラクタによる初期化が行なえません。実行初期(setup()を推奨)に初期化関数init_local(),attach(),init_heap()を呼び出すようにしてください。
push()はエントリをキューに追加します。
pop()はエントリをキューから抹消します。
front()は先頭のエントリ(一番最初に追加されたもの)を参照します。
back()は末尾のエントリ(一番最後に追加されたもの)を参照します。
pop_front()は先頭のエントリを戻り値として参照し、同時にそのエントリをキューから抹消します。
empty()は配列に要素が格納されていない場合にtrueを戻します。is_full()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。
size()はキューに格納されている要素数を返します。
capacity()はキューの最大格納数を返します。
キューのすべての要素を抹消します。
要素にアクセスします。0が最初に追加した要素です。
begin()とend()によるイテレータを取得できます。イテレータの先頭はキューの最初に登録した要素です。イテレータを用いることで範囲for文やアルゴリズムが利用できます。
応用としてがあります。
10、100または1000で割った商と余りを計算します。
センサー値などで100倍した値をuint16_t型にして受け渡しする場合がありますが、除算回路がないマイコンでの計算処理には相応の時間がかかるため、加算・減算・乗算とビットシフトを用いた近似計算と補正により計算を行います。
valに計算したい値、remは余りの格納変数、negは符号を格納する変数を渡します。
戻り値は商の値(常に正)、remには余りの値(常に正)、negは負ならtrueが格納されます。
計算アルゴリズムの制約(桁あふれ)からdiv100()とdiv1000()での計算可能な値域が決まっています。div100()は-99999~99999までの値に対応し、div1000()は-999999~999999までの値に対応します。
10倍程度になります。
割り算の結果を格納するdiv_result_i32クラスにはformat()メソッドを用い_div_charsクラスオブジェクトを得ることが出来る。_div_chars()クラスオブジェクトは文字列バッファを内包していてconst char*型として文字列バッファにアクセスするメソッドが用意されている。また、Serialオブジェクトに対する<<演算子も実装している。
format()メソッドのパラメータの1番目dig_quoは出力桁数(符号部を含まず)を指定します。出力桁数に足りない場合(以下、不足桁)は空白または0で埋めます。2番目のパラメータoptは書式を指定します。
TWELITE 無線マイコンでは除算はコストが高い演算であるため、目的を限定した除算アルゴリズムを追加した。
ライブラリ内では温度・湿度といった一部のセンサー値、100倍の値(25.12℃なら2512)を用いて表現しているため、100で割った商と余りを得るための簡素な手続きを定義した。
dev_result_i32::format()については、書式出力を行う際の煩雑さを避けるためである。

入出力ストリーム
入出力ストリームを処理する上位クラスです。
CRTP (Curiously Recurring Template Pattern) 手法を用いたポリモーフィズムにより、いくつかのクラス(Serial, Wire, SPI, smplbuf) にインタフェースを提供します。
CRTP では下位クラスは template class Derived : public stream<Derived>;のように定義し、上位クラスからも下位クラスのメソッドを参照します。
汎用ディジタルIO(DIO)の操作には以下の関数を利用します。
pinMode()
digitalWrite()
digitalRead()
#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>void setup() {
/*** SETUP section */
// board
auto&& brd = the_twelite.board.use<PAL_MOT>();
brd.set_led(LED_TIMER::BLINK, 100);
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL);
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(0xFE);
/*** BEGIN section */
the_twelite.begin(); // start twelite!
brd.sns_MC3630.begin(SnsMC3630::Settings(
SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));
/*** INIT message */
Serial << "--- PAL_MOT(Cont):" << FOURCHARS
<< " ---" << mwx::crlf;
}
auto&& brd = the_twelite.board.use<PAL_MOT>();
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms) brd.sns_MC3630.begin(SnsMC3630::Settings(
SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));void begin() {
sleepNow(); // the first time is just sleeping.
}void sleepNow() {
pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);
the_twelite.sleep(60000, false);
}void wakeup() {
Serial << "--- PAL_MOT(Cont):" << FOURCHARS
<< " wake up ---" << mwx::crlf;
b_transmit = false;
txid[0] = 0xFFFF;
txid[1] = 0xFFFF;
}void loop() {
auto&& brd = the_twelite.board.use<PAL_MOT>();
if (!b_transmit) {
if (!brd.sns_MC3630.available()) {
Serial << "..sensor is not available."
<< mwx::crlf << mwx::flush;
sleepNow();
}
// send a packet
Serial << "..finish sensor capture." << mwx::crlf
<< " seq=" << int(brd.sns_MC3630.get_que().back().t)
<< "/ct=" << int(brd.sns_MC3630.get_que().size());
// calc average in the queue.
{
int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
x += v.x;
y += v.y;
z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();
Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
}
for (int ip = 0; ip < 2; ip++) {
if(auto&& pkt =
the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
// set tx packet behavior
pkt << tx_addr(0x00)
<< tx_retry(0x1)
<< tx_packet_delay(0, 0, 2);
// prepare packet (first)
uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
uint16_t seq = brd.sns_MC3630.get_que().front().t;
pack_bytes(pkt.get_payload()
, make_pair(FOURCHARS, 4)
, seq
, siz
);
// store sensor data (36bits into 5byts, alas 4bits are not used...)
for (int i = 0; i < siz; i++) {
auto&& v = brd.sns_MC3630.get_que().front();
uint32_t v1;
v1 = ((uint16_t(v.x/2) & 4095) << 20) // X:12bits
| ((uint16_t(v.y/2) & 4095) << 8) // Y:12bits
| ((uint16_t(v.z/2) & 4095) >> 4); // Z:8bits from MSB
uint8_t v2 = (uint16_t(v.z/2) & 255); // Z:4bits from LSB
pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
}
// perform transmit
MWX_APIRET ret = pkt.transmit();
if (ret) {
Serial << "..txreq(" << int(ret.get_value()) << ')';
txid[ip] = ret.get_value() & 0xFF;
} else {
sleepNow();
}
}
}
// finished tx request
b_transmit = true;
} else {
if( the_twelite.tx_status.is_complete(txid[0])
&& the_twelite.tx_status.is_complete(txid[1]) ) {
sleepNow();
}
}
}
if (!b_transmit) {if (!brd.sns_MC3630.available()) {
Serial << "..sensor is not available."
<< mwx::crlf << mwx::flush;
sleepNow();
}Serial << "..finish sensor capture." << mwx::crlf
<< " seq=" << int(brd.sns_MC3630.get_que().front().t)
<< "/ct=" << int(brd.sns_MC3630.get_que().size());
// calc average in the queue.
{
int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
x += v.x;
y += v.y;
z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();
Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
} for (int ip = 0; ip < 2; ip++) {// prepare packet (first)
uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
uint16_t seq = brd.sns_MC3630.get_que().front().t;
pack_bytes(pkt.get_payload()
, make_pair(FOURCHARS, 4)
, seq
, siz
);for (int i = 0; i < siz; i++) {
auto&& v = brd.sns_MC3630.get_que().front();
uint32_t v1;
v1 = ((uint16_t(v.x/2) & 4095) << 20) // X:12bits
| ((uint16_t(v.y/2) & 4095) << 8) // Y:12bits
| ((uint16_t(v.z/2) & 4095) >> 4); // Z:8bits from MSB
uint8_t v2 = (uint16_t(v.z/2) & 255); // Z:4bits from LSB
pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
}MWX_APIRET ret = pkt.transmit();
if (ret) {
Serial << "..txreq(" << int(ret.get_value()) << ')';
txid[ip] = ret.get_value() & 0xFF;
} else {
sleepNow();
}} else {
if( the_twelite.tx_status.is_complete(txid[0])
&& the_twelite.tx_status.is_complete(txid[1]) ) {
sleepNow();
}
}template <typename T, int N, class Intr> smplbuf_local
template <typename T, class Intr> smplbuf_attach
template <typename T, class Intr> smplbuf_heapstruct div_result_i32 {
int32_t quo; // quotient
int16_t rem; // remainder
uint8_t b_neg; // true if negative
uint8_t digits_rem; // digits of remainder
};
div_result_i32 div10(int32_t val);
div_result_i32 div100(int32_t val);
div_result_i32 div1000(int32_t val);

Parent/myAppBhvParent.hpp : 子機用のビヘイビアクラス定義
Parent/myAppBhvParent.cpp : 実装
Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装
役割
例
親機
子機
settings: 設定(インタラクティブモード)を実行するためのビヘイビアです。<SET_STD>を登録します。
パラメータ
解説
u32Periodms
スリープ時間[ms]
bPeriodic
前回の起床時間をもとに次の起床時間を再計算する。 ※次の起床タイミングが迫っているなどの理由で、現在のタイミングからになる場合もあります。
bRamoff
trueに設定すると、RAMを保持しないスリープになる(起床後はwakeup()ではなくsetup()から再初期化する必要がある)
u8Device
スリープに用いるウェイクアップタイマーの指定。
TWENET::SLEEP_WAKETIMER_PRIMARYまたは TWENET::SLEEP_WAKETIMER_SECONDARYを指定する。
不足桁は0で埋め、正の値の場合は+符号の替わりに空白を付加します。
optパラメータ
内容
DIVFMT::STD
標準的な出力で、不足桁は空白で埋め、負の値に限り-を付加します。
DIVFMT::PAD_ZERO
不足桁は0で埋めます。
DIVFMT::SIGN_PLUS
正の値にも+符号を付加します。
DIVFMT::PAD_ZERO_SIGN_PLUS
不足桁は0で埋め、正の値にも+符号を付加します。
DIVFMT::SIGN_SPACE
正の値の場合は+符号の替わりに空白を付加します。
DIVFMT::PAD_ZERO_SIGN_SPACE
// now read DIP sw status can be read.
u8ID = (brd.get_DIPSW_BM() & 0x07);
// Register App Behavior (set differnt Application by DIP SW settings)
if (u8ID == 0) {
// put settings to the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open RX channel
the_twelite.app.use<MY_APP_PARENT>();
} else {
// put settings to the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
the_twelite.app.use<MY_APP_CHILD>();
}void MY_APP_PARENT::receive(mwx::packet_rx& rx) {
uint8_t msg[4];
uint32_t lumi;
uint16_t u16temp, u16humid;
// expand packet payload (shall match with sent packet data structure, see pack_bytes())
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end(), msg);
// if PING packet, respond pong!
if (!strncmp((const char*)msg, (const char*)FOURCHARS, 4)) {
// get rest of data
expand_bytes(np, rx.get_payload().end(), lumi, u16temp, u16humid);
// print them
Serial << format("Packet(%x:%d/lq=%d/sq=%d): ",
rx.get_addr_src_long(), rx.get_addr_src_lid(),
rx.get_lqi(), rx.get_psRxDataApp()->u8Seq)
<< "temp=" << double(int16_t(u16temp)/100.0)
<< "C humid=" << double(int16_t(u16humid)/100.0)
<< "% lumi=" << int(lumi)
<< mwx::crlf << mwx::flush;
}
}MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
// blink LED
digitalWrite(PAL_AMB::PIN_LED,
((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
Serial << "Button Pressed" << mwx::crlf;
static uint32_t u32tick_last;
uint32_t tick = millis();
if (tick - u32tick_last > 100) {
PEV_Process(E_ORDER_KICK, 0UL);
}
u32tick_last = tick;
}void _begin() {
// sleep immediately.
Serial << "..go into first sleep (1000ms)" << mwx::flush;
the_twelite.sleep(1000);
}void wakeup(uint32_t & val) {
Serial << mwx::crlf << "..wakeup" << mwx::crlf;
// init wire device.
Wire.begin();
// turn on LED
digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::LOW);
// KICK it!
PEV_Process(E_ORDER_KICK, 0); // pass the event to state machine
}void transmit_complete(mwx::packet_ev_tx& txev) {
Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}static const uint8_t STATE_IDLE = E_MWX::STATE_0;
static const uint8_t STATE_SENSOR = E_MWX::STATE_1;
static const uint8_t STATE_TX = E_MWX::STATE_2;
static const uint8_t STATE_SLEEP = E_MWX::STATE_3;MWX_APIRET MY_APP_CHILD::shtc3_start()
MWX_APIRET MY_APP_CHILD::shtc3_read()MWX_APIRET MY_APP_CHILD::ltr308als_read()
MWX_APIRET MY_APP_CHILD::ltr308als_start()
static MWX_APIRET WireWriteAngGet(uint8_t addr, uint8_t cmd)MWX_STATE(MY_APP_CHILD::STATE_IDLE, uint32_t ev, uint32_t evarg) {
if (PEV_is_coldboot(ev,evarg)) {
Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
// then perform the first sleep at on_begin().
} else
if (PEV_is_warmboot(ev,evarg)) {
Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
PEV_SetState(STATE_SENSOR);
}
}MWX_STATE(MY_APP_CHILD::STATE_SENSOR, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_NEW_STATE) {
Serial << "[STATE_SENSOR:NEW] Start Sensor." << mwx::crlf;
// start sensor capture
shtc3_start();
ltr308als_start();
// take a nap waiting finish of capture.
Serial << "..nap for 66ms" << mwx::crlf;
Serial.flush();
PEV_KeepStateOnWakeup(); // stay this state on waking up.
the_twelite.sleep(66, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
} else
if (PEV_is_warmboot(ev,evarg)) {
// on wakeup, code starts here.
Serial << "[STATE_SENSOR:START_UP] Wakeup." << mwx::crlf;
PEV_SetState(STATE_TX);
}
}MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg)
static int u8txid;
if (ev == E_EVENT_NEW_STATE) {
Serial << "[STATE_TX:NEW]" << mwx::crlf;
u8txid = -1;
auto&& r1 = shtc3_read();
auto&& r2 = ltr308als_read();
Serial << "..shtc3 t=" << int(i16Temp) << ", h=" << int(i16Humd) << mwx::crlf;
Serial << "..ltr308als l=" << int(u32Lumi) << mwx::crlf;
if (r1 && r2) {
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {void transmit_complete(mwx::packet_ev_tx& txev) {
Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}
// ↓ ↓ ↓ メッセージ送付
} else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
PEV_SetState(STATE_SLEEP);
} if (PEV_u32Elaspsed_ms() > 100) {
// does not finish TX!
Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
the_twelite.reset_system();
}MWX_STATE(MY_APP_CHILD::STATE_SLEEP, uint32_t ev, uint32_t evarg) {
if (ev == E_EVENT_NEW_STATE) {
Serial << "..sleep for 5000ms" << mwx::crlf;
pinMode(PAL_AMB::PIN_BTN, PIN_MODE::WAKE_FALLING_PULLUP);
digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::HIGH);
Serial.flush();
the_twelite.sleep(5000); // regular sleep
}
}void setup() {
...
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL)
<< TWENET::rx_when_idle();
...
the_twelite.begin();
}// シリアル番号を得る
uint32_t u32hwser = the_twelite.get_hw_serial();
// チャネルを 11 に設定する
the_twelite.change_channel(11);
// 1秒のスリープを行う
the_twelite.sleep(1000);
// リセットを行う
the_twelite.reset_system();void setup() {
/*** SETUP section */
// use PAL_AMB board support.
auto&& brd = the_twelite.board.use<PAL_AMB>();
...
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID);
void begin()void setup() {
// use PAL_AMB board support.
auto&& brd = the_twelite.board.use<PAL_AMB>();
// settings
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL)
<< TWENET::rx_when_idle();
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID);
// somo others
// begin the TWENET!
the_twelite.begin();
}inline bool change_channel(uint8_t u8Channel)uint8_t get_channel_phys()inline uint32_t get_hw_serial()inline void sleep(
uint32_t u32Periodms,
bool_t bPeriodic = true,
bool_t bRamOff = false,
uint8_t u8Device = TWENET::SLEEP_WAKETIMER_PRIMARY)bool is_wokeup_by_dio(uint8_t port)bool is_wokeup_by_wktimer()inline void reset_system()inline void stop_watchdog()inline void restart_watchdog()// 例
auto&& brd = the_twelite.board.use<PAL_AMB>();void loop() {
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
}NWK_SIMPLE *pNwk = nullptr;
setup() {
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
pNwk = &nwk;
}
void transmit() {
if (auto&& pkt = pNwk->prepare_tx_packet()) {
...
}
}bool is_complete(uint8_t cbid)bool is_success(uint8_t cbid)bool available()packet_rx& read()
void some_func() {
// 内部に固定配列
smplque_local<uint8_t, 128> q1;
// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;
// ヒープに確保する
smplque_heap<uint8_t> q3;
void setup() {
// グローバル定義のオブジェクトは setup() で初期化
q1.init_local();
q2.attach(buf, 128);
q3.init_heap(128);
}
void some_func() {
// ローカル定義の smplque_local は init_local() は省略できる
smplque_local<uint8_t, 128> q_local;
..
}
void begin() { // begin() は起動時1回のみ動作する
smplque_local<int, 32> q1;
q1.push(1);
q1.push(4);
q1.push(9);
q1.push(16);
q1.push(25);
while(!q1.empty()) {
Serial << int(q1.front()) << ',';
q1.pop();
}
// output -> 1,4,9,16,25,
}void begin() { // begin() は起動時1回のみ動作する
smplque_local<int, 32> q1;
q1.init_local();
q1.push(1);
q1.push(4);
q1.push(9);
q1.push(16);
q1.push(25);
// イテレータを利用
for(int x : q1) {
Serial << int(x) << ',';
}
// STLアルゴリズムの適用
auto&& minmax = std::minmax_element(q1.begin(), q1.end());
Serial << "min=" << int(*minmax.first)
<< ",max=" << int(*minmax.second);
// output -> 1,4,9,16,25,min=1,max=25[]
}smplbuf_local<T,N>
smplbuf_local<T,N>::init_local()
smplbuf_attach<T>
smplbuf_attach<T>::attach(T* buf, uint16_t N)
smplbuf_heap<T>
smplbuf_heap<T>::init_heap(uint16_t N);
//例
// 内部に固定配列
smplque_local<uint8_t, 128> q1;
q1.init_local();
// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;
q2.attach(buf, 128);
// ヒープに確保する
smplque_heap<uint8_t> q3;
q3.init_heap(128); inline void push(T&& c)
inline void push(T& c)
inline void pop()
inline T& front()
inline T& back()
inline T& pop_front()inline bool empty()
inline bool is_full()
inline uint16_t size()
inline uint16_t capacity()inline void clear()inline T& operator[] (int i)inline smplque::iterator begin()
inline smplque::iterator end()div100()
int dv = val * 1311 >> 17;
div1000()
int dv = val * 131 >> 17;auto d1 = div100(sns_val.u16temp_object);
auto d2 = div100(sns_val.u16temp_object);
Serial
<< crlf << format("..Object = %c%2d.%02d"
, d1.b_neg ? '-' : '+', d1.quo, d1.rem)
<< format(" Ambient = %c%2d.%02d"
, d2.b_neg ? '-' : '+', d2.quo, d2.rem);// 変換オプション
struct DIVFMT {
static const int STD = 0; // displays with minimul digits (no padding, no positive sign)
static const int PAD_ZERO = 1; // set padding character as '0' instead of ' '.
static const int SIGN_PLUS = 2; // put '+' sign if value is positive or 0.
static const int PAD_ZERO_SIGN_PLUS = 3; // PAD_ZERO & SIGN_PLUS
static const int SIGN_SPACE = 4; // put ' ' sign if value is positive or 0.
static const int PAD_ZERO_SIGN_SPACE = 5; // PAD_ZERO & SIGN_SPACE
};
// 文字列変換結果を格納するためのクラス
class _div_chars {
...
const char* begin() const {...}
const char* end() const {...}
const char* c_str() const { return begin(); }
operator const char*() const { return begin(); }
};
// format()メソッド
_div_chars div_result_i32::format(
int dig_quo = 0, uint32_t opt = DIVFMT::STD) const;
// Serialへのインタフェースの実装
template <class D> class stream {
...
inline D& operator << (const mwx::_div_chars&& divc);
inline D& operator << (const mwx::div_result_i32&&);
inline D& operator << (const mwx::div_result_i32&);
};//// div_result_i32オブジェクトから直接出力
Serial << div100(-1234) << crlf;
// 結果: -12.34
//// 3桁で出力します
Serial << div100(3456).format(3, DIVFMT::PAD_ZERO_SIGN_PLUE) << crlf;
// 結果: +034.56
//// c_str()を使ってconst char*を得る
char str1[128];
auto x = div100(-5678);
mwx_snprintf(str1, 16, "VAL=%s", x.format.c_str()); // const char*
Serial << str1;
// 結果: VAL=-56.78本クラスでは print メソッド、<< 演算子などの共通処理の定義を行い、下位クラスで実装した write() メソッドなどを呼び出すことで、仮想関数を用いるのと近い実装を行っています。
下位クラスでは、以下に列挙する関数を実装します。
入力が存在する場合は 1、存在しない場合は 0 を返します。
パラメータ
解説
戻り値 int
0: データなし 1:データあり
本実装の戻り値はバッファ長ではありません。
出力をフラッシュ(出力完了まで待つ)します。
ストリームより1バイトデータを入力します。データが存在しない場合は -1 を戻します。
ストリームに1バイト出力します。
パラメータ
解説
n
出力したい文字。
戻り値 size_t
出力が成功すれば 1、失敗すれば 0。
1バイト出力を行うスタティック関数です。クラスメソッドではないため、メンバー変数等の情報は利用できません。替わりにパラメータとして渡される vp にクラスインスタンスへのポインタを渡します。
このスタティック関数は内部的に利用されfctprintf()の1バイト出力関数として関数ポインタが渡ります。これを用いてprintメソッドなどを実装しています。
パラメータ
解説
out
出力したい文字
vp
クラスインスタンスへのポインタ 通常は、元のクラスにキャストして write() メソッドを呼び出す
1バイト出力します。
各種整形出力を行います。
パラメータ
解説
val
整形出力したい数値型
base
出力形式
BIN 二進数 / OCT 8進数 / DEC 10進数 / HEX 16進数
place
小数点以下の桁数
戻り値 size_t
書き出したバイト数
printf 形式での出力を行います。
TWESDK/TWENET/current/src/printf/README.md 参照
引数型
解説
char
1バイト出力 (数値としてフォーマットはしない)
int
整数出力 (printf の "%d")
double
数値出力 (printf の "%.2f")
uint8_t
1バイト出力する(char型と同様)
uint16_t
2バイト出力する(ビッグエンディアン順)
>>演算子を用いた入力タイムアウトとエラーを管理します。
set_timeout() によりタイムアウト時間を指定し、>>演算子により入力処理を行います。所定時間内までに入力が得られない場合は get_error_status() によりエラー値を読み出せます。clear_error_status()によりエラー状況をクリアします。
引数型
解説
centisec
1/10秒単位でタイムアウト時間を設定します。
0xffを指定した場合は、タイムアウトを無効とします。
値
意味
0
エラーなし
1
エラー状況
入力処理を行います。
setup() 内では実行できません。
ポーリング待ちを行うため、タイムアウトの時間設定(タイムアウト無しなど)によっては、ウォッチドッグタイマーが発動してリセットする場合があります。
以下に読み出し格納できる型を列挙します。
引数型
解説
uint8_t, char_t
1バイト入力
uint16_t
2バイト入力(ビッグエンディアン順)
uint32_t
4バイト入力(ビッグエンディアン順)
null_stream(int n)
nバイト読み捨てる
attachIntDio()
detachIntDio()
定義
名称
const uint8_t PIN_DIGITAL::DIO0 .. 19
DIOピン0~19
const uint8_t PIN_DIGITAL::DO0 .. 1
DOピン0,1
以下の列挙値は型名 E_PIN_MODEで取り扱われます。
定義
プルアップ
名称
PIN_MODE::INPUT
無
入力
PIN_MODE::OUTPUT
無
出力
PIN_MODE::INPUT_PULLUP
有
入力
PIN_MODE::OUTPUT_INIT_HIGH
以下の列挙値は型名 E_PIN_MODEで取り扱われます。
定義
名称
PIN_MODE::OUTPUT
出力
PIN_MODE::OUTPUT_INIT_HIGH
出力(初期状態HIGH)
PIN_MODE::OUTPUT_INIT_LOW
出力(初期状態LOW)
PIN_MODE::DISABLE_OUTPUT
出力設定をやめる
以下の列挙値は型名 E_PIN_STATEで取り扱われます。
定義
値
名称
PIN_STATE::HIGH
1
HIGHレベル(=Vccレベル)
PIN_STATE::LOW
0
LOWレベル(=GNDレベル)
以下の列挙値は型名 E_PIN_INT_MODEで取り扱われます。
定義
名称
PIN_INT_MODE::FALLING
立ち下り
PIN_INT_MODE::RISING
立ち上がり
void loop() {
uint8_t c;
while(Serial.available()) {
Serial >> c;
// または c = Serial.read();
switch(c) { ... } // cの値によって処理を分岐する
}
}int available()
// example
while(Serial.available()) {
int c = Serial.read();
// ... any
}void flush()
// example
Serial.println("long long word .... ");
Serial.flush();int read()
// example
int c;
while (-1 != (c = read())) {
// any
}size_t write(int c)
// example
Serial.write(0x30);static void vOutput(char out, void* vp)void mwx::stream::putchar(char c)
// example
Serial.putchar('A');
// result -> Asize_t print(T val, int base = DEC) // T: 整数型
size_t print(double val, int place = 2)
size_t print(const char*str)
size_t print(std::initializer_list<int>)
// example
Serial.print("the value is ");
Serial.print(123, DEC);
Serial.println(".");
// result -> the value is 123.
Serial.print(123.456, 1);
// result -> 123.5
Serial.print({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.size_t printfmt(const char* format, ...);
// example
Serial.printfmt("the value is %d.", 123);
// result -> the value is 123.// examples
Serial << "this value is" // const char*
<< int(123)
<< '.';
<< mwx::crlf;
// result -> this value is 123.
Serial << fromat("this value is %d.", 123) << twe::crlf;
// result -> this value is 123.
Serial << mwx::flush; // flush here
Serial << bigendian(0x1234abcd);
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
Serial << int(0x30) // output 0x30=48, "48"
<< '/'
<< uint8_t(0x31); // output '1', not "48"
// result -> 48/1
smplbuf<char,16> buf = { 0x12, 0x34, 0xab, 0xcd };
Serail << but.to_stream();
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
Seiral << make_pair(buf.begin(), buf.end());
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
Serial << bytelist({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
uint8_t get_error_status()
void clear_error_status()
void set_timeout(uint8_t centisec)
// example
Serial.set_timeout(100); // 1000msのタイムアウトを設定
uint8_t c;
Serial >> c;inline D& operator >> (uint8_t& v)
inline D& operator >> (char_t& v)
template <int S> inline D& operator >> (uint8_t(&v)[S])
inline D& operator >> (uint16_t& v)
inline D& operator >> (uint32_t& v)
inline D& operator >> (mwx::null_stream&& p)
//// 例
uint8_t c;
the_twelite.stop_watchdog(); // ウォッチドッグの停止
Serial.set_timeout(0xFF); // タイムアウト無し
// 1バイト読み出す
Serial >> c;
Serial << crlf << "char #1: [" << c << ']';
// 読み捨てる
Serial >> null_stream(3); // 3バイト分読み捨てる
Serial << crlf << "char #2-4: skipped";
// 4バイト分読み出す (uint8_t 型固定長配列限定)
uint8_t buff[4];
Serial >> buff;
Serial << crlf << "char #5-8: [" << buff << "]";uint32_t
4バイト出力する(ビッグエンディアン順)
format()
printf 形式での出力
mwx::crlf
改行 CRLF の出力
mwx::flush
出力のフラッシュ
bigendian()
数値型をビッグエンディアン順で出力する。(右辺値)
std::pair<T*, T*>
バイト型の begin(), end() ポインタを格納したペア。make_pair により生成できる。Tは uint8_t 型を想定する。(右辺値)
bytelist()
std::initializer_list を用いるバイト列の出力
smplbuf<uint8_t,AL>&
uint8_t型の配列クラスの内容を出力する。
ALCはメモリ確保手段。
smplbuf<uint8_t, AL>::to_stream()
smplbuf<T> のデータを出力する
Tは uint8_t型、ALはメモリ確保手段。
無
出力(初期状態HIGH)
PIN_MODE::OUTPUT_INIT_LOW
無
出力(初期状態LOW)
PIN_MODE::WAKE_FALLING
無
入力、起床ピン、立下り
PIN_MODE::WAKE_RISING
無
入力、起床ピン、立上り
PIN_MODE::WAKE_FALLING_PULLUP
有
入力、起床ピン、立下り
PIN_MODE::WAKE_RISING_PULLUP
有
入力、起床ピン、立上り
PIN_MODE::DISABLE_OUTPUT
有
入力状態に戻す
サンプルアクトの子機からのパケットを受信して、アスキー形式で出力する。
役割
例
親機
子機
サンプルアクトの子機設定 (例: +にを書き込んだもの)
MONOSTICK用のボードビヘイビア<MONOSTICK>をインクルードしています。このボードサポートには、LEDの制御、ウォッチドッグ対応が含まれます。
ボードサポートの使用とLEDの動作を設定しています。
2行目では赤色のLEDを無線パケットを受信したら200ms点灯する設定をしています。最初のパラメータはLED_TIMER::ON_RXが無線パケット受信時を意味します。2番目は点灯時間をmsで指定します。
3行目はLEDの点滅指定です。1番目のパラメータはLED_TIMER::BLINKが点滅の指定で、2番目のパラメータは点滅のON/OFF切り替え時間です。500msごとにLEDが点灯、消灯(つまり1秒周期の点滅)を繰り返します。
ここではNWK_SIMPLE::logical_id(0x00)を設定し親機(論理ID=0x00)であることを指定します。
パケットを受信したとき、その内容を表示します。ここでは2種類の表示を行っています。
最初の出力は<NWK_SIMPLE>の制御データを含めたデータをすべて表示します。制御データは11バイトあります。通常は制御情報を直接参照することはありませんが、あくまでも参考です。
1行目は出力用のシリアルパーサをローカルオブジェクトとして宣言しています。内部にバッファを持たず、外部のバッファを流用し、パーサーの出力機能を用いて、バッファ内のバイト列を書式出力します。
2行目はシリアルパーサーのバッファを設定します。すでにあるデータ配列、つまり受信パケットのペイロード部を指定します。serparser_attach poutは、既にあるバッファを用いたシリアルパーサーの宣言です。pout.begin()の1番目のパラメータは、パーサーの対応書式をPARSER::ASCIIつまりアスキー形式として指定しています。2番目はバッファの先頭アドレス。3番目はバッファ中の有効なデータ長、4番目はバッファの最大長を指定します。出力用で書式解釈に使わないため4番目のパラメータは3番目と同じ値を入れています。
6行目でシリアルポートへ>>演算子を用いて出力しています。
7行目のSerial << mwx::flushは、ここで出力が終わっていないデータの出力が終わるまで待ち処理を行う指定です。(Serial.flush()も同じ処理です)
2番目の出力はより実践的です。ユーザが定義した並び順で書式を構成します。
1行目はアスキー書式に変換する前のデータ列を格納するバッファをローカルオブジェクトとして宣言しています。
2行目はpack_bytes()を用いてデータ列を先ほどのbufに格納します。データ構造はソースコードのコメントを参照ください。pack_bytes()のパラメータにはsmplbuf_u8 (smplbuf<uint8_t, ???>)形式のコンテナを指定することもできます。
13,14,17行目は、シリアルパーサーの宣言と設定、出力です。
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <MONOSTICK>void setup() {
/*** SETUP section */
auto&& brd = the_twelite.board.use<MONOSTICK>();
brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0x00); // set Logical ID. (0x00 means parent device)
/*** BEGIN section */
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- MONOSTICK_Parent act ---" << mwx::crlf;
}auto&& brd = the_twelite.board.use<MONOSTICK>();
brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0x00);void loop() {
// receive RF packet.
while (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;
// output type1 (raw packet)
// uint8_t : 0x01
// uint8_t : src addr (LID)
// uint32_t : src addr (long)
// uint32_t : dst addr (LID/long)
// uint8_t : repeat count
// total 11 bytes of header.
//
// N : payload
{
serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData,
rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);
Serial << "RAW PACKET -> ";
pout >> Serial;
Serial << mwx::flush;
}
// output type2
{
smplbuf_u8<128> buf;
mwx::pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // src addr (LID)
, uint8_t(0xCC) // cmd id (0xCC, fixed)
, uint8_t(rx.get_psRxDataApp()->u8Seq) // seqence number
, uint32_t(rx.get_addr_src_long()) // src addr (long)
, uint32_t(rx.get_addr_dst()) // dst addr
, uint8_t(rx.get_lqi()) // LQI
, uint16_t(rx.get_length()) // payload length
, rx.get_payload() // payload
);
serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
Serial << "FMT PACKET -> ";
pout >> Serial;
Serial << mwx::flush;
}
}
}serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData,
rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);
Serial << "RAW PACKET -> ";
pout >> Serial;
Serial << mwx::flush;
// 参考:制御部のパケット構造
// uint8_t : 0x01 固定
// uint8_t : 送信元のLID
// uint32_t : 送信元のロングアドレス(シリアル番号)
// uint32_t : 宛先アドレス
// uint8_t : 中継回数smplbuf_u8<128> buf;
mwx::pack_bytes(buf
, uint8_t(rx.get_addr_src_lid()) // 送信元の論理ID
, uint8_t(0xCC) // 0xCC
, uint8_t(rx.get_psRxDataApp()->u8Seq) // パケットのシーケンス番号
, uint32_t(rx.get_addr_src_long()) // 送信元のシリアル番号
, uint32_t(rx.get_addr_dst()) // 宛先アドレス
, uint8_t(rx.get_lqi()) // LQI:受信品質
, uint16_t(rx.get_length()) // 以降のバイト数
, rx.get_payload() // データペイロード
);
serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
Serial << "FMT PACKET -> ";
pout >> Serial;
Serial << mwx::flush;Slp_Wk_and_Tx は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。
setup(), loop() の形式では、どうしても loop() 中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()中を switch構文による単純な状態遷移を用いることで、コードの見通しを良くしています。
このアクトの解説の前にBRD_APPTWELITEの解説をご覧いただくことを推奨します。
起動後、速やかにスリープする
setup() : 初期化する
begin() : スリープに遷移する
パケット送信を行うため <NWK_SIMPLE> をインクルードしています。また、アプリケーションIDなど基本的な定義は "Common.h" に記述しています。
Common.h には基本的な定義に加え、以下の列挙体が定義されています。こちらを状態変数として利用します。
the_tweliteクラスオブジェクトの初期化とネットワーク <NWK_SIMPLE> の登録を行います。
setup()の直後に一度だけ呼び出されます。SleepNow()関数夜を呼び出して初回のスリープ手続きを行います。
起床直後に呼び出されます。ここでは状態変数eStateを初期状態E_STATE::INITにセットします。この後loop()が呼び出されます。
上記のコードは、実際のコードを簡略化したものです。
このコードではloop_moreを条件としてdo..while() 構文のループになっています。これはloop()を脱出せずに次の状態のコード(case節)を実行する目的です。
wakeup()でINIT状態にセットされていますので初回のloop()ではINITが評価されます。ここは変数の初期化しているだけです。続く状態の処理を行うのにloop()を脱出する必要がないためloop_moreをtrueにしています。
WORK_JOB状態ではTichTimer.available()になるたびにカウンタを減算し0になったら次の状態TXに遷移します。
TX状態ではvTransmit()関数を呼び出しパケット送信要求を行います。この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。パケット送信要求が成功したらWAIT_TX状態に遷移します。タイムアウトの管理のため、この時点でのシステム時間u32millis_txを保存します。失敗したらEXIT_FATALに遷移します(モジュールのリセット実行)。
WAIT_TX状態では送信IDに対応する送信が完了したかを判定し、完了したらEXIT_NORMAL、100ms以上経過したらEXIT_FATAL状態に遷移します。
EXIT_NORMAL状態はSleepNow()を呼び出し、スリープします。
EXIT_FATAL状態ではシステムリセットを行います。
周期スリープを行います。スリープ時間はrandom()関数を用いて、一定の時間ブレを作っています。これは複数のデバイスの送信周期が同期した場合、著しく失敗率が上がる場合があるためです。
ID=0x00の親機宛に無線パケットの送信要求を行います。格納されるデータはActサンプルで共通に使われている4文字識別子(FOURCC)に加え、システム時間[ms]が含まれます。
MWX_APIRETはuint32_t型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()の戻り型になっており、送信要求の成功と失敗ならびに送信IDをデータ部に格納します。
スリープ起床後、状態変数を初期化し、以下の順に動作を行う
wakeup(): スリープからの起床、各初期化を行う
loop()状態INIT->WORK_JOBに遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し 100 カウント後にTX状態に進む)
loop() 状態TX: 送信要求を行う
loop() 状態WAIT_TX: 送信完了待ちを行う
loop() 状態EXIT_NORMAL: スリープする (1. に戻る)
loop() 状態EXIT_FATAL: エラーが発生した場合は、モジュールリセットする
#include <TWELITE>
#include <NWK_SIMPLE>
#include "Common.h"enum class E_STATE {
INIT = 0, // INIT STATE
WORK_JOB, // do some job (e.g sensor capture)
TX, // reuest transmit
WAIT_TX, // wait its completion
EXIT_NORMAL, // normal exiting.
EXIT_FATAL // has a fatal error (will do system reset)
};void setup() {
/*** SETUP section */
txreq_stat = MWX_APIRET(false, 0);
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(false); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(DEVICE_ID); // set Logical ID.
/*** BEGIN section */
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- Sleep an Tx Act ---" << crlf;
}void begin() {
Serial << "..begin (run once at boot)" << crlf;
SleepNow();
}void wakeup() {
Serial << crlf << int(millis()) << ":wake up!" << crlf;
eState = E_STATE::INIT;
}void loop() {
bool loop_more; // if set, one more loop on state machine.
do {
loop_more = false; // set no-loop at the initial.
switch(eState) {
case E_STATE::INIT:
eState = E_STATE::WORK_JOB; loop_more = true;
break;
case E_STATE::WORK_JOB:
if (TickTimer.available())
if(--dummy_work_count == 0)
eState = E_STATE::TX: loop_more = true;
break;
case E_STATE::TX:
txreq_stat = vTransmit();
if(txreq_stat) {
u32millis_tx = millis();
eState = E_STATE::WAIT_TX;
} else {
eState = E_STATE::EXIT_FATAL; loop_more = true;
}
break;
case E_STATE::WAIT_TX:
if (the_twelite.tx_status.is_complete(txreq_stat.get_value())) {
eState = E_STATE::EXIT_NORMAL; loop_more = true;
} else if (millis() - u32millis_tx > 100) {
eState = E_STATE::EXIT_FATAL; loop_more = true;
}
break;
case E_STATE::EXIT_NORMAL:
SleepNow();
break;
case E_STATE::EXIT_FATAL:
the_twelite.reset_system();
break;
}
} while (loop_more);void SleepNow() {
uint16_t u16dur = SLEEP_DUR;
u16dur = random(SLEEP_DUR - SLEEP_DUR_TERMOR, SLEEP_DUR + SLEEP_DUR_TERMOR);
Serial << int(millis()) << ":sleeping for " << int(u16dur) << "ms" << crlf << mwx::flush;
the_twelite.sleep(u16dur, false);
}MWX_APIRET vTransmit() {
Serial << int(millis()) << ":vTransmit()" << crlf;
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x3 send four times in total)
<< tx_packet_delay(0,0,2); // send packet w/ delay (send first packet with randomized delay from 0 to 0ms, repeat every 2ms)
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCC, 4) // string should be paired with length explicitly.
, uint32_t(millis()) // put timestamp here.
);
// do transmit
//return nwksmpl.transmit(pkt);
return pkt.transmit();
}
return MWX_APIRET(false, 0);
}定数
種別
uint8_t PARSER::ASCII = 1
アスキー形式
uint8_t PARSER::BINARY = 2
バイナリー形式
バイナリ形式の取り扱いはアスキー形式に比べ、必要なツールや確認方法を含め一般に取り扱いが煩雑になります。通常はアスキー形式をお使いください。
アスキー形式は、バイナリで構成されたデータ列を文字列で表現する方法です。
例えばバイト列で 00A01301FF123456 をアスキー形式で表現すると、以下のようになります。先頭は : で B1 がチェックサム、終端は [CR:0x0d][LF:0x0a] となります。
:00A01301FF123456B1[CR][LF]
終端のチェックサムを省略できます。チェックサムからCRLFの系列をXに置き換えます。文字化けによる誤ったデータ系列には弱くなりますが、実験などでデータを送付したいときに便利です。
:00A01301FF123456X
======
元データのバイト数
バイト数
解説
ヘッダ
1
:(0x3A) コロンを指定します。
データ部
N
2N
元データの各バイトをアスキー文字列2文字(A-F は大文字)で表現します。
例えば 0x1F は 1 (0x31) F (0x46) と表現します。
チェックサム
通常はアスキー形式を利用してください。
マイコン間通信での実装を考えるとバイナリ形式のほうが効率的ですが、実験などでの送受信の確認にはバイナリ通信に対応した特別なターミナルなどを準備する必要があり、チェックサムの計算も必須です。アスキー形式より利用の難易度は高くなります。
バイナリ形式は、バイナリで構成されたデータ列にヘッダとチェックサムを付加して送付する方法です。
例えば 00A01301FF123456をバイナリ形式で表現すると、以下のようになります。
0xA5 0x5A 0x80 0x08 0x00 0xA0 0x13 0x01 0xFF 0x12 0x34 0x56 0x3D
======
元データのバイト数
形式におけるバイト数
解説
ヘッダ
2
0xA5 0x5A を指定します。
データ長
2
データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。
例えばデータ部の長さが 8 バイトなら0x80 0x08 を指定します。
データ部
宣言にはメモリの確保クラスを指定します。この指定は煩雑であるため、上述のように別名定義を行っています。
クラス名(別名定義) メモリ確保
内容
serparser_attach
すでにあるバッファをbegin()にて指定する
serparser_local<N>
Nバイトのバッファを内部に確保する
serparser_heap
begin()メソッドのパラメータで指定したサイズをヒープに確保する
メモリ確保クラスに応じたbegin()メソッドを呼び出します。
tyで指定する形式で、pで指定したバッファを用います。バッファの最大長はmax_sizで、バッファの有効データ長をsizで指定します。
この定義は、特に、データ列を書式出力したい場合に用います(>> 演算子参照)
tyで指定する形式で初期化を行います。
tyで指定する形式で、sizで指定したサイズをヒープに確保して初期化します。
一度確保したヒープ領域は解放できません。
内部バッファを返す。バッファは smplbuf<uint8_t, alloc> 型となります。
入力文字を処理する。書式入力の入力文字列を1バイト受け取り書式に従い解釈します。例えばASCII書式では:00112233Xのような系列を入力として受け取りますが : 0 0 ... X の順で1バイトずつ入力し、最後の X を入力した時点で書式の解釈を完了し、完了済みと報告します。
parse()のパラメータは入力バイト、戻り値は解釈完了であればtrueを戻します。
trueならparse()により読み出しが完了した状態で、falseなら解釈中となります。
内部バッファを書式形式でストリーム(Serial)に対して出力します。
// serparser_attach : 既存のバッファを用いる
serparser_attach
// serparser : Nバイトのバッファを内部に確保する
serparser_local<N>
// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap// serparser_attach : 既存のバッファを用いる
serparser_attach p1;
uint8_t buff[128];
p1.begin(ARSER::ASCII, buff, 0, 128);
// serparser : Nバイトのバッファを内部に確保する
serparser p2<128>;
p2.begin(PARSER::ASCII);
// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap p3;
p3.begin(PARSER::ASCII, 128);void begin(uint8_t ty, uint8_t *p, uint16_t siz, uint16_t max_siz)void begin(uint8_t ty)void begin(uint8_t ty, uint16_t siz)BUFTYPE& get_buf()inline bool parse(uint8_t b)while (Serial.available()) {
int c = Serial.read();
if (SerialParser.parse(c)) {
// 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
auto&& b = SerialParser.get_buf();
// 以下は得られたデータ列に対する処理を行う
if (b[0] == 0xcc) {
// ...
}
}
}operator bool() while (Serial.available()) {
int c = Serial.read();
SerialParser.parse(c);
if(SerialParser) {
// 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
auto&& b = SerialParser.get_buf();
// ...
}
}uint8_t u8buf[] = { 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc };
ser_parser pout;
pout.begin(ARSER::ASCII, u8buf, 6, 6); // u8bufの6バイトを指定
Serial << pout;// Serialに書式出力 -> :112233AABBCC??[CR][LF] 2
データ部の各バイトの和を8ビット幅で計算し2の補数をとります。つまりデータ部の各バイトの総和+チェックサムバイトを8ビット幅で計算すると0になります。
チェックサムバイトをアスキー文字列2文字で表現します。
例えば 00A01301FF123456 では 0x00 + 0xA0 + ... + 0x56 = 0x4F となり、この二の補数は0xB1 です。(つまり 0x4F + 0xB1 = 0x00)
フッタ
2
[CR] (0x0D) [LF] (0x0A) を指定する。
N
N
元データを指定します。
チェックサム
1
データ部の各バイトの XOR を計算します。
例えばデータ部が 00A01301FF123456なら 0x00 xor 0xA0 xor ... 0x56 = 0x3D となります。
フッタ
(1)
チェックサムが事実上の終端です。無線モジュールからの出力では 0x04 (EOT) が付加されます。
開閉センサーパル OPEN-CLOSE SENSE PAL を用い、磁気センサーの検出時に割り込み起床し、無線送信します。
コイン電池で動作させるための、スリープ機能を利用します。
役割
例
親機
アクトを動作させる。
子機
+
開閉センサーパルのボード ビヘイビア<PAL_MAG>をインクルードします。
最初にボードビヘイビア<PAL_MAG>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。
ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。
LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。
setup()終了後にsleepNow()を呼び出し初回スリープを実行します。
スリープに入るまえに磁気センサーのDIOピンの割り込み設定をします。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。
7行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。
スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。
ここではウェイクアップタイマーからの起床の場合(the_twelite.is_wokeup_by_wktimer())は再びスリープを実行します。これは上述のウォッチドッグタイマーのリセットを行う目的のみの起床です。
磁気センサーの検出時の起床の場合は、このままloop()処理に移行します。
ここでは、検出された磁気センサーのDIOの確認を行い、パケットの送信を行い、パケット送信完了後に再びスリープを実行します。
b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。
磁気センサーの検出DIOピンの確認を行います。検出ピンは二種類あります。N極検知とS極検知です。単に磁石が近づいたことだけを知りたいならいずれかのピンの検出されたことが条件となります。
起床要因のピンを確認するにはthe_twelite.is_wokeup_by_dio()を用います。パラメータはピン番号です。戻り値をuint8_tに格納しているのはパケットのペイロードに格納するためです。
通信条件の設定やペイロードにデータを格納後、送信を行います。
その後、loop() 中 b_transmit が true になっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。
送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。
#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>void setup() {
/*** SETUP section */
// use PAL_AMB board support.
auto&& brd = the_twelite.board.use<PAL_MAG>();
// now it can read DIP sw status.
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
// the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)
/*** BEGIN section */
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- PAL_MAG:" << FOURCHARS << " ---" << mwx::crlf;
}auto&& brd = the_twelite.board.use<PAL_MAG>();
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)void begin() {
sleepNow(); // the first time is just sleeping.
}void sleepNow() {
uint32_t u32ct = 60000;
pinMode(PAL_MAG::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
pinMode(PAL_MAG::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);
the_twelite.sleep(u32ct);
}void wakeup() {
if (the_twelite.is_wokeup_by_wktimer()) {
sleepNow();
}
}void loop() {
if (!b_transmit) {
if (auto&& pkt =
the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
uint8_t b_north =
the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
uint8_t b_south =
the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);
Serial << "..sensor north=" << int(b_north)
<< " south=" << int(b_south) << mwx::crlf;
// set tx packet behavior
pkt << tx_addr(0x00)
<< tx_retry(0x1)
<< tx_packet_delay(0, 0, 2);
// prepare packet payload
pack_bytes(pkt.get_payload()
, make_pair(FOURCHARS, 4)
, b_north
, b_south
);
// do transmit
MWX_APIRET ret = pkt.transmit();
if (ret) {
u8txid = ret.get_value() & 0xFF;
b_transmit = true;
}
else {
// fail to request
sleepNow();
}
} else {
sleepNow();
}
} else {
if (the_twelite.tx_status.is_complete(u8txid)) {
b_transmit = 0;
sleepNow();
}
}
} if (!b_transmit) {uint8_t b_north =
the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
uint8_t b_south =
the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);// do transmit
MWX_APIRET ret = pkt.transmit();if (the_twelite.tx_status.is_complete(u8txid)) {
b_transmit = 0;
sleepNow();
}環境センサーパル AMPIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
役割
例
親機
アクトを動作させる。
子機
+
環境センサーパル <PAL_AMB> のボードビヘイビアをインクルードします。
最初にボードサポート <PAL_AMB> を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。
ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。
LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle())は含めません。
ボード上のセンサーはI2Cバスを用いますので、バスを利用開始しておきます。
ボード上のセンサーの取得を開始します。startSensorCapture()の解説を参照ください。
このアクトでは、1ms周期で呼び出させる(アプリケーションループの呼び出しタイミングは不定期な遅延が発生します)TickTimerのイベントを起点としてセンサーの読み出しや送信要求を行います。TickTimer.available()がtrueになったときに処理を行います。これによりおよそ1msごとにif節内の処理を行います。
ボード上のセンサーは .sns_LTR308ALS または .sns_SHTC3 という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()がfalse)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER))を送付します。
上記2つのセンサーがavailableになった時点で、無線送信などの処理を行います。
送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。
パケットにセンサーデータを書き込み際に、センサーデータを取得しています。センサーの値を取得するメソッドはセンサーごとに違います。
照度センサーは.get_luminance() : uint32_tで得られます。
温湿度センサーは以下のように取得できます。
.get_temp_cent() : int16_t : 1℃を100とした温度 (25.6 ℃なら 2560)
.get_temp() : float : float値 (25.6 ℃なら 25.6)
.get_humid_dmil() : int16_t : 1%を100とした湿度 (56.8%なら 5680)
.get_temp() : float : float値 (56.8%なら 56.8)
得られた値のうち温度値は int16_t ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_tにキャストしています。
送信が成功すれば b_tansmit を trueにし、u8txidに送信パケットの識別IDを格納し、完了待ちします。送信が失敗すれば sleepNow() によりスリープします。
loop() 中 b_transmit が true になっている場合は、送信完了チェックを行い、完了すれば sleepNow() によりスリープします。
送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。
ボード上のセンサーの取得を開始します。多くのセンサーは、取得を開始してから完了するまで数msから数十msの時間を必要とします。
5行目で、温湿度センサー SHTC3 のデータ取得を開始します。
6行目で、照度センサー LTR308ALS のデータ取得を開始します。
7行目は、送信完了フラグをクリアします。
スリープに入る手続きをまとめています。
ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。
3行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。
スリープ前にflushを行うと、出力が不安定になる場合があります。
スリープに入るには the_twelite.sleep() を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。
パラメータとしてスリープ時間をmsで指定しています。
TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000を超えないように指定してください。
スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。
ここではセンサーのデータ取得を開始しています。
より安全な実装として、このアクトでは以下の例外処理を強化します。
センサー取得部分でavailableにならなかった場合の例外処理が記述されてません。
送信完了待ちで送信完了が通知されない場合の例外処理が記述されていません。
上記いずれもタイムアウト処理を行います。現在の時間は millis() により取得できます。
アクト PAL_AMB-UseNap は、センサーのデータ取得待ちをスリープで行い、より低消費エネルギーで動作できます。
#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_AMB> // include the board support of PAL_AMBvoid setup() {
/*** SETUP section */
// use PAL_AMB board support.
auto&& brd = the_twelite.board.use<PAL_AMB>();
// now it can read DIP sw status.
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
// the twelite main object.
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)
// Register Network
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)
/*** BEGIN section */
Wire.begin(); // start two wire serial bus.
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.
the_twelite.begin(); // start twelite!
startSensorCapture(); // start sensor capture!
/*** INIT message */
Serial << "--- PAL_AMB:" << FOURCHARS << " ---" << mwx::crlf;
}auto&& brd = the_twelite.board.use<PAL_AMB>();
u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms) the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL); // set channel (pysical channel)Wire.begin(); // start two wire serial bus.startSensorCapture();void loop() {
auto&& brd = the_twelite.board.use<PAL_AMB>();
// mostly process every ms.
if (TickTimer.available()) {
// wait until sensor capture finish
if (!brd.sns_LTR308ALS.available()) {
// this will take around 50ms.
// note: to save battery life, perform sleeping to wait finish of sensor capture.
brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
}
if (!brd.sns_SHTC3.available()) {
brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
}
// now sensor data is ready.
if (brd.sns_LTR308ALS.available() && brd.sns_SHTC3.available() && !b_transmit) {
Serial << "..finish sensor capture." << mwx::crlf
<< " LTR308ALS: lumi=" << int(brd.sns_LTR308ALS.get_luminance()) << mwx::crlf
<< " SHTC3 : temp=" << brd.sns_SHTC3.get_temp() << 'C' << mwx::crlf
<< " humd=" << brd.sns_SHTC3.get_humid() << '%' << mwx::crlf
<< mwx::flush;
// get new packet instance.
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0, 0, 2); // send packet w/ delay
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(FOURCHARS, 4) // just to see packet identification, you can design in any.
, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
, uint16_t(brd.sns_SHTC3.get_temp())
, uint16_t(brd.sns_SHTC3.get_humid())
);
// do transmit
MWX_APIRET ret = pkt.transmit();
Serial << "..transmit request by id = " << int(ret.get_value()) << '.' << mwx::crlf << mwx::flush;
if (ret) {
u8txid = ret.get_value() & 0xFF;
b_transmit = true;
}
else {
// fail to request
sleepNow();
}
}
}
}
// wait to complete transmission.
if (b_transmit) {
if (the_twelite.tx_status.is_complete(u8txid)) {
Serial << "..transmit complete." << mwx::crlf << mwx::flush;
// now sleeping
sleepNow();
}
}
} if (TickTimer.available()) { if (!brd.sns_LTR308ALS.available()) {
brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
}
if (!brd.sns_SHTC3.available()) {
brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
} if (brd.sns_LTR308ALS.available()
&& brd.sns_SHTC3.available() && !b_transmit) { pkt << tx_addr(0x00) // 親機0x00宛
<< tx_retry(0x1) // リトライ1回
<< tx_packet_delay(0, 0, 2); // 遅延は最小限 // prepare packet payload
pack_bytes(pkt.get_payload()
, make_pair(FOURCHARS, 4)
, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
, uint16_t(brd.sns_SHTC3.get_temp_cent())
, uint16_t(brd.sns_SHTC3.get_humid_per_dmil())
); // do transmit
MWX_APIRET ret = pkt.transmit();
if (ret) {
u8txid = ret.get_value() & 0xFF;
b_transmit = true;
}
else {
// fail to request
sleepNow();
}// wait to complete transmission.
if (b_transmit) {
if (the_twelite.tx_status.is_complete(u8txid)) {
Serial << "..transmit complete." << mwx::crlf << mwx::flush;
// now sleeping
sleepNow();
}
}void startSensorCapture() {
auto&& brd = the_twelite.board.use<PAL_AMB>();
// start sensor capture
brd.sns_SHTC3.begin();
brd.sns_LTR308ALS.begin();
b_transmit = false;
}void sleepNow() {
uint32_t u32ct = 1750 + random(0,500);
Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
the_twelite.sleep(u32ct);
}void wakeup() {
Serial << mwx::crlf
<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
<< mwx::crlf
<< "..start sensor capture again."
<< mwx::crlf;
startSensorCapture();
}uint32_t t_start;
// 時間待ちの処理を開始した時点でタイムスタンプを保存
t_start = millis();
...
// loop() でタイムアウトのチェック
if (millis() - t_start > 100) {
sleepNow();
}MWXライブラリで記述したアプリケーションプログラムをアクト(Act)と呼びます。まずは、これをビルドして書き込みます。
ビルドディレクトリ構成について
ビルドスクリプトについて
VS Code でのビルドについて
MWSDKをインストールしたディレクトリMWSDK_ROOT (例 C:\MWSDK)を開きます。以下のような構成になっています。
アクトファイルはAct_samples 以下に格納しています。(以下は一部割愛しています)
これらのアクトは、MWXライブラリの記述の参考となるシンプルな例ですが、多くのアクトは以下の機能を有しています。
センサー値を取得する
センサー値取得後、無線パケットを親機宛に送信する
送信完了後、一定時間スリープする(または割り込みを待つ)
Parent-MONOSTICKのアクトによりパケットの受信と表示を行っています。この親機用のアクトは、アスキー形式で出力しています。 (:00112233AABBCC...FF[CR][LF] のような : で始まり、途中は16進数のバイトをアスキー文字2字で表現する形式です。末尾の??は同様に2字のバイトとなりますがLRCというチェックサムバイトになります。参考:)
実際に動作させてみるときは、以下の組み合わせを試してみてください。
では、アクトの中から PingPong のディレクトリの中を見てみましょう。
必ずディレクトリ直下にディレクトリと同名の .cpp ファイルが必要です。
PingPong ディレクトリ直下にアクトファイル PingPong.cpp があります。ディレクトリ名を変更した場合は、必ず .cpp ファイルの名前もディレクトリ名と同名にします。
次にビルドディレクトリを開きます。
ビルドに必要なスクリプトとMakefileが格納されています。
macOS, Linux ではまたはによるビルドを行います。
ビルドスクリプトは、エラーがあまり出ることのない、完成済みのアクト、サンプル提供のアクトなどに向いています。
以下は Windows10用のスクリプト(バッチファイル)です。
実行後にbinファイルが生成されPingPong_BLUE_???.bin またはPingPong_RED_???.bin のファイル名になります。???はバージョン番号やライブラリのバージョン文字に置き換わります。
BINファイルが出来上がればビルド成功です。
ビルドを迅速にするためパラレルビルドのオプションを設定しています。このためエラーが出た場合はエラーメッセージを視認しづらくなっています。
エラーメッセージを効率的に参照したい場合は VS Code などの開発ツールの利用を推奨します。
build-clean.cmdを実行すれば、objs_ で始まるディレクトリを消去します。BINファイルは消去しません。
ビルドがうまくいかない場合は、まずエラーメッセージを確認して下さい。errorという文字列が含まれる行中のメッセージから、エラー原因が容易に特定できる場合も少なくありません。
objs_??? ディレクトリにある中間ファイルを削除してから再実行してみてください。(他の環境でビルドした中間ファイルが残っているとmake cleanを含めすべての操作が失敗します)
コマンドライン環境でのビルドについて補足します。
コマンドライン(bash)についての利用の知識が必要です。
OS環境によっては各実行プログラムの動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)
コマンドラインでのビルドは、bash(Bourne-again shell)が動作するウインドウでmakeを実行します。事前に環境変数MWSDK_ROOTが正しく設定されていることを確認してください。例えばC:/MWSDKにインストールした場合は ~/.profile に以下のような設定を行います。
コマンドライン(bash)よりmakeを実行します。makeがない場合はパッケージをインストールする必要があります。
ビルドは以下のようになります。
ビルドが行われると objs_??? ディレクトリが作成され、その中に中間ファイルが生成されます。このファイルはコンパイルした環境に依存しているため、他の環境のファイルが残っているとmakeがエラーとなりビルドが失敗します。
makeがエラーとなった場合は直接objs_???ディレクトリを削除してください。
詳細はをご覧ください。
ビルドには TWELITE STAGE を推奨します。
MWSDK2020_12 以降、VS Code でのビルド定義については保守更新しません。
OS環境によっては各実行プログラムの動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)
VS Code では、Act_samplesディレクトリ直下にあるワークスペースファイルを開くか、Act_samples以下のアクトディレクトリを開きます。
以下の例では英語インタフェースの画面例で、ワークスペースを開いています。
ワークスペースを開き [Terminal>Run Task...] を選択します。
ビルドするTWELITE無線モジュールの種別(BLUE/RED)とアクト名を選択します。以下の例ではBuild for TWELITE BLUE PingPong (TWELITE BLUE用/PingPongアクト) を選択しています。選択後すぐにビルドが始まります。
ビルド中の経過は画面下部の TERMINAL 部分に出力されます。
正しくビルドできた場合、上記画面例の反転表示部のように .elf ファイルが生成されるメッセージがサイズ情報(text data bss dec hex filenameと記載がある部分)とともに出力されます。
またBINファイル(上記では PingPong_BLUE_???.bin)ファイルがbuildディレクトリ下に出来上がっているはずです。確認してみてください。
ビルドがうまくいかない場合は、まずエラーメッセージを確認して下さい。errorという文字列が含まれる行中のメッセージから、エラー原因が容易に特定できる場合も少なくありません。
念のため、クリーン(objs_??? ディレクトリにある中間ファイルの削除)を行い、ビルドを再実行してみてください。(他の環境でビルドした中間ファイルが残っているとmake cleanを含めすべての操作が失敗します)
は Windows10 のサブシステムとして動作する Linux 環境です。
Windows10 では特別な理由がない限りWSL (Windows Subsystem for Linux)は、MWSDKでアクトをビルドするのに必須ではありません。WSLについて、既に知見があり特に利用したい方のみ本節をご覧ください。ここではWSLのインストール方法や使用方法については紹介しません。
MWSDK/Tools/ba-elf-ba2-r36379.w10 以下に Windows10用とLinux用の実行形式が両方含まれます。WSLを動作させ環境変数MWSDK_ROOTを適切に設定します。
例えば C:\Work\MWSTAGE\MWSDK の場合は以下のように設定します。
TWELITE STAGE SDK パッケージには、WSL用の実行形式は含まれません。
MWSTAGE/Tools/ba-elf-ba2-r36379.wslというフォルダ名で、ツール一式を格納してください。ツールはMWSDK2020_10のMWSDK/Tools/ba-elf-ba2-r36379.w10を用いるのが平易です。
MWSDK/Tools/MkFiles/rules.mkで環境の判定とツールディレクトリの選択をしています。以下はrules.mkの抜粋です。
親
子
解説
親機はM1ピンをLOW(GNDレベル)にして起動する。通常モード(常時稼働)にて、App_TweLiteのような動作を確認できます。
子機同士2台使って動作します。片方から Ping パケットを送ると、相手方から Pong パケットが戻ってきます。
その他
子機用のアクトのパケット送信を確認できます。
バッチファイル名
内容
build-BLUE.cmd
TWELITE BLUE用
build-RED.cmd
TWELITE RED用
コマンド例
解説
make TWELITE=BLUE
TWELITE BLUE用にビルド
make TWELITE=RED
TWELITE RED用にビルド
make cleanall
中間ファイルの削除
MWSDK_ROOT
|
+-ChipLib : 半導体ライブラリ
+-License : ソフトウェア使用許諾契約書
+-MkFiles : makefile
+-Tools : コンパイラ等のツール一式
+-TWENET : TWENET/MWXライブラリ
+-Act_samples : アクトサンプル
...Act_samples
|
+-CoreAppTwelite : App_TweLiteと同じ構成のボード用のアクト
+-PAL_AMB : 環境センス PAL 用のアクト
+-PAL_MAG : 開閉センス PAL 用のアクト
+-PAL_MOT : 動作センス PAL 用のアクト
..
+-Parent-MONOSTICK : 親機アクト、MONOSTICK用
+-PingPong : PingPong アクト
+-PulseCounter : パルスカウンタを利用したアクト
+-act0 : スクラッチ(とりあえず書いてみる)用アクトAct_samples
+-PingPong
+-PingPong.cpp : アクトファイル
+-build : ビルドディレクトリ
+-.vscode : VS Code 用の設定ファイルAct_samples
+-PingPong
+-build
+-Makefile : makefile
+-build-BLUE.cmd : TWELITE BLUE 用ビルドスクリプト
+-build-RED.cmd : TWELITE RED 用ビルドスクリプト
+-build-clean.cmd : obj_* ファイル削除MWSDK_ROOT=/mnt/c/MWSDK
export MWSDK_ROOT$ make
Command 'make' not found, but can be installed with:
sudo apt install make
sudo apt install make-guile
$ sudo apt install make
...$ cd $MWSDK_ROOT
$ cd Act_samples/PingPong/build
$ pwd
/mnt/c/MWSDK/Act_samples/PingPong/build
$ ls
... ファイル一覧の表示
$ rm -rfv objs_*
... 念のため中間ファイルを削除
$ make TWELITE=BLUE
... BLUE用に通常ビルド
$ make -j8 TWELITE=BLUE
... BLUE用にパラレルビルド(同時に8プロセス)...
"windows": {
"command": "sh",
"args": [
"-c", "make TWELITE=BLUE 2>&1 | sed -E -e s#\\(/mnt\\)?/\\([a-zA-Z]\\)/#\\\\\\2:/#g"
],$ export MWSDK_ROOT=/mnt/c/Work/MWSTAGE/MWSDK# Configure for WSL (Windows Subsystem Linux)
ifneq ($(origin WSLENV),undefined)
$(info !!!WSL environment, use Linux toolchain instead.)
TOOLCHAIN_PATH = ba-elf-ba2-r36379.wsl
endifソフトウェア開発環境
TWELITE無線モジュールが利用する無線規格です。MWXライブラリを使用する限り、無線規格の詳細を意識する必要はありません。
無線通信における最小の通信単位です。
最大量は通信方式や通信時の設定によって変わりますが、MWXライブラリ標準の通信<NWK_SIMPLE>では、ユーザが1パケットで送信できるデータ量は90バイトです。
「貨物」といった意味合いですが、無線パケットに含まれるデータ本体のことをいいます。
「点・節」といった意味合いですが、無線ネットワーク内の無線局のことを言います。
本ライブラリを用いて作成したプログラム。そのソースコードまたは動作するプログラムのことを言います。
アクトの中でも特にイベント形式のプログラム。そのソースコードまたは動作するプログラムのことを言います。
ビヘイビアは1つのクラス定義による記述で、TWENETからのコールバック関数やイベントや割り込み処理を記述しひとまとめにしています。MWXライブラリでは以下の3種類のビヘイビアがあります。
アプリケーションビヘイビア:イベントドリブンでのアプリケーション記述を行い、ユーザが定義するクラス。
ボードビヘイビア:TWELITE無線モジュールを実装するボードの機能利用を簡素化するためのクラス。
ネットワークビヘイビア:無線ネットワークの手続きを簡素化するためのクラス。
ビヘイビア名は < > で括って表記します。例えばシンプル中継ネットワークのビヘイビア名は <NWK_SIMPLE> です。
本ライブラリの解説では、ライブラリで最初からグローバル宣言されたオブジェクトをクラスオブジェクトと呼称します。Serial, Wire などです。これらクラスオブジェクトは手続きなしまたは開始手続きを行うことで利用できます。
メモリを比較的多く消費するクラスオブジェクトは、初期化手続きの際(.setup()または.begin()メソッド)に初期化パラメータに沿ったメモリを確保します。
C++言語のこと。
C++規格のバージョンの一つ。2011年式のC++といった意味合いで、2011年にISOで規格化されています。直前のC++03から大きく機能拡張されています。C++14, C++17といったより新しいバージョンがあります。
あるデータに注目して、その手続きをひとまとめにしたもの。構造体に、その構造体を取り扱うための手続きが含まれています。実際にはもっと深い話題に発展しますが、専門書を参考にしてください。
C++においては、キーワードの structと classは本質的には同じもので、いずれのキーワードで宣言してもクラスとなります。
上記のクラス定義をC言語でも行った場合、例えば以下のようになります。
既存のC言語のライブラリやその内部の構造体などをクラスに包含し、C++特有の機能性を追加するなどして、利用の便を図ったものです。解説中でも「~構造体をラップした」といった記述をする場合があります。
クラスに定義される関数で、クラスに紐付いています。
クラスを実体化(メモリ確保)したもの。
本解説ではオブジェクトとインスタンスは同じ意味合いとして取り扱っています。
オブジェクト生成時の初期化手続き。
コンストラクタと対になってオブジェクトが破棄されるときの手続きです。
C++では仮想クラスによりポリモーフィズム(多態性)を実現します。具体的にはvirtualキーワードで指定た純粋仮想関数を定義したクラスです。
MWXライブラリでは、コンパイラの制限や性能上の理由で仮想関数を使用しません。ポリモーフィズムを実現するのに別の手法を用いています。
C/C++言語では { } で括った範囲と考えてください。この中で生成したオブジェクトは、スコープから出るときに破棄されます。この時デストラクタが呼び出されます。
以下は、明示的にスコープを設定したものです。helo2は8行目まで実行された時点で破棄され、デストラクタが呼び出されます。
MWXライブラリでは以下のような記法を用いています。ここではif文の条件判定式内で宣言(C89といった旧いC言語ではこういった場所での宣言はできません)されたオブジェクトの有効期間は、if文の{}内になります。
例えば二線シリアルバスなど、開始と終了の手続きがあって、その間だけオブジェクトによってバスを操作するような手続きです。オブジェクトの生成後、バスの接続が適切であればif文のtrue節が実行され、生成したオブジェクトによってバスの書き込みまたは読み出しを行います。バスの読み書き操作が終了したらif文を脱出し、この時デストラクタが呼び出され、バスの利用終了手続きが行われます。
定義名の重複を避けるためC++では名前空間が積極的に用いられます。名前空間にある定義にアクセスするには::を用います。
テンプレートはC言語のマクロを拡張したものと考えてください。
この例では、簡単な配列を定義しています。TとNはテンプレートのパラメータで、Tは型名をNは数値を指定し、T型で要素数Nの配列クラスを定義しています。
C++11ではNULLポインタをnullptrと記述するようになりました。
C++では、参照型を利用できます。これはポインタによるアクセスに似ていますが、必ずオブジェクトを参照しなければならないという制約があります。
以下のような参照渡しのパラメータを持つ関数ではiの値をincr()内で書き換えることが出来ます。
テンプレートの解説例ですがoperator[]の戻り型をT&に変更しています。こうすることでa[0]=1のように配列内部のデータに対して直接代入操作ができるようになります。
C++11 では型推論のautoキーワードが導入されています。これはコンパイラが初期化の記述からそのオブジェクトの型を推論するため、具体的な型名の記述を省略できます。これはtemplateを用いたクラス名が非常に長くなるような場合に効果的です。
解説中では多くの場合ユニバーサル参照と呼ばれるauto&&を用いています。ユニバーサル参照については、ここでは参照渡しの場合も意識せずに記述できるものと考えてください。
配列など特定のデータ型のオブジェクトを複数個格納するためのクラスをコンテナと呼びます。テンプレートの例で挙げたmyaryのような配列クラスもコンテナと呼んでいます。
C言語で言うところのポインタ(もちろんC++でも同じようにポインタは使えます)を拡張した概念です。C言語のポインタは、メモリが連続した要素を先頭から末尾まで連続的にアクセスする手段と考えることが出来ます。FIFOキューを考えてみます。もっとも単純なキューの実装はリングバッファによるものですが、メモリーの連続性はありません。こういったデータ構造であっても、イテレータを用いるとポインタと同じように記述できます。
イテレータを取得するため.begin(),.end() のメソッドが用いられます。コンテナの先頭を指すイテレータを.begin()で取得します。末尾の次を指すイテレータを.end()で取得します。末尾ではなく、末尾の次である理由にはforやwhile文のループ記述の明快さ、コンテナに格納される要素数が0の場合の取り扱いが挙げられます。
上記では、queの各要素について、イテレータpを用いて各要素にsome_process()を適用しています。pは++演算子によって次の要素を指すイテレータとしてインクリメントしています。本来ポインタでは記述できないデータ構造を持つコンテナであっても、このようにポインタを用いた処理と同じような処理が出来ます。
.end()が末尾の次を示すため、while文の終了判定は(p != e)のように簡潔です。キューに要素がない場合は.begin()は.end()と同じイテレータを返します。(何も格納されていない要素のイテレータの次ですから、最初に格納すべき領域を示すイテレータと考えればよいでしょう)
メモリ上で連続したコンテナの場合、通常、そのイテレータは通常のポインタとなります。その操作時に大きなオーバーヘッドにはならないことが期待できます。
C++の標準ライブラリにはSTL(Standard Template Library)が含まれます。MWXライブラリでの一部を利用しています。
TWELITE向けのC/C++コンパイラの制約から、利用できる機能はごく一部です。
例えば最大や最小値を求めるといった処理をC言語では型に応じて別々に記述していました。こういったコードは型の部分だけ違って他は同じといったものも少なくありません。C++ではtemplateやイテレータなどを用いて、こういった処理を型に依存せず記述することができます。これをアルゴリズムと呼んでいます。
例えば上記のように最大値を求めるアルゴリズムです。このアルゴリズムは型に依存しません。(ジェネリックプログラミングと呼ばれます)
ここではqueのイテレータを指定し、その最大と最小を得るアルゴリズムstd::minmax_elenetを適用しています。std::minmax_elemetはC++標準ライブラリ内に定義されています。その戻り値は任意の2つの値を組み合わせるstd::pairです。このアルゴリズムは、イテレータの示す要素同士で<,>,==といった演算子での比較が出来れば最大と最小を計算してくれます。戻り型もイテレータの型から導かれます。
struct myhello {
int _i;
void say_hello() { printf("hello %d\n", _i); }
};typedef struct _c_myhello {
int _i;
void (*pf_say_hello)(struct _c_myhello *);
} c_myhello;
void say_hello(c_myhello*p) { p->pf_say_hello(); }
void init_c_my_hello(c_myhello*p) {
p->pf_say_hello = say_hello;
}struct myhello {
int _i;
void say_hello() { printf("hello %d\n", _i); } //メソッド
};void func() {
myhello obj_hello; // obj_helloがmyhelloクラスのオブジェクト
obj_hello._i = 10;
obj_hello.say_hello();
}struct myhello {
int _i;
void say_hello() { printf("hello %d\n", _i); }
myhello(int i = 0) : _i(i) {} // コンストラクタ
};
void my_main() {
myhello helo(10); // ここでコンストラクタが呼び出され_i=10にセットされる
}struct myhello {
int _i;
void say_hello() { printf("hello! %d\n", _i); }
myhello(int i = 0) : _i(i) {} // コンストラクタ
~myhello() {
printf("good bye! %d\n", _i);
} //デストラクタ
};struct Base {
virtual void say_hello() = 0;
};
struct DeriveEng : public Base {
void say_hello() { printf("Hello!"); }
};
struct DeriveJpn : public Base {
void say_hello() { printf("Kontiwa!"); }
};void my_main() {
myhello helo1(1);
helo1.say_hello();
{
myhello helo2(2);
helo2.say_hello();
}
}
// hello! 1
// hello! 2
// good bye! 2
// good bye! 1struct myhello {
int _i;
void say_hello() { printf("hello! %d\n", _i); }
operator bool() { return true; } // if()での判定用の演算子
myhello(int i = 0) : _i(i) {} // コンストラクタ
~myhello() { printf("good bye! %d\n", _i); } // コンストラクタ
};
// myhello オブジェクトを生成する関数 (ジェネレータ)
myhello gen_greeting() { return my_hello(); }
void my_main() {
if (myhello x = gen_greeting()) {
// myhelloのオブジェクト x は if文中有効
x.say_hello();
}
// if 分を抜けるときにオブジェクトxは破棄される
}const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) { //バスの初期化、接続判定
wrt(SHTC3_TRIG_H); // 書き出し
wrt(SHTC3_TRIG_L);
} // バスの利用終了手続きnamespace MY_NAME { // 名前空間の宣言
const uint8_t MYVAL1 = 0x00;
}
...
void my_main() {
uint8_t i = MY_NAME::MYVAL1; // MY_NAME の参照
}
template <typename T, int N>
class myary {
T _buf[N];
public:
myary() : _buf{} {}
T operator [] (int i) { return _buf[i % N]; }
};
myary<int, 10> a1; // int 型で要素数10の配列
myary<char, 128> a2; // char 型の要素数128の配列void incr(int& lhs, int rhs) { lhs += rhs; }
void my_main() {
int i = 10; j = 20;
incr(i, j);
}template <typename T, int N>
class myary {
T _buf[N];
public:
myary() : _buf{} {}
T& operator [] (int i) { return _buf[i % N]; }
};
myary<int, 10> a1;
void my_main() {
a1[0] = 1;
a1[1] = 2;
}auto&& p = std::make_pair("HELLO", 5);
// const char* と int のペア std::pairmy_queue que; // my_queue はキューのクラス
auto&& p = que.begin();
auto&& e = que.end();
while(p != e) {
some_process(*p);
++p;
}
// 任意のイテレータをパラメータとし最大値を持つイテレータを戻す
template <class Iter>
Iter find_max(Iter b, Iter e) {
Iter m = b; ++b;
while(b != e) {
if (*b > *m) { m = b; }
++b;
}
return m;
}#include <algorithm>
auto&& minmax = std::minmax_element( // 最大最小を得るアルゴリズム
que.begin(), que.end());
auto&& min_val = *minmax.first;
auto&& max_val = *minmax.second;



2台のシリアル接続しているTWELITEの片方からPING(ピン)の無線パケットを送信すると、他方からPONG(ポン)の無線パケットが返ってきます。
いずれかを2台。
でUART接続されているなど
全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク をインクルードしておきます。
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
大まかな流れは、各部の初期設定、各部の開始となっています。
このオブジェクトはTWENETを操作するための中核クラスオブジェクトです。
the_twelite に設定を反映するには << を用います。
TWENET::appid(APP_ID) アプリケーションIDの指定
TWENET::channel(CHANNEL) チャネルの指定
TWENET::rx_when_idle() 受信回路をオープンにする指定
次にネットワークを登録します。
1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。
2行目は、<NWK_SIMPLE>の設定で、0xFE(ID未設定の子機)という指定を行います。
3行目は、中継回数の最大値を指定しています。この解説では中継には触れませんが、複数台で動作させたときにパケットの中継が行われます。
setup() 関数の末尾で the_twelite.begin() を実行しています。
ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。
初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。
ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。
ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。
パラメータにはPIN_ANALOGUE::A1(ADC0)とPIN_ANALOGUE::VCC(モジュール電源電圧)が指定されています。
2番目のパラメータには50が指定されています。ADCの動作はデフォルトではTickTimerで開始されていて、
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_BTN (12) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。10を指定しているので10msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
Serial オブジェクトは、初期化や開始手続きなく利用できます。
シリアルポートへの文字列出力を行います。mwx::crlfは改行文字です。
ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。
TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。
したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。
Serial.available()がtrueの間はシリアルポートからの入力があります。内部のFIFOキューに格納されるためある程度の余裕はありますが、速やかに読み出すようにします。データの読み出しはSerial.read()を呼びます。
ここでは't'キーの入力に対応してvTransmit()関数を呼び出しPINGパケットを送信します。
DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。
1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば btn_state & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。
初回確定以外の場合かつPIN_BTNのボタンが離されたタイミングでvTransmit()を呼び出しています。押したタイミングにするには(!(btn_state && (1UL << PIN_BTN)))のように条件を論理反転します。
Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。
AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。
u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。
受信パケットがある場合の処理です。
無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。
TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。
内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。
まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。
次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。
MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。
1行目から3行目までは、データを格納する変数を指定しています。
6行目でexpand_bytes()によりパケットのペイロードのデータを変数に格納します。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。
3番目以降のパラメータに変数を列挙します。列挙した順番にペイロードの読み出しとデータ格納が行われます。
msgに読み出した4バイト文字列の識別子が"PING"の場合はPONGメッセージを送信する処理です。
続いて到着したパケット情報を表示します。
数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)
mwx::crlfは改行文字(CR LF)を、mwx::flushは出力完了待ちを指定します。
無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。
ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。
ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。
パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。
tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。
tx_retry() パラメータに再送回数を指定します。例の3は再送回数が3回、つまり合計4回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。
ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。
送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。
上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。
上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。
pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。
そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。
3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。
4,5,6行目は、数値型の値 (uint8_t, uint16_t, uint32_t)を格納します。符号付などの数値型、char型など同じ数値型であっても左記の3つの型にキャストして投入します。
analogRead()とanalogRead_mv()は、ADCの結果を取得するものです。前者はADC値(0..1023)、後者は電圧[mv](0..2470)となります。モジュールの電源電圧は内部的に分圧抵抗の値を読んでいるためその変換を行うadalogRead_mv()を利用しています。
これでパケットの準備は終わりです。あとは、送信要求を行います。
パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。
tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ100msから200msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから20ms置きに再送を行うという意味です。// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>// application ID
const uint32_t APP_ID = 0x1234abcd;
// channel
const uint8_t CHANNEL = 13;
// DIO pins
const uint8_t PIN_BTN = 12;
/*** function prototype */
void vTransmit(const char* msg, uint32_t addr);
/*** application defs */
// packet message
const int MSG_LEN = 4;
const char MSG_PING[] = "PING";
const char MSG_PONG[] = "PONG";void setup() {
/*** SETUP section */
Buttons.setup(5); // init button manager with 5 history table.
Analogue.setup(true, 50); // setup analogue read (check every 50ms)
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0xFE) // set Logical ID. (0xFE means a child device with no ID)
<< NWK_SIMPLE::repeat_max(3); // can repeat a packet up to three times. (being kind of a router)
/*** BEGIN section */
Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
} // the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)// 以下の記述は MWX ライブラリでは利用できません。
#include <iostream>
std::cout << "hello world" << std::endl;auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0xFE);
<< NWK_SIMPLE::repeat_max(3);the_twelite.begin(); // start twelite!Analogue.setup(true);Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC), 50); Buttons.setup(5);Buttons.begin(pack_bits(PIN_BTN),
5, // history count
10); // tick deltaSerial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;void loop() {
// read from serial
while(Serial.available()) {
int c = Serial.read();
Serial << mwx::crlf << char(c) << ':';
switch(c) {
case 't':
vTransmit(MSG_PING, 0xFF);
break;
default:
break;
}
}
// Button press
if (Buttons.available()) {
uint32_t btn_state, change_mask;
Buttons.read(btn_state, change_mask);
// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
// PIN_BTN pressed
vTransmit(MSG_PING, 0xFF);
}
}
// receive RF packet.
while (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
// rx >> Serial; // debugging (display longer packet information)
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;
// expand packet payload (shall match with sent packet data structure, see pack_bytes())
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, msg // 4bytes of msg
// also can be -> std::make_pair(&msg[0], MSG_LEN)
, adcval // 2bytes, A1 value [0..1023]
, volt // 2bytes, Module VCC[mV]
, timestamp // 4bytes of timestamp
);
// if PING packet, respond pong!
if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
// transmit a PONG packet with specifying the address.
vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
}
// display the packet
Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
, rx.get_psRxDataApp()->u32SrcAddr
, rx.get_lqi()
, rx.get_length()
, rx.get_psRxDataApp()->u8Seq
)
<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
, msg
, adcval
, volt
, timestamp
)
<< mwx::crlf
<< mwx::flush;
}
} while(Serial.available()) {
int c = Serial.read();
Serial << mwx::crlf << char(c) << ':';
switch(c) {
case 't':
vTransmit(MSG_PING, 0xFF);
break;
default:
break;
}
} if (Buttons.available()) {
uint32_t btn_state, change_mask;
Buttons.read(btn_state, change_mask);// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
// PIN_BTN pressed
vTransmit(MSG_PING, 0xFF);if (Timer0.available()) {
static uint8_t u16ct;
u16ct++;
if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
if ((u16ct % 32) == 0) { // every 32ticks of Timer0
transmit();
}
}
}while (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;
// expand packet payload (shall match with sent packet data structure, see pack_bytes())
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, msg // 4bytes of msg
// also can be -> std::make_pair(&msg[0], MSG_LEN)
, adcval // 2bytes, A1 value [0..1023]
, volt // 2bytes, Module VCC[mV]
, timestamp // 4bytes of timestamp
);
// if PING packet, respond pong!
if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
// transmit a PONG packet with specifying the address.
vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
}
// display the packet
Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
, rx.get_psRxDataApp()->u32SrcAddr
, rx.get_lqi()
, rx.get_length()
, rx.get_psRxDataApp()->u8Seq
)
<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
, msg
, adcval
, volt
, timestamp
)
<< mwx::crlf
<< mwx::flush;
}while (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();Serial << format("..receive(%08x/%d) : ",
rx.get_addr_src_long(), rx.get_addr_src_lid());uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;
// expand packet payload (shall match with sent packet data structure, see pack_bytes())
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, msg // 4bytes of msg
// also can be -> std::make_pair(&msg[0], MSG_LEN)
, adcval // 2bytes, A1 value [0..1023]
, volt // 2bytes, Module VCC[mV]
, timestamp // 4bytes of timestamp
);if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
} Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
, rx.get_psRxDataApp()->u32SrcAddr
, rx.get_lqi()
, rx.get_length()
, rx.get_psRxDataApp()->u8Seq
)
<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
, msg
, adcval
, volt
, timestamp
)
<< mwx::crlf
<< mwx::flush;void vTransmit(const char* msg, uint32_t addr) {
Serial << "vTransmit()" << mwx::crlf;
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
// set tx packet behavior
pkt << tx_addr(addr) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x3) // set retry (0x3 send four times in total)
<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
, uint32_t(millis()) // put timestamp here.
);
// do transmit
pkt.transmit();
}
} if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) { pkt << tx_addr(addr) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x3) // set retry (0x3 send four times in total)
<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)# 先頭バイトのインデックス: データ型 : バイト数 : 内容
00: uint8_t[4] : 4 : 4文字識別子
08: uint16_t : 2 : AI1のADC値 (0..1023)
06: uint16_t : 2 : Vccの電圧値 (2000..3600)
10: uint32_t : 4 : millis()システム時間// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
, uint32_t(millis()) // put timestamp here.
);pkt.transmit();システム時刻[ms]を得ます。
uint32_t millis()システム時刻はTickTimerの割り込みで更新されます。
MWX ライブラリ内で用いる C++ 言語について、その仕様、制限事項、本書記載留意事項、設計メモを記載します。
MWXライブラリでアプリケーション記述する場合は、このページを読み飛ばしても差し支えありません。
アプリケーションのループ記述では、一般によく用いられる API 体系に近い記述を出来ることを目的とするが、TWELITEの特性に合わせた実装とする。
TWENET はイベントドリブンによるコード記述であり、これを扱えるようにクラス化を行う。上記クラス化によりアプリケーションのふるまいをカプセル化できるようにする。
イベントドリブンとループの記述は共存できる形とする。
代表的なペリフェラルはクラス化して手続きを簡素化する。可能な限りループ記述でアクセスできるようにする。
gcc version 4.7.4
C++11 (コンパイラ対応状況は一般の情報を参考にしてください)
※ 当社で把握しているものについての記載です。
new, new[] 演算子でのメモリ確保は行えますが、確保したメモリを破棄することはできません。C++ライブラリで動的メモリ確保をするものは殆どが事実上利用不可能です。一度だけ生成してそれ以降破棄しないオブジェクトに使用しています。
グローバルオブジェクトのコンストラクタが呼び出されません。
参考:必要な場合は、初期化関数(setup()) で new ((void*)&obj_global) class_foo(); のように初期化することでコンストラクタの呼び出しを含めた初期化を行えます。
例外 exception
本節ではMWXライブラリのコードを参照する際に理解の助けとなる情報を記載します。
限られた時間で実装を進めているため、詳細部分の整備が十分でない場合があります。例えば const に対する考慮は多くのクラスで十分なされていません。
名前空間について、以下の方針としています。
定義は原則として共通の名前空間mwxに配置する。
名前空間の識別子なしで利用できるようにしたいが、一部の定義は識別子を必須としたい。
クラス名については比較的長い命名とし、ユーザが利用するものは別名定義とする。
クラス・関数・定数は一部の例外を除きmwx名(正確にはinline namespace L1 で囲んだmwx::L1)の名前空間内に定義しています。inline namespaceを指定しているのは、mwx::の指定を必須とする定義と、必須としない定義を共存させるためです。
殆どの定義はusing namespaceにより名前空間名を指定しなくても良いようになっています。これらの指定はライブラリ内のusing_mwx_def.hppで行っています。
例外的に比較的短い名前についてはmwx::crlf, mwx::flushのように指定します。これらはinline namespaceのmwx::L2の名前空間に配置されています。using namespace mwx::L2;を指定することで名前空間名の指定なしで利用できるようになります。
また、いくつかのクラス名はusing指定をしています。
MWXライブラリ内で利用するstd::make_pairをusing指定しています。
仮想関数 (virtual), 実行時型情報(RTTI) が利用できない、かつ利用できるようにしたとしても、パフォーマンス面で難があるため、これに代わる設計手法として を用いています。CRTPは、継承元の親クラスから子クラスのメソッドを呼び出すためのテンプレートパターンです。
以下の例では Base を継承した Derived クラスに interface() というインタフェースを実装する例です。BaseからはDerived::print()メソッドを呼び出しています。
MWXライブラリで利用されている主要クラスは以下です。
イベント処理の基本部分mwx::appdefs_crtp
ステートマシンpublic mwx::processev_crtp
ストリーム mwx::stream
CRTPクラスは、継承元のクラスはインスタンスごとに違います。このため、親クラスにキャストして、同じ仲間として取り扱うといったこともできませんし、仮想関数(virtual)やRTTI(実行時型情報)を用いたような高度なポリモーフィズムも使うことが出来ません。
以下は上述のCRTPの例を、仮想関数で実装した例です。CRTPではBase* b[2]のように同じ配列にインスタンスをまとめて管理することは、そのままではできません。
MWXライブラリでは、CRTP のクラスインスタンスを格納するための専用クラスを定義し、このクラスに同様のインタフェースを定義することで解決しています。以下にコード例を挙げます。
VBase クラスのメンバ変数 p_inst は、Base <T> 型のオブジェクトへのポインタを格納し、pf_intrfaceは Base<T>::s_intrface へのメンバ関数ポインタです。 Base<T>::s_intrface は、自身のオブジェクトインスタンスを引数として渡され、T型にstatic_castすることでT::intrfaceメソッドを呼び出します。
VBaseへの格納は、ここでは = 演算子のオーバーロードによって実装しています(ソース例は後述)。
上記の例ではb[0].intrface()の呼び出しを行う際には、VBase::pf_intrface関数ポインタを参照しBase<Derived1>::s_intrface()が呼び出されることになります。さらにDerived1::intrface()の呼び出しを行うことになります。この部分はコンパイラによるinline展開が期待できます。
VBase 型から元のDerived1やDerived2への変換を行うことも、強制的なキャストにより可能ですが、void*で格納されたポインタの型を直接知る方法はありません。完全に安全な方法はないものの、以下のようにクラスごとに一意のID(TYPE_ID)を設けて、キャスト実行時(get()メソッド)にIDのチェックを行うようにしています。違う型を指定して get()メソッドを呼び出したときは、エラーメッセージを表示するといった対処になります。
Base<T>型としてのポインタが格納されるとT型に正しく変換できない可能性(Tが多重継承している場合など)あるため、<type_trails>のis_base_ofによりBase<T>型の派生であることをコンパイル時に static_assertによる判定を行っています。
TWELITEモジュールのマイコンには十分なメモリもなければ、高度なメモリ管理もありません。しかしマイコンのメモリマップの末尾からスタックエリアまでの領域はヒープ領域として、必要に応じて確保できる領域があります。以下にメモリマップの概要を図示します。APPがアプリケーションコードで確保されたRAM領域、HEAPはヒープ領域、STACKはスタック領域です。
たとえdeleteできなくてもnew演算子が有用である場面も想定されます。そのため、以下のようにnew, new[]演算子を定義しています。pvHear_Alloc()は半導体ライブラリで提供されているメモリ確保の関数で、u32HeapStart, u32HeapEndも同様です。0xdeadbeefはダミーアドレスです。beefがdeadなのは変だとかいう指摘はしないでください。
例外も使えないため失敗したときの対処はありません。また、メモリ容量を意識せず確保を続けた場合、スタック領域と干渉する可能性もあります。
MWXライブラリでは、マイコンのリソースが小さい点、メモリーの動的確保ができない点を考慮し標準ライブラリで提供されるコンテナクラスの利用はせず、シンプルなコンテナクラスを2種類定義しています。コンテナクラスにはイテレータやbegin(), end()メソッドを定義しているため、範囲for文やSTLのアルゴリズムの一部を利用できます。
コンテナクラスではメモリの確保方法をtemplate引数のパラメータとして指定します。
MWXライブラリでは、バイト列やビット列の操作、printf相当の処理を行う処理に可変数引数を用いています。下記の例は指定のビット位置に1をセットする処理です。
この処理では template のパラメータパック (typename... の部分) で、再帰的な処理を行い引数の展開を行っています。上記の例ではconstexprの指定があるため、コンパイル時に計算が行われマクロ定義やb2のようなconst値の指定と同等の結果になります。また変数を引数として動的に計算する関数としても振る舞うこともできます。
以下の例では、expand_bytes関数により、受信パケットのデータ列からローカル変数に値を格納しています。パラメータパックを用いた場合各引数の型を把握できるため、下記のように、受信パケットのバイト列から、サイズや異なる型にあった値を格納することができます。
イテレータはポインタの抽象化で、例えばメモリの連続性のないようなデータ構造においても、あたかもポインタを使ったようにデータ構造にアクセスできる効果があります。
以下の例では、通常のポインタでは連続的なアクセスができないFIFOキューのイテレータ、さらに、FIFOキューの構造体の特定メンバー(例ではX軸)のみを抽出するイテレータを利用する例です。
以下は smplque クラスのイテレータの実装の抜粋です。このイテレータでは、キューオブジェクトの実体と、インデックスにより管理しています。キューのメモリが不連続になる(末尾の次は先頭を指す必要があるリングバッファ構造)部分はsmplque::operator []で解決しています。オブジェクトのアドレスが一致することとインデックスが一致すればイテレータは同じものを指していることになります。
この実装部分には <iterator> が要求する typedef なども含まれ、より多くのSTLのアルゴリズムが適用できるようになります。
構造体を格納したコンテナ中の、特定構造体メンバーだけアクセスするイテレータは少々煩雑です。構造体のメンバーにアクセスするメンバー関数を予め定義しておきます。このメンバー関数をパラメータ(R& (T::*get)())としたテンプレートを定義します。Iterはコンテナクラスのイテレータ型です。
値にアクセスするoperator *この上述のメンバー関数を呼び出しています。(*_pはaxis_xyzt構造体で、(*_p.*get)()は、T::*getに&axis_xyzt::get_xを指定した場合_p->get_x()を呼び出します)
_axis_xyzt_iter_genクラスはbegin(), end()のみを実装し、上記のイテレータを生成します。これで範囲for文やアルゴリズムが利用できるようになります。
このクラス名は非常に長くなりソースコード中に記述するのは困難です。このクラスを生成するためのジェネレータ関数を用意します。下記の例では末尾の行の get_axis_x() です。このジェネレータ関数を用いることで冒頭のようなauto&& vx = get_axis_x(que);といった簡潔な記述になります。
また、この軸だけを抽出するイテレータは、配列型のsmplbufクラスでも同様に利用できます。
ユーザ定義クラスによりアプリケーション動作を記述するため、代表的なハンドラは必須メソッドとして定義が必要ですが、それ以外に多数ある割り込みハンドラ、イベントハンドラ、ステートマシンの状態ハンドラをすべて定義するのは煩雑です。ユーザが定義したものだけ定義され、それのみのコードが実行されるのが理想です。
MWXライブラリでは、数の多い DIO割り込みハンドラ(TWELITEハード上は単一の割り込みですが、利用しやすさのためDIO一つずつにハンドラを割り当てることにしました)などを、テンプレートによる空のハンドラーとして定義した上、ユーザ定義のメンバー関数をそのテンプレートの特殊化することにより定義する手法を採用しました。
実際のユーザ記述コードは、マクロ化やヘッダファイルのインクルードを行うことで、簡素化されていますが、上記は解説のために必要なコードを含めています。
TWENETからの割り込みハンドラからmy_app_def::cbTweNet_u8HwInt() が呼び出されます。cppファイル中では、int_dio_handler<12>のみが特殊化されて記載された内容でインスタンス化されます。12番以外はhppファイル中のテンプレートからインスタンス化されます。結局以下のように展開されることになります。
最終的に、コンパイラの最適化により12番以外のコードは無意味と判断されコード中から消えてしまうことが期待できます(ただし、上記のように最適化されることを保証するものではありません)。
つまりユーザコード上では12番の割り込み時の振る舞いを定義したいときはint_dio_handler<12> を記述するだけで良い、ということになります(注:DIO割り込みを有効にするには attachInterrupt() を呼び出す必要があります)。登録しないハンドラはコンパイル時の最適化により低コストな呼び出しで済むことが期待できます。
ストリームクラスは、主にUART(シリアルポート)の入出力に用います。MWXライブラリでは、出力用の手続きを主に定義しています。一部入力用の定義もあります。
ここでは派生クラスが必要とする実装について解説します。
上記は1文字書き出すwrite()メソッドの実装です。親クラスのstream<serial_jen>からはキャストを実行するget_Drived()メソッドを用いて、serial_jen::write()メソッドにアクセスしています。
必要に応じて write(), read(), flush(), available() といったメソッドを定義します。
書式出力にはMarco Paland氏によるを利用しています。MWXライブラリから利用するための実装が必要になります。下記の例で派生クラスのserial_jenで必要なことは1バイト出力のための vOutput() メソッドを定義することと、vOutput()がstaticメソッドであるため出力のための補助情報を親クラスのpvOutputContextに保存することです。
get_pfcOutput()により、派生クラスで定義したvOutput()関数を指定し、そのパラメータとしてpvOutputContextが渡されます。上記の例では<<演算子がint型で呼び出されたときserial_jen::vOutput()とUART用に設定済みのTWE_tsFILE*をfctprintf()関数に渡しています。
Wireクラスでは、2線デバイスとの送信・受信時に、通信開始から終了までを管理する必要があります。ワーカーオブジェクトを利用する記述について内容を記述します。
periph_twowire::writer クラスの抜粋です。streamインタフェースを実装するために mwx::stream<writer> を継承しています。steamインタフェースを利用するために write() と vOutput()メソッドの実装を行っています。
コンストラクタでは2線シリアルの通信開始を、デストラクタで通信終了のメソッドを呼び出しています。また、operator bool()演算子では、2線シリアルのデバイスの通信開始に成功した場合 true を返すようになっています。
get_writer()メソッドによりオブジェクトwrtを生成します。この時にオブジェクトのコピーは通常発生しません。C++コンパイラのRVO(Return Value Optimization)という最適化により、writerはwrtに直接生成されるためコピーは発生せず、コンストラクタで実行されているバスの初期化を多重に行ったりすることはありません。ただしRVOはC++の仕様では保証されておらず、念のためMWXライブラリ中ではコピー、代入演算子の削除、moveコンストラクタを定義しています(moveコンストラクタが評価される可能性はないと考えられますが)。
if節の中の wrtは、まずコンストラクタにより初期化され同時に通信開始します。通信開始でエラーがなければ、条件判定時のbool演算子がtrueを返し、if節スコープ内の処理が行われます。スコープを脱出するとデストラクタにより、2線シリアルバスの利用終了処理を行います。通信の相手先がない場合は falseが戻り、wrtオブジェクトは破棄されます。
Wire, SPI特有の定義としてoperator << (int)の定義をオーバーライドしています。ストリームのデフォルトの振る舞いは、数値を文字列に変換して出力するのですが、WireやSPIで数値文字列をバスに書き込むことは稀で、反対に設定値など数値型のリテラルをそのまま入力したいことが多いのですが、数値型リテラルは多くの場合int型として評価されるため、この振舞を変更します。
ここではint型の値については8bitに切り詰めて、その値を出力しています。
当社で販売する MONOSTICK/PAL といったボードを利用する手続きをクラス化し手続きを簡素化する。(例えば外部のウォッチドッグタイマーの利用を自動化する)
アプリケーションクラスやボードクラスは、ポリモーフィズムの考え方を導入し、統一した手続きによって利用できるようにする。(例えば、いくつかの振る舞いをするアプリケーションクラスを始動時にロードするような場合、また TWENET C ライブラリの接続部のコードを都度定義しなくてよいようにするため)。
C++の機能については、特に制限を設けず利用する。例えば、無線パケットを取り扱うにあたり煩雑なパケット構築、分解といった代表的な手続きを簡略化する手段を提供する。
演算子 -> を極力使用しないようにし、原則として参照型による API とする。
仮想関数 virtualが使用できません。
クラス名
概要
smplbuf
配列クラスで、最大領域 (capacity) と最大領域範囲内で都度サイズを指定できる利用領域(size)を管理します。
また本クラスは stream インタフェースを実装しているため、<< 演算子を用いてデータを書き込むことができます。
smplque
FIFOキューを実装しています。キューのサイズはテンプレートのパラメータで決定します。割り込み禁止を用いキューを操作するためのテンプレート引数もあります。
クラス名
内容
alloc_attach
すでに確保済みのバッファメモリを指定する。
Cライブラリ向けに確保したメモリ領域を管理したいとき、同じバッファ領域の分断領域として処理したい時などに使用します。
alloc_static
クラス内に静的配列として確保する。
事前にサイズが決まっていたり、一時利用の領域として使用します。
alloc_heap
ヒープ領域に確保する。 システムのヒープに確保後は破棄できませんが、初期化時にアプリケーションの設定などに従い領域を確保するといった使い方に向いています。
// at some header file.
namespace mwx {
inline namespace L1 {
class foobar {
// class definition...
};
}
}
// at using_mwx_def.hpp
using namespace mwx::L1; // mwx::L1 内の定義は mwx:: なしでアクセスできる
// しかし mwx::L2 は mwx:: が必要。template <class T>
class Base {
public:
void intrface() {
T* derived = static_cast<T*>(this);
derived->prt();
}
};
class Derived : public class Base<Derived> {
void prt() {
// print message here!
my_print("foo");
}
}class Base {
virtual void prt() = 0;
public:
void intrface() { prt(); }
};
class Derived1 : public Base {
void prt() { my_print("foo"); }
};
class Derived2 : public Base {
void prt() { my_print("bar"); }
};
Derived1 d1;
Derived2 d2;
Base* b[2] = { &d1, &d2 };
void tst() {
for (auto&& x : b) { x->intrface(); }
}class VBase {
public:
void* p_inst;
void (*pf_intrface)(void* p);
public:
void intrface() {
if (p_inst != nullptr) {
pf_intrface(p_inst);
}
}
};
template <class T>
class Base {
friend class VBase;
static void s_intrface(void* p) {
T* derived = static_cast<T*>(p);
derived->intrface();
}
public:
void intrface() {
T* derived = static_cast<T*>(this);
derived->prt();
}
};
class Derived1 : public Base<Derived1> {
friend class Base<Derived1>;
void prt() { my_print("foo"); }
};
class Derived2 : public Base<Derived2> {
friend class Base<Derived2>;
void prt() { my_print("bar"); }
};
Derived1 d1;
Derived2 d2;
VBase b[2];
void tst() {
b[0] = d1;
b[1] = d2;
for (auto&& x : b) {
x.intrface();
}
}#include <type_trails>
class Derived1 : public Base<Derived1> {
public:
static const uint8_t TYPE_ID = 1;
}
class Derived1 : public Base<Derived1> {
public:
static const uint8_t TYPE_ID = 2;
}
class VBase {
uint8_t type_id;
public:
template <class T>
void operator = (T& t) {
static_assert(std::is_base_of<Base<T>, T>::value == true,
"is not base of Base<T>.");
type_id = T::TYPE_ID;
p_inst = &t;
pf_intrface = T::s_intrface;
}
template <class T>
T& get() {
static_assert(std::is_base_of<Base<T>, T>::value == true,
"is not base of Base<T>.");
if(T::TYPE_ID == type_id) {
return *reinterpret_cast<T*>(p_inst);
} else {
// panic code here!
}
}
}
Derived1 d1;
Derived2 d2;
VBase b[2];
void tst() {
b[0] = d1;
b[1] = d2;
Derived1 e1 = b[0].get<Derived1>(); // OK
Derived2 e2 = b[1].get<Derived2>(); // OK
Derived2 e3 = b[1].get<Derived1>(); // PANIC!
}
|====APP====:==HEAP==.. :==STACK==|
0 32KBvoid* operator new(size_t size) noexcept {
if (u32HeapStart + size > u32HeapEnd) {
return (void*)0xdeadbeef;
} else {
void *blk = pvHeap_Alloc(NULL, size, 0);
return blk;
}
}
void* operator new[](size_t size) noexcept {
return operator new(size); }
void operator delete(void* ptr) noexcept {}
void operator delete[](void* ptr) noexcept {}smplbuf<int16_t, alloc_local<int16_t, 16>> buf;
buf.push_back(-1); // push_back() は末尾に追加
buf.push_back(2);
...
buf.push_back(10);
//範囲for文
for(auto&& x : buf) { Serial << int(x) << ',' }
//アルゴリズム std::minmax
auto&& minmax = std::minmax_element(buf.begin(), buf.end());
Serial << "Min=" << int(*minmax.first)
<< ",Max=" << int(*minmax.second);
// packing bits with given arguments, which specifies bit position.
// pack_bits(5, 0, 1) -> (b100011) bit0,1,5 are set.
// 再帰取り出しの一番最初の関数
template <typename Head>
constexpr uint32_t pack_bits(Head head) { return 1UL << head; }
// head を取り出し、残りのパラメータを再帰呼び出しにて pack_bits に転送
template <typename Head, typename... Tail>
constexpr uint32_t pack_bits(Head head, Tail&&... tail) {
return (1UL << head) | pack_bits(std::forward<Tail>(tail)...);
}
// コンパイル後、以下の2つは同じ結果になります。
constexpr uint32_t b1 = pack_bits(1, 4, 0, 8);
// b1 and b2 are the same!
const uint32_t b2 = (1UL << 1)|(1UL << 4)|(1UL << 0)|(1UL << 8);auto&& rx = the_twelite.receiver.read(); // 受信パケット
// 展開後のパケットの内容を格納する変数
// パケットのペイロードはバイト列で以下のように並んでいる。
// [B0][B1][B2][B3][B4][B5][B6][B7][B8][B9][Ba][Bb]
// <message ><adc* ><vcc* ><timestamp* >
// * 数値型はビッグエンディアン並び
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;
// expand packet payload
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, msg // 4bytes of msg
, adcval // 2bytes, A1 value [0..1023]
, volt // 2bytes, Module VCC[mV]
, timestamp // 4bytes of timestamp
);smplque<uint8_t, alloc_local<uint8_t, 5> > que;
que.push('a'); que.push('b'); que.pop(); que.push('c'); ...
auto&& p = que.begin();
auto&& e = que.end();
while(p != e) { // pがeまで進んだ=全要素処理した
Serial << *p;
++p; // イテレータのインクリメントは前置演算子を使います。
// この場合、p++ と記述すると、コンパイラによる最適化が行われる可能性は
// 高いものの、コード上はイテレータのコピーが発生します。
}#include <algorithm>
#include <cctype>
// ラムダ式による文字変換
std::for_each(que.begin(), que.end(),
[](uint8_t& x) { x = std::toupper(x); });
// 範囲for文
for (uint8_t x : que) {
Serial << x;
}// XYZTの4軸構造体を要素とする要素数5のキュー
smplque<axis_xyzt, alloc_local<axis_xyzt, 5> > que;
// テスト用にデータを投入
que.push(axis_xyzt(1, 2, 3, 4));
que.push(axis_xyzt(5, 2, 3, 4));
...
// 構造体としてのイテレータを用いたアクセス
for (auto&& e : v) { Serial << int(e.x) << ','; }
// キューの中の X 軸を取り出す
auto&& vx = get_axis_x(que);
// X軸のイテレータを用いたアクセス
for (auto&& e : vx) { Serial << int(e) << ','; }
// int16_t要素のイテレータなので、STLのアルゴリズム(最大最小)が使える
auto&& minmax = std::minmax_element(vx.begin(), vx.end());class iter_smplque {
typedef smplque<T, alloc, INTCTL> BODY;
private:
uint16_t _pos; // index
BODY* _body; // point to original object
public: // for <iterator>
typedef iter_smplque self_type;
typedef T value_type;
typedef T& reference;
typedef T* pointer;
typedef std::forward_iterator_tag iterator_category;
typedef int difference_type;
public: // pick some methods
inline reference operator *() {
return (*_body)[_pos];
}
inline self_type& operator ++() {
_pos++;
return *this;
}
};struct axis_xyzt {
int16_t x, y, z;
uint16_t t;
int16_t& get_x() { return x; }
int16_t& get_y() { return y; }
int16_t& get_z() { return z; }
};
template <class Iter, typename T, typename R, R& (T::*get)()>
class _iter_axis_xyzt {
Iter _p;
public:
inline self_type& operator ++() {
_p++;
return *this; }
inline reference operator *() {
return (*_p.*get)(); }
};
template <class Ixyz, class Cnt>
class _axis_xyzt_iter_gen {
Cnt& _c;
public:
_axis_xyzt_iter_gen(Cnt& c) : _c(c) {}
Ixyz begin() { return Ixyz(_c.begin()); }
Ixyz end() { return Ixyz(_c.end()); }
};
// 長いので using で短縮
template <typename T, int16_t& (axis_xyzt::*get)()>
using _axis_xyzt_axis_ret = _axis_xyzt_iter_gen<
_iter_axis_xyzt<typename T::iterator, axis_xyzt, int16_t, get>, T>;
// X 軸を取り出すジェネレータ
template <typename T>
_axis_xyzt_axis_ret<T, &axis_xyzt::get_x>
get_axis_x(T& c) {
return _axis_xyzt_axis_ret<T, &axis_xyzt::get_x>(c);
}class my_app_def {
public: // 必須メソッドの定義
void network_event(twe::packet_ev_nwk& pEvNwk) {}
void receive(twe::packet_rx& rx) {}
void transmit_complete(twe::packet_ev_tx& pEvTx) {}
void loop() {}
void on_sleep(uint32_t& val) {}
void warmboot(uint32_t& val) {}
void wakeup(uint32_t& val) {}
public: // これらを必須記述とするのは煩雑
// DIO割り込みハンドラ 20種類ある
// DIOイベントハンドラ 20種類ある
// タイマー割り込みハンドラ 5種類ある
// タイマーイベントハンドラ 5種類ある
// ...
}// hpp file
class my_app_def : class app_defs<my_app_def>, ... {
// 空のハンドラ
template<int N> void int_dio_handler(uint32_t arg, uint8_t& handled) { ; }
...
// 12番だけ実装する
public:
// TWENET から呼び出されるコールバック関数
uint8 cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap);
};
// cpp file
template <>
void my_app_def::int_dio_handler<12>(uint32_t arg, uint8_t& handled) {
digitalWrite(5, LOW);
handled = true;
return;
}
void cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap) {
uint8_t b_handled = FALSE;
switch(u32DeviceId) {
case E_AHI_DEVICE_SYSCTRL:
if (u32ItemBitmap & (1UL << 0)){int_dio_handler<0>(0, b_handled);}
if (u32ItemBitmap & (1UL << 1)){int_dio_handler<1>(1, b_handled);}
...
if (u32ItemBitmap & (1UL << 12)){int_dio_handler<12>(12, b_handled);}
...
if (u32ItemBitmap & (1UL << 19)){int_dio_handler<19>(19, b_handled);}
break;
}
}
case E_AHI_DEVICE_SYSCTRL:
if (u32ItemBitmap & (1UL << 0)){;}
if (u32ItemBitmap & (1UL << 1)){;}
...
if (u32ItemBitmap & (1UL << 12)){
int_dio_handler<12>(12, b_handled);}
...
if (u32ItemBitmap & (1UL << 19)){;}
break;
// ↓ ↓ ↓
// 結局、このように最適化されることが期待できる。
case E_AHI_DEVICE_SYSCTRL:
if (u32ItemBitmap & (1UL << 12)){
// int_dio_handler<12> もinline展開
digitalWrite(5, LOW);
handled = true;
}
break;
// mwx_appcore.cpp
void wakeup() __attribute__((weak));
void wakeup() { }template <class D>
class stream {
protected:
void* pvOutputContext; // TWE_tsFILE*
public:
inline D* get_Derived() { return static_cast<D*>(this); }
inline D& operator << (char c) {
get_Derived()->write(c);
return *get_Derived();
}
};
class serial_jen : public mwx::stream<serial_jen> {
public:
inline size_t write(int n) {
return (int)SERIAL_bTxChar(_serdef._u8Port, n);
}
};template <class D>
class stream {
protected:
void* pvOutputContext; // TWE_tsFILE*
public:
inline tfcOutput get_pfcOutout() { return get_Derived()->vOutput; }
inline D& operator << (int i) {
(size_t)fctprintf(get_pfcOutout(), pvOutputContext, "%d", i);
return *get_Derived();
}
};
class serial_jen : public mwx::stream<serial_jen> {
using SUPER = mwx::stream<serial_jen>;
TWE_tsFILE* _psSer; // シリアル出力のためのローレベル構造体
public:
void begin() {
SUPER::pvOutputContext = (void*)_psSer;
}
static void vOutput(char out, void* vp) {
TWE_tsFILE* fp = (TWE_tsFILE*)vp;
fp->fp_putc(out, fp);
}
};if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
Serial << "{I2C SHTC3 connected.";
wrt << SHTC3_TRIG_H;
wrt << SHTC3_TRIG_L;
Serial << " end}";
}class periph_twowire {
public:
class writer : public mwx::stream<writer> {
friend class mwx::stream<writer>;
periph_twowire& _wire;
public:
writer(periph_twowire& ref, uint8_t devid) : _wire(ref) {
_wire.beginTransmission(devid); // コンストラクタで通信開始
}
~writer() {
_wire.endTransmission(); // デストラクタで通信終了
}
operator bool() {
return (_wire._mode == periph_twowire::MODE_TX);
}
private: // stream interface
inline size_t write(int n) {
return _wire.write(val);
}
// for upper class use
static void vOutput(char out, void* vp) {
periph_twowire* p_wire = (periph_twowire*)vp;
if (p_wire != nullptr) {
p_wire->write(uint8_t(out));
}
}
};
public:
writer get_writer(uint8_t address) {
return writer(*this, address);
}
};
class periphe_twowire Wire; // global instance
// ユーザコード
if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
Serial << "{I2C SHTC3 connected.";
wrt << SHTC3_TRIG_H;
wrt << SHTC3_TRIG_L;
Serial << " end}";
} writer& operator << (int v) {
_wire.write(uint8_t(v & 0xFF));
return *this;
}M1を読み取り、親機か子機を決める。
DI1-DI4 の値を読み取ります。Buttons クラスにより、チャタリングの影響を小さくするため、連続で同じ値になったときにはじめて変化が通知されます。変化があったときには通信を行います。
AI1-AI4 の値を読み取ります。
DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。
受信したパケットの値に応じで DO1-4, PWM1-4 に設定する。
役割
例
親機
最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。
子機
最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。
全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク <NWK_SIMPLE> とボードサポート <BRD_APPTWELITE>をインクルードしておきます。
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
大まかな流れは、各部の初期設定、各部の開始となっています。
このオブジェクトはTWENETの中核としてふるまいます。
ボードの登録(このアクトでは<BRD_APPTWELITE>を登録しています)。以下のように use の後に <> で登録したいボードの名前を指定します。
ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。
ここではボードオブジェクトを用いbrd.get_M1()を呼び出すことでM1ピンの設定を読み出します。1がスイッチをセットしGND側になっていることを示します。M1 がセットされた場合にu8devidを0x00に、そうでなければ0xFEを指定しています。これはネットワーク上での役割が親機(0x00)であるか子機(0xFE)あるかを決定します。この値は <NWK_SIMPLE>の設定に使用します。
the_twelite に設定を反映するには << を用います。
TWENET::appid(APP_ID) アプリケーションIDの指定
TWENET::channel(CHANNEL) チャネルの指定
TWENET::rx_when_idle() 受信回路をオープンにする指定
次にネットワークを登録します。
1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。
2行目は、<NWK_SIMPLE>の設定です。先ほどボードから得たM1ピンの状態によって親機アドレス(0x00)か子機アドレス(0xFE)を格納したu8devidを指定します。
setup() 関数の末尾で the_twelite.begin() を実行しています。
ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。
初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。2番目のパラメータは、ADCの開始をTimer0に同期して行う指定です。
ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。
ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。
パラメータと指定されるBRD_APPTWELITE::にはPIN_AI1..4が定義されています。App_Tweliteで用いるAI1..AI4に対応します。AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 と割り当てられています。PIN_ANALOGUE::にはADCで利用できるピンの一覧が定義されています。
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_DI1-4 (DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。
上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。
Timer0.begin()を呼び出したあと、タイマーが稼働します。
Serial オブジェクトは、初期化や開始手続きなく利用できます。
上記の例では "--- BRD_APPTWELITE(254) ---\r\n" といった文字列を表示します。ここでは10進数の数値文字列として表示するためにint(u8devid)としています。int型でない場合は出力の意味合いが変わりバイトの書き出しになるので注意してください。
ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。
TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。
したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。
DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。
1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば bp & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。
次にビットマップから値を取り出してu8DI_BMに格納しています。ここではMWXライブラリで用意したcollect_bits()関数を用いています。
collect_bits() は、上述のpack_bits()と同様のビット位置の整数値を引数とします。可変数引数の関数で、必要な数だけパラメータを並べます。上記の処理では bit0 は DI1、bit1 は DI2、bit2 は DI3、bit3 は DI4の値としてu8DI_BMに格納しています。
App_Twelite では、DI1から4に変化があった場合に無線送信しますので、Buttons.available()を起点に送信処理を行います。transmit()処理の内容は後述します。
ADCのアナログディジタル変換が終了した直後のloop()で available になります。次の ADC が開始するまでは、データは直前に取得されたものとして読み出すことが出来ます。
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は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。
AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。
u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。
受信パケットがある場合の処理です。
無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。
TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。
内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。
無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。
MWX_APIRETはuint32_t型のデータメンバを持つ戻り値を取り扱うクラスです。MSB(bit31)が成功失敗、それ以外が戻り値として利用するものです。
ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。
ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。
パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。
tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。
tx_retry() パラメータに再送回数を指定します。例の1は再送回数が1回、つまり合計2回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。
tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ0msから50msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから10ms置きに再送を行うという意味です。
ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。
送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。
上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。
上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。
pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。
そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。
3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。
4行目はuint8_t型でDI1..DI4のビットマップを書き込みます。
7-9行目ではau16AI配列の値を順に書き込んでいます。この値はuint16_t型で2バイトですが、ビッグエンディアンの並びで書き込みます。
これでパケットの準備は終わりです。あとは、送信要求を行います。
パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。戻り値としてMWX_APIRET型を返していますが、このアクトでは使っていません。
パケットの受信が確認できた(つまりthe_twelite.receiver.available()がtrueになった)ときの処理です。ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。
まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。
次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。
MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。
1行目ではデータ格納のためのchar型の配列を宣言しています。サイズが5バイトなのは末尾にヌル文字を含め、文字出力などでの利便性を挙げるためです。末尾の{}は初期化の指定で、5バイト目を0にすれば良いのですが、ここでは配列全体をデフォルトの方法で初期化、つまり0にしています。
2行目でexpand_bytes()により4バイト文字列を取り出しています。パラメータにコンテナ型を指定しない理由は、この続きを読み出すための読み出し位置を把握する必要があるためです。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。
3番目に読み出す変数を指定しますが、ここでもmake_pairによって文字列配列とサイズのペアを指定します。
読み出した4バイト文字列の識別子が、このアクトで指定した識別子と異なる場合は、このパケットを処理しません。
続いて、データ部分の取得です。DI1..DI4の値とAI1..AI4の値を別の変数に格納します。
先ほどのexpand_bytes()の戻り値npを1番目のパラメータにしています。先に読み取った4バイト文字列識別子の次から読み出す指定です。2番目のパラメータは同様です。
3番目以降のパラメータはデータペイロードの並びに一致した型の変数を、送り側のデータ構造と同じ順番で並べています。この処理が終われば、指定した変数にペイロードから読み出した値が格納されます。
確認のためシリアルポートへ出力します。
数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)
1行目の "DI:%04b" は"DI:0010"のようにDI1..DI4のビットマップを4桁で表示します。3行目の"/%04d"は"/3280/0010/0512/1023/1023"のように Vcc/AI1..AI4の値を順に整数で出力します。5行目のmwx::crlfは改行文字列を出力します。
これで必要なデータの展開が終わったので、あとはボード上のDO1..DO4とPWM1..PWM4の値を変更します。
digitalWrite()はディジタル出力の値を変更します。1番目のパラメータはピン番号で、2番目はHIGH(Vccレベル)かLOW(GNDレベル)を指定します。
Timer?.change_duty()はPWM出力のデューティ比を変更します。パラメータにデューティ比 0..1024 を指定します。最大値が1023でないことに注意してください(ライブラリ内で実行される割り算のコストが大きいため2のべき乗である1024を最大値としています)。0にするとGNDレベル、1024にするとVccレベル相当の出力になります。
// 以下の記述は MWX ライブラリでは利用できません。
#include <iostream>
std::cout << "hello world" << std::endl;for(int i = 0; i < sizeof(au16AI)/sizeof(uint16_t)); i++) {
pack_bytes(pkt.get_payload(), au16AI[i]);
}// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <BRD_APPTWELITE>/*** Config part */
// application ID
const uint32_t APP_ID = 0x1234abcd;
// channel
const uint8_t CHANNEL = 13;
/*** function prototype */
MWX_APIRET transmit();
void receive();
/*** application defs */
const char APP_FOURCHAR[] = "BAT1";
uint8_t u8devid = 0;
uint16_t au16AI[5];
uint8_t u8DI_BM;void setup() {
// 変数の初期化
for(auto&& x : au16AI) x = 0xFFFF;
u8DI_BM = 0xFF;
/*** SETUP section */
// App_Twelite仕様のボード定義をシステムに登録します。
auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
// check DIP sw settings
u8devid = (brd.get_M1()) ? 0x00 : 0xFE;
// setup analogue
Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)
// setup buttons
Buttons.setup(5); // init button manager with 5 history table.
// the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)
// Register Network
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(u8devid); // set Logical ID. (0x00 means parent device)
/*** BEGIN section */
// start ADC capture
Analogue.begin(pack_bits(
BRD_APPTWELITE::PIN_AI1,
BRD_APPTWELITE::PIN_AI2,
BRD_APPTWELITE::PIN_AI3,
BRD_APPTWELITE::PIN_AI4,
PIN_ANALOGUE::VCC)); // _start continuous adc capture.
// Timer setup
Timer0.begin(32, true); // 32hz timer
// start button check
Buttons.begin(pack_bits(
BRD_APPTWELITE::PIN_DI1,
BRD_APPTWELITE::PIN_DI2,
BRD_APPTWELITE::PIN_DI3,
BRD_APPTWELITE::PIN_DI4),
5, // history count
4); // tick delta (change is detected by 5*4=20ms consequtive same values)
the_twelite.begin(); // start twelite!
/*** INIT message */
Serial << "--- BRD_APPTWELITE(" << int(u8devid) << ") ---" << mwx::crlf;
}auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();u8devid = (brd.get_M1()) ? 0x00 : 0xFE; // the twelite main class
the_twelite
<< TWENET::appid(APP_ID) // set application ID (identify network group)
<< TWENET::channel(CHANNEL) // set channel (pysical channel)
<< TWENET::rx_when_idle(); // open receive circuit (if not set, it can't listen packts from others)auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(u8devid);the_twelite.begin(); // start twelite!Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); Analogue.begin(pack_bits(
BRD_APPTWELITE::PIN_AI1,
BRD_APPTWELITE::PIN_AI2,
BRD_APPTWELITE::PIN_AI3,
BRD_APPTWELITE::PIN_AI4,
PIN_ANALOGUE::VCC));Buttons.setup(5);Buttons.begin(pack_bits(
BRD_APPTWELITE::PIN_DI1,
BRD_APPTWELITE::PIN_DI2,
BRD_APPTWELITE::PIN_DI3,
BRD_APPTWELITE::PIN_DI4),
5, // history count
4); // tick deltaTimer0.begin(32, true); // 32hz timerSerial << "--- BRD_APPTWELITE("
<< int(u8devid)
<< ") ---" << mwx::crlf;/*** loop procedure (called every event) */
void loop() {
if (Buttons.available()) {
uint32_t bp, bc;
Buttons.read(bp, bc);
u8DI_BM = uint8_t(collect_bits(bp,
BRD_APPTWELITE::PIN_DI4, // bit3
BRD_APPTWELITE::PIN_DI3, // bit2
BRD_APPTWELITE::PIN_DI2, // bit1
BRD_APPTWELITE::PIN_DI1)); // bit0
transmit();
}
if (Analogue.available()) {
au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
}
if (Timer0.available()) {
static uint8_t u16ct;
u16ct++;
if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
if ((u16ct % 32) == 0) { // every 32ticks of Timer0
transmit();
}
}
}
// receive RF packet.
if (the_twelite.receiver.available()) {
receive();
}
} if (Buttons.available()) {
uint32_t bp, bc;
Buttons.read(bp, bc);u8DI_BM = uint8_t(collect_bits(bp,
BRD_APPTWELITE::PIN_DI4, // bit3
BRD_APPTWELITE::PIN_DI3, // bit2
BRD_APPTWELITE::PIN_DI2, // bit1
BRD_APPTWELITE::PIN_DI1)); // bit0
/* collect_bits は以下の処理を行います。
u8DI_BM = 0;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI1)) u8DI_BM |= 1;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI2)) u8DI_BM |= 2;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI3)) u8DI_BM |= 4;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI4)) u8DI_BM |= 8;
*/transmit();if (Analogue.available()) {
au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
}if (Timer0.available()) {
static uint8_t u16ct;
u16ct++;
if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
if ((u16ct % 32) == 0) { // every 32ticks of Timer0
transmit();
}
}
}if (the_twelite.receiver.available()) {
receive();
}MWX_APIRET transmit() {
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
Serial << "..DI=" << format("%04b ", u8DI_BM);
Serial << format("ADC=%04d/%04d/%04d/%04d ", au16AI[1], au16AI[2], au16AI[3], au16AI[4]);
Serial << "Vcc=" << format("%04d ", au16AI[0]);
Serial << " --> transmit" << mwx::crlf;
// set tx packet behavior
pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x1 send two times in total)
<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
, uint8_t(u8DI_BM)
);
for (auto&& x : au16AI) {
pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
}
// do transmit
return pkt.transmit();
}
return MWX_APIRET(false, 0);
}MWX_APIRET transmit() if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00) // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
<< tx_retry(0x1) // set retry (0x3 send four times in total)
<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)# 先頭バイトのインデックス: データ型 : バイト数 : 内容
00: uint8_t[4] : 4 : 4文字識別子
04: uint8_t : 1 : DI1..4のビットマップ
06: uint16_t : 2 : Vccの電圧値
08: uint16_t : 2 : AI1のADC値 (0..1023)
10: uint16_t : 2 : AI2のADC値 (0..1023)
12: uint16_t : 2 : AI3のADC値 (0..1023)
14: uint16_t : 2 : AI4のADC値 (0..1023)auto&& payl = pkt.get_payload();
payl.reserve(16); // 16バイトにリサイズ
payl[00] = APP_FOURCHAR[0];
payl[01] = APP_FOURCHAR[1];
...
payl[08] = (au16AI[0] & 0xFF00) >> 8; //Vcc
payl[09] = (au16AI[0] & 0xFF);
...
payl[14] = (au16AI[4] & 0xFF00) >> 8; // AI4
payl[15] = (au16AI[4] & 0xFF);// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
, uint8_t(u8DI_BM)
);
for (auto&& x : au16AI) {
pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
}return pkt.transmit();void receive() {
auto&& rx = the_twelite.receiver.read();
// expand the packet payload
char fourchars[5]{};
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, make_pair((uint8_t*)fourchars, 4) // 4bytes of msg
);
// check header
if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
// read rest of payload
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
expand_bytes(np, rx.get_payload().end()
, u8DI_BM_remote
, au16AI_remote[0]
, au16AI_remote[1]
, au16AI_remote[2]
, au16AI_remote[3]
, au16AI_remote[4]
);
Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
for (auto&& x : au16AI_remote) {
Serial << format("/%04d", x);
}
Serial << mwx::crlf;
// set local DO
digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
// set local PWM : duty is set 0..1024, so 1023 is set 1024.
Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
}
auto&& rx = the_twelite.receiver.read();Serial << format("..receive(%08x/%d) : ",
rx.get_addr_src_long(), rx.get_addr_src_lid());char fourchars[5]{};
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
, make_pair((uint8_t*)fourchars, 4) // 4bytes of msg
);if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; } // read rest of payload
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
expand_bytes(np, rx.get_payload().end()
, u8DI_BM_remote
, au16AI_remote[0]
, au16AI_remote[1]
, au16AI_remote[2]
, au16AI_remote[3]
, au16AI_remote[4]
);Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
for (auto&& x : au16AI_remote) {
Serial << format("/%04d", x);
}
Serial << mwx::crlf;// set local DO
digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);
// set local PWM : duty is set 0..1024, so 1023 is set 1024.
Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);


