BRD_APPTWELITE
IO通信(標準アプリケーションApp_Tweliteの基本機能)
App_TweLite で必要な配線と同じ配線想定したボードサポート <BRD_APPTWELITE> を用いたサンプルです。
このサンプルは App_TweLite と通信できません。
アクトの機能
M1を読み取り、親機か子機を決める。
DI1-DI4 の値を読み取ります。Buttons クラスにより、チャタリングの影響を小さくするため、連続で同じ値になったときにはじめて変化が通知されます。変化があったときには通信を行います。
AI1-AI4 の値を読み取ります。
DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。
受信したパケットの値に応じで DO1-4, PWM1-4 に設定する。
アクトの使い方
必要なTWELITEと配線例
役割
例
親機
最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。
子機
最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。

アクトの解説
インクルード
全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク <NWK_SIMPLE> とボードサポート <BRD_APPTWELITE>をインクルードしておきます。
宣言部
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
セットアップ setup()
大まかな流れは、各部の初期設定、各部の開始となっています。
the_twelite
このオブジェクトはTWENETの中核としてふるまいます。
ボードの登録(このアクトでは<BRD_APPTWELITE>を登録しています)。以下のように use の後に <> で登録したいボードの名前を指定します。
ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。
ここではボードオブジェクトを用いbrd.get_M1()を呼び出すことでM1ピンの設定を読み出します。1がスイッチをセットしGND側になっていることを示します。M1 がセットされた場合にu8devidを0x00に、そうでなければ0xFEを指定しています。これはネットワーク上での役割が親機(0x00)であるか子機(0xFE)あるかを決定します。この値は <NWK_SIMPLE>の設定に使用します。
the_twelite に設定を反映するには << を用います。
TWENET::appid(APP_ID)アプリケーションIDの指定TWENET::channel(CHANNEL)チャネルの指定TWENET::rx_when_idle()受信回路をオープンにする指定
次にネットワークを登録します。
1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。
2行目は、<NWK_SIMPLE>の設定です。先ほどボードから得たM1ピンの状態によって親機アドレス(0x00)か子機アドレス(0xFE)を格納したu8devidを指定します。
setup() 関数の末尾で the_twelite.begin() を実行しています。
Analogue
ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。
初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。2番目のパラメータは、ADCの開始をTimer0に同期して行う指定です。
ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。
ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。
パラメータと指定されるBRD_APPTWELITE::にはPIN_AI1..4が定義されています。App_Tweliteで用いるAI1..AI4に対応します。AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 と割り当てられています。PIN_ANALOGUE::にはADCで利用できるピンの一覧が定義されています。
Buttons
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_DI1-4 (DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
Timer0
App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。
上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。
Timer0.begin()を呼び出したあと、タイマーが稼働します。
Serial
Serial オブジェクトは、初期化や開始手続きなく利用できます。
上記の例では "--- BRD_APPTWELITE(254) ---\r\n" といった文字列を表示します。ここでは10進数の数値文字列として表示するためにint(u8devid)としています。int型でない場合は出力の意味合いが変わりバイトの書き出しになるので注意してください。
ループ loop()
ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。
TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。
したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。
Buttons
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()処理の内容は後述します。
Analogue
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
Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。
AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。
u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。
the_twelite.receiver
受信パケットがある場合の処理です。
無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。
TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。
内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。
transmit()
無線パケットの送信要求を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型を返していますが、このアクトでは使っていません。
receive()
パケットの受信が確認できた(つまり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レベル相当の出力になります。
最終更新