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...
保証・ライセンス
本パッケージ内で、ライセンス上特別な記述のないものは、モノワイヤレスソフトウェア使用許諾契約書(MW-SLA)を適用します。
本ドキュメントについても、本ライブラリパッケージ部の一部としてMW-SLA下の取り扱いとします。
本ソフトウェアについては、モノワイヤレス株式会社が正式にサポートを行うものではありません。お問い合わせにはご回答できない場合もございます。予めご了承ください。
不具合などのご報告に対してモノワイヤレス株式会社は、修正や改善をお約束するものではありません。
また導入パッケージなどお客様の環境に依存して動作しない場合もございます。
MWXライブラリについて
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&&
と記載します。
auto
はC言語ではローカル変数(自動変数)を宣言する際のキーワードとなっていますが、ここでは型推論により宣言を行う意味です。C++のテンプレート構文では非常に煩雑な型名になることが多く、同時に型名を明示的に記述しなくても実装できる場面で便利な記法です。
下記の例では、v
の型に対する最大値最小値を発見する標準ライブラリのアルゴリズム std::minmax_element
を用いた例で、結果の戻り値を auto
により宣言しています。この場合、autoで推論された型はstd::pair<int, int>
になります。
auto &&
の&&
ついては、厳格な意味合いは専門書籍などを紐解いていただく必要がありますが、ここでは「戻りが参照型(C言語でいうポインタ渡しに近い)であっても値であっても、気にせず宣言できる」とお考え下さい。
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
が使用できません。
仮想関数 virtual
が使用できません。
上記の制約があるためSTLなどC++標準ライブラリの一部のみの利用となります。
※ 当社で把握しているものについての記載です。
標準ライブラリについては利用可否、また利用できそうなものについての包括的な検証は行っておりません。動作の不都合が確認できた場合は、別の方法で実装するようにしてください。
ソースコードは以下から参照できます。
{MWSDKインストールフォルダ}/TWENET/current/src/mwx
用語
本資料で利用する用語について補足します。
用語の解説は、規格などで定められる定義に沿っていない場合があります。
ソフトウェア開発環境
TWELITE無線マイコンのソフトウェア開発用のSDKをTWELITE SDK (またはMWSDK)と呼称します。
TWELITE無線モジュールが利用する無線規格です。MWXライブラリを使用する限り、無線規格の詳細を意識する必要はありません。
無線通信における最小の通信単位です。
最大量は通信方式や通信時の設定によって変わりますが、MWXライブラリ標準の通信<NWK_SIMPLE>では、ユーザが1パケットで送信できるデータ量は90バイトです。
「貨物」といった意味合いですが、無線パケットに含まれるデータ本体のことをいいます。
「点・節」といった意味合いですが、無線ネットワーク内の無線局のことを言います。
本ライブラリを用いて作成したプログラム。そのソースコードまたは動作するプログラムのことを言います。
アクトの中でも特にイベント形式のプログラム。そのソースコードまたは動作するプログラムのことを言います。
ビヘイビアは1つのクラス定義による記述で、TWENETからのコールバック関数やイベントや割り込み処理を記述しひとまとめにしています。MWXライブラリでは以下の3種類のビヘイビアがあります。
アプリケーションビヘイビア:イベントドリブンでのアプリケーション記述を行い、ユーザが定義するクラス。
ボードビヘイビア:TWELITE無線モジュールを実装するボードの機能利用を簡素化するためのクラス。
ネットワークビヘイビア:無線ネットワークの手続きを簡素化するためのクラス。
ビヘイビア名は < >
で括って表記します。例えばシンプル中継ネットワークのビヘイビア名は <NWK_SIMPLE>
です。
本ライブラリの解説では、ライブラリで最初からグローバル宣言されたオブジェクトをクラスオブジェクトと呼称します。Serial
, Wire
などです。これらクラスオブジェクトは手続きなしまたは開始手続きを行うことで利用できます。
メモリを比較的多く消費するクラスオブジェクトは、初期化手続きの際(.setup()
または.begin()
メソッド)に初期化パラメータに沿ったメモリを確保します。
一般の用語です。C言語の知識を前提に解説します。
C++言語のこと。
MWXライブラリはC++とC言語によって記述されています。
C++規格のバージョンの一つ。2011年式のC++といった意味合いで、2011年にISOで規格化されています。直前のC++03から大きく機能拡張されています。C++14, C++17といったより新しいバージョンがあります。
MWXライブラリはC++11で追加された機能や構文を用いて実装されています。MWSDKのコンパイラ対応はC++11までとなります。
あるデータに注目して、その手続きをひとまとめにしたもの。構造体に、その構造体を取り扱うための手続きが含まれています。実際にはもっと深い話題に発展しますが、専門書を参考にしてください。
C++においては、キーワードの struct
と class
は本質的には同じもので、いずれのキーワードで宣言してもクラスとなります。
上記のクラス定義をC言語でも行った場合、例えば以下のようになります。
既存のC言語のライブラリやその内部の構造体などをクラスに包含し、C++特有の機能性を追加するなどして、利用の便を図ったものです。解説中でも「~構造体をラップした」といった記述をする場合があります。
MWXライブラリは、TWENETの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
のように配列内部のデータに対して直接代入操作ができるようになります。
MWXライブラリのプログラミングインタフェースは、原則といてポインタ型の利用をせず、参照型を用いています。
C++11 では型推論のauto
キーワードが導入されています。これはコンパイラが初期化の記述からそのオブジェクトの型を推論するため、具体的な型名の記述を省略できます。これはtemplateを用いたクラス名が非常に長くなるような場合に効果的です。
解説中では多くの場合ユニバーサル参照と呼ばれるauto&&
を用いています。ユニバーサル参照については、ここでは参照渡しの場合も意識せずに記述できるものと考えてください。
配列など特定のデータ型のオブジェクトを複数個格納するためのクラスをコンテナと呼びます。テンプレートの例で挙げたmyary
のような配列クラスもコンテナと呼んでいます。
MWXライブラリでは、配列クラスsmplbuf
とFIFOキュークラスsmplque
を用意しています。
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
です。このアルゴリズムは、イテレータの示す要素同士で<,>,==
といった演算子での比較が出来れば最大と最小を計算してくれます。戻り型もイテレータの型から導かれます。
環境 (OSなど)
開発環境を構築するためには、ソフトウェア群のインストール、またこれらの利用許諾に同意する必要があります。また、PC、ワークステーション上でセキュリティ設定等が必要になる場合があります。
配布時には十分注意しておりますが、ウィルスなどの確認はお客様のほうでも留意いただくようお願いいたします。
お客様のセキュリティの考え方や運用(例:外部アプリケーションのインストールの可否)については、お客様の環境の管理者にご確認ください。
また、開発環境をインストールまた動作するにあたり、OSが介在し設定等必要になる場合があります(例:開発元が不明なアプリケーションの実行。開発環境または紹介するツール群の多くは、アプリケーションは開発元を証明する仕組みが組み込まれせん)。設定方法については、一般の情報を参考いただくようお願いいたします。
MWXライブラリを用いてアプリケーションを記述するには以下が必要です。
MWSDK(ソフトウェア開発環境)
開発用エディタ(Microsoft社のVisualStudio Codeを紹介します)
コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のWindows10,11バージョンを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
Windows11 21H2 (Visual Studio 2019)
FTDI社のドライバが動作していること (MONOSTICK, TWELITE Rを動作させるため)
WSL (Windows Subsystem Linux) 環境下でもコンパイラを動作させることが出来ます。ただしコンパイルのみでファームウェアの書き換え等はWindows10上のユーティリティから実施してください。
WSL環境は必須ではありません。
コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
Ubuntu 18.04 LTS 64bit
Ubuntu 20.04 LTS 64bit
*32bitのシステムはサポートしません。
コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。
以下、開発で使用しているバージョンを挙げます。
macOS 10.14.6 (Mojave Intel)
macOS 12.4 (Monterey Apple Silicon)
開発環境を動作させるための環境や使用方法については、その開発元やコミュニティの情報を参照ください。
コード記述作業の効率面で Visual Studio Code (VSCode) の利用を推奨します。
MWXライブラリでは、C言語の開発に比べ、読み込むヘッダファイルが多くなるため、VSCode上でのコード解釈等にはより多くのPCのリソースを要求します。
Linux/WSL環境下/macOSのビルド結果はWindows10の結果と異なります。通常系の動作で差異が見られることは当社が把握する限りありませんが、特にgccのLTOを無効にしているためバイナリサイズが数%程度大きくなる傾向にあります。
動作等に疑問を感じた際は、必ず Windows10,11 上のビルドを実施し再現することを確認してから、お問い合わせください。
改版履歴
軽微な修正にあたるものは、本改版履歴には記載を行わずGitHub上の改版のみとなります。必要に応じて修正を引用ください。
TWELITE STAGE の配布パッケージリリース後の修正・追加分などはGitHubレポジトリに格納しております。必要に応じて配布パッケージの位置を差し替えて利用いただくようお願いいたします。
MWSDKの他の更新が必要になる場合があります。更新時のリリース記述を参照してください。MWSDKの更新についてはこちらを参照ください。
ライブラリのソースコードは GitHub (https://github.com/monowireless/mwx)にて公開しています。ライブラリのソースコードの差し替えは、以下の手順で行ってください。
各リリースのリンクよりGitのクローンを行うか zip 形式でソースコードをダウンロードします。
以下のフォルダの内容を差し替えます。
リリース前の更新については上記に掲載する場合があります。
mwx
twesettings
TWENET C
1.3.5
ヒープ領域へのメモリ確保を行う Wire オブジェクトを変更した。
utils.h
での名前の衝突を避けるため、関数名をG_OCTET()
からG_BYTE()
に変更した。
attachIntDio()
において、vAHI_DioInterruptEnable()
の順番を変更した。
ユニバーサルレシーバ (NWK_LAYERED, NWK_SIMPLE またはネットワークレスパケットを同一実行コードで受信する) をサポートするために the_twelite.network2
を追加した。
NWK_LAYERED を追加 (現時点では親機受信のみ対応)
MWXの初期化時にアプリケーションのバージョンを設定する MWX_Set_Usder_App_Ver()
関数を導入した。
mwx::pnew() を追加し配置newの記述を簡素化した。
EASTLのサポート追加
EASTL用のnew[]
演算子の追加
MWXのソースコードのほとんどをプリコンパイルし、コンパイルの高速化を図った。
修正されました。DIOイベントが無関係なポートに引き渡されていたのを修正。
mwx
twesettings
TWENET C
1.3.5
インタラクティブモード中で Serialクラスオブジェクトを用いた出力を可能とする内部手続きを追加 (Serial._force_Serial_out_during_intaractive_mode()
)
mwx
twesettings
TWENET C
1.3.5
Serial1
のポート、代替ポートの定義が適切でなかった
Serial
(UART0)のボーレートを変更できるようにした
受信パケット(on_rx_packet()
)、送信完了(on_tx_comp()
)を知らせるイベントコールバックを追加
コールバック関数の定義をしなければ従前の手続きも利用可能
<STG_STD>
インタラクティブモード設定の定義ID間違いや一部デフォルト値の変更など
<STG_STD>
インタラクティブモード設定でAppIDに加えて、チャネルと論理デバイスIDのデフォルト値を変更できるようにした
the_twelite
と <NWK_SIMPLE>
オブジェクトの設定を、一部の設定についてインタラクティブモード<STG_STD>
オブジェクトで行えるようにした
<NWK_SIMPLE>
で再送回数のデフォルト値を設定できるようにした
<STG_STD>
インタラクティブモードの画面が出ている間はアプリケーションからのSerial
(UART0)の入出力を行わないようにした
CUE::PIN_SET
, PAL???"":PIN_SET
を追加 (PIN_BTN
はボタンのないCUEでPIN_BTN
を用いるのは不自然であるため)
random()
の名前空間をmwx::
に移動 (グローバル名にエリアス)
MONOSTICKのウォッチドッグ設定を32ms単位で行うようにした
BRD_TWELITE
を用いスリープを行うと、復帰時にピンが正しく初期化されなかった
mwx
twesettings
TWENET C
1.3.4
TWELITE CUE のボードビヘイビア(https://mwx.twelite.info/v/v0.1.7/boards/cue)を追加。
NWK_SIMPLE 利用時に NWK_SIMPLE 形式でない他のパケット(ネットワーク利用無し)を受信する方法を追加。NWK_SIMPLE::receive_nwkless_pkt()
を追加してNWK_SIMPLEを初期化する。 このパケット情報を用いる場合は .get_psRxDataApp()
による TWENET C ライブラリ層の構造体、および .get_payload()
により得られるデータ配列のみを利用してください。受信パケット(auto&& rx = the_twelite.receiver.read()
)の他のメソッドから得られる情報は不定です。
get_stream_helper()
コードのリファインと読み書き位置のAPIの整備。
smplbuf::get_stream_helper()
の不具合修正
serparser/pktparser
を他のプラットフォームでビルドできるようサンプルを用意しました (https://github.com/monowireless/mwx/tree/master/stdio)
mwx
twesettings
TWENET C
1.3.4
商・余を計算する div100()
をSerial等へ出力できるようにした
smplbuf<>
配列クラスの実装変更。消費メモリの削減などを目的としてmwx::stream
の継承をやめ、別途継承クラスとヘルパークラス定義した
mwx_printf()
mwx_snprintf()
の関数を追加した
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
を追加。バスクロックの主要な設定値を追加した。
mwx
twesettings
TWENET C
1.3.4
チャネルマネージャ chmgr
の実装
mwx
twesettings
TWENET C
1.3.3
delayMilliseconds()
の追加
digitalReadBitmap()
の追加
delay()
の精度向上
Serial1
インスタンスが定義されていない問題を修正
Analogue
の割り込みハンドラが呼び出されない問題を修正
MWSDK2020_05 に対応
重複チェッカ duplicate_checker の初期化等に不備があり期待通りの除去を行っていなかった
format() の実装を機種依存の少ないものとした。また、引数を最大8までとした。64bit引数が含まれる場合は引数の数は制限される。
修正は MWSDK2020_05 を前提としています。
本修正については、更新を推奨します。
MWSDK2020_04 に対応
Timer0..4の初期化の問題を修正
mwx::format() の内部処理を変更
インタラクティブモード対応のための実験的なコードの追加
本修正は MWSDK2020_04 を前提としています。
本修正については、更新を推奨します。
パケット内の中継フラグの扱いについての問題を修正
本修正については、更新を推奨します。
初版リリース (SDL 2019/12月号収録)
インストール・ビルド
MWXライブラリを用いてアプリケーションを記述(本書ではアクトと呼びます)し、実行するために開発環境のセットアップが必要です。
(オプション) Visual Stdio codeのインストール
新しいプロジェクトの作成
新しいプロジェクトの作成は、すでにあるサンプルアクトのフォルダを別の名前でコピーし、ファイル名の編集を行います。
コピー先のフォルダは MWSDK 配下のフォルダでなくても構いません。ただし、フォルダ名に空白文字や日本語名が含まれてはいけません。
プロジェクトのファイル構造は以下のようになっています(ここでは PingPong
を例に挙げます)。
この PingPong
フォルダを別の場所(ただしフォルダ名に日本語や空白が含まない)にコピーします。
編集の必要があるのは、PingPong.cpp
のファイル名です。これをフォルダ名と同じAlphaBravo.cpp
に変更します。
build\build-BLUE.cmd
を実行してBINファイルが生成されれば完了です(Windows10)。
Linux/WSL/macOS ではmake TWELITE=BLUE
を実行して、ビルドが成功するか確認します。
ビルド対象のファイルを追加する場合は build/Makefile を編集します。プロジェクト直下にある .c
.cpp
ファイルは自動で追加されますが、それ以外のファイルについては編集が必要です。
編集方法は Makefile の解説をご覧ください。
VSCode を利用する場合は、必要に応じて .vscode 以下の定義を編集してください。
TWELITE STAGE SDK に含まれるサンプルの多くは、以下のようになっています。
TWELITE STAGE SDK ライブラリのソースコードは ${env:MWSDK_TWENET_LIBSRC}/include/**
${env:MWSDK_TWENET_LIBSRC}/src/**
を引用する。この環境変数 MWSDK_TWENET_LIBSRC
は TWELITE STAGE アプリから VSCode でプロジェクトを開いた場合には自動で設定されます。
ビルドタスクについては、デフォルトで -D
などの追加的なオプション等は設定されていない。
Mono Wireless C++ Library for TWELITE.
資料の取り扱いについてをご参照ください。 お気付きの点がありましたら、当サポート窓口にご連絡いただければ幸いです。
このページには開発中の情報が含まれます。本ページの内容は、まだ公開ソースコードに反映されていない場合があります。
MWX ライブラリは、TWELITE 無線モジュールのコード表記を簡素化することを目的としています。MWXで作成されたプログラムをアクト act と呼びます。アクトにはループによる記述と、イベントによる記述(ビヘイビア behavior と呼びます)の二種類があります。
本ページではアクトの主な特徴を紹介します。
ループによる記述 (setup(), loop()
)。小規模な機能を記述するのに向いています。
イベントドリブンのアプリケーション記述。各種イベント・割り込みハンドラの定義、アプリケーションの複雑な振る舞いを記述するのに向いたステートマシンをクラス内に定義して見通しの良いコードを記述できます。この記述をビヘイビアと呼びます。
ペリフェラルの手続きを簡素化。よく使われる UART, I2C, SPI, ADC, DIO, タイマー, パルスカウンタを取り扱うクラスオブジェクトを定義しています。
シンプルな中継ネットワークを定義。この中継ネットワークは TWELITE 標準アプリケーションと同等の実装で、個体アドレスの管理は 8bit の論理IDで行うこと、ネットワーク構築のための通信を行わないため電源投入後すぐにネットワーク宛に無線パケットを送ることができる点が特徴です。
TWELITE 標準アプリケーションとのパケットの相互通信はできませんが、以下の点で自由度が上がっています。
論理IDは 0 が親機である点は同様ですが、子機アドレスとして 0x01..0xEF を利用できるため、識別数を 200 以上とすることができます。
原則3回までとしていた中継回数について、最大数を64回までを設定できるようにしています。(※ パケットが遠回りして一定時間経過後に戻ってきた場合、重複パケットの管理テーブルがクリアされ中継済みのパケットであっても、再中継が発生する場合があります。中継回数を大きくする設定は注意してください)
PAL や MONOSTICK 向けのボード定義。ボード上のセンサーなどを容易に取り扱えます。
ビルド定義 Makefile
Makefileはbuild/Makefileに格納されています。makeコマンドを実行することで、アクトをビルドするよう予め定義されています。
MWSDK 2020-04 では、プロジェクトフォルダ中の .cpp ファイルを自動で検出するため、通常は Makefile の修正は不要です。
ソースファイルをサブフォルダに格納するような場合は、編集が必要になります。
MWSDK 2019-12では、.cpp ファイルが複数ある場合は、Makefile の編集が必要です。
プロジェクトフォルダを他の環境からコピーしたあとには、必ず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
を編集します。
上記はサンプルPAL_AMB-bihaviorでのMakefileの例です。
バージョン番号を指定します。ビルド結果ファイル名に反映されます。
コンパイル中は -DVERSION_MAIN=0
-DVERSION_SUB=1
-DVERSION_VAR=0
のように定義として渡されます。
(MWSDK 2020-04)
サブフォルダにファイルを配置しない場合は、追加指定は不要になりました。プロジェクトファイルにある .c .cpp ファイルがすべて追加されます。
ソースファイルを追加する際に必要なのはAPPSRC_CXX
とAPP_COMMON_SRC_DIR_ADD?
です。
サブフォルダにソースファイルを配置する場合は必ずフォルダ APP_COMMON_SRC_DIR_ADD? の指定が必要です。
ソースファイル名をAPPSRC_CXXに追記します。このファイル名にはフォルダ名が含まれてはいけません。サブフォルダにあるものもフォルダなしで指定します(つまり同じファイル名がサブフォルダにある場合は、ビルドが失敗します)
次にソースファイルがプロジェクトフォルダ以外の場所に格納されている場合の検索パスを指定します。最大4つまで設定できます。
フォルダの指定はMakefileからの相対パスになります。
その他にもいくつかのオプションをコンパイラ・リンカに渡すことができます。
CXXFLAGS
C++ソースファイルに対してコンパイルオプションを指定します。
CFLAGS
C/C++ソースファイルに対してコンパイルオプションを指定します。
INCFLAGS
ヘッダファイルのインクルードファイル指定をします。
OPTFLAGS
特別な理由があって-Os以外のコンパイルオプションを適用したい場合に定義します。
LDFLAGS
リンカオプションを指定します。(上記Makefileのコメントには記述はありませんが指定は可能です)
MWX ライブラリ内で用いる C++ 言語について、その仕様、制限事項、本書記載留意事項、設計メモを記載します。
MWX ライブラリ内で用いる C++ 言語について、その仕様、制限事項、本書記載留意事項、設計メモを記載します。
MWXライブラリでアプリケーション記述する場合は、このページを読み飛ばしても差し支えありません。
このページはライブラリの動作の理解や改造などのためライブラリソースコードを参照する場面を想定しています。ライブラリを利用するのと比べより高度なC++言語に関する知識を前提となります。
アプリケーションのループ記述では、一般によく用いられる API 体系に近い記述を出来ることを目的とするが、TWELITEの特性に合わせた実装とする。
TWENET はイベントドリブンによるコード記述であり、これを扱えるようにクラス化を行う。上記クラス化によりアプリケーションのふるまいをカプセル化できるようにする。
イベントドリブンとループの記述は共存できる形とする。
代表的なペリフェラルはクラス化して手続きを簡素化する。可能な限りループ記述でアクセスできるようにする。
当社で販売する MONOSTICK/PAL といったボードを利用する手続きをクラス化し手続きを簡素化する。(例えば外部のウォッチドッグタイマーの利用を自動化する)
アプリケーションクラスやボードクラスは、ポリモーフィズムの考え方を導入し、統一した手続きによって利用できるようにする。(例えば、いくつかの振る舞いをするアプリケーションクラスを始動時にロードするような場合、また TWENET C ライブラリの接続部のコードを都度定義しなくてよいようにするため)。
C++の機能については、特に制限を設けず利用する。例えば、無線パケットを取り扱うにあたり煩雑なパケット構築、分解といった代表的な手続きを簡略化する手段を提供する。
演算子 ->
を極力使用しないようにし、原則として参照型による API とする。
限られた時間で実装を進めているため、細かい点まで網羅した設計ではありませんが、設計・実装等でお気づきの点がありましたら、当社サポートにご連絡ください。
gcc version 4.7.4
C++11 (コンパイラ対応状況は一般の情報を参考にしてください)
※ 当社で把握しているものについての記載です。
new, new[]
演算子でのメモリ確保は行えますが、確保したメモリを破棄することはできません。C++ライブラリで動的メモリ確保をするものは殆どが事実上利用不可能です。一度だけ生成してそれ以降破棄しないオブジェクトに使用しています。
グローバルオブジェクトのコンストラクタが呼び出されません。
参考:必要な場合は、初期化関数(setup()
) で new ((void*)&obj_global) class_foo();
のように初期化することでコンストラクタの呼び出しを含めた初期化を行えます。
例外 exception
が使用できません。
仮想関数 virtual
が使用できません。
本節では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 (Curiously recurring template pattern : 奇妙に再帰したテンプレートパターン)を用いています。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なのは変だとかいう指摘はしないでください。
例外も使えないため失敗したときの対処はありません。また、メモリ容量を意識せず確保を続けた場合、スタック領域と干渉する可能性もあります。
システム(MAC層など)が確保するメモリは約2.5KBです。
MWXライブラリでは、マイコンのリソースが小さい点、メモリーの動的確保ができない点を考慮し標準ライブラリで提供されるコンテナクラスの利用はせず、シンプルなコンテナクラスを2種類定義しています。コンテナクラスにはイテレータやbegin(), end()
メソッドを定義しているため、範囲for文やSTLのアルゴリズムの一部を利用できます。
smplbuf
配列クラスで、最大領域 (capacity) と最大領域範囲内で都度サイズを指定できる利用領域(size)を管理します。また本クラスは stream インタフェースを実装しているため、<< 演算子を用いてデータを書き込むことができます。
smplque
FIFOキューを実装しています。キューのサイズはテンプレートのパラメータで決定します。割り込み禁止を用いキューを操作するためのテンプレート引数もあります。
コンテナクラスではメモリの確保方法をtemplate
引数のパラメータとして指定します。
alloc_attach
すでに確保済みのバッファメモリを指定する。Cライブラリ向けに確保したメモリ領域を管理したいとき、同じバッファ領域の分断領域として処理したい時などに使用します。
alloc_static
クラス内に静的配列として確保する。事前にサイズが決まっていたり、一時利用の領域として使用します。
alloc_heap
ヒープ領域に確保する。 システムのヒープに確保後は破棄できませんが、初期化時にアプリケーションの設定などに従い領域を確保するといった使い方に向いています。
MWXライブラリでは、バイト列やビット列の操作、printf
相当の処理を行う処理に可変数引数を用いています。下記の例は指定のビット位置に1をセットする処理です。
この処理では template のパラメータパック (typename...
の部分) で、再帰的な処理を行い引数の展開を行っています。上記の例ではconstexpr
の指定があるため、コンパイル時に計算が行われマクロ定義やb2
のようなconst
値の指定と同等の結果になります。また変数を引数として動的に計算する関数としても振る舞うこともできます。
以下の例では、expand_bytes
関数により、受信パケットのデータ列からローカル変数に値を格納しています。パラメータパックを用いた場合各引数の型を把握できるため、下記のように、受信パケットのバイト列から、サイズや異なる型にあった値を格納することができます。
イテレータはポインタの抽象化で、例えばメモリの連続性のないようなデータ構造においても、あたかもポインタを使ったようにデータ構造にアクセスできる効果があります。
C++のSTLでは、begin()
メソッドで得られるコンテナの先頭を示すイテレータと、end()
メソッドで得られるコンテナの末尾の「次」を示すイテレータの組み合わせが良く用いられます。
コンテナの末尾の「次」をend()
としているのは、以下のような記述を想定しているためです。MWXライブラリでもこれに倣ってコンテナの実装を行っています。
イテレータを標準ライブラリの仕様に適合させることで、範囲for文が利用できたり、標準ライブラリのアルゴリズムを利用できるようになります。
(MWXライブラリではC++標準ライブラリに対する適合度や互換性についての検証は行っていません。動作確認の上利用ください)
以下の例では、通常のポインタでは連続的なアクセスができない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()
を呼び出す必要があります)。登録しないハンドラはコンパイル時の最適化により低コストな呼び出しで済むことが期待できます。
ユーザが関数を定義したときにこれを有効にし、定義しない場合は別の関数を呼び出す手法として、リンク時の解決方法があります。下記のように__attribute__((weak))
の指定します。ユーザコードで wakeup()
関数が定義された場合は、ユーザーコードを関数をリンクし、未定義の場合は中身が空の関数をリンクします。
上記ハンドラの実装においてはweak指定したメンバー変数を明示的に生成する必要があり、またinline化による最適化が行いにくいため使用しませんが、wakeup()
といったいくつかのTWENETからのコールバック関数の受け皿としてweak指定の関数を定義しています。
ストリームクラスは、主にUART(シリアルポート)の入出力に用います。MWXライブラリでは、出力用の手続きを主に定義しています。一部入力用の定義もあります。
ここでは派生クラスが必要とする実装について解説します。
上記は1文字書き出すwrite()
メソッドの実装です。親クラスのstream<serial_jen>
からはキャストを実行するget_Drived()
メソッドを用いて、serial_jen::write()
メソッドにアクセスしています。
必要に応じて write(), read(), flush(), available()
といったメソッドを定義します。
書式出力にはMarco Paland氏によるprintfライブラリを利用しています。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に切り詰めて、その値を出力しています。
他のプラットフォーム
他のプラットフォームでも一部の機能(serparser, pktparser, コンソール用Serialオブジェクト)をビルドできるように、ビルド定義を用意しています。必要なファイルのみを切り出しています。
ビルド定義は{mwxライブラリ格納}/stdio
フォルダに格納しています。ビルド方法はREADME.md(リンクはGitHub上)を参照してください。
C++11でのコンパイルが出来ること。
C++11の標準ライブラリヘッダが利用できること (utility, algorithm, functional, iteratorなど)
new/delete/virtualは使用しません。
newによるメモリ確保は例外的に使用する場合があります。
serparser/pktparserでnew演算子を利用するalloc_heap
ではdelete
による処理を行っています。
(参考) ただしmwxライブラリとしてはdelete
については考慮しない前提で設計されている部分もあります。
最初に試すシンプルなAct(アクト)
act0 から始まるアクト(Act)は、actを始める - Opening actで紹介されたものを収録しています。LEDやボタンの動作のみの単純なものですが、最初にお試しいただくことをお勧めします。
act0
処理の記述がないテンプレート
act1
Lチカ(LEDの点滅)
act2
タイマーを用いたLチカ
act3
2つのタイマーを用いたLチカ
act4
ボタン(スイッチ)を用いたLED点灯
テンプレートコード
テンプレートコードです。
the_twelite
を設定してアプリケーションID APP_ID
, 無線チャネルCHANNEL
、受信有を設定します。
またnwk
を生成し、子機アドレス0xFE
を指定しています。このアドレスは子機でアドレスを指定していない名無しの子機という意味です。
設定できるアドレスは0x00: 親機,0x01~0xEF: 子機, 0xFE:子機アドレス未指定の範囲です。
送信先として指定するアドレスは0x00は親機宛、0x01~0xEFは指定の親機アドレス、0xFEは任意の子機アドレス、0xFFは親機を含む任意のアドレスです。
またButtons
オブジェクトを初期化します。連続参照によるチャタリング抑制アルゴリズムです。10msごとに5回連続同じ値になれば対象のポート(PIN_BTN
のみ)のHI
またはLOW
を確定します。pack_bits(N1, N2, ..)
は1UL<<N1 | 1UL << N2 | ...
を行いビットマップを生成します。
the_twelite
を開始するための手続きです。act0..4では出てきませんでしたがthe_twelite
の設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。
始動時setup()
の後に1回だけ呼び出されます。メッセージの表示のみ。
Buttonsによる連続参照により状態を確定します。ボタン状態が変化したらシリアルに出力します。
Serial.available()
が_true
_の場合は、シリアルポートからの入力が保存されています。シリアルから1文字読み込んで、入力文字に応じた処理をします。
t
を入力して無線送信't
'を入力したときは送信を行います。このサンプルではtx_busy
フラグを用い連続的に入力は行わないようにしています。
送信要求は一定数までキューに保存されるため、キューの範囲(3パケット)で要求を積むことは可能です。
以下はif(!tx_busy)
の判定をしないようにして 'tttt
'と連続的に入力した場合の処理例です。4つ目の要求でキューが一杯になって要求は失敗しています。Transmit()
の.prepare_tx_packet()
で得られたpkt
オブジェクトが_false
_になります。
送信タイミングはランダム化されるため、送信完了は送信要求順にはなりません。
s
を入力してスリープ5000ms=5秒のスリープを実施します。復帰後はwakeup()
が実行されます。
スリープ起床時に最初に呼び出されます。メッセージの表示のみ。
送信要求を行う最小限の手続きです。
この関数を抜けた時点では、まだ要求は実行されていません。しばらく待つ必要があります。この例では100-200msの送信開始の遅延を設定しているため、送信が開始されるのは早くて100ms後です。
送信完了時に呼び出されます。evには送信IDと完了ステータスが含まれます。
パケットを受信したら、送信元のアドレス情報を表示します。
Sample Acts
アクトの動作を理解するため、いくつかのサンプルを用意しています。
サンプルは MWSDK をインストールしたフォルダにある Act_samples
にあります。
以下には、目的別のアクトを紹介します。
act0..4は、無線機能などを使わないごくシンプルな例です。Actの基本構造が理解できます。
I2Cセンサーを接続し、スリープによる簡潔動作を行いながら無線パケットを送信する、無線センサーの実装例です。比較的簡潔、かつ、代表的な構造ですので、act0
~act4
を確認後に参照することを推奨します。
TWELITE で無線センサーを実装するための代表的な要素(シンプル中継ネット <NWK_SIMPLE>
の利用・インタラクティブモード <STG_STD>
、I2Cセンサーの取り扱い Wire
、スリープによる間欠動作など)が含まれます。
無線パケットを送信、または送受信するサンプルですが、各々少し違った視点で実装されています。
Scratch は、UARTから1バイトコマンドを受けて、送信などを行うシンプルなコードです。
Slp_Wk_and_Txは、ステートマシンを用い、スリープを用いた間欠動作で、スリープ復帰→無線送信→スリープを繰り返します。
PingPongは、一方から他方にパケットを送信し、受信した他方がパケットを送り返すサンプルです。送信と受信についての基本的な手続きが含まれます。
WirelessUARTは、UART入力をserparserを用いアスキー形式を解釈し、これを送信します。
注:このサンプルに含まれるアクトの無線パケットの受信には App_Wings を利用することもできます。
独自の受信側親機アプリケーションを実装するときに参照してください。
Parent_MONOSTICKは、専ら受信のみを行い、シリアルポートへ受信結果を出力します。このサンプルの無線パケットで、親機向け(0x00)や子機ブロードキャスト(0xFE)とアドレス設定しているものは受信できます。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。
Rcv_Univsl はユニバーサルパケットレシーバ (TWENETレイヤーツリーネットワーク, App_Twelite, Act, ... など) のサンプルコードです。また、コンテナやアルゴリズムにEASTLライブラリを使用しています。
インタラクティブモードを使用するアクトの解説には大まかな流れを記しています(ここでは上述の BRD_I2C_TEMPHUMID を引用します)。どのサンプルの解説も大きな差はありません。
BRD_I2C_TEMPHUMIDは、I2Cセンサーデバイスの読み書きコマンドを実行し I2C センサーから得られた計測値を無線送信します。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。
Settings は、インタラクティブモード<STG_STD>のより高度なカスタマイズを行います。詳細はコードを参照ください。
内蔵ペリフェラルや外部センサーデバイスからセンサー情報を得るサンプルです。
BRD_APPTWELITEはディジタル入力、アナログ入力、ディジタル出力、アナログ出力を用いた双方向通信を行っています。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。
BRD_I2C_TEMPHUMIDは、I2Cセンサーデバイスの読み書きコマンドを実行し I2C センサーから得られた計測値を無線送信します。またインタラクティブモード<STG_STD>をActに追加するための手続きが含まれます。
PulseCounter は、パルスカウンター機能を用い、スリープ中も含め入力ポートで検出したパルス数を計数し、これを無線送信します。
PAL_AMB_behavior は、ビヘイビアを用いた例です。PAL_AMBでは温湿度センサーはライブラリ内部のコードが呼ばれますが、このサンプルでは温湿度センサーのアクセスのための独自の手続きも含まれます。
TWELITE PAL には標準的なPALアプリが書き込まれていますが、PALアプリを用いずアクトによる記述を行うことができます。MWXライブラリには、PALで使用するセンサーを動作させるための標準的な手続きが用意されています。
各種PAL基板用のサンプルです。PAL基板上のセンサー値を取得し、送信し、スリープします。
以下は応用例で、上記のアクトより少し複雑な記述になっています。
PAL_AMB_usenap は、数十msかかるセンサーの動作時間にTWELITEマイコンを短くスリープさせ、より省電力を目指すサンプルです。
PAL_AMB_behavior は、ビヘイビアを用いた例です。PAL_AMBでは温湿度センサーはライブラリ内部のコードが呼ばれますが、このサンプルでは温湿度センサーのアクセスのための独自の手続きも含まれます。
PAL_MOT_fifo は、加速度センサーのFIFOおよびFIFOの割り込みを用いて、サンプルを中断することなく、連続的に取得し無線送信するためのサンプルです。
PAL_MOT 用のアクトが利用できます。小修整が必要な場合があります。
PAL_MOT_fifo は、加速度センサーのFIFOおよびFIFOの割り込みを用いて、サンプルを中断することなく、連続的に取得し無線送信するためのサンプルです。
[BRD_ARIA] は、TWELITE ARIA を動作させるためのアクトです。
BRD_I2C_TEMPHUMID は、I2C センサー利用のためのテンプレートですが、実装例として TWELITE ARIA で利用する SHT40 センサー用のコードが含まれます。
PAL_AMB 用のアクトを小修整することで利用可能です。
Unitから始まる名前のActは機能やAPIの紹介を目的としています。
最新版のコードや MWSDK バージョン間の修正履歴を確認する目的で Github にソース一式を置いています。以下のリンク先を参照してください。
アクトのサンプル中で以下の項目は共通の設定項目になり、以下で解説します。
サンプルアクト共通として以下の設定をしています。
アプリケーションID 0x1234abcd
チャネル 13
アプリケーションIDとチャネルはともに他のネットワークと混在しないようにする仕組みです。
アプリケーションIDが異なる者同士は、チャネルが同じであっても混信することはありません。ただし、別のアプリケーションIDのシステムが頻繁に無線送信しているような場合はその無線送信が妨害となりますので影響は出ます。
チャネルは通信に使う周波数を決めます。TWELITE無線モジュールでは原則として16個のチャネルが利用でき、通常のシステムでは実施しないような極めて例外的な場合を除き、違うチャネルとは通信できません。
サンプルアクト共通の仕様として、パケットのペイロード(データ部)の先頭には4バイトの文字列(APP_FOURCHAR[]
)を格納しています。種別の識別性には1バイトで十分ですが、解説のための記述です。こういったシステム特有の識別子やデータ構造を含めるのも混信対策の一つです。
アクトのビルド
MWXライブラリで記述したアプリケーションプログラムをアクト(Act)と呼びます。まずは、これをビルドして書き込みます。
ビルドフォルダ構成について
Visual Studio Code (VSCodeと記載) でのビルドについて
本ページでは、いくつかのビルド方法を記載していますが、いずれの方法も最終的にはmakeコマンドを実行しています。詳細はMakefileの解説を参照ください。
OS環境によっては各実行プログラム(make
やgcc
などビルドツールチェイン)の動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)
MWSDKをインストールしたフォルダMWSDK_ROOT (例 C:\MWSDK)
を開きます。以下のような構成になっています。
アクトファイルはAct_samples
以下に格納しています。(以下は一部割愛しています)
これらのアクトは、MWXライブラリの記述の参考となるシンプルな例ですが、多くのアクトは以下の機能を有しています。
センサー値を取得する
センサー値取得後、無線パケットを親機宛に送信する
送信完了後、一定時間スリープする(または割り込みを待つ)
Parent-MONOSTICK
のアクトによりパケットの受信と表示を行っています。この親機用のアクトは、アスキー形式で出力しています。 (:00112233AABBCC...FF[CR][LF]
のような : で始まり、途中は16進数のバイトをアスキー文字2字で表現する形式です。末尾の??は同様に2字のバイトとなりますがLRCというチェックサムバイトになります。参考:アスキー形式)
実際に動作させてみるときは、以下の組み合わせを試してみてください。
親機はM1ピンをLOW(GNDレベル)にして起動する。通常モード(常時稼働)にて、App_TweLiteのような動作を確認できます。
子機同士2台使って動作します。片方から Ping パケットを送ると、相手方から Pong パケットが戻ってきます。
その他
子機用のアクトのパケット送信を確認できます。
では、アクトの中から PingPong のフォルダの中を見てみましょう。
Act_samples
にある他のアクトもビルドできます。その場合、フォルダ名・ファイル名は読み替えるようにしてください。
必ずフォルダ直下にフォルダと同名の .cpp
ファイルが必要です。
小規模なアクトならこの.cpp
ファイル内に記述します。規模が大きくなってきたときはMakefileの解説を参考にして複数のファイルに分割してビルドすることが出来ます。
PingPong
フォルダ直下にアクトファイル PingPong.cpp
があります。フォルダ名を変更した場合は、必ず .cpp
ファイルの名前もフォルダ名と同名にします。
次にビルドフォルダを開きます。
ビルドに必要なスクリプトとMakefile
が格納されています。
このMakefile
のあるフォルダで make TWELITE={BLUEまたはRED} を実行することで、ビルドが行われます。VSCode でのビルドも同様で内部的に make を呼び出します。
TWETLITE STAGE アプリを用いて、ビルドから書き込み、実行までを行えます。ここでは、TWELITE STAGE アプリの起動からビルドまでを解説します。
MONOSTICKまたはTWELITE Rをお使いのUSBポートに接続します。
TWELITE は繊細な電子部品ですので、取り扱いには十分注意してください。以下に代表的な注意事項を記載します。
特にTWELITE Rを用いている場合は、多くの場合電子基板がケースなどを介さず直接外部に触れる状態で使用するため、意図しないショートやノイズなどUSBデバイスが正常に動作しない状態になる場合があります。
この場合は、アプリケーションを終了させUSBデバイスを抜き差しすることで通常は回復します。最悪、USBデバイスの破損やPCの破損も考えられます。
電子基板の取り扱いには十分注意してください。
回路の間違い
電源を入れる前にはもう一度回路を確認してください。
電池の逆差しや過大電圧には注意してください。
静電気
人感がない電圧であっても半導体の故障になりえます。大掛かりな対応をしなくとも、金属部に触れてから作業する・リストバンド・専用マットなど簡易にできる対応でも相応の効果はあります。
金属物などが触れることでのショート
電子基板の近くには金属物がないようにしてください。クリップなどが散らかっているとこれが原因でショートすることもありますし、電池が大放電して加熱する危険な状況も考えられます。
{TWELITE SDK インストール} フォルダにある実行形式 TWELITE_Stage.{拡張子}
を起動します(参考: TWELITE STAGE アプリマニュアル-使用法)。
以下は、TWELITE STAGE アプリ動作中の画面例です。左側の主画面とコマンドプロンプト画面がありますが、主画面を操作します。コマンドプロンプト画面は通常使用しませんが、諸情報及びTWELITEマイコンシリアルポートからの入力データが表示されます。
主画面での主な操作は以下です。
マウス左クリック (選択)
マウス右ダブルクリック(前の画面に戻る)
素早く ESC
を2回, 一部画面では ESC
1回 (前の画面に戻る)
Alt(Cmd) キーを押し続ける(ヘルプ画面)
通常キーボード入力(画面に従う)
(参考: TWELITE STAGE アプリマニュアル-キー操作・マウス操作)。
TWELITE STAGE アプリを起動すると最初に表示される画面です。事前に TWELITE R や MONOSTICK を接続しておけば、この画面に列挙されます。この画面で操作したい TWELITE を選択します。この画面で選択せずに、別の操作で選択することも可能です。
(参考: TWELITE STAGE アプリマニュアル)
シリアルポート選択画面を抜けると、メインメニューが表示されます。ビルドや書込は「アプリ書換」メニューを選択します。
(参考: TWELITE STAGE アプリマニュアル)
アプリ書換メニューを選択する前に、TWELITE の接続とシリアルポートの選択を確認しておいてください。シリアルポートの選択状況は Alt(Cmd) キーを押し続けると出現するヘルプ画面上で確認できます。
TWELITE STAGE アプリから参照できるプロジェクトはいくつかに分類されています。右側のヘルプ
は関連情報をブラウザで表示します。フォルダ
はプロジェクトのあるフォルダを表示します。
TWELITE が接続済みであれば、メニューを選択したときに、TWELITE のモデルが判定されます。(TWELITE STAGE アプリ内部では、この判定したTWELITEモデルに応じたビルドを行うようになっています)。
ここでエラーが出た場合は、この画面からメインメニューに戻って、メニューを再選択します。解決しない場合は、必要に応じてTWELITE STAGE アプリ上で Alt(Cmd)
+ 0
を入力してシリアルポート選択を解除を行ったうえで USB 接続を含む各種接続を確認するなどします。USB接続のエラーによっては、お使いのコンピュータを再起動しないと解決しない場合もあります。
(参考: TWELITE STAGE アプリマニュアル)
ここでは「アプリ書換メニュー」から「Actビルド&書換」を選択します。
サンプルアクトなどのプロジェクト名が列挙されます。右側のヘルプ
は関連情報をブラウザで表示します。フォルダ
はプロジェクトのあるフォルダを表示します。
(参考: TWELITE STAGE アプリマニュアル-Actビルド&書換)
ここでは、先ほどのプロジェクト選択画面中で BRD_APPTWELITE
を選択します。
選択すると、以下の画面例のように書き込みが行われます。エラーが表示された場合は、画面の指示に従うか、前の画面戻ってやり直してください。
(参考: TWELITE STAGE アプリマニュアル-ビルド画面)
書換が正常に終了すると、続けてインタラクティブモード(設定画面)に移行します。ただし、インタラクティブモードに対応するファームウェアでないと画面は表示されません。
インタラクティブモードでは、TWELITE の無線チャネルなど、各種設定を行えます。
(参考: TWELITE STAGE アプリマニュアル-インタラクティブモード)
ルートメニューに戻って「ビューア」→「ターミナル」を選択します。
ごく簡易的なターミナルです。TWELITE からのメッセージを確認と、TWELITE への入力を行えます。
画面では、約1秒おきに無線送信したときのメッセージが表示されます。+ + +
入力によるインタラクティブモード画面への遷移も行えます。
(参考: TWELITE STAGE アプリマニュアル-ターミナル)
VSCode はソース編集の強力なエディタですが、VSCode上で TWELITE マイコン用のファームウェアをビルドすることも可能です。
VSCode の起動は TWELITE STAGE アプリの「ビルド&書換」メニュー以下のプロジェクト一覧より行います。(この動作を行うために TWELITE STAGE アプリの設定が必要です。設定の簡便のため Windows, Linux, macOS では TWELITE_Stage_VSCode.{拡張子}
の実行形式を用意しています。)
STAGE設定で 「code でフォルダを開く(VSCode)」を 1
にする。
ビルドのリスト中の右端 [VSCode]
を押します。
すでに VSCode が立ち上がった状態では必要な設定が反映されない場合があります。その場合は、一旦 VSCode を終了して、改めて TWELITE STAGE アプリから VSCode を起動します。
情報の反映のためにシステム環境変数を用いてるため、原理的に、別々のライブラリフォルダを参照する複数の TWELITE STAGE の同時運用では問題が出る場合があります。VSCode 上で Terminal を開き、環境変数 MWSDK_ROOT
が適切に設定されている場合はビルドは正常に行われることが期待できます。
最初にビルドしたいワークスペースを開いておきます。TWELITE STAGE SDK添付のワークスペースにはビルドタスクの定義が追加されています。
以下の例では英語インタフェースの画面例で、ワークスペースを開いています。
ワークスペースを開き [Terminal>Run Task...]
を選択します。
ビルドするTWELITE無線モジュールの種別(BLUE/RED)とアクト名を選択します。以下の例では Build for TWELITE BLUE
を選択しています。選択後すぐにビルドが始まります。
ビルド中の経過は画面下部のターミナル部分に出力されます。
正しくビルドできた場合、上記画面例の反転表示部のように .elf
ファイルが生成されるメッセージがサイズ情報(text data bss dec hex filename
と記載がある部分)とともに出力されます。
またBINファイル(上記では BRD_APPTWELITE_BLUE_???.bin
)ファイルがbuildフォルダ下に出来上がっているはずです。確認してみてください。
VSCodeのタスク定義には Windowsのファイルシステムに適合しないフォルダ名 (例:/c/User/...
)を変換する(例:C:/User/...
) 定義を追加しています。
下記.vscode/tasks.json
中の変換ルールは完全ではありませんが、出力メッセージ中のドライブ名相当部分の文字列をVSCodeが識別できる形式に書き換えています。この書換によりコンパイルメッセージからエラーが発生しているファイル名と行番号をVSCode中で表示できます。
ビルドがうまくいかない場合は、まずエラーメッセージを確認して下さい。errorという文字列が含まれる行中のメッセージから、エラー原因が容易に特定できる場合も少なくありません。
また、クリーン(objs_???
フォルダにある中間ファイルの削除)を行い、ビルドを再実行してみてください。(他の環境でビルドした中間ファイルが残っているとmake clean
を含めすべての操作が失敗します)
コマンドライン環境でのビルドについて補足します。
コマンドライン(bash)についての利用の知識が必要です。
OS環境によっては各実行プログラムの動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)
コマンドラインでのビルドは、bash
(または他のシェル)が動作するウインドウでmake
を実行します。事前に環境変数MWSDK_ROOT
が正しく設定されていることを確認してください。例えば/work/MWSDK
にインストールした場合は ~/.profile
に以下のような設定を行います。
コマンドライン(bash)よりmakeを実行します。make
がない場合はパッケージをインストールする必要があります。
Linux環境ではmake
またはbuild-essential
パッケージをインストールします。
macOS環境ではXcodeでCommand Line Toolsをインストールします。
Windowsでは {MWSTAGE SDK インストール}/MWSDK/WIN_BASH.cmd
を実行します。 環境変数や make ユーティリティが設定済みです。
ビルドは以下のようになります。
詳細はMakefileの解説をご覧ください。
make TWELITE=BLUE
TWELITE BLUE用にビルド
make TWELITE=RED
TWELITE RED用にビルド
make cleanall
中間ファイルの削除
ビルドが行われると objs_???
フォルダが作成され、その中に中間ファイルが生成されます。このファイルはコンパイルした環境に依存しているため、他の環境のファイルが残っているとmakeがエラーとなりビルドが失敗します。
直接objs_???
フォルダを削除するとmake
のエラーが解消する場合があります。
TWELITE SDK のインストール
TWELITE STAGE SDK 配布アーカイブ(ZIPなど)をダウンロードし、適切なフォルダに展開します。
展開先の各階層のフォルダ名には、半角数字 0..9
, 半角アルファベットa..zA..Z
,一部の記号 -_.
以外は含まれないようにしてください。空白や漢字・ひらがななどが含まれてはいけません
TWELITE STAGE SDK アーカイブ展開後のフォルダ例です (windows, c:\work\MWSTAGE\...
)
詳しくはTWELITE STAGE SDKのインストールを参照してください。
通常のインストール作業はここまでです。続く「環境変数の設定」以下は必要に応じて参照してください。
TWELITE STAGE アプリを用いる場合は、環境変数の設定は不要です。コマンドラインでビルドを行う場合は設定してください。
MWSDK_ROOT
, MWSDK_ROOT_WINNAME
(Windows10のみ) の設定が必要です。
ここでは展開後のフォルダ名を C:\MWSTAGE
とします。別のフォルダにインストールした場合は、読み替えてください。
C:\MWSTAGE\Tools\SET_ENV.CMD
を実行してください。以下の環境変数を設定します。
MWSDK_ROOT
MWSDK_ROOT_WINNAME
例えば以下のような設定になります。
インストールしたPC上からTWELITE STAGE SDKをアンインストールするには以下を行ってください。
UNSET_ENV.cmd
を実行してください。環境変数の設定を解除します。
MWSTAGEフォルダを削除してください。
開発環境やシェルに MWX_ROOT
環境変数を反映されるように設定してください。
方法はいくつかありますが、ホームフォルダの.profile
(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。
MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK/
export MWSDK_ROOT
エディタを使用せずに追加するには以下のようにコマンド入力します。$
はプロンプトで環境によって表示が違います。/foo/bar/MSWSDK
の部分はインストールしたフォルダに応じて書き換えてください。
開発環境やシェルに MWX_ROOT
環境変数を反映されるように設定してください。
方法はいくつかありますが、ホームフォルダの.profile
(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。
MWSDK_ROOT=/foo/bar/MWSTAGE/MWSDK/
export MWSDK_ROOT
エディタを使用せずに追加するには以下のようにコマンド入力します。$
はプロンプトで環境によって表示が違います。/foo/bar/MSWSDK
の部分はインストールしたフォルダに応じて書き換えてください。
環境全体にMWSDK_ROOT
を適用にするにはLaunchDを用います。
VSCodeの一部の設定で環境変数を参照していますが、ビルドには必須ではありません。
VSCodeのインストール
TWELTIE STAGE SDK では、アクト(ソースコード)記述をより容易に行うため、VisualStudio Code(VSCode)を紹介しています。添付のアクトには、VSCodeで適切にコード解釈が行われるように設定したファイルが含まれます。
VSCodeはソースファイルやヘッダファイルを読み込み、ソースコードを解釈し、これによりソースコードの記述の助けとなる関数定義情報や、関数・メソッド名の補完などを行います。C言語の開発に比べて、MWXライブラリでは読み込まれるヘッダファイルの分量が多くなります。環境によってはエディタの動作が重く感じる場合があります。
ソースコードの解析や VSCode からのビルドを行うために、ライブラリソースコードが格納されるフォルダの情報などの情報が必要になります。これらの情報は TWELITE STAGE アプリから VSCode を呼び出すことによって反映されます。(具体的には適切な環境変数を設定して VSCode を始動します。プロジェクトの設定は環境変数を参照しています)
当サポートでは VSCode のインストール方法、使用方法のお問い合わせについては対応いたしません。一般で得られる情報を参照ください。
環境によっては、インストールのためにセキュリティ設定などが必要になる場合があります。インストールの可否はシステム管理者にご確認の上、手順は配布元や一般の情報を参考にしてください。
VSCode では、以下のことができます。
ソースコードの編集
ソースコード解釈に基づく intellisense(*全ての定義が正確に解釈されることを保証するわけではありません)
VSCode はリンク先より入手してください。
Visual Studio Code が C/C++ 言語の記述を解釈できるようにするために、プラグインをインストールします。
C/C++ for Visual Studio Code
TWELITE STAGE から VSCode を呼び出すために、code
コマンドが有効になっている必要があります。
以下は code.visualstudio.com の情報です。
macOS - code コマンドを実行できるように PATH 設定が必要です。
MWXライブラリのサンプルには .vscode の定義を含めています。この定義は MWSDK_ROOT 環境変数を用い、ライブラリのソースコード({MWSDK_ROOT}/TWENET/current
以下)を特定しています。
TWELITE STAGEからVSCodeを始動した場合、上記の環境変数などを設定します。既に始動済みであるような場合など、設定が反映されない場合もあります。
VSCode のソースコードの解釈はコンパイラでの解釈とは完全には一致しません。またソースコードの編集状況によっては解釈がより不完全な状態になる場合もあります。
親機アプリケーション(MONOSTICK用)
MONOSTICKを親機として使用するアクトです。子機からのパケットのデータペイロードをシリアルポートに出力します。サンプルアクトの多くのサンプルでのパケットを表示することが出来ます。
このアクトには以下が含まれます。
無線パケットの受信
受信したパケットのデータ解釈
インタラクティブモードの設定 -
バイト列のアスキー形式への変換 -
サンプルアクトの子機からのパケットを受信して、シリアルポートへ出力する。
最初は以下のデフォルトの設定にて確認してください。
アプリケーションID: 0x1234abcd
チャネル: 13
<NWK_SIMPLE> 簡易中継ネットの定義を読み込みます
<STG_STD> インタラクティブモードの定義を読み込みます。
デフォルト値や関数プロトタイプなどの宣言をしています。
setup()
では、まず<MONOSTICK>
ボードビヘイビア、<STG_STD>
インタラクティブモード ビヘイビア、<NWK_SIMPLE>
ビヘイビアをuse<>
を用い読み込みます。この手続きは必ずsetup()内で行います。
appname
→ 設定画面中のタイトル行にでるアクト名称
appid_default
→ デフォルトのアプリケーションID
ch_default
→ デフォルトのチャネル
lid_default
→ デバイスID(LID)のデフォルト値
.hide_items()
→ 項目の非表示設定
設定値を読み出す前には必ず.reload()
を呼び出します。設定値は.u32opt1()
のように設定値ごとに読み出し用のメソッドが用意されています。
<MONOSTICK>
ボードビヘイビアではLED点灯制御のための手続きを利用できます。
1行目では赤色のLEDを無線パケットを受信したら200ms点灯する設定をしています。最初のパラメータはLED_TIMER::ON_RX
が無線パケット受信時を意味します。2番目は点灯時間をmsで指定します。
2行目はLEDの点滅指定です。1番目のパラメータはLED_TIMER::BLINK
が点滅の指定で、2番目のパラメータは点滅のON/OFF切り替え時間です。500msごとにLEDが点灯、消灯(つまり1秒周期の点滅)を繰り返します。
the_twelite
を開始するための手続きです。act0..4では出てきませんでしたがthe_twelite
の設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。
このサンプルではloop()
中の処理はありません。
パケットを受信したときに呼び出されるコールバック関数です。この例では受信したパケットデータに対していくつかの出力を行っています。
関数の末尾で呼び出されるanalyze_payload()は、いくつかのサンプルアクトのパケットを解釈するコードが含まれています。サンプルアクト中のパケット生成部分と対応させてコードを参照してください。
この関数では最初に4文字識別データをfourchars[5]
配列に読み込みます。
この記述で対応するのは配列長さN
が指定されるuint8_t[N]
型のみで、uint8*
型、char*
型、char[]
型などを用いる場合は、make_pair(char*,int)
を用いた指定が必要になります。
つづいて4バイトヘッダに対応した処理を行います。ここではサンプルアクトSlp_Wk_and_Txのパケットを解釈し内容を表示します。
他の解釈部の判定をスキップするようにb_handled
を_true
_に設定します。
読み出しに成功すれば内容を出力して終了です。
1行目はアスキー書式に変換する前のデータ列を格納するバッファをローカルオブジェクトとして宣言しています。
2行目はpack_bytes()
を用いてデータ列を先ほどのbuf
に格納します。データ構造はソースコードのコメントを参照ください。pack_bytes()
のパラメータにはsmplbuf_u8 (smplbuf<uint8_t, ???>)
形式のコンテナを指定することもできます。
パケットのシーケンス番号は、<NWK_SIMPLE>
で自動設定され、送信パケット順に割り振られます。この値はパケットの重複検出に用いられます。
LQI (Link Quality Indicator)は受信時の電波強度に相当する値で、値が大きければ大きいほどより強い電界強度で受信できていることになります。ただしこの値と物理量との厳格な関連は定義されていませんし、環境のノイズと相対的なものでLQIがより大きな値であってもノイズも多ければ通信の成功率も低下することになります。
13,14,17行目は、シリアルパーサーの宣言と設定、出力です。
最初の出力(if(0)
により実行されないようになっています)は<NWK_SIMPLE>
の制御データを含めたデータをすべて表示します。制御データは11バイトあります。通常は制御情報を直接参照することはありませんが、あくまでも参考です。
2行目はシリアルパーサーのバッファを設定します。すでにあるデータ配列、つまり受信パケットのペイロード部を指定します。serparser_attach pout
は、既にあるバッファを用いたシリアルパーサーの宣言です。pout.begin()
の1番目のパラメータは、パーサーの対応書式をPARSER::ASCII
つまりアスキー形式として指定しています。2番目はバッファの先頭アドレス。3番目はバッファ中の有効なデータ長、4番目はバッファの最大長を指定します。出力用で書式解釈に使わないため4番目のパラメータは3番目と同じ値を入れています。
6行目でシリアルポートへ>>
演算子を用いて出力しています。
7行目のSerial << mwx::flush
は、ここで出力が終わっていないデータの出力が終わるまで待ち処理を行う指定です。(Serial.flush()
も同じ処理です)
Slp_Wk_and_Tx
Slp_Wk_and_Tx
は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。
setup(), loop()
の形式では、どうしても loop()
中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()
中をを用いて _switch_構文による単純な状態遷移を用いることで、コードの見通しを良くしています。
このアクトには以下が含まれます。
代表的な間欠動作(スリープ→起床→計測→無線送信→スリープ)の制御構造について
送信パケットの生成と送信手続き、完了待ちについて
起動後、初期化処理を経て、一旦スリープする
setup()
初期化する
begin()
スリープ実行する
スリープ起床後、状態変数を初期化し、以下の順に動作を行う
wakeup()
スリープからの起床、各初期化を行う
loop()
状態INIT
->WORK_JOB
に遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し乱数で決めたカウント後にTX
状態に進む)
loop()
状態TX
送信要求を行う
loop()
状態WAIT_TX
送信完了待ちを行う
loop()
状態EXIT_NORMAL
スリープする (1. に戻る)
loop()
状態EXIT_FATAL
エラーが発生した場合は、モジュールリセットする
パケット送信を行うため <NWK_SIMPLE>
をインクルードしています。また、アプリケーションIDなど基本的な定義は "Common.h"
に記述しています。
loop()
内の順次処理を記述うするため、このサンプルではステートマシン(状態遷移)の考え方を用います。ごく単純な状態遷移の処理をまとめた<SM_SIMPLE>
を用います。
Common.h
に以下の状態に対応する列挙体 STATE
が定義されています。
ここで宣言されたstep
は、状態の管理、タイムアウト、処理待ちを行うための機能が含まれています。
このサンプルではセンサーデーターの処理は行いませんが、ダミーデータを用意しておきます。
変数やクラスオブジェクトの初期化を行います。
step
ステートマシンの初期化
the_twelite
クラスオブジェクトの初期化
ネットワーク <NWK_SIMPLE>
の登録と初期化(DEVICE_ID
の登録)を行います。
つづいてクラスオブジェクトやハードウェアなどの開始処理を行います。
the_twelite
を開始するための手続きです。act0..4では出てきませんでしたがthe_twelite
の設定や各種ビヘイビアの登録を行った場合は、必ず呼び出すようにしてください。
setup()
の直後に一度だけ呼び出されます。SleepNow()
関数夜を呼び出して初回のスリープ手続きを行います。
起床直後に呼び出されます。ここではセンサーデータ領域の初期化と、起床時のメッセージを出力しています。
上記のコードは、実際のコードを簡略化したものです。
step.b_more_loop()
は、.next()
により状態遷移があった場合_true_に設定されます。これは状態遷移が発生したときloop()
を脱出せずに次の状態のコード(case節)を実行する目的です。
以下に各状態の解説を行います。
ダミーーのセンサー値を初期化します。一つは加算カウンタ、一つはカウンター停止値でランダムに決定しています。
WORK_JOB状態では1msごとのタイマー単位で処理します。TickタイマーごとにTickTimer.available()
になります。Tickタイマーごとにカウンタを加算しdummy_work_ct_max
になったら、次の状態STATE::TX
に遷移します。
Transmit()
関数を呼び出しパケット送信要求を行います。送信要求が成功した場合はSTATE::WAIT_TXEVENT
に遷移して送信完了を待つことになります。ここでは完了待ちとしてSM_SIMPLEステートマシンのタイムアウトとフラッグ機能を用います(待ちループ中での変数値の変化により判定する単純なものです)。
単一の送信要求が失敗することは通常想定しませんが、失敗時はSTATE::EXIT_FATAL
として例外処理する状態に遷移します。
この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。多くの場合、送信完了を待ってから、続く処理を行います。
Transmit()
関数はMWX_APIRET
オブジェクトを返しますが、このオブジェクトは_bool_型の成功の可否と、最大31ビットの値を保持しています。_bool_型として評価できますから、if_文の判定は送信要求が成功したら_true、失敗したら_false_を返します。
送信完了待ちは後述のon_tx_comp()
によりステートマシン機能のフラッグをセットすることで判定しています。タイムアウトは.is_timeout()
を呼び出すことで.set_timeout()
を行ったときからの経過時間により判定します。
送信が成功しても失敗しても通常は完了通知がありますが、タイムアウトを設け例外処理のための状態STATE::EXIT_FATAL
に遷移します。
SleepNow()
を呼び出して、スリープ処理に入ります。
重大なエラーとして、システムリセットを行います。
周期スリープを行います。スリープ時間はrandom()
関数を用いて、一定の時間ブレを作っています。これは複数のデバイスの送信周期が同期した場合、著しく失敗率が上がる場合があるためです。
スリープ前にはSM_SIMPLEステートマシンの状態を.on_sleep()
を呼び出してセットしておきます。
ID=0x00
の親機宛に無線パケットの送信要求を行います。格納されるデータはActサンプルで共通に使われている4文字識別子(FOURCC
)に加え、システム時間[ms]とダミーセンサー値(sensor.dummy_work_ct_now
)を格納します。
まず最初に送信パケットを格納するオブジェクトを取得します。このオブジェクトを操作し、送信データや条件を設定します。
mwx ライブラリでは、_if_文中でオブジェクトを取得し、そのオブジェクトの_bool_判定で_true_の場合に処理を行う記述を採用しています。ここではthe_twelite.network.use<NWK_SIMPLE>()
によりボードオブジェクトを取得し、ボードオブジェクトの.prepare_tx_packet()
によりパケットオブジェクトを取得しています。パケットオブジェクトの取得失敗は通常想定しませんが、失敗時は送信キューが一杯で送信要求が受け付けられない場合です。このサンプルは単一の送信のみですから、エラーは想定外の重大な問題に限られます。
この関数は可変数引数により指定できます。一番最初のパラメータは.get_payload()
より得られた配列オブジェクトです。
make_pair(FOURCC,4)
: _make_pair_はC++標準ライブラリのもので、_std::pair_オブジェクトを生成します。文字列型に対して先頭から4バイト分を書き出すという意味になります。
(文字列型の配列は終端を含める、含めないといった話題が混乱を生むため、明示的に書き出すバイト数を指定するために、このような指定をします)
_uint32_t
_型のデータを指定するとビッグエンディアン並びで4バイト分のデータを書き込みます。
_uint16_t
_型のデータについても同様です。
uint8_t 型のポインタを用いてデータの書き込みを行うことも出来ます。
.get_payload()
から得られた配列オブジェクトは、何も格納されていないサイズ0の配列ですが、この配列にデータを書き込むことでサイズが拡張され(実際は内部の固定長のバッファに対してデータを書き込み、内部管理のデータサイズを更新します)、最終的なサイズがペイロードのデータサイズです。
ここでは.begin()
を用いて_uint8_t*
_のポインタを得て、このポインタを用いてデータを書き込み、最後に書き込んだサイズを.redim()
で設定します。
S_OCTET(), S_WORD(), S_DWORD()
といった関数を書き込みに用いていますが、例えばS_OCTET(p, 'H')
は *p = 'H'; p++;
と同じ処理を行うポインタを用いたデータ書き込みです。
最後の.redim()
は配列のサイズをバッファの初期化をせずに変更する手続きです。.resize()
を呼び出すとすべて0クリアされます。
最後に.transmit()
を呼び出して、送信要求を行います。戻り値はMWX_APIRET
型です。要求後、実際の送信が行われますが、送信パラメータや送信サイズにもよりますが、完了まで数ms~数十ms程度はかかります。完了時にはon_tx_comp()
が呼び出されます。
送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()
により完了としています。
PingPong
2台のシリアル接続しているTWELITEの片方からPING(ピン)の無線パケットを送信すると、他方からPONG(ポン)の無線パケットが返ってきます。
このアクトには以下が含まれます。
無線パケットの受信からの速やかな応答送信
相手のアドレスを直接指定した送信
シリアルポートからの入力 -
ディジタル(ボタン)入力 -
アナログ入力 -
いずれかを2台。
でUART接続されているなど
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
大まかな流れは、各部の初期設定、各部の開始となっています。
このオブジェクトはTWENETを操作するための中核クラスオブジェクトです。
the_twelite
に設定を反映するには <<
を用います。
TWENET::appid(APP_ID)
アプリケーションIDの指定
TWENET::channel(CHANNEL)
チャネルの指定
TWENET::rx_when_idle()
受信回路をオープンにする指定
<<, >>
演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。
次にネットワークを登録します。
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で開始されていて、
初回を除き ADC の開始は、割り込みハンドラ内で行います。
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()
で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin()
で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::
に定義されるPIN_BTN
(12) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。10
を指定しているので10msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。
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になります。
初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。
初回確定以外の場合かつPIN_BTNのボタンが離されたタイミングでvTransmit()
を呼び出しています。押したタイミングにするには(!(btn_state && (1UL << PIN_BTN)))
のように条件を論理反転します。
無線パケットの送信要求を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置きに再送を行うという意味です。
ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。
送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。
データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。
IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。
上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload()
により simplbuf<uint8_t>
型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。
上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()
を用意しています。
pack_bytes
の最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()
です。
そのあとのパラメータは可変数引数でpack_bytes
で対応する型の値を必要な数だけ指定します。pack_bytes
は内部で.push_back()
メソッドを呼び出して末尾に指定した値を追記していきます。
3行目のmake_pair()
は標準ライブラリの関数でstd::pair
を生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()
の1番目のパラメータに文字列型(char*
やuint8_t*
型、uint8_t[]
など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。
4,5,6行目は、数値型の値 (uint8_t
, uint16_t
, uint32_t
)を格納します。符号付などの数値型、char
型など同じ数値型であっても左記の3つの型にキャストして投入します。
analogRead()
とanalogRead_mv()
は、ADCの結果を取得するものです。前者はADC値(0..1023)、後者は電圧[mv](0..2470)となります。モジュールの電源電圧は内部的に分圧抵抗の値を読んでいるためその変換を行うadalogRead_mv()
を利用しています。
これでパケットの準備は終わりです。あとは、送信要求を行います。
パケットを送信するにはpkt
オブジェクトのpkt.transmit()
メソッドを用います。
このアクトでは使用しませんが、戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。
受信パケットがある場合の処理です。
まず受信パケットのデータはパラメータrx
として渡されます。rx
から無線パケットのアドレス情報やデータペイロードにアクセスします。
次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。
<NWK_SIMPLE>
では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。
MWXライブラリにはtransmit()
の時に使ったpack_bytes()
の対になる関数expand_bytes()
が用意されています。
1行目から3行目までは、データを格納する変数を指定しています。
6行目でexpand_bytes()
によりパケットのペイロードのデータを変数に格納します。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*
ポインタ)を指定します。.begin()
メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()
メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。
3番目以降のパラメータに変数を列挙します。列挙した順番にペイロードの読み出しとデータ格納が行われます。
このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()
の戻り値により判定してください。
expand_bytes()
の戻り値は uint8_t*
ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)
を戻します。
msg
に読み出した4バイト文字列の識別子が"PING"
の場合はPONGメッセージを送信する処理です。
続いて到着したパケット情報を表示します。
数値のフォーマット出力が必要になるのでformat()
を用いています。>>
演算子向けに_printf()_と同じ構文を利用できるようにしたヘルパークラスですが、引数の数は最大8つまで(32bitパラメータの場合)に制限されています。(制限を超えるとコンパイルエラーが出ます。なおSerial.printfmt()
には引数の数の制限がありません。)
mwx::crlf
は改行文字(CR LF)を、mwx::flush
は出力完了待ちを指定します。(mxw::flush
はSerial.flush()
と記述しても構いません)
I2C センサーデバイスを用いて、定期起床からの計測および送信を行うサンプルです。
このサンプルでは、当社の あるいは に搭載の I2C センサーデバイスを利用しています。しかし、I2Cコマンド送受信部分を書き換えることで、その他の一般的な I2C センサーデバイス(図中 Generic I2C Sensor Module) を利用することもできます。その場合には、以下のように配線してください。
このアクトには以下が含まれます。
無線パケットの送受信
インタラクティブモードによる設定 -
ステートマシンによる状態遷移制御 -
I2C デバイスのコマンド送受信を行います。
コイン電池で動作させるための、スリープ機能を利用します。
無線送受信に必要な <NWK_SIMPLE>
、インタラクティブモードを追加するための <STG_STD>
、アプリケーションループの記述を簡素化するための <SM_SIMPLE>
をインクルードしています。
この例では SHTC3 (TWELITE AMB PAL) と、SHT40 (TWELITE ARIA) の2種類のコードがあり #ifdef
により切り替えています(USE_SHTC3
またはUSE_SHT40
のどちらかを #define
してください)。コードの移植性のため2種類は同じ関数インタフェースとして定義しています。2種類のコードは同メーカ、同系列のセンサーであるため似通っています。
以下では SHTC3 の例を示します。
ここではソースコードを整理するため I2C センサー関連の手続きを構造体(クラス) STHC3 にまとめています。この構造体には I2C アドレス I2C_ADDR
と、値取得のための待ち時間 CONV_TIME
をメンバー変数として持っており、sensor_device
という実体名で宣言しています。
この構造体(クラス)は以下のメンバー関数を持っています。
処理を一つ一つ見ていきます。
メンバー変数に I2C アドレスと、センサー値取得待ち時間(上記は10ms)を設定します。
これらの値は原則として固定値ですので変数設定する必要はありません。変数として扱う有効な例として、設定によってより高精度なセンサー稼働をさせるような場合に必要な変換時間を管理する、設定によって I2C の副アドレスを選択するような場合などが考えられます。
センサーを動作させるために指令を書き込みます。
if 文中で Wire.get_writer(I2C_ADDR)
は、アドレスI2C_ADDR
に対応するI2Cデバイスを開き、その読み書き用のオブジェクトを生成します。読み書きオブジェクト wrt
は if 節の (bool)
評価により、デバイスのオープンに失敗したときなどには false を返します。trueが戻った時は無事にオープンできたことになり if節内の処理を行います。
ここでは wrt << 0x60;
のように、ストリーム演算子 <<
を用いて1バイト I2C デバイスに書き込んでいます。このストリーム演算子は原則 uint8_t
型の1バイトを書き込むためのものです。
CONV_TIME
の値を返すための関数です。
センサーデータを読み出します。
SHTC3では、begin()
によりセンサー読み出しを開始してから、数ms待ち時間をおいてセンサー値を読み出します。 センサー値の並びは以下のようになっています。
begin()
ではデータを書き出していましたが、ここではデータを読み込みます。データを読み込むには同様に Wire.get_reader()
により、ヘルパーオブジェクト rdr
を生成します。エラーがなければ rdr
は if 節中で true を返します。ここで get_reader(I2C_ADDR, 6)
の2番目に与えたパラメータ 6
は、読み出しバイト数です。このバイト数を読みだした時点で I2C バスの読出しを終了する手続きを行います。(デバイスによっては、こういった手続きを省略しても動作するものもありますが、通常は適切な値を与えるようにしてください)
最終的に i16Temp
・ i16Humd
に温度[℃]の100倍値、および湿度[%]の100倍値を計算して格納しています。計算式について I2C デバイスのデータシートを参照してください。
setup()
関数は TWELITE 無線マイコンが始動したときに1度だけ呼び出される関数です。この関数では、各種初期化を行います。
ステートマシン(状態遷移マシン)は、都度呼び出される loop()
文中の記述を簡素化するために用います。もちろん、アプリケーションの記述を行うのに、この例で使用する SM_SMPLE
を使用しなくても構いません。
ビヘイビアは、プログラム中で利用する機能のまとまりです。各種イベントが発生したときの振る舞いが記述されています。
ここでは、インタラクティブモード画面 <STG_STD>
と、シンプル中継ネットワーク <NWK_SMPLE>
の2種類のビヘイビアを利用します。
SETTINGS::appname
: アプリケーション名(文字列)を指定します。インタラクティブモード画面上で先頭行に表示されます。画面上の文字数には余裕がないので最小限の文字列にします。
SETTINGS::appid_default
: アプリケーションIDの規定値です。独自のアプリケーションで独自の規定アプリケーションIDを持たせたい場合に実行します。
SETTINGS::ch_default
: チャネルの規定値です。独自のアプリケーションで既定のチャネルを持たせたい場合に実行します。
続いて set.hide_items()
では、既定のインタラクティブモードの画面上で不要な設定項目を削除しています。すべて表示しても構わない場合は、この呼び出しを行う必要はありません。
DIO12 のピンが LOW (GNDレベル) で、電源投入またはリセットされた場合は、インタラクティブモードで起動する記述です。digitalRead()
でピンの状態を読み取り、SETTINGS::open_at_start()
を反映させます。
インタラクティブモード中に通常のアプリケーション処理が行われてしまうと不都合であるため、ステートマシンの状態を STATE::INTERACTIVE
に設定します。この状態では、一切の入力等の処理を行わず同じ状態にとどまります。
最後にインタラクティブモードのデータを読み込みます。set.reload()
を呼び出すことで、EEPROM に書き込まれたデータを読み込みます。設定が行われず EEPROM に何も情報がない場合は、規定値として読みだせます。
ここではオプションビット set.u32opt1()
と、8ビットの論理ID set.u8devid()
を読み出します。LID が 0
の場合は、通常親機として運用されるため、この値が記録されている場合は 0xFE
(IDを割り振らない子機) としています。
最後に the_twelite
と nwk
に設定情報(の一部)を反映させています。アプリケーションIDやチャネルといった無線通信に必須の情報が反映されます。上記ではこれらの設定に対する明示的な読み出しコードは存在しませんが set.reload()
で、設定がなければ規定値に、あれば設定値が読み出されます。
I2C センサーの初期化設定を行っています。
the_twelite.begin()
は MWX ライブラリの初期化完了を宣言する手続きです。この処理を行わないと、MWX ライブラリは適切に動作しません。
起動時のメッセージなどもここで表示します。
上記の do while 文の制御構造を記述しておきます。ステート(状態)は step.state()
で判定します。while の条件式は step.b_more_loop()
となっています。これは、ある状態からさらに別の状態に遷移したときに、loop()
を抜けずに連続的に処理したい場合があるためです。つまり、別の状態に遷移して switch 節を抜けた場合、次の状態の case 節が呼び出されます。この動作には注意してください。
インタラクティブモード中にメインループが動作するのは都合が悪いため、この状態に固定します。
センサーのデータ取得を開始します。set_timeout()
で、センサー取得の時間待ちを行います。
センサーの値を sensor_device.read()
により取得して sensor
構造体に値を格納します。
無線センサーとしてセンサー側の TWELITE のシリアルポートに結果を出力する必要はありませんが、動作確認を容易にするためシリアルポートに取得値を表示しています。ここで Serial.flush()
を行い出力待ちを行っていますが、これは TWELITE がスリープするまでにシリアルポート出力が終わらないことを想定した記述です。この処理も、電池消耗の原因になるためSerial.flush()
を行わないようにするか、シリアルポートへの出力をしないようにします。
通信手続きを記述します。この状態で待ち処理などを行うことはなく、処理を実行したら速やかに次の状態に遷移します。あらかじめ step.next(STATE::GO_SLEEP)
と記述しているのは、エラーなどの検出は複数個所で行われるため、すべての場所で同じ記述を行うことを避けるためです。
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
では、送信パケットのオブジェクトを生成し、オブジェクトの生成に成功したら if 節を実行するという処理です。
最初に送信の設定を行います。宛先 tx_addr(0x00)
を親機 0x00
に設定し、再送回数 tx_retry(0x1)
を1回にし、パケットの遅延の設定 tx_packet_delay(0, 0, 2)
を初回送信までの遅延は 0、再送間隔を 2ms と設定しています。
パケットのペイロード部に識別子のFOURCHARS
とセンサーデータを格納します。得られた値のうち温度値は int16_t
ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_t
にキャストしています。
pkt.transmit()
を呼び送信要求を行います。この時点ではまだ送信処理は始らず、送信要求を内部のキューに設定しただけです。MWXライブラリ内で適切なタイミングで送信要求が処理されます。
送信要求に成功した場合 ret
は true になります。完了を判定するためのフラグの初期化 step.clear_flag()
、送信失敗時など予期しないエラーを処理するためのタイムアウト step.set_timeout(100)
を設定し、次の状態を STATE::TX_WAIT_COMP
にします(STATE::GO_SLEEP
の指定は上書きされます)。
ここでは送信の完了待ちを行います。タイムアウトの判定(エラー時)または送信完了イベントの判定を行います。
sleepNow()
の処理を行います。この関数を呼び出すことで TWELITE 無線マイコンはスリープ状態になります。
送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()
を呼び出しstep
のフラグをセットします。
スリープに入る手続きをまとめています。
スリープ前に.on_sleep(false)
によりステートマシンの状態を初期化します。パラメータのfalseはスリープ復帰後STATE::INIT(=0)
から始めます。
ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。
周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。
8,9行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。
12行目、スリープに入るには the_twelite.sleep()
を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。
パラメータとしてスリープ時間をmsで指定しています。
TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000
を超えないように指定してください。
スリープから復帰し起床すると wakeup()
が呼び出されます。そのあとloop()
が都度呼び出されます。wakeup()
の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。
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 に設定する。
またインタラクティブモードを追加するために <STG_STD>
をインクルードしています。
サンプルアクト共通宣言
長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)
アプリケーション中のデータ保持するための変数
大まかな流れは、各部の初期設定、各部の開始となっています。
システムの振る舞いを決めるためのビヘイビアオブジェクトを登録します。インタラクティブモードの設定管理で合ったり、ボードサポート、また無線パケットのネットワーク記述です。
setup()
内で登録しないと動作しません。
インタラクティブモードの初期化を行います。まずset
オブジェクトを取得しています。続いて以下の処理を行っています。
アプリケーション名を"BRD_APPTWELITE"
に設定(メニューで利用される)
デフォルトのアプリケーションIDとチャネル値を書き換える
不要な項目を削除する
set.reload()
により保存された設定値を読み出す
OPT_BITS
とLID
の値を変数にコピーする
読み出したインタラクティブモードの設定の反映については後述します。
以下は画面例です。+ + + と + を三回 0.2秒から 1 秒の間をあけて入力するとインタラクティブモード画面を出すことが出来ます。
※ Option Bits は、メニューに表示はしていますが、このサンプリでは使用していません。
このオブジェクトはTWENETの中核としてふるまいます。
ボードの登録(このアクトでは<BRD_APPTWELITE>
を登録しています)。以下のように use の後に <>
で登録したいボードの名前を指定します。
ユニバーサル参照(auto&&
)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。以下ではボードオブジェクトを用い、M1ピンの状態を確認しています。M1ピンがLOであれば、LID=0、つまり親機アドレスと設定します。
the_twelite を動作させるには初期設定が必要です。アプリケーションIDや無線チャネルの設定は必須といえます。
the_twelite
に設定を反映するには <<
を用います。
TWENET::rx_when_idle()
受信回路をオープンにする指定です。
<<, >>
演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。
次にネットワークを登録します。
1行目は、ボードの登録と同じ書き方で <>
には <NWK_SIMPLE>
を指定します。
2,3行目は、<NWK_SIMPLE>
の設定です。先にインタラクティブモードの設定値を反映させます。反映される項目はLIDと再送回数です。このアプリケーションではM1ピンの状態によってLID=0にする場合があるため、3行目で再度LIDを設定しています。
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で利用できるピンの一覧が定義されています。
初回を除き ADC の開始は、割り込みハンドラ内で行います。
DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。
初期化は Buttons.setup()
で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。
開始は Buttons.begin()
で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::
に定義されるPIN_DI1-4
(DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4
を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。
ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。
App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。
上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrue
にするとソフトウェア割り込みが有効になります。
Timer0.begin()
を呼び出したあと、タイマーが稼働します。
setup()
関数の末尾で the_twelite.begin()
を実行しています。
Serial オブジェクトは、初期化や開始手続きなく利用できます。
このサンプルでは始動時のメッセージとしていくつかのシステム設定値を表示しています。Serial
オブジェクトには const char* 型の文字列や、_int_型(他の整数型はNG)、printf_とほぼ同じ振る舞いをするformat()
、改行文字を出力するcrlf
などを<<_演算子に与えます。
サンプル中では名前空間mwx::
を省略している場合もあります。上記ではmwx::crlf
と記載していますがcrlf
と記載しても構いません。mwx::名前空間は、一部を省略可能とするように設計しています。
ループ関数は 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になります。
初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。
次にビットマップから値を取り出してu8DI_BM
に格納しています。ここではMWXライブラリで用意したcollect_bits()
関数を用いています。
collect_bits()
は、上述のpack_bits()
と同様のビット位置の整数値を引数とします。可変数引数の関数で、必要な数だけパラメータを並べます。上記の処理では bit0 は DI1、bit1 は DI2、bit2 は DI3、bit3 は DI4の値としてu8DI_BMに格納しています。
App_Twelite では、DI1から4に変化があった場合に無線送信しますので、Buttons.available()
を起点に送信処理を行います。transmit()
処理の内容は後述します。
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の値が格納されていない場合は何もしません。
無線パケットの送信要求を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置きに再送を行うという意味です。
ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。
送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。
データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。
IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。
上記の例は、解説のためある程度の妥協をしています。節約を考える場合 00: の識別子は1バイトの簡単なものにすべきですし、Vccの電圧値は8ビットに丸めてもかまわないでしょう。また各AI1..AI4の値は10bitで、合計40bit=5バイトに対して6バイト使用しています。
上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload()
により simplbuf<uint8_t>
型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。
上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()
を用意しています。
pack_bytes
の最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()
です。
そのあとのパラメータは可変数引数でpack_bytes
で対応する型の値を必要な数だけ指定します。pack_bytes
は内部で.push_back()
メソッドを呼び出して末尾に指定した値を追記していきます。
3行目のmake_pair()
は標準ライブラリの関数でstd::pair
を生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()
の1番目のパラメータに文字列型(char*
やuint8_t*
型、uint8_t[]
など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。
4行目はuint8_t
型でDI1..DI4のビットマップを書き込みます。
7-9行目ではau16AI
配列の値を順に書き込んでいます。この値はuint16_t
型で2バイトですが、ビッグエンディアンの並びで書き込みます。
7行目のfor文はC++で導入された範囲for文です。サイズのわかっている配列やbegin(), end()
によるイテレータによるアクセスが可能なコンテナクラスなどは、この構文が使用できます。au16AIの型もコンパイル時に判定できるため auto&&
(ユニバーサル参照)で型の指定も省略してます。
通常のfor文に書き換えると以下のようになります。
これでパケットの準備は終わりです。あとは、送信要求を行います。
パケットを送信するにはpkt
オブジェクトのpkt.transmit()
メソッドを用います。戻り値としてMWX_APIRET
型を返していますが、このアクトでは使っていません。
戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。
無線パケットが受信できたときは、受信イベントとしてon_rx_packet()
が呼び出されます。
the_twelite.receiver
による手続きでは一旦受信パケットを内部キュー(2パケットまで格納)に格納してからの処理でしたが、on_rx_packet()
ではTWENETライブラリからのコールバックから直接呼び出され、より取りこぼし等が発生しにくい手続きです。ただしloop()
文中で長時間処理を止めてしまうような記述を行うと、同じように取りこぼしの原因となります。
ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。
まず受信パケットのデータrx
をはパラメータとして渡されます。rx
から無線パケットのアドレス情報やデータペイロードにアクセスします。パラメータhandled
は通常利用しません。
受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。インタラクティブモード画面が表示されているときは出力を抑制します。
<NWK_SIMPLE>
では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。
MWXライブラリにはtransmit()
の時に使ったpack_bytes()
の対になる関数expand_bytes()
が用意されています。
1行目ではデータ格納のためのchar
型の配列を宣言しています。サイズが5バイトなのは末尾にヌル文字を含め、文字出力などでの利便性を挙げるためです。末尾の{}
は初期化の指定で、5バイト目を0にすれば良いのですが、ここでは配列全体をデフォルトの方法で初期化、つまり0
にしています。
2行目でexpand_bytes()
により4バイト文字列を取り出しています。パラメータにコンテナ型を指定しない理由は、この続きを読み出すための読み出し位置を把握する必要があるためです。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*
ポインタ)を指定します。.begin()
メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()
メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。
3番目に読み出す変数を指定しますが、ここでもmake_pair
によって文字列配列とサイズのペアを指定します。
このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()
の戻り値により判定してください。
expand_bytes()
の戻り値は uint8_t*
ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)
を戻します。
読み出した4バイト文字列の識別子が、このアクトで指定した識別子と異なる場合は、このパケットを処理しません。
TWENETではアプリケーションIDと物理的な無線チャネルが合致する場合は、どのアプリケーションもたとえ種別が違ったとしても、受信することが出来ます。他のアプリケーションで作成したパケットを意図しない形で受信しない目的で、このような識別子やデータペイロードの構造などのチェックを行い、偶然の一致が起きないように対処することを推奨します。
シンプルネットワーク<NWK_SIMPLE>
でのパケット構造の要件も満足する必要があるため、シンプルネットワークを使用しない他のアプリケーションが同じ構造のパケットを定義しない限り(非常にまれと思われます)、パケットの混在受信は発生しません。
続いて、データ部分の取得です。DI1..DI4の値とAI1..AI4の値を別の変数に格納します。
先ほどのexpand_bytes()
の戻り値np
を1番目のパラメータにしています。先に読み取った4バイト文字列識別子の次から読み出す指定です。2番目のパラメータは同様です。
3番目以降のパラメータはデータペイロードの並びに一致した型の変数を、送り側のデータ構造と同じ順番で並べています。この処理が終われば、指定した変数にペイロードから読み出した値が格納されます。
確認のためシリアルポートへ出力します。インタラクティブモード画面が表示されているときは出力は抑制します。
数値のフォーマット出力が必要になるのでformat()
を用いています。>>
演算子向けに_printf()
_と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()
には引数の数の制限がありません。)
1行目の "DI:%04b"
は"DI:0010"
のようにDI1..DI4のビットマップを4桁で表示します。3行目の"/%04d"
は"/3280/0010/0512/1023/1023"
のように Vcc/AI1..AI4の値を順に整数で出力します。5行目のmwx::crlf
は改行文字列を出力します。
これで必要なデータの展開が終わったので、あとはボード上のDO1..DO4とPWM1..PWM4の値を変更します。
digitalWrite()
はディジタル出力の値を変更します。1番目のパラメータはピン番号で、2番目はHIGH
(Vccレベル)かLOW
(GNDレベル)を指定します。
Timer?.change_duty()
はPWM出力のデューティ比を変更します。パラメータにデューティ比 0..1024 を指定します。最大値が1023でないことに注意してください(ライブラリ内で実行される割り算のコストが大きいため2のべき乗である1024を最大値としています)。0
にするとGNDレベル、1024
にするとVccレベル相当の出力になります。
MONOSTICK用のボードビヘイビアをインクルードしています。このボードサポートには、LEDの制御、ウォッチドッグ対応が含まれます。
続いてインタラクティブモードの設定と設定値の読み出しを行います。では、標準的な項目が用意されていますが、作成するアクトごとにいくつかのカスタマイズを行えるようになっています。
いくつかの設定値はすることが出来ます。また、DIPスイッチの設定などにより特定の値を書き換えたいような場合などは、反映されたあとに別個に値を書き換えることも出来ます。上記の例ではオブジェクトにアプリケーションID、チャネル、無線出力などを設定し、オブジェクトに対してLIDと再送回数の設定をしてから、再度LIDを0に設定し直しています。
読み込みは関数を用います。この関数の第1・第2パラメータはC++の標準ライブラリの作法に倣い、受信パケットのペイロード部の先頭ポインタ.begin()
と末尾ポインタの次.end()
を与えます。続くパラメータは可変引数として、読み込むデータ変数を与えます。戻り値はエラー時は_nullptr
_、それ以外は次の解釈ポインタとなります。末尾まで解釈した場合は.end()
が戻ります。ここでのパラメータはuint8_t fourchars[4]
です。
"TXSP"
のパケットではuint32_t
型のシステムタイマーカウントと、uint16_t
型のダミーカウンタの値が格納されています。各々変数を宣言して関数を用い読み込みます。上述と違うのは、読み出しの最初のポインタとして第一パラメータがnp
となっている点です。tick_ms
とu16work_ct
をパラメータとして与え、ビッグエンディアン形式のバイト列としてペイロードに格納された値を読み出します。
ユーザが定義した並び順でにより構成します。
1行目は出力用のシリアルパーサをローカルオブジェクトとして宣言しています。内部にバッファを持たず、外部のバッファを流用し、パーサーの出力機能を用いて、バッファ内のバイト列を出力します。
状態を示す列挙体STATE
を用いてを宣言します。
この制御構造はを利用しています。do..while() 構文のループになっています。ループの中は_switch case_節となっていて、.state()
で得られた状態により処理を分岐しています。状態の遷移は.next()
を呼び出しステートマシン内の内部変数を新しい状態値に書き換えます。
得られたpkt
オブジェクトに対して、送信条件(宛先や再送など)を<<演算子を用いて設定します。はパケットの宛先を指定します。は再送回数、は送信遅延の指定です。
パケットのペイロード(データ部分)はpkt.get_payload()
により得られるの配列です。この配列に対して直接値を設定しても構いませんが、ここではを用いた値の設定を行います。
ペイロードの最大長は上記の例では91バイトですが、詳しくはを参照ください。
``はuint32_t
型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()
の戻り型になっており、送信要求の成功と失敗(_bool_型へのキャスト)ならびに送信IDをデータ部(.get_value()
)に格納しています。
全てのアクトで<TWELITE>
をインクルードします。ここでは、シンプルネットワーク をインクルードしておきます。
MWXライブラリでは、を用いたI2Cバスへの読み書きに2種類の異なった記述方法がありますが、こちらはを用いる方法です。
読み出しはストリーム演算子 >>
により行っています。読み出し方法にはほかにもいくつかあります。詳しくは を参照してください。ストリーム演算子を用いる場合は、事前に宣言した uint8_t
, uint16_t
, uint32_t
型の変数に値を入力します。rdr >> u16temp
は、uint16_t
型の変数に対して2バイトI2Cバスから読み出し**ビッグエンディアン形式(1バイト目は上位バイト)**で格納します。
は、ごく短いコードで実装されており、状態への遷移と、タイムアウトの管理、フラグの管理を簡易的に行えます。状態はあらかじめ列挙体で定義しておきます。上記の例では enum class STATE
です。ステートマシンの実体は定義済みの列挙体 STATE
をパラメータとして SM_SMPLE<STATE> step
のように宣言します。
記述するアプリケーションに合わせたインタラクティブモードの設定項目にするため、 に対して初期設定を行います。
loop()
は、step
を用いた制御を行っています。スリープ復帰からセンサー値取得、無線パケット送信、送信完了待ち、スリープといった一連の流れを簡潔に表現するためです。
時間待ちの非常に長いセンサーなどは、ここでいったんスリープを行うといった処理を記述すると電池寿命を延ばすことができますが、構造が複雑になるためこの例では割愛します。必要な場合はを参照してください。
最初に step.is_timeout()
によるタイムアウトチェックを行います。タイムアウトの起点は先ほどの step.set_timeout()
です。タイムアウトしない場合は、if 節は実行されず、そのまま loop()
を抜けます。次のハードウェアイベント(多くの場合はシステムタイマーである1ms ごとに割り込みを発生するの割り込み)が来るまではTWELITE マイコンは低電力でCPUが待機するDOZE(ドーズ)モードになります。
ここで使用する は、低コストで100の除算を行う関数です。TWELITE 無線マイコンには除算回路がありませんので、除算処理は極力行わないことを推奨します。
全てのアクトで<TWELITE>
をインクルードします。ここでは、シンプルネットワーク とボードサポート <BRD_APPTWELITE>
をインクルードしておきます。
set
はインタラクティブモードから読み出した設定の一部(アプリケーションIDや無線チャネルなど)反映させます。反映される項目はの解説を参照してください。
親機
MONOSTICK BLUEまたはREDアクトParent_MONOSTICKを動作させる。
子機
setup()
構造体の初期化を行う。(コンパイラの制約でグローバル宣言した場合コンストラクタが呼び出されないため)コンストラクタの代わりに呼び出す。
begin()
センサー値の取得を開始する。開始後、適切なセンサー値が得られるまで一定時間待つ必要がある。
get_convtime()
センサー値の取得待ち時間を返す。
read(int&, int&)
センサー値を取得する。
0
温度センサー値(上位バイト)
1
温度センサー値(下位バイト)
2
バイト0,1のCRC8値
3
湿度センサー値(上位バイト)
4
湿度センサー値(下位バイト)
5
バイト3,4のCRC8値
※ SHTC3では、センサー取得開始時に与えるパラメータによって、データの並び順も変化しますが上記begin()
で書き込んだ0x609C
コマンドで開始した場合は、温度データが先に来ます。
親機
TWELITE DIP 最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。
子機
TWELITE DIP 最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。
親機
子機
サンプルアクトの子機設定 (例: Slp_Wk_and_Tx
, PAL_AMB
, PAL_MAG
, PAL_MOT???
など)
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番目のタイマーを使用します。
あまり短いスリープ時間を設定してもスリープ復帰後のシステムの再初期化などのエネルギーコストと釣り合いません。目安として最小時間を30-50ms程度とお考え下さい。
Act/behavior Programming Interface
MWXライブラリのAPIは、今後、改善を目的として仕様の変更を行う場合があります。
TWELITE ARIA - トワイライトアリア を用い、センサー値の取得を行います。
このアクトには以下が含まれます。
無線パケットの送信
インタラクティブモードによる設定 - <STG_STD>
ステートマシンによる状態遷移制御 - <SM_SIMPLE>
<ARIA>ボードビヘイビアによるボード操作
TWELITE ARIA - トワイライトアリア を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
親機
子機
TWELITE ARIA<ARIA>
のボードビヘイビアをインクルードします。
最初に変数などの初期化を行います。ここではステートマシンstepの初期化を行っています。
最初にボードサポート <ARIA>
を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。
つづいて、インタラクティブモード関連の初期化と読出しを行います。
ここではsetオブジェクトの取得、アプリ名の反映、デフォルトのアプリケーションIDと通信チャネルの反映、設定メニューで不要項目の削除を行います。
次にSETピンの状態を読み出します。このサンプルはスリープによる間欠動作を行うため、+++入力によるインタラクティブモード遷移は出来ません。替わりに起動時のSETピン=LO状態でインタラクティブモードに遷移します。このときSETTINGS::open_at_start()
を指定していますが、これはsetup()
を終了後速やかにインタラクティブモード画面に遷移する指定です。
最後に.reload()
を実行して設定値をEEPROMから読み出します。設定値を各変数にコピーしています。
このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle()
)は含めません。
続いてLEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
loop()
は、SM_SIMPLEステートマシンstep
を用いた制御を行っています。スリープ復帰からセンサー値取得、無線パケット送信、送信完了待ち、スリープといった一連の流れを簡潔に表現するためです。ループの戦闘ではbrd
オブジェクトを取得しています。
インタラクティブモード中にメインループが動作するのは都合が悪いため、この状態に固定します。
センサーのデータ取得を開始します。
ボード上のセンサーは .sns_SHT4x
という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()
がfalse
)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER)
)を送付します。
センサーがavailableになった時点で、センサー値を取得し、STATE_TXに遷移します。
温湿度センサーは以下のように取得できます。
.get_temp_cent()
: int16_t
: 1℃を100とした温度 (25.6 ℃なら 2560)
.get_temp()
: float
: float値 (25.6 ℃なら 25.6)
.get_humid_dmil()
: int16_t
: 1%を100とした湿度 (56.8%なら 5680)
.get_temp()
: float
: float値 (56.8%なら 56.8)
送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。
パケットのペイロード部に識別子のFOURCHARS
とセンサーデータを格納します。得られた値のうち温度値は int16_t
ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_t
にキャストしています。
送信要求を行います。送信要求が成功したら送信完了街の準備を行います。完了イベントを待つために.clear_flag()
、万が一のときのタイムアウトをset_timeout(100)
を指定します。パラメータの100の単位はミリ秒[ms]です。
ここではタイムアウトの判定、送信完了イベントの判定を行います。
sleepNow()
の処理を行います。
送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()
により完了としています。
スリープに入る手続きをまとめています。
スリープ前に.on_sleep(false)
によりステートマシンの状態を初期化します。パラメータのfalseはスリープ復帰後STATE::INIT(=0)
から始めます。
ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。
周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。
8,9行目では、スリープに入るまえに磁気センサーのDIOピンの割り込み設定をします。pinMode()
を用います。2番めのパラメータはPIN_MODE::WAKE_FALLING
を指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。
11,12行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。
12行目、スリープに入るには the_twelite.sleep()
を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。
パラメータとしてスリープ時間をmsで指定しています。
TWELITE ARIA では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000
を超えないように指定してください。
スリープから復帰し起床すると wakeup()
が呼び出されます。そのあとloop()
が都度呼び出されます。wakeup()
の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。
PAL_MAG
開閉センサーパル OPEN-CLOSE SENSE PAL を用い、センサー値の取得を行います。
このアクトには以下が含まれます。
無線パケットの送受信
インタラクティブモードによる設定 - <STG_STD>
ステートマシンによる状態遷移制御 - <SM_SIMPLE>
開閉センサーパル 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値です。
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
このアクトには以下が含まれます。
無線パケットの送受信
インタラクティブモードによる設定 - <STG_STD>
ステートマシンによる状態遷移制御 - <SM_SIMPLE>
<PAL_AMB>ボードビヘイビアによるボード操作
環境センサーパル AMPIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
親機
子機
環境センサーパル <PAL_AMB>
のボードビヘイビアをインクルードします。
最初に変数などの初期化を行います。ここではステートマシンstepの初期化を行っています。
最初にボードサポート <PAL_AMB>
を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。
つづいて、インタラクティブモード関連の初期化と読出しを行います。
ここではsetオブジェクトの取得、アプリ名の反映、デフォルトのアプリケーションIDの反映、設定メニューで不要項目の削除を行います。
次にSETピンの状態を読み出します。このサンプルはスリープによる間欠動作を行うため、+++入力によるインタラクティブモード遷移は出来ません。替わりに起動時のSETピン=LO状態でインタラクティブモードに遷移します。このときSETTINGS::open_at_start()
を指定していますが、これはsetup()
を終了後速やかにインタラクティブモード画面に遷移する指定です。
最後に.reload()
を実行して設定値をEEPROMから読み出します。設定値を各変数にコピーしています。
続いてLEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。
このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle()
)は含めません。
ボード上のセンサーはI2Cバスを用いますので、バスを利用開始しておきます。
ボード上のセンサーの取得を開始します。startSensorCapture()
の解説を参照ください。
loop()
は、SM_SIMPLEステートマシンstep
を用いた制御を行っています。スリープ復帰からセンサー値取得、無線パケット送信、送信完了待ち、スリープといった一連の流れを簡潔に表現するためです。ループの戦闘ではbrd
オブジェクトを取得しています。
インタラクティブモード中にメインループが動作するのは都合が悪いため、この状態に固定します。
センサーのデータ取得を開始します。
ボード上のセンサーは .sns_LTR308ALS
または .sns_SHTC3
という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()
がfalse
)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER)
)を送付します。
上記2つのセンサーがavailableになった時点で、センサー値を取得し、STATE_TXに遷移します。
照度センサーは.get_luminance() : uint32_t
で得られます。
温湿度センサーは以下のように取得できます。
.get_temp_cent()
: int16_t
: 1℃を100とした温度 (25.6 ℃なら 2560)
.get_temp()
: float
: float値 (25.6 ℃なら 25.6)
.get_humid_dmil()
: int16_t
: 1%を100とした湿度 (56.8%なら 5680)
.get_temp()
: float
: float値 (56.8%なら 56.8)
送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。
パケットのペイロード部に識別子のFOURCHARS
とセンサーデータを格納します。得られた値のうち温度値は int16_t
ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_t
にキャストしています。
送信要求を行います。送信要求が成功したら送信完了街の準備を行います。完了イベントを待つために.clear_flag()
、万が一のときのタイムアウトをset_timeout(100)
を指定します。パラメータの100の単位はミリ秒[ms]です。
ここではタイムアウトの判定、送信完了イベントの判定を行います。
sleepNow()
の処理を行います。
送信完了時に呼び出されるシステムイベントです。ここでは.set_flag()
により完了としています。
スリープに入る手続きをまとめています。
スリープ前に.on_sleep(false)
によりステートマシンの状態を初期化します。パラメータのfalseはスリープ復帰後STATE::INIT(=0)
から始めます。
ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。
周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。
8,9行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。
12行目、スリープに入るには the_twelite.sleep()
を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。
パラメータとしてスリープ時間をmsで指定しています。
TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000
を超えないように指定してください。
スリープから復帰し起床すると wakeup()
が呼び出されます。そのあとloop()
が都度呼び出されます。wakeup()
の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。
アクト PAL_AMB-UseNap は、センサーのデータ取得待ちをスリープで行い、より低消費エネルギーで動作できます。
PulseCounter
パルスカウンターを用いたアクト例です。
パルスカウンターは、マイコンを介在せず信号の立ち上がりまたは立ち下りの回数を計数するものです。不定期のパルスを計数し一定回数までカウントが進んだ時点で無線パケットで回数を送信するといった使用方法が考えられます。
子機側のDIO8に接続したパルスを計数し、一定時間経過後または一定数のカウントを検出した時点で無線送信する。
子機側はスリープしながら動作する。\
親機
子機
パルスカウンターの初期化を行います。
パルスカウンターの動作を開始し、初回スリープを実行します。PulseCounter.begin()
の最初のパラメータは、起床割り込みを発生させるためのカウント数100
で、2番目は立ち下がり検出PIN_INT_MODE::FALLING
を指定しています。
起床時にPulseCounter.available()
を確認しています。availableつまりtrue
になっていると、指定したカウント数以上のカウントになっていることを示します。ここではfalse
の場合再スリープしています。
カウント数が指定以上の場合はloop()
で送信処理と送信完了待ちを行います。
パルスカウント値の読み出しを行います。読み出した後カウンタはリセットされます。
PAL_MOT-fifo
動作センサーパル MOTION SENSE PAL を用い、センサー値の取得を行います。
のアクトには以下が含まれます。
無線パケットの送受信
インタラクティブモードによる設定 - <STG_STD>
ステートマシンによる状態遷移制御 - <SM_SIMPLE>
動作センサーパル 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値です。
WirelessUART
WirelessUARTはシリアル通信を行います。
2台のUART接続のTWELITE同士をアスキー書式で通信する。
PCにシリアル接続されている以下のデバイスを2台。
でUART接続されているなど
親機宛のパケットであればでも受信できます。
インタラクティブモードを初期化しています。このサンプルでは互いに論理デバイスID(LID)が異なるデバイスを2台以上用意します。
シリアルからのデータ入力があった時点で、シリアルパーサーに1バイト入力します。アスキー形式が最後まで受け付けられた時点でSerialParser.parse()
はtrue
を戻します。
SerialParser
は内部バッファに対してsmplbuf
でアクセスできます。上の例ではバッファの1バイト目を送信先のアドレスとして取り出し、2バイト目から末尾までをtransmit()
関数に渡します。
パケットを受信したときには、送信元を先頭バイトにし続くペイロードを格納したバッファsmplbuf_u8<128> buf
を生成し、出力用のシリアルパーサーserparser_attach pout
からシリアルに出力しています。
テストデータは必ずペースト機能を用いてターミナルに入力してください。入力にはタイムアウトがあるためです。
参考: TWE ProgrammerやTeraTermでのペーストはAlt+Vを用います。
入力の末尾にCR LFが必要です。
最初はCR LFが省略できるXで終わる系列を試してください。終端文字列が入力されない場合は、その系列は無視されます。
任意の子機宛に00112233
を送付します。
子機3番に対してAABBCC00112233
を送付します。
任意の親機または子機宛(0xFF
)、親機宛(0x00
)に送付します。
Unit_???
Unit_で始まるアクト(Act)は、ごく単機能の記述や動作を確認するためのものです。
Rcv_Unvsl (ユニバーサル レシーバー)
MWXライブラリの twe_twelite.network
に NWK_LAYERED
、 twe_twelite.network2
に NWK_SIMPLE
を動作させることで、レイヤーツリーネット (TWELITE PAL, ARIAなど)のパケットを含む、さまざまな種類のパケットを受信、解釈できます。
ただし無線パケットは、同一チャネルであることと、同一アプリケーションIDであることが条件です。
setup()
, loop()
、受信パケットのコールバック関数 on_rx_packet()
を記述しています。
これらのオブジェクトは pkt_handler.cpp
で宣言され setup()
中でpnew()
により初期化されます。主にパケットのペイロード(データ)を解釈します。
2つのネットワークオブジェクトを生成しています。必ず the_twelite.network
にNWK_LAYERED
にします。
このサンプルではloop()
の処理で重要なのは約1秒おきに行っている .refresh()
処理です。g_pkt_apptwelite().refresh()
のみ重複チェッカのタイムアウト処理を行っています。それ以外のオブジェクトは何もしません。
このサンプルコードで最も重要な部分です。auto type = rx.get_network_type();
によりパケットの種別を判定しています。
mwx::NETWORK::LAYERED
: NWK_LAYERED
レイヤーツリーネットのケット
mwx::NETWORK::SIMPLE
: NWK_SIMPLE
のパケット
mwx::NETWORK::NONE
: ネットワークなし(App_Tweliteなど)
その他 : エラーまたは未対応のパケット
mwx::NETWORK::NONE
の場合は、再送などで複数送信されうる同一パケットの重複チェッカ等の処理は MWX ライブラリ内部で行われません。これらの対応を記述する必要があります。本サンプルでは dup_checker.hpp
, dup_checker.cpp
を用意しています。
パケットの解釈はtsRxDataApp*
をラップした packet_rx&
オブジェクトを参照します。packet_rx
クラス自体は特別な機能はなく、get_psRxDataApp()
を用いてtsRxDataApp*
から得られる一部の情報へのアクセス手段を定義しているのみです。
パケット解釈部分のインタフェースを統一する目的で定義しています。
analyze()
: パケットのペイロードを解釈する。
display()
: パケット情報を表示する。
refresh()
: 1秒おきの処理を記述します。
self()
: 派生クラスD
にキャストします。
さらにパケット解釈クラス(上記例 pkt_handler_apptwelite
)には、メンバーオブジェクトの pkt
が含まれます。実際のパケットの解釈部分は、pkt_???.cpp
にある各々の analyze()
の実装を参照してください。
パケット種別ごとのパケット解釈部 analyze()
と、データ構造 data
が定義されています。メンバーdata
は、構造体ですがPktDataCommon
の共通構造体を継承しています。この共通部を用いてパケットのデータのシリアル出力のコードを簡潔に記述しています。
PAL関連のパケットに対応します。PALのパケット構造は複雑なデータ構造を持っています。ここでは EASTL のコンテナを用いた実装を行っています。
_vect_pal_sensors
: _pal_sensor
オブジェクトのプール。このオブジェクトは instusive map で使用するための専用クラスです。
_map_pal_sensors
: センサーデータを効率よく検索するための intrusive map 構造。
1パケット中の複数データに対して各々が追加されるたびに_vect_pal_sensors
にエントリを確保して値を格納します。パケット中のすべてのデータを解釈した時点でセンサータイプをキーとした_map_pal_sensors
を構築します。
重複チェッカーを実装します。チェッカーの動作はテンプレート引数によってカスタマイズできます。
MODE
: MODE_REJECT_SAME_SEQ
を指定すると、同じシーケンス番号のパケットを除外します。パケット順が並び変わるような場合に使用します。MODE_REJECT_OLDER_SEQ
はより新しい番号を採用します。
TIMEOUT_ms
: 重複データベースの初期化を行う間隔です。1000
と指定すると1秒経過したデータは抹消されます。直前では除外されていたパケットも、重複データベースの初期化されると再び採用されることになります。
N_ENTRIES
: データ構造に最大確保される要素数です。
N_BUCKET_HASH
: ハッシュ値の最大数です。素数を指定します。受信される無線ノードの種類をもとに決めます。
_mmap_entries
: intrusive ハッシュ マルチ マップ構造です。検索キーは無線ノードのシリアル番号です。
_vect_pool
: マップ構造で用いられる要素を固定数(N_ENTRIES
)を確保します。
_ring_vecant_idx
: _mmap_entries
に利用されていない_vect_pool
の要素を配列インデックス番号で管理します。リングバッファの構造で、要素を追加するときはリングバッファから値を一つ取り出し、削除するときはリングバッファに値を返します。
マルチマップ構造からデータを検索するには .equal_range()
を呼び出します。得られた r
はイテレータで、同一のシリアル番号の要素を列挙します。
各要素(_dup_checker_entry
)にはタイムスタンプやシーケンス番号が記録されています。この値に従い重複を確認します。
PAL_MOT-single
このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。
のアクトには以下が含まれます。
無線パケットの送受信
インタラクティブモードによる設定 -
ステートマシンによる状態遷移制御 -
またはボードビヘイビアによるボード操作
起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。
加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。
MOT PALまたはTWELITE CUEに対応するため、インクルード部分はマクロになっています。USE_PAL_MOT
または、USE_CUE
のいずれかを定義します。
センサーデータを格納するためのデータ構造です。
ボード、設定、ネットワークの各ビヘイビアオブジェクトの登録を行います。
インタラクティブモードの初期化を行います。
まず、設定項目の調整を行います。ここでは、メニュー項目で表示されるタイトル名SETTINGS::appname
、アプリケーションIDのデフォルト値の設定SETTINGS::appid_default
、チャネルのデフォルトSETTINGS::ch_default
、論理デバイスIDのデフォルトSETTINGS::lid_default
、非表示項目の設定.hide_items()
を行います。
このサンプルでは起動時にSETピンがLOである場合にインタラクティブモードに遷移します。digitalRead(brd.PIN_SET)
によりピンがLOであることを確認できた場合は、SETTINGS::open_at_start()
を指定します。この指定によりsetup()
を抜けた後に速やかにインタラクティブモード画面が表示されます。画面が表示されてもbegin()
やloop()
が実行されます。このサンプルでは状態STATE::INTERACTIVE
としてloop()
中ではスリープなどの動作はせず何もしないようにします。
続いて設定値を読み出します。設定値を読むには必ず.reload()
を実行します。このサンプルではオプションビット設定.u32opt1()
を読み出します。
the_twelite
は、システムの基本的な振る舞いを管理するクラスオブジェクトです。このオブジェクトは、setup()
内でアプリケーションIDやチャネルなど様々な初期化を行います。
インタラクティブモード設定で反映した項目を別の設定に変更したい場合は、続いて上書きしたい設定を行います。
ネットワークビヘイビアオブジェクトに対しても設定を行います。インタラクティブモードの論理デバイスID(LID)と再送設定が反映されます。
LEDのブリンク設定などを行います。
setup()
を終了した後に呼ばれます。ここでは初回スリープを実行しています。ただしインタラクティブモードの画面が表示される場合はスリープしません。
起床後は状態変数eState
を初期状態INITにセットしています。この後loop()
が実行されます。
loop()
の基本構造は<SM_STATE>
ステートマシンstate
を用い_switch ... case_節での制御です。初期状態はSTATE::INIT
またはSTATE::INTERACTIVE
です。
インタラクティブモード画面が表示されているときの状態です。何もしません。この画面ではSerialの入出力はインタラクティブモードが利用します。
初期状態のINITです。
状態INITでは、初期化(結果格納用のキューのクリア)や結果格納用のデータ構造の初期化を行います。STATE::START_CAPTUREに遷移します。この遷移設定後、もう一度_while_ループが実行されます。
状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。
例外処理のためのタイムアウトの設定と、次の状態STATE::WAIT_CAPTURE
に遷移します。
状態WAIT_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されるとsns_MC3630.available()
がtrue
になります。sns_MC3630.end()
を呼び出し処理を終了します。
サンプル数とサンプルのシーケンス番号を取得します。
すべてのサンプルデータに対して読み出し、平均値をとる処理をします。
ここでは取得されたサンプルに対して、各軸に対応するイテレータを用い最大・最小を得ています。
C++ Standard Template Library のアルゴリズムを使用する例としてstd::mimmax_element
紹介していますが、コメント内のようにforループ内で最大、最小を求めても構いません。
.sns_MC3630.get_que().clear()
を呼び出し、キューにあるデータをクリアします。これを呼び出さないと続くサンプル取得ができません。その後STATE::REQUEST_TX
状態に遷移します。
.is_timeout()
はタイムアウトをチェックします。タイムアウト時は異常としてSTATE::EXIT_FATAL
に遷移します。
状態REQUEST_TX
ではローカル定義関数TxReq()
を呼び出し、得られたセンサーデータの処理と送信パケットの生成・送信を行います。送信要求は送信キューの状態などで失敗することがあります。送信要求が成功した場合、TxReq()はtrueとして戻りますが、まだ送信は行われません。送信完了はon_tx_comp()
コールバックが呼び出されます。
また.clear_flag()
により送信完了を知らせるためのフラグをクリアしておきます。同時にタイムアウトも設定します。
状態STATE::WAIT_TX
では、無線パケットの送信完了を待ちます。フラグはon_tx_comp()
コールバック関数でセットされ、セット後に.is_flag_ready()
が_true_になります。
一連の動作が完了したときは状態STATE::EXIT_NORMAL
に遷移しローカル定義の関数sleepNow()
を呼び出しスリープを実行します。またエラーを検出した場合は状態STATE::EXIT_FATAL
に遷移し、システムリセットを行います。
最期にパケットの生成と送信を要求を行います。パケットには続き番号、サンプル数、XYZの平均値、XYZの最小サンプル値、XYZの最大サンプル値を含めます。
スリープの手続きです。
シリアルポートはスリープ前にSerial.flush()
を呼び出してすべて出力しておきます。
<SM_SIMPLE>
ステートマシンはon_sleep()
を行う必要があります。
アクトを動作させる。
アクトを動作させる。
+
アクトを動作させる。
+
アクトを動作させる。
1. 2. +
アクトを動作させる。
+
を初期化します。
USE_PAL_MOT
が定義されている場合は動作センサーパルのボードビヘイビアをインクルードしています。
loop()
中の順次処理を行うために状態を定義し、またstep
を宣言します。
ここではを反映しています。
Unit_ADC
ADCを動作させるサンプルです。100msごとにADCを連続実行しつつ約1秒おきに読み出し表示します。[s]
キーでスリープします。
Unit_I2Cprobe
I2Cバスをスキャンして、応答のあるデバイス番号を表示します(この手順で応答しないデバイスもあります)。
Unit_delayMicoroseconds
delayMicroseconds()
の動作を確認します。16MhzのTickTimerのカウントとの比較をします。
Unit_brd_CUE
TWELITE CUEの加速度センサー,磁気センサー,LEDの動作確認を行います。ターミナルから[a]
,[s]
,[l]
キーを入力します。
Unit_brd_ARIA
TWELITE ARIAの温湿度センサー、磁気センサー、LEDの動作確認を行います。ターミナルから[t]
,[s]
,[l]
キーを入力します。
Unit_brd_PAL_NOTICE
通知パル(NOTICE PAL)のLED点灯を試します。起動時に全灯フラッシュ、その後はシリアル入力で操作します。
- r
,g
,b
,w
: 各色の点灯モードをトグルする
- R
,G
,B
,W
: 各色の明るさを変更する(消灯・全灯時は無効)
- c
: 周期を変化させる(点滅時)
- C
: 点滅時のデューティを変化させる(点滅時)
Unit_div100
10,100,1000の割り算と商を求めるdiv10()
,div100()
,div1000()
の動作確認を行います。-99999~99999まで計算を行い通常の/
,%
による除算との経過時間の比較をします。
Unit_div_format
div10()
,div100()
,div1000()
の結果を文字列出力します。
Unit_UART1
Unit_Pkt_Parser
パケット情報のパーサーpktparserの使用サンプルです。App_Wingsの出力を解釈することが出来ます。 ※ TWELITE無線モジュール同士をシリアル接続して、一方をApp_Wingsとして他方でその出力を解釈したいような場合です。他方をTWELITE無線モジュール以外に接続したい場合は「他のプラットフォーム」を参照ください。
Uint_EEPROM
EEPROMの読み書きテストコードです。
Unit_Cue_MagBuz
TWELITE CUEの磁石センサーとSETピン(圧電ブザーを接続)を用いた、磁石を離すとブザーが鳴るプログラムです。
Unit_doint-bhv
IO割り込みを処理するビヘイビア記述例です。
Unit_EASTL
EASTL ライブラリを用いた断片コード集です。
クラスオブジェクト
クラスオブジェクトは、MWXライブラリであらかじめ定義されたオブジェクトで、TWENETを取り扱うthe_twelite
、ペリフェラルの利用のためのオブジェクトが定義されています。
各オブジェクトは.setup()
, .begin()
メソッドの呼び出しを行って初期化する必要があります。(UART0を利用するSerial
オブジェクトのみ初期化は必要ありません)
ADC (mwx::periph_analogue.hpp)
Analogueは、ADCの実行と値の取得を行います。一度に複数のチャネルを連続取得でき、またこれをタイマーなどの周期に合わせて逐次実行可能です。
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
AI4
uint8_t PIN_ANALOGUE::VCC = 4
Vcc 電源電圧
標準アプリ(App_Twelite)では、半導体データシート中のピン名ADC2/ADC3が、TWELITE DIPの並びにあわせてAI3/AI2 となっています。ご注意ください。
*1 ディジタル、アナログ共用のADC2/ADC3ピンは利用手続きと利用制限があります。
ADC開始前に利用するピンをプルアップ無しとします。これを実行しないと常にプルアップ電圧をADCで観察することになります。
通常の回路構成では、スリープ時には電流リークが発生します。 ソフトウェアの記述のみで回避することは出来ません。
スリープ時の電流リーク回避には、アナログ回路部分のGNDをFETスイッチなどで切り離し、スリープ中はフローティング状態にします。またスリープ前には入力かつプルアップ状態にピンを設定します。
ADCの初期化を行います。setup()では、半導体内部のレギュレータの始動、周期実行するためのタイマーデバイスの指定、指定チャネルすべてのADCが終了したときに呼び出されるコールバック関数の指定します。
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キューなどに別途格納したい場合に利用する。
1番目のパラメータにはADCを行いたいポートを指定します。ポートの指定はピンの定義で述べたポート番号に対応するビットをセットしたビットマップになります。例えば PIN_ANALOGUE::A2
とPIN_ANALOGUE::VCC
の2つのピンの値を得たい場合は (1 <<PIN_ANALOGUE::A1 | 1<<PIN_ANALOGUE::VCC )
を指定します。pack_bits
を用い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)を戻します。
Vccはread()
で読み出すことを推奨します。read_raw()
の値からmVに変換するには、特別な変換式を適用する必要があるためです。
ADC完了(available)後、次のADC処理が実行するタイミング付近まで遅れて値を読み出すと、次のADC値が戻される場合があります。ADCの処理は割り込みハンドラで実施されているためloop()
の処理中であっても値が更新されるためです。
ADCの割り込みハンドラはsetup()
の呼び出し時にperiph_analogue::ADC_handler()
に設定されます。
半導体のペリフェラルライブラリで別途ハンドラを指定すると正常に動作しなくなります。
ADCがbegin()
により周期実行状態であれば、スリープ復帰後もADC処理を再開します。
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を指定すると、その指定は無効になります。
インタラクティブモードの設定値を反映します。
反映される項目は以下です。
app_id
channel
tx_power
MAC ack 使用時の再送回数
MWXライブラリコード中には他にも設定項目がありますが、現時点ではライブラリの機能に無関係な設定であったり、設定を行うと矛盾を起こす可能性があるものです。
事前に設定(<<
演算子参照)や、ビヘイビアの登録を済ませた後に実行します。通常はsetup()
関数内の一番最後に記述します。
the_twelite
設定完了
ビヘイビアの初期化
TWENETの初期化は setup()
関数が終了した後にも実行されます。多くの処理はsetup()
が終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。
チャネル設定を変更します。失敗時にはチャネルは変更されずfalse
を戻します。
現在設定中のチャネル番号(11..26)を取得する。MAC層のAPIより取得します。
モジュールのシリアル番号を取得します。
モジュールをスリープさせる。
u32Periodms
スリープ時間[ms]
bPeriodic
前回の起床時間をもとに次の起床時間を再計算する。 ※次の起床タイミングが迫っているなどの理由で、現在のタイミングからになる場合もあります。
bRamoff
true
に設定すると、RAMを保持しないスリープになる(起床後はwakeup()
ではなくsetup()
から再初期化する必要がある)
u8Device
スリープに用いるウェイクアップタイマーの指定。TWENET::SLEEP_WAKETIMER_PRIMARY
または TWENET::SLEEP_WAKETIMER_SECONDARY
を指定する。
スリープ前に組み込みオブジェクトやビヘイビアの on_sleep()
メソッドが呼び出され、スリープ前の手続きを行います。スリープ復帰後は反対に on_wakeup()
メソッドにより復帰処理が行われます。
スリープからの復帰要因が指定したディジタルピンである場合にtrue
を返します。
スリープからの復帰要因がウェイクアップタイマーである場合にtrue
を返します。
システムをリセットします。リセット後はsetup()
からの処理となります。
ウォッチドッグタイマーを停止します。長時間のポーリング待ちを行うような場合はタイマーを停止します。
ウォッチドッグタイマーはライブラリ内部のメインループで都度再開(restart)しています。タイマーが切れリセットがかかるまで約4秒です。
ウォッチドッグタイマーを再開します。
twe_twelite
には3つのビヘイビアを登録でき、これらを格納する以下のクラスオブジェクトを定義されています。
network
: ネットワークを実装するビヘイビアです。通常は<NWK_SIMPLE>
を登録します。
network2
: ネットワークを実装するビヘイビアです。最初に network
でペイロードのデータ構造などの判定により受理しなかったパケットを、別のネットワーク ビヘイビアで処理させたい場合に使用します。(参考: NWK_LAYERED と NWK_SIMPLEの併用)
board
: ボード対応のビヘイビアです。ボード上の各デバイス利用手続きが付加されます。
app
: ユーザアプリケーションを記述したビヘイビアです。割り込みやイベント記述、ステートマシンによる状態遷移によるふるまいの記述が可能です。また複数のアプリケーション記述を定義しておいて、起動時に全く振る舞いの違うアプリケーションを選択する記述が容易に行えます。
settings
: 設定(インタラクティブモード)を実行するためのビヘイビアです。<SET_STD>
を登録します。
ビヘイビア<B>を登録します。登録はsetup()
内で行います。戻り値は登録したビヘイビアに対応するオブジェクトの参照です。
登録後は登録時と同じ書式でオブジェクトの取得を行います。
誤ったビヘイビアを指定した場合は、パニック動作(無限ループ)となりプログラムの動作が停止します。
グローバル変数としてビヘイビアのオブジェクトを宣言することを想定していません。利用都度use<B>()
を用いてください。
ただし、グローバル変数にオブジェクトのポインタを定義して以下のように記述することは可能です。(MWXライブラリでは原則としてポインタ型の利用を最小限にとどめ参照型を利用する方針ですので、下記のような記述は推奨しません)
the_twelite
には上述のboard
, network
, app
の3つのクラスオブジェクトが定義されていますが他に以下が定義されています。
送信完了状態を通知する。
イベントドリブンのビヘイビアの記述ではtransmit_complete()コールバックで管理します。
指定したIDのパケットが送信完了したときにtrue
を返す。
指定したIDのパケットが送信完了し、かつ送信成功したときにtrue
を返す。
受信パケットを取得する。
the_twelite.receiver
は推奨されません。
従来loop()
内での記述を意図して the_twelite.receiver
による処理を行っていましたが、キューによる遅延処理である点で原理的に取りこぼしが発生し、また記述も煩雑になりがちである点から on_rx_packet()
を追加しました。
イベントドリブンのビヘイビアの記述ではreceive()コールバックで取得します。
read()
メソッドで得られる受信パケットデータは、続くパケットが受信処理時に上書きされる設計となっています。available
直後に読み出してなにか短い処理をする場合は問題になることはありませんが、原則として読み出し→アプリケーションが使うため必要なデータのコピー→loop()
の終了を速やかに行います。例えばloop()
中で長いdelay()
を行うと受信パケットの取りこぼしなどが発生します。
まだ読み出していない受信パケットが存在する場合にtrue
を返す。
パケットを読み出します。
ディジタル入力管理クラス (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が稼働状態であれば、復帰後に再開します。再開後、初回確定を行います。
TWELITE 無線マイコンの内蔵EEPROMに対して読み書きを実行
TWELITE 無線マイコンの内蔵EEPROMに対して読み書きを実行します。
内蔵EEPROMはアドレス0x000~0xEFFまでの3480バイトが利用可能です。
先頭部分は設定(インタラクティブモード)に利用されるため、併用する場合は後半のアドレスの利用を推奨します。設定(インタラクティブモード)でどの程度の領域を消費するかは、その実装に依存します。最小限度の設定であっても先頭から256バイトまでは利用されるため、それ以降の利用を推奨します。
EEPROMからaddress
に対応するデータを読み出します。
エラーの検出は行いません。
EEPROMからaddress
に対してvalue
を書き込みます。
エラーの検出は行いません。
write()
と同じく書き込みを行いますが、先にaddress
にあるデータを読み出してvalue
と違う場合のみ、書き込みを行います。EEPROMの書き換え寿命を考慮し、書換回数を減らしたいときに用います。
後述のmwx::stream
を用いた読み書きを行うために、ヘルパーオブジェクトを取得します。
stream_helper ヘルパーオブジェクトを経由して、mwx::stream
による演算子やメソッドを用います。mwx::stream
を用いるとuint16_t
やuint32_t
型といった整数型の読み書き、uint8_t
の固定長配列型の読み書き、format()
オブジェクトによる書式整形などが可能になります。
このオブジェクトに対して<<
演算子などmwx::stream
で定義されたインタフェースを利用できます。
.seek()
を用いてEEPROMのアドレスを1024に移動しています。
上記では8バイト文字列(00bc614e
)、4バイト整数(0x12ab34cd
)、16バイトバイト列(HELLO WORLD!...
)、1バイト終端文字を書き込んでいます。
.seek()
を用いてEEPROMのアドレスを1024に移動しています。
先ほど書き出したデータ列を読み出します。順番に8バイト文字、4バイト整数、16バイト文字列を>>
演算子を用いて読み出します。
パルスカウンタ (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にリセットします。
SPI (メンバ関数版)
begin()
メソッドによりハードウェアの初期化を行った後、beginTransaction()
によりバスの読み書きができるようになります。beginTransaction()
を実行するとSPIのセレクトピンが選択されます。読み書きはtransfer()
関数を用います。SPIは読み出しと書き込みを同時に実行します。
バスの利用開始を行います。SPIのセレクトピンをセットします。
settings
パラメータを与えて呼び出した場合は、バスの設定を行います。
バスの利用を終了します。SPIのセレクトピンを解除します。
バスの読み書きを行います。trasnfer()
は8bit、transfer16()
は16bit、transfer32()
は32bitの転送を行います。
beginTransaction(), endTransaction(), transfer(), transfer16(), transfer32()
transceiver
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
を指定します。
0
-
100Khz
1
2
3.7Khz
2
4
2.2Khz
3
8
1.2Khz
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 MODE 3 に設定する
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) により変更できます。
buf_tx
TX用のFIFOバッファサイズ
buf_rx
RX用のFIFOバッファサイズ
ハードウェアの初期化を行う。
Serial
(UART0) は ライブラリ内で begin()
の呼び出しが自動で行われます。ユーザによる呼び出しを行う必要はありません。
speed
UART のボーレートを指定する。
config
serial_jen::E_CONF::PORT_ALT
ビットを指定したときは、UART1をDIO14,15で初期化します。指定しない場合はDIO11(TxD),9(RxD)で初期化します。
指定したボーレートの下2桁の数値は0に丸めて処理します。またハードウェアの制限により指定したボーレートより誤差が生じます。
ボーレートの計算には除算が発生し計算時間がかかる場合があります。9600,38400,115200を指定する場合は、除算をせずに計算を行います。処理の詳細は、constexpr uint16_t _serial_get_hect_baud(uint32_t baud)
を参照してください。
(未実装)ハードウェアの使用を停止する。
Cライブラリで利用する TWE_tsFILE*
形式での構造体を得る。
Serial (UART) では、_sSerial
構造体が定義されています。
シリアルポート用書式入力 (mwx::serial_parser)
この組み込みクラスはシリアルポートでの書式入力に利用することを想定して組み込みオブジェクトとして定義しています。
初期化時(begin()
)にヒープから内部で使用するバッファ領域を確保するmwx::serial_parser<mwx::alloc_heap<uint8_t>>
型として定義されています。
詳細はクラス serparser を参照してください。
システムタイマー (mwx::periph_ticktimer)
TickTimerはTWENETの内部制御用に利用され、暗黙に実行されています。タイマーの周期は1msです。loop()
中でTickTimerイベントにより1msごとの処理を記述する目的でavailable()
メソッドのみを定義しています。
必ず1ms刻みでavailableになる訳ではない点に注意してください。
ユーザプログラムの記述内容や、システム内部の割り込み処理などが要因で、大きな遅延が発生することもあり、イベントが飛ばされるような場合もあります。
available()
TickTimer割り込み発生後にセットされ、その直後のloop()
でtrue
になります。loop()
終了後にクリアされます。
SPI (ヘルパークラス版)
ヘルパークラス版はより抽象度が高い実装です。読み書きを行うオブジェクト transceiver
を生成することが、バスの利用開始となり、オブジェクトを破棄するとバス利用の終了手続きを行います。
if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。
また、読み書きオブジェクトは、mwx::stream
インタフェースを実装しているため<<
演算子などを利用することができます。
バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぎます
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で指定したデータバイト分だけ読み飛ばします。
Wire (メンバ関数版)
メンバ関数を利用した方法は、抽象度が比較的低く、C言語ライブラリで提供されるような一般的なAPI体系に倣っています。二線シリアルバスの操作手続きがより直感的です。
ただしバスの利用の開始と終了を明示的に意識して記述する必要があります。
指定バイト数分を一括で読み出します。読みだした結果はキューに保存されるため、直後にキューが空になるまで .read()
メソッドを呼び出すようにしてください。
u8address
読み出し対象のI2Cアドレス
length
読み出しバイト数
b_send_stop=true
true
の時、読み込み終了時にSTOP
ビットを設定する。
戻り値型 size_type
読み出したバイト数。 0
は読み出しの失敗。
書き出し処理は、beginTransmission()
を実行後、write()
メソッドにより行います。一連の書き出しが終了したら endTranmission()
を呼びます。
書き出しの転送を初期化する。書き出し処理終了後、速やかに endTransmission()
を呼び出す。
u8address
書き出し対象のI2Cアドレス
1バイトの書き出しを行う。
value
書き込むバイト
戻り値 size_type
書き込んだバイト数。0
はエラー。
バイト列の書き出しを行います。
*value
書き込むバイト列
size_type
バイト数
戻り値 size_type
書き込んだバイト数。0はエラー。
書き出しの終了処理を行います。
sendStop = true
STOPビットを発行します。
戻り値 uint8_t
0: 成功 4: 失敗
二線シリアル(I2C) master の読み書き (mwx::periph_wire)
二線シリアル(I2C) master の読み書きを行います。
mwx::periph_wire<MWX_TWOWIRE_RCVBUFF>
はTwoWire
として参照可能です。
以下の定義型で引数や戻り値の型を記載します。
API 中に STOP ビットの扱いが厳格でない呼び出しを行うものもあります。
write(), writer::operator() ()
には、本解説以外にもいくつか引数が定義されてます。
固定配列型
uint8_t cmds[]={11,12};
...
Wire.write(cmds);
initializer_list<>
型
Wire.write({11,12})
ライブラリ内でインスタンスの生成と必要な初期化は行われます。ユーザコードでは Wire.begin()
を呼び出すことで利用可能となります。
requestFrom()
メソッドを用いる場合、データを一時保管するための FIFO キューのサイズを指定できます。コンパイル時にマクロMWX_TWOWIRE_BUFF
に必要なバイト数を指定してコンパイルする。デフォルトは 32 バイトです。
例:
-DMWX_TWOWIRE_BUFF=16
ハードウェアの初期化を行います。
初期化せずにWireの操作を行うとTWELITE無線モジュールがハングアップします。
スリープからの起床時は、スリープ直前で動作していた場合、直前の状態に復帰します。
u8mode
バス周波数を指定する。デフォルトは100Khz(WIRE_CONF::WIRE_100KHZ
)
周波数はWIRE_CONF::WIRE_??KHZ
で指定し??
には50
,66
,80
,100
,133
,160
,200
,266
,320
,400
を指定できる。
b_portalt
ハードウェアのピン割り当てを変更する。
読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。
メンバ関数版 (以下のメンバ関数を用いた入出力)
requestFrom(), beginTransmission(), endTransmission(), write()
ヘルパークラス版(stream機能が使用可能)
reader, writer
address
で指定したデバイスが応答するかを確認します。デバイスが存在する場合は true
が戻ります。
本来はバス周波数を変更するための手続きですが、何も処理をしません。
タイマー, PWM (mwx::periph_timer)
タイマーでは、指定周期でのソフトウェア割り込みを発生させる目的、指定周期でPWM出力を行う2つの機能があります。TWELITE無線モジュールには0..4まで合計5つのタイマーが利用可能です。
組み込みオブジェクト名は Timer0..4
ですが、このページでは TimerX
と記載します。
タイマーを初期化します。この呼び出しにより必要なメモリ領域の確保を行います。
タイマーを開始します。1番目のパラメータは、タイマーの周期でHzで指定します。2番目のパラメータをtrue
にするとソフトウェア割り込みが有効になります。3番目のパラメータをtrue
にするとPWM出力を有効にします。
change_hz()
で周波数を変更することが出来ます。change_hz()
ではbegin()
の指定より細かい指定が可能です。
change_duty()
でPWM出力のデューティー比を変更できます。
割り込みハンドラの処理を記述するには、の定義が必要です。
タイマーの動作を停止します。
タイマー割り込みが発生した直後のloop()
でtrue
になり、loop()
が終了すればfalse
になります。
デューティー比の設定を行う。1番目のパラメータにデューティ比を指定します(小さい値を指定すると波形の平均はGNDレベルに近づき、大きい値を指定するとVccレベルに近づく)。2番目のパラメータはデューティ比の最大値を指定します。
duty_max
は1024,4096,16384
のいずれかの指定を推奨します。
内部でのカウント値の計算に除算が発生しますが、これら3つに限りビットシフトによる演算を行っていますが、これ以外の値では計算量の大きい除算処理が実行されます。
タイマーの周波数を設定します。2番目のパラメータは周波数の小数点3桁分の値を整数で指定します。例えば 10.4 Hz としたい場合は hz=10, mil=400
と指定します。
alloc
コンテナクラス(smplbuf
, smplque
)のテンプレート引数として指定し、内部で利用するメモリの確保または領域指定します。
このクラスはユーザコードから直接呼び出すものではありませんが、内部的にコンテナの宣言に用いられています。
alloc_attach
やalloc_heap
ではメモリ確保クラスに応じた初期化メソッド (init_???()
)を実行する必要があります。
バッファーp
・サイズn
で初期化します。
バッファのサイズを返す。
想定したallocクラスと違うメソッド呼び出し記述に対して、static_assert
のように、コンパイルエラーを発生させるためのメソッドです。
alloc_attach<T>
すでにあるバッファを指定する
alloc_local<T, int N>
Nバイトのバッファを内部に静的確保する
alloc_heap<T>
指定したサイズをヒープに確保する
MWX_APIRET
Wire (ヘルパークラス版)
ヘルパークラス版はより抽象度が高い実装です。読み書きに対応するオブジェクト reader, writer
を生成することがバスの利用開始となり、オブジェクトを破棄するとバス利用の終了手続きを行います。
if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。
また読み書きオブジェクトはmwx::stream
インタフェースを実装しているため<<
演算子などを利用することができます。
バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぐ
mwx::stream
インタフェースによる読み書き手続きの統一
読み込み処理とその終了手続きをスコープ内 if() { ... }
で行うためのヘルパークラスを用いた読み込み方法です。
上記では get_readr()
メソッドにより生成された rdr
オブジェクトを用いて1バイトずつ読み出しします。 メソッドのパラメータには読み込みたい二線シリアル ID を指定します。
if(...)
内で rdr
オブジェクトを生成。(型は、型推論によるユニバーサル参照 auto&&
で解決しています。)
生成した rdr
オブジェクトには operator bool ()
が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true
となる。
rdr
オブジェクトには int operator () (void)
演算子が定義されていて、これを呼び出すことで2線シリアルバスから1バイトのデータを読み出す。読み込みに失敗したときは -1
が戻り、成功した場合は読み込んだバイト値が戻る。
if() { ... }
スコープの末尾で rdr
のデストラクタが呼び出され、二線シリアルバスの STOP
を行う。
I2C 読み出しに用いるワーカーオブジェクトを取得します。
addr
読み込み用のI2Cアドレス
read_count
読み出しバイト数(この値を指定すると最後の転送で STOP ビットを発行する)。0を指定した場合は STOP ビットなしとなる(デバイスによっては動作するものもあります)
書き出し処理とその終了手続きをスコープ内 if() { ... }
で行うためのヘルパークラスを用いた読み込み方法です。
上記では get_writer()
メソッドにより生成された wrt
オブジェクトを用いて1バイトずつ書き出す。 メソッドのパラメータには読み出したい二線シリアル ID を指定します。
if(...)
内で wrt
オブジェクトを生成する。(型名は長くなるため auto で解決)
生成した wrt
オブジェクトには operator bool ()
が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true
となる。
wrt
オブジェクトには int operator () (void)
演算子が定義されていて、これを呼び出すことで2線シリアルバスに1バイトのデータを書き出しす。失敗したときは -1
が戻り、成功した場合は書き込んだバイト値が戻る。
if() { ... }
スコープの末尾で wrt
のデストラクタが呼び出され、二線シリアルバスの STOP
を行う。
I2C書き出しに用いるワーカーオブジェクトを取得します。
addr
書き出し用のI2Cアドレス
int
型,uint8_t
型は8bitの転送を行います。データ並び順はビッグエンディアン形式(上位バイトが先に転送される)です。
1バイト書き出す。
それぞれのデータ型のサイズ分だけ読み出します。データ並び順はビッグエンディアン形式(先に転送されたほうが上位バイトに格納される)です。
1バイト読み出します。エラーがある場合は-1を戻し、正常時は読み出したバイト値を戻します。
b_stop
をtrue
にすると、その読み出しにおいてSTOPビットを発行します。
以下の例は、環境センサーパルの温湿度センサーSHTC3の計測例です。
packet_rx
このクラスはTWENETのtsRxDataApp
構造体のラッパークラスです。
このクラスオブジェクトは、ビヘイビアのコールバック関数またはon_rx_packets()
により取得できます。
packet_rx
では、特にパケットのデータペイロードをsmplbuf
コンテナで取り扱えるようにし、expand_bytes()
などのユーティリティ関数によりペイロードの解釈記述を簡素化しています。
現時点では、シンプル中継ネットワーク<NWK_SIMPLE>
で必要とされるものを中心にメソッド等のインタフェースを実装しています。
パケットのデータペイロードを取得する。
<NWK_SIMPLE>
を用いた場合は、先頭に<NWK_SIMPLE>
用のヘッダデータがあります。戻りとして参照されるコンテナは、このヘッダ部分を除いた部分配列になります。ヘッダ部分まで参照したいときはget_psRxDataApp()
によりtsRxDataApp
構造体を参照してください。
TWENET Cライブラリの受信構造体を得る。
ペイロードのデータ長を返す。.get_payload().size()
と同じ値になる。
LQI値 (Link Quality Indicator)を得る。
LQIとは電波通信品質を示す値です。0から255までの数値で表されます。
ちなみに、いくつかの段階で評価する場合は、50未満(悪い -80dbm 未満)、50~100(やや悪い)、100~150(良好)、150以上(アンテナの近傍)といった区分けも可能です。これらは目安である点にご留意ください。
送信元のアドレスを得る。
get_addr_src_long()
は送信元のシリアル番号で、MSB(bit31)が必ず1になります。
get_addr_src_lid()
は送信元の論理IDで0x00
-0xFE
までの値をとります(<NWK_SIMPLE>
で指定する論理IDです)。
宛先アドレスを得ます。
宛先アドレスは、送信元で指定され、宛先の種別によって値の範囲が変わります。
MSB(bit31)がセットされている
宛先としてシリアル番号を指定しています。
0x00
-0xFF
宛先として論理ID(8bit)が指定されています。
暗号化パケットの場合は true
を返し、平文の時はfalse
を返します。
ネットワークビヘイビアで識別されるパケットのネットワークタイプを返す。
mwx::NETWORK::LAYERED
<NWK_LAYERED>
からのパケット
mwx::NETWORK::SIMPLE
<NWK_SIMPLE>
からのパケット
mwx::NETWORK::NONE
ネットワークを介さないパケット (App_Tweliteなど)
その他
エラーまたは識別できないパケット
packet_tx
このクラスはTWENET CライブラリのtsTxDataApp
構造体のラッパクラスで、このクラスをベースとした派生クラスのオブジェクトをネットワークビヘイビアまたはon_tx_comp()
により取得します。
ネットワークビヘイビアの .prepare_tx_packet()
によって行います。
上記の例ではthe_twelite.network.use<NWK_SIMPLE>()
によってネットワークビヘイビアのオブジェクトを取り出します。このオブジェクトの.prepare_tx_packet()
によってオブジェクトpkt
が生成されます。型名はauto&&で推論されていますがpacket_tx
の派生クラスになります。
このpkt
オブジェクトは、まず、()
内の条件判定にてtrue
かfalse
を返します。false
が返ってくるのは、送信用のキューが一杯でこれ以上要求が追加できない時です。
無線パケットには宛先情報など相手に届けるための様々な設定を行います。設定には設定内容を含むオブジェクトを<<演算子の右辺値に与えます。
以下に設定に用いるオブジェクトについて記載します。
各設定の利用可否や意味合いは、ネットワーク ビヘイビアの仕様によります。
宛先アドレスaddr
を指定します。宛先アドレスの値については、ネットワークビヘイビアの仕様を参照してください。
<NWK_SIMPLE>
MSB(bit31=0x80000000
)がセットされるアドレスは、無線モジュールのシリアル番号宛という意味になります。 0x00
..0xEF
は、8bitの論理IDを意味します。0xFEは子機宛(0x01
..0xEF
)の同報通信(ブロードキャスト)、0xFF
は親機子機関係なく同報通信(ブロードキャスト)します。
再送回数の指定を行います。再送回数はu8countで指定します。force_retryは、送信が成功しようがしまいが、指定回数の再送を行う設定です。
<NWK_SIMPLE>
ネットワークビヘイビア<NWK_SIMPLE>
では、同じ内容のパケットをu8count+1
回送信します。 force_retry
の設定は無視されます。
パケットを送信するまでの遅延と再送間隔を設定します。u16DelayMin
とu16DelayMax
の2つの値をミリ秒[ms]で指定します。送信要求をしてからこの間のどこかのタイミングで送信を開始します。再送間隔をu16RetryDur
の値[ms]で指定します。再送間隔は一定です。
内部処理の都合で指定通りのタイミングで送信処理が始まらない場合もあります。また、IEEE802.15.4の処理でもパケット創出までの時間ブレが発生します。これらのタイミングのブレは、多くのシステムではパケットの衝突回避を行う上で有効な手立てとなります。
厳格なタイミングでのパケット送信は、IEEE802.15.4の規格の性質上、例外的な使用方法とお考え下さい。
<NWK_SIMPLE>
この指定は有効です。 最初の送信から1秒を超えて再送され到達した同一パケットについては、新たなパケットが到達したとして重複除外が為されません。再送間隔を長く設定したり、中継でのパケット到達遅延により1秒を超えて同じパケットを受信する場合があります。 重複パケットの処理の設定は<NWK_SIMPLE>
ビヘイビアの初期化で設定できます。
パケット送信を「できるだけ速やかに」実行するように要求する設定です。TWENETでのパケット送信処理は、1msごとに動作するTickTimer起点で行われています。この設定をすることで、要求後速やかにパケット送信要求が処理されます。もちろん、tx_packet_delay(0,0,0)
以外の設定では意味がない指定になります。
他のパケット送信処理が行われている場合は、通常の処理になります。
<NWK_SIMPLE>
この指定は有効です。
無線パケット通信では、送信完了後、送信相手先からACK(アック)という短い無線電文を得て、送信成功とする送信方法があります。このオプションを設定することで、ACK付き送信を行います。
<NWK_SIMPLE>
<NWK_SIMPLE>
では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>
は、シンプルに動作する中継ネットワークの実装を目的としており、ACK付きの通信は行いません。
ブロードキャストの指定を行います。
<NWK_SIMPLE>
<NWK_SIMPLE>
では、この指定は無効です。コンパイルエラーになります。 替わりに宛先アドレスtx_addr(0xFF)
(ブロードキャスト)またはtx_addr(0xFE)
(子機宛のブロードキャスト)を指定します。
0..7の指定ができるTWENET無線パケットのタイプIDを指定します。
<NWK_SIMPLE>
<NWK_SIMPLE>
では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>
ではタイプIDを内部的に使用しています。ユーザは使用できません。
シリアル書式入出力 (mwx::serial_parser)
シリアル書式の入出力のために用います。内部に解釈済みのバイナリ系列を保持するバッファを持ち、入力時は1バイトずつ系列を読み出し書式に従い内部バッファに格納し、系列の解釈が完了した時点で完了状態になるものです。反対に出力時は内部バッファから所定の出力書式に従いバッファを出力します。
メモリバッファ取り扱い方法()に応じて3種類のクラス名が定義されています。
begin()
の初期化のパラメータで渡す書式の種別です。ここではアスキー形式とバイナリー形式の2種類があります。
バイナリ形式の取り扱いはアスキー形式に比べ、必要なツールや確認方法を含め一般に取り扱いが煩雑になります。通常はアスキー形式をお使いください。
アスキー形式は、バイナリで構成されたデータ列を文字列で表現する方法です。
例えばバイト列で 00A01301FF123456
をアスキー形式で表現すると、以下のようになります。先頭は :
で B1
がチェックサム、終端は [CR:0x0d][LF:0x0a]
となります。
:00A01301FF123456B1[CR][LF]
終端のチェックサムを省略できます。チェックサムからCRLFの系列をX
に置き換えます。文字化けによる誤ったデータ系列には弱くなりますが、実験などでデータを送付したいときに便利です。
:00A01301FF123456X
通常はアスキー形式を利用してください。
マイコン間通信での実装を考えるとバイナリ形式のほうが効率的ですが、実験などでの送受信の確認にはバイナリ通信に対応した特別なターミナルなどを準備する必要があり、チェックサムの計算も必須です。アスキー形式より利用の難易度は高くなります。
バイナリ形式は、バイナリで構成されたデータ列にヘッダとチェックサムを付加して送付する方法です。
例えば 00A01301FF123456
をバイナリ形式で表現すると、以下のようになります。
0xA5 0x5A 0x80 0x08 0x00 0xA0 0x13 0x01 0xFF 0x12 0x34 0x56 0x3D
宣言にはメモリの確保クラスを指定します。この指定は煩雑であるため、上述のように別名定義を行っています。
メモリ確保クラスに応じたbegin()
メソッドを呼び出します。
この定義は、特に、データ列を書式出力したい場合に用います(>>
演算子参照)
一度確保したヒープ領域は解放できません。
内部バッファを返す。バッファは smplbuf<uint8_t, alloc>
型となります。
入力文字を処理する。書式入力の入力文字列を1バイト受け取り書式に従い解釈します。例えばASCII書式では:00112233X
のような系列を入力として受け取りますが : 0 0 ... X
の順で1バイトずつ入力し、最後の X
を入力した時点で書式の解釈を完了し、完了済みと報告します。
parse()
のパラメータは入力バイト、戻り値は解釈完了であればtrue
を戻します。
parse()
で読み出し完了になったとき、次のparse()
を実行すると読み出し中のステータスに戻ります。
true
ならparse()
により読み出しが完了した状態で、false
なら解釈中となります。
内部バッファを書式形式でストリーム(Serial)に対して出力します。
ty
で指定するで、p
で指定したバッファを用います。バッファの最大長はmax_siz
で、バッファの有効データ長をsiz
で指定します。
ty
で指定するで初期化を行います。
ty
で指定するで、siz
で指定したサイズをヒープに確保して初期化します。
axis_xyzt
を格納したコンテナクラスのXYZ軸のいずれかの軸を取り出した仮想的なコンテナクラスを生成する関数です。この生成したクラスにはbegin()
とend()
メソッドのみ実装されています。このbegin()
とend()
メソッドで取得できるイテレータは前節のイテレータと同じものになります。
ヘッダ
1
:
(0x3A) コロンを指定します。
データ部
N
2N
元データの各バイトをアスキー文字列2文字(A-F は大文字)で表現します。
例えば 0x1F は 1
(0x31) F
(0x46) と表現します。
チェックサム
2
データ部の各バイトの和を8ビット幅で計算し2の補数をとります。つまりデータ部の各バイトの総和+チェックサムバイトを8ビット幅で計算すると0になります。
チェックサムバイトをアスキー文字列2文字で表現します。
例えば 00A01301FF123456
では 0x00 + 0xA0 + ... + 0x56 = 0x4F となり、この二の補数は0xB1 です。(つまり 0x4F + 0xB1 = 0x00)
フッタ
2
[CR] (0x0D) [LF] (0x0A) を指定する。
ヘッダ
2
0xA5 0x5A
を指定します。
データ長
2
データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。
例えばデータ部の長さが 8 バイトなら0x80 0x08
を指定します。
データ部
N
N
元データを指定します。
チェックサム
1
データ部の各バイトの XOR を計算します。
例えばデータ部が 00A01301FF123456
なら 0x00 xor 0xA0 xor ... 0x56 = 0x3D となります。
フッタ
(1)
チェックサムが事実上の終端です。無線モジュールからの出力では 0x04
(EOT) が付加されます。
クラス名(別名定義) メモリ確保
内容
serparser_attach
すでにあるバッファをbegin()
にて指定する
serparser_local<N>
Nバイトのバッファを内部に確保する
serparser_heap
begin()
メソッドのパラメータで指定したサイズをヒープに確保する
uint8_t PARSER::ASCII = 1
アスキー形式
uint8_t PARSER::BINARY = 2
バイナリー形式
identify_packet_type()
TwePacketTwelite
TwePacketTwelite
クラスは、標準アプリApp_Tweliteの0x81コマンドを解釈したものです。
パケットデータ内の諸情報はparse<TwePacketTwelite>()
実行後にパケット情報がDataTwelite
に格納されます。
TwePacketIO
TwePacketAppIO
クラスは、標準アプリApp_IOのシリアルメッセージ(0x81)を解釈したものです。
パケットデータ内の諸情報はparse<TwePacketIO>()
実行後にDataTwelite
に格納されます。
TwePacket
パケット型の基底クラスですが、メンバー構造体common
にはアドレス情報など共通情報が含まれます。
種別を混在してpktparser型として配列等に格納するような場合に、アドレス情報などを最小限の情報を取得したい場合に使用します。
pktparser
pktparser(parser_packet)は、serparserで変換したバイト列に対して、内容の解釈を行います。
上記の例は、標準アプリケーションの0x81メッセージの解釈を行っています。parser_serオブジェクトによりSerialより入力された電文をバイト列に変換します。このバイト列をまずidentify_packet_type()
により電文の種別E_PKT
を特定します。電文の種別が判定できたら次に.parse<TwePacketTwelite>()
により電文を解析します。解析結果はTwePacketTwelite
型になりますが、このオブジェクトを取り出す手続きが.use<TwePacketTwelite>()
です。TwePacketTwelite
型はクラスですが構造体として直接メンバー変数を参照します。
バイト列を解析します。
T
には解析対象のパケット型を指定します。例えば標準アプリケーションの0x81メッセージならTwePacketTwelite
を指定します。
p
とe
はバイト列の先頭と終端の次を指定します。
戻り値はE_PKT
型です。エラーの場合はE_PKT::PKT_ERROR
が戻ります。
解釈したバイト列に対応するパケット型に対応するオブジェクトの参照を返します。事前にparse<T>を実行しエラーがなかった場合に呼び出すせます。
T
はparse<T>
で実行した型と同じもの、または基本的な情報のみ取得できるTwePacket
を指定します。
パケット種別定義
以下のパケットに対応します。
App_Wings の親機で出力されるアスキー書式に対応します。
PKT_ERROR
パケット解釈前やパケット種別が特定できないなど、TwePacketには意味のあるデータが格納されていない
PKT_TWELITE
標準アプリ App_Twelite の を解釈したもの
PKT_PAL
のシリアル形式を解釈したもの
PKT_APPIO
リモコンアプリ のを解釈したもの
PKT_APPUART
シリアル通信アプリ のを解釈したもの。
PKT_APPTAG
無線タグアプリApp_TagのUARTメッセージを解釈したもの。センサ固有部分は解釈されずpayloadとしてバイト列を報告します。
PKT_ACT_STD
のサンプルなどで使用される出力書式。
TwePacketUART
TwePacketAppUart
クラスは、App_UARTの拡張書式を親機・中継器アプリApp_Wingsで受信したときの形式です。
パケットデータ内の諸情報はparse<TwePacketUART>()
実行後にDataAppUART
に格納されます。
簡易形式は解釈できません。parse<TwePacketUART>()
ではE_PKT::PKT_ERROR
を戻します。内容を確認するには元のバイト列を直接参照してください。
payload
はデータ部分ですが、マクロ定義によってデータ格納の方法が変わります。
MWX_PARSER_PKT_APPUART_FIXED_BUF
の値が0
としてコンパイルした場合は、payload
はパケット解析を行うバイト列を直接参照します。元のバイト列の値が変更されるとpayload
中のデータは破壊されます。
MWX_PARSER_PKT_APPUART_FIXED_BUF
の値を0
より大きい値として定義した場合は、payload
にはその値(バイト数)のバッファが確保されます。ただしシリアル電文のデータがバッファサイズを超えた場合はparse<TwePacketAppUART>()
は失敗しE_PKT::PKT_ERROR
を戻します。
smplbuf
内部が配列構造のコンテナクラスです。初期化時にバッファの最大サイズを決定しますが、その最大サイズまでの範囲で可変長の配列として振る舞います。
smplbuf
は要素の型T
とメモリの確保方法alloc
で指定したメモリ領域に対して配列の操作を提供するコンテナクラスです。alloc
の指定は煩雑であるためusing
を用いた別名定義が行っています。
オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、サイズは0です。必要に応じてサイズを拡張しながら使用します。
上記のuint8_t
型に限り別名定義があります。
通常の配列のように[]演算子などを用いて要素にアクセスできますし、イテレータを用いたアクセスも可能です。
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の配列を出力先と見立ててストリーム出力手続きを行えます。
方法は2種類あります。
.get_stream_helper()
により生成されるヘルパーオブジェクトを利用する。
mwx::stream
を継承したsmplbufクラスを利用する。
FIFOキューを構造のコンテナクラス
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文やアルゴリズムが利用できます。
応用としてaxis_xyzt構造体の特定のメンバーに注目したイテレータによるアクセスがあります。
TwePacketPAL
TwePacketPal
クラスは、TWELITE PALのパケットデータを解釈したものです。このクラスはTWELITE PAL(センサーデータなど上り方向)共通に取り扱います。
PAL共通データはDataPal
に定義されています。
PALの各センサー基板特有のデータを取り出すためのジェネレータ関数を用意しています。
PALは接続されるセンサーなどによってパケットデータ構造が異なりますが、DataPal
では共通部のデータ構造を保持します。
PALのパケットデータ構造は大まかに2つのブロックからなり、全てのPAL共通部と個別のデータ部になります。個別のデータ部は、パケットの解釈を行わずそのまま格納しています。取り扱いを単純化するため32バイトを超えるデータは動的に確保するuptr_snsdata
に格納します。
個別のデータ部は、PalBase
をベースクラスに持つ構造体に格納されます。この構造体は、TwePacketPal
に定義されるジェネレータ関数により生成されます。
parse<TwePacketPAL>()
実行時にMWX_PARSER_PKT_APPPAL_FIXED_BUF
に収まるサイズであれば、センサー個別のオブジェクトを生成します。
収まらない場合はau8snsdata
に解析時のバイト列の参照が保存されます。この場合、解析に用いたバイト列のデータが書き換えられた場合は、センサー個別のオブジェクトは生成できなくなります。
PALの各センサーのデータ構造体はすべてPalBase
を継承します。センサーデータの格納状況u32StoredMask
が含まれます。
PALイベントは、センサーなどの情報を直接送るのではなく、センサー情報を加工し一定の条件が成立したときに送信される情報です。例えば加速度センサーの静止状態から一定以上の加速度が検出された場合などです。
イベントデータが存在する場合はTwePacketPal
の.is_PalEvent()
がtrue
になることで判定でき、.get_PalEvent()
によりPalEvent
データ構造を得られます。
センサーPALの各種データを取り出すためのジェネレータ関数です。
ジェネレータ関数を利用するには、まずpkt
がイベントかどうか判定(.is_PalEvent()
)します。イベントの場合はget_PalEvent()
を持ちます。それ以外はu8palpcb
に応じてオブジェクトを生成します。
.u8palpcb==E_PAL_PCB::MAG
の場合、開閉センサーパルのデータPalMag
を取り出します。
.u8palpcb==E_PAL_PCB::AMB
の場合、環境センサーパルのデータPalAmb
を取り出します。
.u8palpcb==E_PAL_PCB::MOT
の場合、動作センサーパルのデータPalMot
を取り出します。
.is_PalEvent()
がtrue
の場合PalEvent
(PALイベント)を取り出します。
smplbuf_strm_u8
uint8_t
型のsmplbuf_strm_u8???
はインタフェースも有しているため、いくつかのストリーム用のメソッドを使用することができます。
例
.get_stream_helper()
uint8_t
型のsmplbuf配列を参照したを経由して、による演算子やメソッドを用います。
ヘルパーオブジェクトの型名は長くなるためauto&&
により解決しています。このオブジェクトに対して<<
演算子などmwx::stream
で定義されたインタフェースを利用できます。
生成されたヘルパーオブジェクトbs
は生成時に大本の配列b
の先頭位置から読み書きを始めます。配列の末尾の場合はappend()
によりデータを追加します。読み書きを行うたびに位置は次に移動していきます
ヘルパー関数では読み出し用の>>
演算子が利用できます。
mwx::stream に printf の書式を入力
mwx::stream
の << 演算子に対してフォーマット書式を書き出すヘルパークラスです。ライブラリ内では Using format=mwx::mwx_format;
として別名定義しています。
可変数引数リストに登録できる引数は最大8つです。doubleやuint64_t型など64bitのパラメータが含まれる場合は引数の数が制限されます。制限を超えた場合はstatic_assertによるコンパイルエラーになります。
コンストラクタで受け取った引数リストを、パラメータパックの展開機能を用いてクラス内部変数に格納する
operator <<
が呼び出された時点で、fctprintf()
を呼び出し、ストリームにデータを書き出す
コンストラクタでは、書式のポインタとパラメータを保存します。続く <<
演算子による呼び出しでフォーマットを解釈して出力処理を行います。
fmt
は本オブジェクトが破棄されるまで、アクセス可能であることが必要です。
入出力ストリーム
入出力ストリームを処理する上位クラスです。
CRTP (Curiously Recurring Template Pattern) 手法を用いたポリモーフィズムにより、いくつかのクラス(Serial, Wire, SPI, smplbuf
) にインタフェースを提供します。
CRTP では下位クラスは template class Derived : public stream<Derived>;
のように定義し、上位クラスからも下位クラスのメソッドを参照します。
本クラスでは print
メソッド、<<
演算子などの共通処理の定義を行い、下位クラスで実装した write()
メソッドなどを呼び出すことで、仮想関数を用いるのと近い実装を行っています。
下位クラスでは、以下に列挙する関数を実装します。
入力が存在する場合は 1、存在しない場合は 0 を返します。
本実装の戻り値はバッファ長ではありません。
出力をフラッシュ(出力完了まで待つ)します。
ストリームより1バイトデータを入力します。データが存在しない場合は -1
を戻します。
ストリームに1バイト出力します。
1バイト出力を行うスタティック関数です。クラスメソッドではないため、メンバー変数等の情報は利用できません。替わりにパラメータとして渡される vp にクラスインスタンスへのポインタを渡します。
このスタティック関数は内部的に利用されfctprintf()
の1バイト出力関数として関数ポインタが渡ります。これを用いてprint
メソッドなどを実装しています。
1バイト出力します。
各種整形出力を行います。
printf 形式での出力を行います。
TWESDK/TWENET/current/src/printf/README.md 参照
バイト列として出力する際は、uint8_t, uint16_t, uint32_t
型にキャストします。また文字列として数値出力する場合は明示的にint
形にキャストするようにしてください。
1バイト型は型名によって取り扱いが違います。通常はサイズを意識したuint8_t[S]
型を用いるようにしてください。
>>
演算子を用いた入力タイムアウトとエラーを管理します。
set_timeout()
によりタイムアウト時間を指定し、>>
演算子により入力処理を行います。所定時間内までに入力が得られない場合は get_error_status()
によりエラー値を読み出せます。clear_error_status()
によりエラー状況をクリアします。
入力処理を行います。
setup()
内では実行できません。
ポーリング待ちを行うため、タイムアウトの時間設定(タイムアウト無しなど)によっては、ウォッチドッグタイマーが発動してリセットする場合があります。
通常はloop()
中で以下のような読み出しを行います。
以下に読み出し格納できる型を列挙します。
n
出力したい文字。
戻り値 size_t
出力が成功すれば 1、失敗すれば 0。
out
出力したい文字
vp
クラスインスタンスへのポインタ 通常は、元のクラスにキャストして write() メソッドを呼び出す
val
整形出力したい数値型
base
出力形式BIN 二進数 / OCT 8進数 / DEC 10進数 / HEX 16進数
place
小数点以下の桁数
戻り値 size_t
書き出したバイト数
char
1バイト出力 (数値としてフォーマットはしない)
int
整数出力 (printf の "%d")
double
数値出力 (printf の "%.2f")
uint8_t
1バイト出力する(char型と同様)
uint16_t
2バイト出力する(ビッグエンディアン順)
uint32_t
4バイト出力する(ビッグエンディアン順)
const char*``uint8_t*``const char[S]
終端文字までを出力します。出力には終端文字は含まれません。(S
は固定配列のサイズ指定)
uint8_t[S]
配列サイズS
バイト分をそのまま出力します。(S
は固定配列のサイズ指定)
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
はメモリ確保手段。
centisec
1/10秒単位でタイムアウト時間を設定します。0xff
を指定した場合は、タイムアウトを無効とします。
0
エラーなし
1
エラー状況
uint8_t, char_t
1バイト入力
uint16_t
2バイト入力(ビッグエンディアン順)
uint32_t
4バイト入力(ビッグエンディアン順)
uint8_t[S]
S
バイト分入力(S
は固定配列のサイズ指定)
null_stream(int n)
n
バイト読み捨てる
fmt
フォーマット書式。TWESDK/TWENET/current/src/printf/README.md 参照
...
フォーマット書式に応じたパラメータ。 ※ 最大数は4で、5つ以上のパラメータではコンパイルエラーとなる。※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。
戻り値 int
0: データなし 1:データあり
twe::stream に改行コードを出力する
mwx::stream
の <<
演算子に対して改行コード (CR LF) を出力するためのヘルパークラスのインスタンスです。
twe::stream へのバッファ出力をフラッシュする。
mwx::stream
の出力バッファをフラッシュする。flush()
メソッドを呼び出すヘルパークラスへのインスタンス。
シリアルポートの場合は出力完了までポーリング待ちを行う
mwx::simpbuf
バッファの場合は 0x00
を末尾に出力する(サイズは変更しない)
SM_SIMPLE ステートマシン
SM_SIMPLEは、サンプルコード中の状態遷移、タイムアウト待ち、送信完了などの処理待ちを行うために用意しています。
SM_SIMPLEの基本的なコード抜粋を示します。
SM_SIMPLEを利用するには状態一覧としてのenum class
定義が必要です。上記ではSTATE
として定義しています。このステージをパラメータとしてSM_SIMPLE<STATE> step;
のようにクラスオブエクトを生成します。生成したクラスオブジェクトは.setup()
により初期化しておきます。
SM_SIMPLEの初期状態は値が0で、上記の例ではSTATE::INIT
が対応します。現在の状態を取得するには.state()
を用、上記例のように_do while_文中の_switch_節の判定式に用います。
状態の遷移には.next()
を呼び出します。状態が変更された場合、b_more_loop()
がtrue
になり_do while_節のループがもう一度実行されます。例ではSTATE::SENSOR
状態から.next(STATE::TX)
を呼び出すことで、ループがもう一度実行されcase STATE::TX:
節も実行されることになります。状態を変更しない場合は_do while_ループを脱出しloop()
を一旦終了します。次のloop()
の呼び出しまで一旦待ちます。
送信完了などの処理待ちをしたい場合は.clear_flag()
を呼び出し、別のコールバック関数などで.set_flag(uint32_t)
により処理完了を知らせます。ここで指定したuint32_t
型のパラメータをは.get_flag_value()
から読み出せます。
またタイムアウトの処理を行いたい場合は.set_timeout(uint32_t)
を呼び出した時刻を記録し、.is_timeout()
によりタイムアウト時間が経過したかを調べることができます。
スリープからの復帰で再びSM_SIMPLEを利用することになりますが、スリープ前に必ず.on_sleep(bool)
を呼び出すようにします。パラメータにfalse
を入れると復帰後に0
状態から開始し、true
を入れるとスリープ直前の状態から再開します。
以下にSM_SIMPLEのソースコードを示します。
バージョンによって内容が変化する場合があり。
本体はmwxライブラリソースフォルダのSM_SIMPLE.hppに格納されます。
コールバック関数
アプリケーションの記述を行うコールバック関数です。コールバックはシステム(ライブラリ)から呼び出されるという意味です。ユーザがいくつかのコールバック関数を定義することでシステムの振る舞いを記述します。
以下のコールバック関数は必須定義です。
setup()
loop()
それ以外の関数は定義しない場合は、何も実行しない空の関数が替わりにリンクされます。
正確なふるまいを参照したい方はソースコードmwx_appcore.cpp
を参照してください。
正確なふるまいを参照したい方はソースコードmwx_appcore.cpp
を参照してください。
loop()
アプリケーションのメインループです。ループ終了後はCPUがDOZEモードに遷移し低消費電流で次の割り込みを待ちます。
アクトの記述では、ほとんどの処理がこのループ内に記述されます。
バック関数定義は省略可能です。
begin()
loop()
関数の初回コールの手前で一度だけ呼び出されます。TWENET の初期化は終了しているのでsetup()
のような制約を考慮する必要はありません。
主な使い方は、
始動メッセージの表示
テスト用のコードを記述
始動直後のスリープ遷移
setup()
で不都合がある処理(無線パケット処理・タイマー動作など)
このコールバック関数定義は省略可能です。
stream_helper
stream_helperは、mwx::stream
インタフェースを付与するヘルパーオブジェクトです。データクラスを参照するヘルパーオブジェクトを生成し、ヘルパーオブジェクト経由でデータの入出力を行います。
以下にはsmplbufの配列b
からヘルパーオブジェクトbs
を生成しmwx::stream::operator <<()
演算子によるデータ入力を行っています。
stream_helper はデータ配列をストリームに見立てて振舞います。
内部にはデータ配列中の読み書き位置を保持しています。次のようにふるまいます。
読み出しまたは書き込みをすると次の読み書き位置に移動します。
最期のデータを読み出した後、またはデータを末尾に追記した後には、読み書き位置は終端となります。
読み書き位置が終端の場合、
available()
がfalse
になります。
読み出しは出来ません。
書き込みは書き込み可能範囲であれば追記します。
stream_helper は、データクラス (smplbuf, EEPROM) のメンバー関数より生成します。
読み書き位置を先頭に移動します。
読み書き位置を設定します。
MWX_SEEK_SET
先頭位置から設定します。offset
に0
を指定するとrewind()
と同じ意味になります。
MWX_SEEK_CUR
現在位置を基準にoffset
分移動しまします。
MWX_SEEK_END
終端位置にします。offset
は0
にすると終端に設定します。-1
を設定すると最後の文字に移動します。
読み書き位置を返します。終端位置の場合は-1
を返します。
読み書き位置が終端であれば0
を返します。終端でなければそれ以外の値を返します。
wakeup()
スリープから起床したときにloop()
に移行する前に呼ばれ、スリープ復帰後の初期化処理や復帰状態によって処理を分岐するための手続きを含めます。
センサーの読み出しなどの処理のみでloop()
での処理がないときは、この関数内で再びスリープを実行できます。
このコールバック関数定義は省略可能です。
init_coldboot()
通常は使用しません。
ペリフェラルAPIも初期化もされていない、コード実行の再初期に呼び出されます。
このコールバック関数定義は省略可能です。
setup()
コード実行の初期に呼び出され、初期化コードを記述します。
TWENETの初期化は setup()
関数が終了した後にも実行されます。多くの処理はTWENETが終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。
注意すべき事項を以下に列挙します。
スリープthe_twenet.sleep()
の実行はできません。初期化後速やかにスリープしたいときはbegin()
関数内に最初のスリープ処理を記述してください。
delay()
関数は後述の処理*に置き換えられます。この場合、パラメータのms
はミリ秒を指定するものではありません。\
* delay()
の代替処理
init_warmboot()
通常は使用しません。
スリープ復帰後、ペリフェラルAPIが初期化されない再初期に呼び出されます。
この関数では割り込み要因の検出を行うことができます。
このコールバック関数定義は省略可能です。
on_rx_packet()
受信パケットを受け取ります。
無線パケットを受信したときにpacket_rxとして pkt
にデータが格納された状態で、本関数が MWX ライブラリ内から呼び出されます。アプリケーション中で本関数が定義されない場合は何もしない weak 関数がリンクされます。
本関数中で b_handled
に true をセットすると、MWX ライブラリに受信パケットがアプリケーション内で処理されたことを伝達します。処理済みとした場合、不要な処理を抑制します。(the_twelite.receiver
の処理を行わない)
ビヘイビアを用いる場合は、ビヘイビア中のコールバック関数を用いてください。
the_twelite.receiver
は推奨されません。
従来loop()
内での記述を意図して the_twelite.receiver
による処理を行っていましたが、キューによる遅延処理である点で原理的に取りこぼしが発生し、また記述も煩雑になりがちである点から on_rx_packet()
を追加しました。
ビヘイビア
ビヘイビアは、指定の方法でクラスを定義することで、the_twelite
クラスオブジェクトに登録できるようになります。登録したビヘイビアはTWENETに組み込まれて動作し、ユーザコードではアプリケーションの振る舞いを記述できるようになります。ループでの記述ではできなかったTWENETからの割り込みやイベントのコールバック関数を定義することが出来ます。ループでの記述に比べ、定義が多くなりますが、より複雑なアプリケーションを記述するのに向いています。
ビヘイビアのサンプルPAL_AMB-behaviorを参照してください。
ビヘイビアの定義には下記に示すようなクラス定義が必要です。
上記の例では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
かどうか判定します。
delay()
ポーリングによる時間待ちを行います。
ms
にて与えられた期間待ち処理を行います。
時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。
delay()
を呼び出してから約5ms経過するごとにTWELITEマイコン内部のウォッチドッグ処理を行います。
※例えばwhile(1) delay(1);
を実行した場合は、delay()
内部で5ms経過しないためウォッチドッグ処理が行われず、一定時間後リセットが実行されます。
setup(), wakeup()
関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。
パラメータに1,2といった短い時間を指定した場合は、誤差が大きくなる場合があります。
on_tx_comp()
送信完了時に呼び出されます。
無線パケットの送信が終了したときにpacket_ev_tx
として ev
にデータが格納された状態で、本関数が MWX ライブラリ内から呼び出されます。アプリケーション中で本関数が定義されない場合は何もしない weak 関数がリンクされます。
ev.u8CbId
は送信時のID、ev.bStatus
は送信の成功(1)または失敗(0)を示すフラグです。
本関数中で b_handled
に true をセットすると、MWX ライブラリに受信パケットがアプリケーション内で処理されたことを伝達します。処理済みとした場合、不要な処理を抑制します。(the_twelite.app
, .board
, .settings
に対してはイベントコールバック関数を呼び出さない)
PAL_AMB-behavior
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
このアクトの解説の前にBRD_APPTWELITEの解説、PAL_AMBの解説、PAL_AMB-usenapの解説を参照してください。またビヘイビアの解説についても参照ください。
このサンプルはビヘイビアの記述方法のサンプルです。ビヘイビアはより複雑なアプリケーションを記述する際に用います。
環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
コイン電池で動作させるための、スリープ機能を利用します。
親機
子機
親機にPALを使用する場合は、コイン電池での動作はできません。目安として50mA以上の電流を安定して得られるような電源環境を用意してください。
PAL_AMB-behavior.hpp : setup()
のみの定義です。DIP-SWを読み出し、D1..D3が上位置の場合は親機として動作し、それ以外は子機としてDIP SWに対応するIDをセットします。
Parent/myAppBhvParent.hpp : 親機用のビヘイビアクラス定義
Parent/myAppBhvParent.cpp : 実装
Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装
Parent/myAppBhvParent.hpp : 子機用のビヘイビアクラス定義
Parent/myAppBhvParent.cpp : 実装
Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装
親機のビヘイビア名は<MY_APP_PARENT>
、子機は<MY_APP_CHILD>
です。
ビルドファイルの追加はMakefileの解説を参照してください。
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センサーの動作開始を行います。一定時間経過すれば、センサーはデータ取得可能な状態になります。この時間待ちを66
ms設定のスリープで行います。スリープ前に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()
を実行してください。
delayMicroseconds()
MWSDK2020_05 には含まれません。対応パッケージはMWSDK_2020_07_UNOFFICIAL以降となります。
ポーリングによる時間待ちを行います(μ秒指定)。
microsec
にて与えられた期間待ち処理を行います。
時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。
setup(), wakeup()
関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。
パラメータに10以下といった短い時間を指定した場合は、誤差が大きくなる場合があります。
millis()
システム時刻[ms]を得ます。
システム時刻はTickTimerの割り込みで更新されます。
+
random()
乱数を生成します。
1行目は0..(maxval-1)
の値を戻します。maxvalの値が最大値ではないことに注意してください。
2行目はminval..maxval-1
の値を戻します。
DESC
入力設定のポートの値を読み出す。
事前に入力に設定したピンの入力値をLOW
またはHIGH
で得ます。
E_PIN_STATE
型からint
型への変換演算子は定義していないため、数値型への直接的な代入はできません。
DIO 汎用ディジタルIO
汎用ディジタルIO(DIO)の操作には以下の関数を利用します。
pinMode()
digitalWrite()
digitalRead()
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
無
出力(初期状態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
有
入力状態に戻す
以下の列挙値は型名 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
立ち上がり
attachIntDio()
DIO割り込みを有効にします。
事前に入力設定したピンに対して、1番目のパラメータは割り込みを有効にしたいピン番号で、2番目は割り込み方向(立ち上がり、立ち下がり)を指定します。
割り込みハンドラ、イベントハンドラの記述はアプリケーションビヘイビアで行います。
DIO5のピンがHIGHからLOWに変化したときに割り込みが発生する設定を行う。
アプリケーションビヘイビアmyAppClass
の基本定義。詳細は省略している。
アプリケーションビヘイビアmyAppClass
の割り込みハンドラの記述。DIO5の割り込み発生時にDIO12の出力設定を反転させ、割り込みハンドラが終了してから発生するイベントではシリアルポートSerial
に*
を表示する。
digitalWrite()
ディジタル出力ピンの設定を変更します。
事前にpinMode()
にて設定対象のピンを出力用に設定しておきます。1番目のパラメータは、設定対象のピン番号を指定します。2番目のパラメータはHIGH
かLOW
のいずれかを指定します。
入力が E_PIN_STATE
型となっています。E_PIN_STATE
からint
型への変換演算子は定義していませんので、数値による直接の入力はできないようになっています。
detachIntDio()
割り込みハンドラの登録を解除します。