MWXライブラリでアプリケーション記述する場合は、このページを読み飛ばしても差し支えありません。このページはライブラリの動作の理解や改造などのためライブラリソースコードを参照する場面を想定しています。ライブラリを利用するのと比べより高度なC++言語に関する知識を前提となります。体系に近い記述を出来ることを目的とするが、TWELITEの特性に合わせた実装とする。イベントドリブンによるコード記述であり、これを扱えるようにクラス化を行う。上記クラス化によりアプリケーションのふるまいをカプセル化できるようにする。イベントドリブンとループの記述は共存できる形とする。代表的なペリフェラルはクラス化して手続きを簡素化する。可能な限りループ記述でアクセスできるようにする。いったボードを利用する手続きをクラス化し手続きを簡素化する。(例えば外部のウォッチドッグタイマーの利用を自動化する)アプリケーションクラスやボードクラスは、ポリモーフィズムの考え方を導入し、統一した手続きによって利用できるようにする。ライブラリの接続部のコードを都度定義しなくてよいようにするため)。C++の機能については、特に制限を設けず利用する。無線パケットを取り扱うにあたり煩雑なパケット構築、分解といった代表的な手続きを簡略化する手段を提供する。限られた時間で実装を進めているため、細かい点まで網羅した設計ではありませんが、設計・実装等でお気づきの点がありましたら、当社サポートにご連絡ください。当社で把握しているものについての記載です。演算子でのメモリ確保は行えますが、確保したメモリを破棄することはできません。C++ライブラリで動的メモリ確保をするものは殆どが事実上利用不可能です。一度だけ生成してそれ以降破棄しないオブジェクトに使用しています。グローバルオブジェクトのコンストラクタが呼び出されません。ように初期化することでコンストラクタの呼び出しを含めた初期化を行えます。exceptionが使用できません。virtualが使用できません。設計メモ本節ではMWXライブラリのコードを参照する際に理解の助けとなる情報を記載します。限られた時間で実装を進めているため、詳細部分の整備が十分でない場合があります。ついて、以下の方針としています。定義は原則として共通の名前空間mwxに配置する。名前空間の識別子なしで利用できるようにしたいが、一部の定義は識別子を必須としたい。クラス名については比較的長い命名とし、ユーザが利用するものは別名定義とする。囲んだmwx::L1)の名前空間内に定義しています。namespaceを指定しているのは、mwx::の指定を必須とする定義と、必須としない定義を共存させるためです。namespaceにより名前空間名を指定しなくても良いようになっています。これらの指定はライブラリ内のusing_mwx_def.hppで行っています。mwx::flushのように指定します。namespaceのmwx::L2の名前空間に配置されています。mwx::L2;を指定することで名前空間名の指定なしで利用できるようになります。いくつかのクラス名はusing指定をしています。MWXライブラリ内で利用するstd::make_pairをusing指定しています。奇妙に再帰したテンプレートパターン)を用いています。CRTPは、継承元の親クラスから子クラスのメソッドを呼び出すためのテンプレートパターンです。いうインタフェースを実装する例です。BaseからはDerived::print()メソッドを呼び出しています。}}MWXライブラリで利用されている主要クラスは以下です。仮想化CRTPクラスは、継承元のクラスはインスタンスごとに違います。このため、親クラスにキャストして、同じ仲間として取り扱うといったこともできませんし、仮想関数(virtual)やRTTI(実行時型情報)を用いたような高度なポリモーフィズムも使うことが出来ません。以下は上述のCRTPの例を、仮想関数で実装した例です。b[2]のように同じ配列にインスタンスをまとめて管理することは、そのままではできません。クラスインスタンスを格納するための専用クラスを定義し、このクラスに同様のインタフェースを定義することで解決しています。以下にコード例を挙げます。メンバ関数ポインタです。自身のオブジェクトインスタンスを引数として渡され、T型にstatic_castすることでT::intrfaceメソッドを呼び出します。演算子のオーバーロードによって実装しています(ソース例は後述)。上記の例ではb[0].intrface()の呼び出しを行う際には、VBase::pf_intrface関数ポインタを参照しBase<Derived1>::s_intrface()が呼び出されることになります。Derived1::intrface()の呼び出しを行うことになります。この部分はコンパイラによるinline展開が期待できます。型から元のDerived1やDerived2への変換を行うことも、強制的なキャストにより可能ですが、void*で格納されたポインタの型を直接知る方法はありません。完全に安全な方法はないものの、以下のようにクラスごとに一意のID(TYPE_ID)を設けて、キャスト実行時(get()メソッド)にIDのチェックを行うようにしています。get()メソッドを呼び出したときは、エラーメッセージを表示するといった対処になります。static_assertによって判定しています。演算子TWELITEモジュールのマイコンには十分なメモリもなければ、高度なメモリ管理もありません。マイコンのメモリマップの末尾からスタックエリアまでの領域はヒープ領域として、必要に応じて確保できる領域があります。以下にメモリマップの概要を図示します。APPがアプリケーションコードで確保されたRAM領域、HEAPはヒープ領域、STACKはスタック領域です。32KBたとえdeleteできなくてもnew演算子が有用である場面も想定されます。new[]演算子を定義しています。u32HeapEndも同様です。0xdeadbeefはダミーアドレスです。beefがdeadなのは変だとかいう指摘はしないでください。{}例外も使えないため失敗したときの対処はありません。メモリ容量を意識せず確保を続けた場合、スタック領域と干渉する可能性もあります。システム(MAC層など)が確保するメモリは約2.5KBです。コンテナクラスMWXライブラリでは、マイコンのリソースが小さい点、メモリーの動的確保ができない点を考慮し標準ライブラリで提供されるコンテナクラスの利用はせず、シンプルなコンテナクラスを2種類定義しています。end()メソッドを定義しているため、範囲for文やSTLのアルゴリズムの一部を利用できます。最大領域範囲内で都度サイズを指定できる利用領域(size)を管理します。演算子を用いてデータを書き込むことができます。FIFOキューを実装しています。キューのサイズはテンプレートのパラメータで決定します。割り込み禁止を用いキューを操作するためのテンプレート引数もあります。コンテナクラスのメモリについてコンテナクラスではメモリの確保方法をtemplate引数のパラメータとして指定します。確保済みのバッファメモリを指定する。Cライブラリ向けに確保したメモリ領域を管理したいとき、同じバッファ領域の分断領域として処理したい時などに使用します。クラス内に静的配列として確保する。事前にサイズが決まっていたり、一時利用の領域として使用します。ヒープ領域に確保する。システムのヒープに確保後は破棄できませんが、初期化時にアプリケーションの設定などに従い領域を確保するといった使い方に向いています。可変数引数MWXライブラリでは、バイト列やビット列の操作、printf相当の処理を行う処理に可変数引数を用いています。下記の例は指定のビット位置に1をセットする処理です。コンパイル後、以下の2つは同じ結果になります。再帰的な処理を行い引数の展開を行っています。上記の例ではconstexprの指定があるため、コンパイル時に計算が行われマクロ定義やb2のようなconst値の指定と同等の結果になります。変数を引数として動的に計算する関数としても振る舞うこともできます。以下の例では、expand_bytes関数により、受信パケットのデータ列からローカル変数に値を格納しています。パラメータパックを用いた場合各引数の型を把握できるため、下記のように、受信パケットのバイト列から、サイズや異なる型にあった値を格納することができます。パケットのペイロードはバイト列で以下のように並んでいる。timestamp);イテレータイテレータはポインタの抽象化で、例えばメモリの連続性のないようなデータ構造においても、あたかもポインタを使ったようにデータ構造にアクセスできる効果があります。C++のSTLでは、begin()メソッドで得られるコンテナの先頭を示すイテレータと、end()メソッドで得られるコンテナの末尾の「次」を示すイテレータの組み合わせが良く用いられます。イテレータのインクリメントは前置演算子を使います。高いものの、コード上はイテレータのコピーが発生します。}イテレータを標準ライブラリの仕様に適合させることで、範囲for文が利用できたり、標準ライブラリのアルゴリズムを利用できるようになります。x;}(MWXライブラリではC++標準ライブラリに対する適合度や互換性についての検証は行っていません。動作確認の上利用ください)以下の例では、通常のポインタでは連続的なアクセスができないFIFOキューのイテレータ、さらに、FIFOキューの構造体の特定メンバー(例ではX軸)のみを抽出するイテレータを利用する例です。クラスのイテレータの実装の抜粋です。このイテレータでは、キューオブジェクトの実体と、インデックスにより管理しています。[]で解決しています。オブジェクトのアドレスが一致することとインデックスが一致すればイテレータは同じものを指していることになります。含まれ、より多くのSTLのアルゴリズムが適用できるようになります。*this;}};構造体を格納したコンテナ中の、特定構造体メンバーだけアクセスするイテレータは少々煩雑です。構造体のメンバーにアクセスするメンバー関数を予め定義しておきます。(T::*get)())としたテンプレートを定義します。Iterはコンテナクラスのイテレータ型です。*この上述のメンバー関数を呼び出しています。end()のみを実装し、上記のイテレータを生成します。これで範囲for文やアルゴリズムが利用できるようになります。このクラス名は非常に長くなりソースコード中に記述するのは困難です。このクラスを生成するためのジェネレータ関数を用意します。get_axis_x(que);といった簡潔な記述になります。この軸だけを抽出するイテレータは、配列型のsmplbufクラスでも同様に利用できます。割り込み・イベント・状態ハンドラの実装ユーザ定義クラスによりアプリケーション動作を記述するため、代表的なハンドラは必須メソッドとして定義が必要ですが、それ以外に多数ある割り込みハンドラ、イベントハンドラ、ステートマシンの状態ハンドラをすべて定義するのは煩雑です。ユーザが定義したものだけ定義され、それのみのコードが実行されるのが理想です。DIO割り込みハンドラ(TWELITEハード上は単一の割り込みですが、利用しやすさのためDIO一つずつにハンドラを割り当てることにしました)などを、テンプレートによる空のハンドラーとして定義した上、ユーザ定義のメンバー関数をそのテンプレートの特殊化することにより定義する手法を採用しました。}}実際のユーザ記述コードは、マクロ化やヘッダファイルのインクルードを行うことで、簡素化されていますが、上記は解説のために必要なコードを含めています。cppファイル中では、int_dio_handler<12>のみが特殊化されて記載された内容でインスタンス化されます。12番以外はhppファイル中のテンプレートからインスタンス化されます。結局以下のように展開されることになります。結局、このように最適化されることが期待できる。break;最終的に、コンパイラの最適化により12番以外のコードは無意味と判断されコード中から消えてしまうことが期待できます(ただし、上記のように最適化されることを保証するものではありません)。呼び出す必要があります)。登録しないハンドラはコンパイル時の最適化により低コストな呼び出しで済むことが期待できます。ユーザが関数を定義したときにこれを有効にし、定義しない場合は別の関数を呼び出す手法として、リンク時の解決方法があります。関数が定義された場合は、ユーザーコードを関数をリンクし、未定義の場合は中身が空の関数をリンクします。}上記ハンドラの実装においてはweak指定したメンバー変数を明示的に生成する必要があり、またinline化による最適化が行いにくいため使用しませんが、wakeup()といったいくつかのTWENETからのコールバック関数の受け皿としてweak指定の関数を定義しています。Streamクラスストリームクラスは、主にUART(シリアルポート)の入出力に用います。MWXライブラリでは、出力用の手続きを主に定義しています。一部入力用の定義もあります。ここでは派生クラスが必要とする実装について解説します。n);}};上記は1文字書き出すwrite()メソッドの実装です。親クラスのstream<serial_jen>からはキャストを実行するget_Drived()メソッドを用いて、serial_jen::write()メソッドにアクセスしています。いったメソッドを定義します。Paland氏によるprintfライブラリを利用しています。MWXライブラリから利用するための実装が必要になります。メソッドを定義することと、vOutput()がstaticメソッドであるため出力のための補助情報を親クラスのpvOutputContextに保存することです。fp);}};get_pfcOutput()により、派生クラスで定義したvOutput()関数を指定し、そのパラメータとしてpvOutputContextが渡されます。上記の例では<<演算子がint型で呼び出されたときserial_jen::vOutput()とUART用に設定済みのTWE_tsFILE*をfctprintf()関数に渡しています。SPIのワーカーオブジェクトWireクラスでは、2線デバイスとの送信・受信時に、通信開始から終了までを管理する必要があります。ワーカーオブジェクトを利用する記述について内容を記述します。