PingPong
2台のシリアル接続しているTWELITEの片方からPING(ピン)の無線パケットを送信すると、他方からPONG(ポン)の無線パケットが返ってきます。
アクトの使い方
必要なTWELITE
いずれかを2台。
TWELITE R でUART接続されているTWELITE DIPなど
アクトの解説
インクルード
全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク <NWK_SIMPLE> をインクルードしておきます。
宣言部
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
セットアップ setup()
大まかな流れは、各部の初期設定、各部の開始となっています。
the_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() を実行しています。
Analogue
ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。
初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。
ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。
ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。
パラメータにはPIN_ANALOGUE::A1(ADC0)とPIN_ANALOGUE::VCC(モジュール電源電圧)が指定されています。
2番目のパラメータには50が指定されています。ADCの動作はデフォルトではTickTimerで開始されていて、
Buttons
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_BTN (12) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。10を指定しているので10msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
Serial
Serial オブジェクトは、初期化や開始手続きなく利用できます。
シリアルポートへの文字列出力を行います。mwx::crlfは改行文字です。
ループ loop()
ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。
TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。
したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。
Serial
Serial.available()がtrueの間はシリアルポートからの入力があります。内部のFIFOキューに格納されるためある程度の余裕はありますが、速やかに読み出すようにします。データの読み出しはSerial.read()を呼びます。
ここでは't'キーの入力に対応してvTransmit()関数を呼び出しPINGパケットを送信します。
Buttons
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)))のように条件を論理反転します。
transmit()
無線パケットの送信要求を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回のみの送信では条件が良くても数%程度の失敗はあります。tx_packet_delay()送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ100msから200msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから20ms置きに再送を行うという意味です。
パケットのデータペイロード
ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。
送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。
上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは 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()メソッドを用います。
on_rx_packet()
受信パケットがある場合の処理です。
まず受信パケットのデータはパラメータ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()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数は最大8つまで(32bitパラメータの場合)に制限されています。(制限を超えるとコンパイルエラーが出ます。なおSerial.printfmt()には引数の数の制限がありません。)
mwx::crlfは改行文字(CR LF)を、mwx::flushは出力完了待ちを指定します。(mxw::flushはSerial.flush()と記述しても構いません)
最終更新