セクションの複数ページをまとめています。 印刷またはPDF形式で保存...

もとのページに戻る

2024-11-14 現在

MWX ライブラリ

最新版

1 - 全般

MWXライブラリについて
MWX ライブラリは、TWELITE モジュールのプログラムをより容易にかつ拡張性を高めるために設計されています。これまでMWSDKで利用していた TWENET C ライブラリを基本とし、MWXライブラリはアプリケーション開発層のライブラリとして開発しております。
+-----------------------+
|   act (USER APPs)...  |
+-----------------------+
| MWX C++ LIB           |
+---------------+       |
| TWENET C LIB  |       |
+------------+----------+
| MAC LAYER  | AHI APIs |
+-----------------------+
| TWELITE HARDWARE      |
+-----------------------+

MWX ライブラリの名称は Mono Wireless C++ Library for TWELITE です。MW は MonoWireless から、また C++ -> CXX -> double X -> WX。この MW と WX を重ねて MWX になりました。 このライブラリを用いて記述したコードを「アクト(act)」と呼びます。

表記等について

本解説での表記について記載します。

auto&&

ユニバーサル参照と呼ばれ、標準ライブラリなどで良く用いられます。当ライブラリでもほとんどの場合auto&& と記載します。

名前空間について

namespace, inline namespace, using を用いて、名前の再定義などを行っています。解説中でも一部省略して記載しています。

制限事項 (TWENET)

MWXライブラリは、下層に位置する各ライブラリ・機能(TWNET Cライブラリでの機能、また半導体ベンダが提供するマイコン・ペリフェラル機能、IEEE802.15.4の機能)について、その全てに対応する目的では開発しておりません。

制限事項 (C++の利用)

MWXライブラリはC++言語で記述されておりアクトの記述においても C++ での記述を行うことになります。しかしながらC++言語であってもすべての機能が使えるわけではありません。特に以下の点に注意してください。

  • new, new[] 演算子でのメモリ確保は行えますが、確保したメモリを破棄することはできません。C++ライブラリで動的メモリ確保をするものは殆どが事実上利用不可能です。
  • グローバルオブジェクトのコンストラクタが呼び出されません。
  • 参考:必要な場合は、初期化関数(setup()) で new ((void*)&obj_global) class_foo(); のように初期化することでコンストラクタの呼び出しを含めた初期化を行えます。
  • 例外 exceptionが使用できません。
  • 仮想関数 virtual が使用できません。
  • 上記の制約があるためSTLなどC++標準ライブラリの一部のみの利用となります。

※ 当社で把握しているものについての記載です。

ライブラリのソースコードについて

ソースコードは以下から参照できます。

1.1 - ライセンス

保証・ライセンス

保証・ライセンス

本パッケージ内で、ライセンス上特別な記述のないものは、モノワイヤレスソフトウェア使用許諾契約書(MW-SLA)またはモノワイヤレスオープンソースソフトウェア使用許諾契約書(MW-OSSLA)を適用します。

本ドキュメントについても、本ライブラリパッケージ部の一部としてMW-SLA下の取り扱いとします。

本ソフトウェアについては、モノワイヤレス株式会社が正式にサポートを行うものではありません。お問い合わせにはご回答できない場合もございます。予めご了承ください。

不具合などのご報告に対してモノワイヤレス株式会社は、修正や改善をお約束するものではありません。

また導入パッケージなどお客様の環境に依存して動作しない場合もございます。

1.2 - 用語

用語解説
本資料で利用する用語について解説します。

一般的な用語

SDK (TWELITE SDK, MWSDK)

ソフトウェア開発環境

IEEE802.15.4

TWELITE無線モジュールが利用する無線規格です。MWXライブラリを使用する限り、無線規格の詳細を意識する必要はありません。

パケット

無線通信における最小の通信単位です。

最大量は通信方式や通信時の設定によって変わりますが、MWXライブラリ標準の通信<NWK_SIMPLE>では、ユーザが1パケットで送信できるデータ量は90バイトです。

ペイロード

「貨物」といった意味合いですが、無線パケットに含まれるデータ本体のことをいいます。

ノード

「点・節」といった意味合いですが、無線ネットワーク内の無線局のことを言います。

MWXライブラリ特有の用語

アクト/act

本ライブラリを用いて作成したプログラム。そのソースコードまたは動作するプログラムのことを言います。

ビヘイビア

アクトの中でも特にイベント形式のプログラム。そのソースコードまたは動作するプログラムのことを言います。

ビヘイビアは1つのクラス定義による記述で、TWENETからのコールバック関数やイベントや割り込み処理を記述しひとまとめにしています。MWXライブラリでは以下の3種類のビヘイビアがあります。

  • アプリケーションビヘイビア:イベントドリブンでのアプリケーション記述を行い、ユーザが定義するクラス。
  • ボードビヘイビア:TWELITE無線モジュールを実装するボードの機能利用を簡素化するためのクラス。
  • ネットワークビヘイビア:無線ネットワークの手続きを簡素化するためのクラス。

ビヘイビア名は < > で括って表記します。例えばシンプル中継ネットワークのビヘイビア名は <NWK_SIMPLE> です。

クラスオブジェクト

本ライブラリの解説では、ライブラリで最初からグローバル宣言されたオブジェクトをクラスオブジェクトと呼称します。Serial, Wire などです。これらクラスオブジェクトは手続きなしまたは開始手続きを行うことで利用できます。

メモリを比較的多く消費するクラスオブジェクトは、初期化手続きの際(.setup()または.begin()メソッド)に初期化パラメータに沿ったメモリを確保します。

C++に関する用語

C++

C++言語のこと。

C++11

C++規格のバージョンの一つ。2011年式のC++といった意味合いで、2011年にISOで規格化されています。直前のC++03から大きく機能拡張されています。C++14, C++17といったより新しいバージョンがあります。

クラス

あるデータに注目して、その手続きをひとまとめにしたもの。構造体に、その構造体を取り扱うための手続きが含まれています。実際にはもっと深い話題に発展しますが、専門書を参考にしてください。

C++においては、キーワードの structclassは本質的には同じもので、いずれのキーワードで宣言してもクラスとなります。

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); }
};

上記のクラス定義をC言語でも行った場合、例えば以下のようになります。

typedef struct _c_myhello {
  int _i;
  void (*pf_say_hello)(struct _c_myhello *);
} c_myhello;

void say_hello(c_myhello*p) { p->pf_say_hello(); }
void init_c_my_hello(c_myhello*p) {
  p->pf_say_hello = say_hello;
}

ラッパークラス

既存のC言語のライブラリやその内部の構造体などをクラスに包含し、C++特有の機能性を追加するなどして、利用の便を図ったものです。解説中でも「~構造体をラップした」といった記述をする場合があります。

メソッド/メンバ関数

クラスに定義される関数で、クラスに紐付いています。

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); } //メソッド
};

オブジェクト/インスタンス

クラスを実体化(メモリ確保)したもの。

void func() {
    myhello obj_hello; // obj_helloがmyhelloクラスのオブジェクト
    obj_hello._i = 10;
    obj_hello.say_hello();
}

本解説ではオブジェクトとインスタンスは同じ意味合いとして取り扱っています。

コンストラクタ

オブジェクト生成時の初期化手続き。

struct myhello {
  int _i;
  void say_hello() { printf("hello %d\n", _i); }

  myhello(int i = 0) : _i(i) {} // コンストラクタ
};

void my_main() {
  myhello helo(10); // ここでコンストラクタが呼び出され_i=10にセットされる
}

デストラクタ

コンストラクタと対になってオブジェクトが破棄されるときの手続きです。

struct myhello {
  int _i;
  void say_hello() { printf("hello! %d\n", _i); }

  myhello(int i = 0) : _i(i) {} // コンストラクタ
  ~myhello() {
    printf("good bye! %d\n", _i);
  } //デストラクタ
};

抽象クラス

C++では仮想クラスによりポリモーフィズム(多態性)を実現します。具体的にはvirtualキーワードで指定た純粋仮想関数を定義したクラスです。

struct Base {
  virtual void say_hello() = 0;
};

struct DeriveEng : public Base {
  void say_hello() { printf("Hello!"); }
};

struct DeriveJpn : public Base {
  void say_hello() { printf("Kontiwa!"); }
};

スコープ

C/C++言語では { } で括った範囲と考えてください。この中で生成したオブジェクトは、スコープから出るときに破棄されます。この時デストラクタが呼び出されます。

以下は、明示的にスコープを設定したものです。helo2は8行目まで実行された時点で破棄され、デストラクタが呼び出されます。

void my_main() {
  myhello helo1(1);
  helo1.say_hello();

  {
    myhello helo2(2);
    helo2.say_hello();
  }
}

// hello! 1
// hello! 2
// good bye! 2
// good bye! 1

MWXライブラリでは以下のような記法を用いています。ここではif文の条件判定式内で宣言(C89といった旧いC言語ではこういった場所での宣言はできません)されたオブジェクトの有効期間は、if文の{}内になります。

struct myhello {
  int _i;
  void say_hello() { printf("hello! %d\n", _i); }
  operator bool() { return true; } // if()での判定用の演算子

  myhello(int i = 0) : _i(i) {} // コンストラクタ
  ~myhello() { printf("good bye! %d\n", _i); } // コンストラクタ
};

// myhello オブジェクトを生成する関数 (ジェネレータ)
myhello gen_greeting() { return my_hello(); }

void my_main() {
  if (myhello x = gen_greeting()) {
    // myhelloのオブジェクト x は if文中有効
    x.say_hello();
  }
  // if 分を抜けるときにオブジェクトxは破棄される
}

例えば二線シリアルバスなど、開始と終了の手続きがあって、その間だけオブジェクトによってバスを操作するような手続きです。オブジェクトの生成後、バスの接続が適切であればif文のtrue節が実行され、生成したオブジェクトによってバスの書き込みまたは読み出しを行います。バスの読み書き操作が終了したらif文を脱出し、この時デストラクタが呼び出され、バスの利用終了手続きが行われます。

const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) { //バスの初期化、接続判定
	wrt(SHTC3_TRIG_H); // 書き出し
	wrt(SHTC3_TRIG_L);
} // バスの利用終了手続き

名前空間 (namespace)

定義名の重複を避けるためC++では名前空間が積極的に用いられます。名前空間にある定義にアクセスするには::を用います。

namespace MY_NAME { // 名前空間の宣言
  const uint8_t MYVAL1 = 0x00;
}

...
void my_main() {
  uint8_t i = MY_NAME::MYVAL1; // MY_NAME の参照
}

テンプレート (template)

テンプレートはC言語のマクロを拡張したものと考えてください。

template <typename T, int N>
class myary {
  T _buf[N];
public:
  myary() : _buf{} {}
  T operator [] (int i) { return _buf[i % N]; }
};

myary<int, 10> a1; // int 型で要素数10の配列
myary<char, 128> a2; // char 型の要素数128の配列

この例では、簡単な配列を定義しています。TNはテンプレートのパラメータで、Tは型名をNは数値を指定し、T型で要素数Nの配列クラスを定義しています。

nullptr

C++11ではNULLポインタをnullptrと記述するようになりました。なお NULL0 を表すマクロですが、nullptr は多くの場合 0 とは異なる実体を持ちます。

参照型

C++では、参照型を利用できます。これはポインタによるアクセスに似ていますが、必ずオブジェクトを参照しなければならないという制約があります。

以下のような参照渡しのパラメータを持つ関数ではiの値をincr()内で書き換えることが出来ます。

void incr(int& lhs, int rhs) { lhs += rhs; }

void my_main() {
  int i = 10; j = 20;
  incr(i, j);
}

テンプレートの解説例ですがoperator[]の戻り型をT&に変更しています。こうすることでa[0]=1のように配列内部のデータに対して直接代入操作ができるようになります。

template <typename T, int N>
class myary {
  T _buf[N];
public:
  myary() : _buf{} {}
  T& operator [] (int i) { return _buf[i % N]; }
};

myary<int, 10> a1;
void my_main() {
  a1[0] = 1;
  a1[1] = 2;
}

型推論

C++11 では型推論のautoキーワードが導入されています。これはコンパイラが初期化の記述からそのオブジェクトの型を推論するため、具体的な型名の記述を省略できます。これはtemplateを用いたクラス名が非常に長くなるような場合に効果的です。

解説中では多くの場合ユニバーサル参照と呼ばれるauto&&を用いています。ユニバーサル参照については、ここでは参照渡しの場合も意識せずに記述できるものと考えてください。

auto&& p = std::make_pair("HELLO", 5);
       // const char* と int のペア std::pair

コンテナ

配列など特定のデータ型のオブジェクトを複数個格納するためのクラスをコンテナと呼びます。テンプレートの例で挙げたmyaryのような配列クラスもコンテナと呼んでいます。

イテレータ, .begin(), .end()

C言語で言うところのポインタ(もちろんC++でも同じようにポインタは使えます)を拡張した概念です。C言語のポインタは、メモリが連続した要素を先頭から末尾まで連続的にアクセスする手段と考えることが出来ます。FIFOキューを考えてみます。もっとも単純なキューの実装はリングバッファによるものですが、メモリーの連続性はありません。こういったデータ構造であっても、イテレータを用いるとポインタと同じように記述できます。

イテレータを取得するため.begin(),.end() のメソッドが用いられます。コンテナの先頭を指すイテレータを.begin()で取得します。末尾の次を指すイテレータを.end()で取得します。末尾ではなく、末尾の次である理由にはforwhile文のループ記述の明快さ、コンテナに格納される要素数が0の場合の取り扱いが挙げられます。

my_queue que; // my_queue はキューのクラス

auto&& p = que.begin();
auto&& e = que.end();

while(p != e) {
  some_process(*p);
  ++p;
}

上記では、queの各要素について、イテレータpを用いて各要素にsome_process()を適用しています。p++演算子によって次の要素を指すイテレータとしてインクリメントしています。本来ポインタでは記述できないデータ構造を持つコンテナであっても、このようにポインタを用いた処理と同じような処理が出来ます。

.end()が末尾の次を示すため、while文の終了判定は(p != e)のように簡潔です。キューに要素がない場合は.begin().end()と同じイテレータを返します。(何も格納されていない要素のイテレータの次ですから、最初に格納すべき領域を示すイテレータと考えればよいでしょう)

メモリ上で連続したコンテナの場合、通常、そのイテレータは通常のポインタとなります。その操作時に大きなオーバーヘッドにはならないことが期待できます。

C++標準ライブラリ

C++の標準ライブラリにはSTL(Standard Template Library)が含まれます。MWXライブラリでの一部を利用しています。

アルゴリズム

例えば最大や最小値を求めるといった処理をC言語では型に応じて別々に記述していました。こういったコードは型の部分だけ違って他は同じといったものも少なくありません。C++ではtemplateやイテレータなどを用いて、こういった処理を型に依存せず記述することができます。これをアルゴリズムと呼んでいます。

// 任意のイテレータをパラメータとし最大値を持つイテレータを戻す
template <class Iter>
Iter find_max(Iter b, Iter e) {
  Iter m = b; ++b;
  while(b != e) {
    if (*b > *m) { m = b; }
    ++b;
  }
  return m;
}

例えば上記のように最大値を求めるアルゴリズムです。このアルゴリズムは型に依存しません。(ジェネリックプログラミングと呼ばれます)

#include <algorithm>

auto&& minmax = std::minmax_element( // 最大最小を得るアルゴリズム
  que.begin(), que.end());

auto&& min_val = *minmax.first;
auto&& max_val = *minmax.second;

ここではqueのイテレータを指定し、その最大と最小を得るアルゴリズムstd::minmax_elenetを適用しています。std::minmax_elemetはC++標準ライブラリ内に定義されています。その戻り値は任意の2つの値を組み合わせるstd::pairです。このアルゴリズムは、イテレータの示す要素同士で<,>,==といった演算子での比較が出来れば最大と最小を計算してくれます。戻り型もイテレータの型から導かれます。

1.3 - 設計情報

設計情報
MWX ライブラリ内で用いる C++ 言語について、その仕様、制限事項、本書記載留意事項、設計メモを記載します。

設計方針について

  • アプリケーションのループ記述では、一般によく用いられる API 体系に近い記述を出来ることを目的とするが、TWELITEの特性に合わせた実装とする。
  • TWENET はイベントドリブンによるコード記述であり、これを扱えるようにクラス化を行う。上記クラス化によりアプリケーションのふるまいをカプセル化できるようにする。
  • イベントドリブンとループの記述は共存できる形とする。
  • 代表的なペリフェラルはクラス化して手続きを簡素化する。可能な限りループ記述でアクセスできるようにする。
  • 当社で販売する MONOSTICK/PAL といったボードを利用する手続きをクラス化し手続きを簡素化する。(例えば外部のウォッチドッグタイマーの利用を自動化する)
  • アプリケーションクラスやボードクラスは、ポリモーフィズムの考え方を導入し、統一した手続きによって利用できるようにする。(例えば、いくつかの振る舞いをするアプリケーションクラスを始動時にロードするような場合、また TWENET C ライブラリの接続部のコードを都度定義しなくてよいようにするため)。
  • C++の機能については、特に制限を設けず利用する。例えば、無線パケットを取り扱うにあたり煩雑なパケット構築、分解といった代表的な手続きを簡略化する手段を提供する。
  • 演算子 -> を極力使用しないようにし、原則として参照型による API とする。

C++ コンパイラについて

バージョン

gcc version 4.7.4

C++言語規格

C++11 (コンパイラ対応状況は一般の情報を参考にしてください)

C++ の制限事項

※ 当社で把握しているものについての記載です。

  • 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で行っています。

// at some header file.
namespace mwx {
  inline namespace L1 {
    class foobar {
      // class definition...
    };
  }
}

// at using_mwx_def.hpp
using namespace mwx::L1; // mwx::L1 内の定義は mwx:: なしでアクセスできる
                         // しかし mwx::L2 は mwx:: が必要。

例外的に比較的短い名前についてはmwx::crlf, mwx::flushのように指定します。これらはinline namespacemwx::L2の名前空間に配置されています。using namespace mwx::L2;を指定することで名前空間名の指定なしで利用できるようになります。

また、いくつかのクラス名はusing指定をしています。

MWXライブラリ内で利用するstd::make_pairusing指定しています。

CRTP(奇妙に再帰したテンプレートパターン)

仮想関数 (virtual), 実行時型情報(RTTI) が利用できない、かつ利用できるようにしたとしても、パフォーマンス面で難があるため、これに代わる設計手法として CRTP(Curiously recurring template pattern : 奇妙に再帰したテンプレートパターン)を用いています。CRTPは、継承元の親クラスから子クラスのメソッドを呼び出すためのテンプレートパターンです。

以下の例では Base を継承した Derived クラスに interface() というインタフェースを実装する例です。BaseからはDerived::print()メソッドを呼び出しています。

template <class T>
class Base {
public:
  void intrface() {
    T* derived = static_cast<T*>(this);
    derived->prt();
  }
};

class Derived : public class Base<Derived> {
  void prt() {
     // print message here!
     my_print("foo");
  }
}

MWXライブラリで利用されている主要クラスは以下です。

  • イベント処理の基本部分mwx::appdefs_crtp
  • ステートマシンpublic mwx::processev_crtp
  • ストリーム mwx::stream

CRTP での仮想化

CRTPクラスは、継承元のクラスはインスタンスごとに違います。このため、親クラスにキャストして、同じ仲間として取り扱うといったこともできませんし、仮想関数(virtual)やRTTI(実行時型情報)を用いたような高度なポリモーフィズムも使うことが出来ません。

以下は上述のCRTPの例を、仮想関数で実装した例です。CRTPではBase* b[2]のように同じ配列にインスタンスをまとめて管理することは、そのままではできません。

class Base {
	virtual void prt() = 0;
public:
	void intrface() { prt(); }
};

class Derived1 : public Base {
	void prt() { my_print("foo"); }
};

class Derived2 : public Base {
	void prt() { my_print("bar"); }
};

Derived1 d1;
Derived2 d2;
Base* b[2] = { &d1, &d2 };

void tst() {
	for (auto&& x : b) { x->intrface(); }
}

MWXライブラリでは、CRTP のクラスインスタンスを格納するための専用クラスを定義し、このクラスに同様のインタフェースを定義することで解決しています。以下にコード例を挙げます。

class VBase {
public:
	void* p_inst;
	void (*pf_intrface)(void* p);

public:
	void intrface() {
		if (p_inst != nullptr) {
			pf_intrface(p_inst);
		}
	}
};

template <class T>
class Base {
	friend class VBase;
	static void s_intrface(void* p) {
		T* derived = static_cast<T*>(p);
		derived->intrface();
	}
public:
	void intrface() {
		T* derived = static_cast<T*>(this);
		derived->prt();
	}
};

class Derived1 : public Base<Derived1> {
	friend class Base<Derived1>;
	void prt() { my_print("foo"); }
};

class Derived2 : public Base<Derived2> {
	friend class Base<Derived2>;
	void prt() { my_print("bar"); }
};

Derived1 d1;
Derived2 d2;

VBase b[2];

void tst() {
	b[0] = d1;
	b[1] = d2;

	for (auto&& x : b) {
		x.intrface();
	}
}

VBase クラスのメンバ変数 p_inst は、Base <T> 型のオブジェクトへのポインタを格納し、pf_intrfaceBase<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 型から元のDerived1Derived2への変換を行うことも、強制的なキャストにより可能ですが、void*で格納されたポインタの型を直接知る方法はありません。完全に安全な方法はないものの、以下のようにクラスごとに一意のID(TYPE_ID)を設けて、キャスト実行時(get()メソッド)にIDのチェックを行うようにしています。違う型を指定して get()メソッドを呼び出したときは、エラーメッセージを表示するといった対処になります。

Base<T>型としてのポインタが格納されるとT型に正しく変換できない可能性(Tが多重継承している場合など)あるため、<type_trails>is_base_ofによりBase<T>型の派生であることをコンパイル時に static_assertによって判定しています。

#include <type_trails>

class Derived1 : public Base<Derived1> {
public:
   static const uint8_t TYPE_ID = 1;
}

class Derived1 : public Base<Derived1> {
public:
   static const uint8_t TYPE_ID = 2;
}

class VBase {
  uint8_t type_id;
public:

	template <class T>
	void operator = (T& t) {
		static_assert(std::is_base_of<Base<T>, T>::value == true,
						"is not base of Base<T>.");

		type_id = T::TYPE_ID;
		p_inst = &t;
		pf_intrface = T::s_intrface;
	}

  template <class T>
  T& get() {
    static_assert(std::is_base_of<Base<T>, T>::value == true,
					  "is not base of Base<T>.");

		if(T::TYPE_ID == type_id) {
			return *reinterpret_cast<T*>(p_inst);
		} else {
			// panic code here!
		}
  }
}

Derived1 d1;
Derived2 d2;

VBase b[2];

void tst() {
	b[0] = d1;
	b[1] = d2;

  Derived1 e1 = b[0].get<Derived1>(); // OK
  Derived2 e2 = b[1].get<Derived2>(); // OK

  Derived2 e3 = b[1].get<Derived1>(); // PANIC!
}

new, new[] 演算子

TWELITEモジュールのマイコンには十分なメモリもなければ、高度なメモリ管理もありません。しかしマイコンのメモリマップの末尾からスタックエリアまでの領域はヒープ領域として、必要に応じて確保できる領域があります。以下にメモリマップの概要を図示します。APPがアプリケーションコードで確保されたRAM領域、HEAPはヒープ領域、STACKはスタック領域です。

|====APP====:==HEAP==..   :==STACK==|
0                                  32KB

たとえdeleteできなくてもnew演算子が有用である場面も想定されます。そのため、以下のようにnew, new[]演算子を定義しています。pvHear_Alloc()は半導体ライブラリで提供されているメモリ確保の関数で、u32HeapStart, u32HeapEndも同様です。0xdeadbeefはダミーアドレスです。

beefdeadなのは変だとかいう指摘はしないでください。

void* operator new(size_t size) noexcept {
    if (u32HeapStart + size > u32HeapEnd) {
        return (void*)0xdeadbeef;
    } else {
        void *blk = pvHeap_Alloc(NULL, size, 0);
        return blk;
    }
}
void* operator new[](size_t size) noexcept {
    return operator new(size); }
void operator delete(void* ptr) noexcept {}
void operator delete[](void* ptr) noexcept {}

例外も使えないため失敗したときの対処はありません。また、メモリ容量を意識せず確保を続けた場合、スタック領域と干渉する可能性もあります。

コンテナクラス

MWXライブラリでは、マイコンのリソースが小さい点、メモリーの動的確保ができない点を考慮し標準ライブラリで提供されるコンテナクラスの利用はせず、シンプルなコンテナクラスを2種類定義しています。コンテナクラスにはイテレータやbegin(), end()メソッドを定義しているため、範囲for文やSTLのアルゴリズムの一部を利用できます。

smplbuf<int16_t, alloc_local<int16_t, 16>> buf;
buf.push_back(-1); // push_back() は末尾に追加
buf.push_back(2);
...
buf.push_back(10);

//範囲for文
for(auto&& x : buf) { Serial << int(x) << ',' }
//アルゴリズム std::minmax
auto&& minmax = std::minmax_element(buf.begin(), buf.end());
Serial << "Min=" << int(*minmax.first)
       << ",Max=" << int(*minmax.second);
クラス名概要
smplbuf配列クラスで、最大領域 (capacity) と最大領域範囲内で都度サイズを指定できる利用領域(size)を管理します。また本クラスは stream インタフェースを実装しているため、<< 演算子を用いてデータを書き込むことができます。
smplqueFIFOキューを実装しています。キューのサイズはテンプレートのパラメータで決定します。割り込み禁止を用いキューを操作するためのテンプレート引数もあります。

コンテナクラスのメモリについて

コンテナクラスではメモリの確保方法をtemplate引数のパラメータとして指定します。

クラス名概要
alloc_attachすでに確保済みのバッファメモリを指定する。Cライブラリ向けに確保したメモリ領域を管理したいとき、同じバッファ領域の分断領域として処理したい時などに使用します。
alloc_staticクラス内に静的配列として確保する。事前にサイズが決まっていたり、一時利用の領域として使用します。
alloc_heapヒープ領域に確保する。システムのヒープに確保後は破棄できませんが、初期化時にアプリケーションの設定などに従い領域を確保するといった使い方に向いています。

可変数引数

MWXライブラリでは、バイト列やビット列の操作、printf相当の処理を行う処理に可変数引数を用いています。下記の例は指定のビット位置に1をセットする処理です。

// packing bits with given arguments, which specifies bit position.
//   pack_bits(5, 0, 1) -> (b100011) bit0,1,5 are set.

// 再帰取り出しの一番最初の関数
template <typename Head>
constexpr uint32_t pack_bits(Head head) { return  1UL << head; }

// head を取り出し、残りのパラメータを再帰呼び出しにて pack_bits に転送
template <typename Head, typename... Tail>
constexpr uint32_t pack_bits(Head head, Tail&&... tail) {
  return (1UL << head) | pack_bits(std::forward<Tail>(tail)...);
}

// コンパイル後、以下の2つは同じ結果になります。
constexpr uint32_t b1 = pack_bits(1, 4, 0, 8);
// b1 and b2 are the same!
const uint32_t b2 = (1UL << 1)|(1UL << 4)|(1UL << 0)|(1UL << 8);

この処理では template のパラメータパック (typename... の部分) で、再帰的な処理を行い引数の展開を行っています。上記の例ではconstexprの指定があるため、コンパイル時に計算が行われマクロ定義やb2のようなconst値の指定と同等の結果になります。また変数を引数として動的に計算する関数としても振る舞うこともできます。

以下の例では、expand_bytes関数により、受信パケットのデータ列からローカル変数に値を格納しています。パラメータパックを用いた場合各引数の型を把握できるため、下記のように、受信パケットのバイト列から、サイズや異なる型にあった値を格納することができます。

auto&& rx = the_twelite.receiver.read(); // 受信パケット

// 展開後のパケットの内容を格納する変数
// パケットのペイロードはバイト列で以下のように並んでいる。
//   [B0][B1][B2][B3][B4][B5][B6][B7][B8][B9][Ba][Bb]
//   <message       ><adc*  ><vcc*  ><timestamp*    >
//   * 数値型はビッグエンディアン並び
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;

// expand packet payload
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, msg       // 4bytes of msg
		, adcval    // 2bytes, A1 value [0..1023]
	  , volt      // 2bytes, Module VCC[mV]
	  , timestamp // 4bytes of timestamp
);

イテレータ

イテレータはポインタの抽象化で、例えばメモリの連続性のないようなデータ構造においても、あたかもポインタを使ったようにデータ構造にアクセスできる効果があります。

以下の例では、通常のポインタでは連続的なアクセスができないFIFOキューのイテレータ、さらに、FIFOキューの構造体の特定メンバー(例ではX軸)のみを抽出するイテレータを利用する例です。

// XYZTの4軸構造体を要素とする要素数5のキュー
smplque<axis_xyzt, alloc_local<axis_xyzt, 5> > que;

// テスト用にデータを投入
que.push(axis_xyzt(1, 2, 3, 4));
que.push(axis_xyzt(5, 2, 3, 4));
...

// 構造体としてのイテレータを用いたアクセス
for (auto&& e : v) { Serial << int(e.x) << ','; }

// キューの中の X 軸を取り出す
auto&& vx = get_axis_x(que);
// X軸のイテレータを用いたアクセス
for (auto&& e : vx) { Serial << int(e) << ','; }

// int16_t要素のイテレータなので、STLのアルゴリズム(最大最小)が使える
auto&& minmax = std::minmax_element(vx.begin(), vx.end());

以下は smplque クラスのイテレータの実装の抜粋です。このイテレータでは、キューオブジェクトの実体と、インデックスにより管理しています。キューのメモリが不連続になる(末尾の次は先頭を指す必要があるリングバッファ構造)部分はsmplque::operator []で解決しています。オブジェクトのアドレスが一致することとインデックスが一致すればイテレータは同じものを指していることになります。

この実装部分には <iterator> が要求する typedef なども含まれ、より多くのSTLのアルゴリズムが適用できるようになります。

class iter_smplque {
	typedef smplque<T, alloc, INTCTL> BODY;

private:
	uint16_t _pos; // index
	BODY* _body;   // point to original object

public: // for <iterator>
	typedef iter_smplque self_type;
	typedef T value_type;
	typedef T& reference;
	typedef T* pointer;
	typedef std::forward_iterator_tag iterator_category;
	typedef int difference_type;

public: // pick some methods
	inline reference operator *() {
		return (*_body)[_pos];
	}

	inline self_type& operator ++() {
		_pos++;
		return *this;
	}
};

構造体を格納したコンテナ中の、特定構造体メンバーだけアクセスするイテレータは少々煩雑です。構造体のメンバーにアクセスするメンバー関数を予め定義しておきます。このメンバー関数をパラメータ(R& (T::*get)())としたテンプレートを定義します。Iterはコンテナクラスのイテレータ型です。

struct axis_xyzt {
    int16_t x, y, z;
    uint16_t t;
    int16_t& get_x() { return x; }
    int16_t& get_y() { return y; }
    int16_t& get_z() { return z; }
};

template <class Iter, typename T, typename R, R& (T::*get)()>
class _iter_axis_xyzt {
    Iter _p;

public:
    inline self_type& operator ++() {
        _p++;
        return *this; }

    inline reference operator *() {
        return (*_p.*get)(); }
};

template <class Ixyz, class Cnt>
class _axis_xyzt_iter_gen {
    Cnt& _c;

public:
    _axis_xyzt_iter_gen(Cnt& c) : _c(c) {}
    Ixyz begin() { return Ixyz(_c.begin()); }
    Ixyz end() { return Ixyz(_c.end()); }
};

// 長いので using で短縮
template <typename T, int16_t& (axis_xyzt::*get)()>
using _axis_xyzt_axis_ret = _axis_xyzt_iter_gen<
    _iter_axis_xyzt<typename T::iterator, axis_xyzt, int16_t, get>, T>;

// X 軸を取り出すジェネレータ
template <typename T>
_axis_xyzt_axis_ret<T, &axis_xyzt::get_x>
get_axis_x(T& c) {
    return _axis_xyzt_axis_ret<T, &axis_xyzt::get_x>(c);
}

値にアクセスするoperator *この上述のメンバー関数を呼び出しています。(*_paxis_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クラスでも同様に利用できます。

割り込み・イベント・状態ハンドラの実装

ユーザ定義クラスによりアプリケーション動作を記述するため、代表的なハンドラは必須メソッドとして定義が必要ですが、それ以外に多数ある割り込みハンドラ、イベントハンドラ、ステートマシンの状態ハンドラをすべて定義するのは煩雑です。ユーザが定義したものだけ定義され、それのみのコードが実行されるのが理想です。

class my_app_def {
public: // 必須メソッドの定義
	void network_event(twe::packet_ev_nwk& pEvNwk) {}
	void receive(twe::packet_rx& rx) {}
	void transmit_complete(twe::packet_ev_tx& pEvTx) {}
	void loop() {}
	void on_sleep(uint32_t& val) {}
	void warmboot(uint32_t& val) {}
	void wakeup(uint32_t& val) {}

public: // これらを必須記述とするのは煩雑
  // DIO割り込みハンドラ 20種類ある
  // DIOイベントハンドラ 20種類ある
  // タイマー割り込みハンドラ 5種類ある
  // タイマーイベントハンドラ 5種類ある
  // ...
}

MWXライブラリでは、数の多い DIO割り込みハンドラ(TWELITEハード上は単一の割り込みですが、利用しやすさのためDIO一つずつにハンドラを割り当てることにしました)などを、テンプレートによる空のハンドラーとして定義した上、ユーザ定義のメンバー関数をそのテンプレートの特殊化することにより定義する手法を採用しました。

// hpp file
class my_app_def : class app_defs<my_app_def>, ... {
  // 空のハンドラ
  template<int N> void int_dio_handler(uint32_t arg, uint8_t& handled) { ; }

  ...
  // 12番だけ実装する

public:
  // TWENET から呼び出されるコールバック関数
  uint8 cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap);
};

// cpp file
template <>
void my_app_def::int_dio_handler<12>(uint32_t arg, uint8_t& handled) {
  digitalWrite(5, LOW);
  handled = true;
  return;
}

void cbTweNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap) {
  uint8_t b_handled = FALSE;
  switch(u32DeviceId) {
  	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 0)){int_dio_handler<0>(0, b_handled);}
      if (u32ItemBitmap & (1UL << 1)){int_dio_handler<1>(1, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 12)){int_dio_handler<12>(12, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 19)){int_dio_handler<19>(19, b_handled);}
    break;
  }
}

実際のユーザ記述コードは、マクロ化やヘッダファイルのインクルードを行うことで、簡素化されていますが、上記は解説のために必要なコードを含めています。

TWENETからの割り込みハンドラからmy_app_def::cbTweNet_u8HwInt() が呼び出されます。cppファイル中では、int_dio_handler<12>のみが特殊化されて記載された内容でインスタンス化されます。12番以外はhppファイル中のテンプレートからインスタンス化されます。結局以下のように展開されることになります。

  	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 0)){;}
      if (u32ItemBitmap & (1UL << 1)){;}
      ...
      if (u32ItemBitmap & (1UL << 12)){
          int_dio_handler<12>(12, b_handled);}
      ...
      if (u32ItemBitmap & (1UL << 19)){;}
      break;

    // ↓ ↓ ↓

    // 結局、このように最適化されることが期待できる。
   	case E_AHI_DEVICE_SYSCTRL:
      if (u32ItemBitmap & (1UL << 12)){
        // int_dio_handler<12> もinline展開
        digitalWrite(5, LOW);
        handled = true;
      }
      break;

最終的に、コンパイラの最適化により12番以外のコードは無意味と判断されコード中から消えてしまうことが期待できます(ただし、上記のように最適化されることを保証するものではありません)。

つまりユーザコード上では12番の割り込み時の振る舞いを定義したいときはint_dio_handler<12> を記述するだけで良い、ということになります(注:DIO割り込みを有効にするには attachInterrupt() を呼び出す必要があります)。登録しないハンドラはコンパイル時の最適化により低コストな呼び出しで済むことが期待できます。

Streamクラス

ストリームクラスは、主にUART(シリアルポート)の入出力に用います。MWXライブラリでは、出力用の手続きを主に定義しています。一部入力用の定義もあります。

ここでは派生クラスが必要とする実装について解説します。

template <class D>
class stream {
protected:
	void* pvOutputContext; // TWE_tsFILE*
public:
  inline D* get_Derived() { return static_cast<D*>(this); }
	inline D& operator << (char c) {
		get_Derived()->write(c);
		return *get_Derived();
	}
};

class serial_jen : public mwx::stream<serial_jen> {
public:
 	inline size_t write(int n) {
		return (int)SERIAL_bTxChar(_serdef._u8Port, n);
	}
};

上記は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に保存することです。

template <class D>
class stream {
protected:
	void* pvOutputContext; // TWE_tsFILE*
public:
	inline tfcOutput get_pfcOutout() { return get_Derived()->vOutput; }

	inline D& operator << (int i) {
		(size_t)fctprintf(get_pfcOutout(), pvOutputContext, "%d", i);
		return *get_Derived();
	}
};

class serial_jen : public mwx::stream<serial_jen> {
	using SUPER = mwx::stream<serial_jen>;
	TWE_tsFILE* _psSer; // シリアル出力のためのローレベル構造体
public:
  void begin() {
    SUPER::pvOutputContext = (void*)_psSer;
  }

	static void vOutput(char out, void* vp) {
		TWE_tsFILE* fp = (TWE_tsFILE*)vp;
		fp->fp_putc(out, fp);
	}
};

get_pfcOutput()により、派生クラスで定義したvOutput()関数を指定し、そのパラメータとしてpvOutputContextが渡されます。上記の例では<<演算子がint型で呼び出されたときserial_jen::vOutput()とUART用に設定済みのTWE_tsFILE*fctprintf()関数に渡しています。

Wire, SPIのワーカーオブジェクト

Wireクラスでは、2線デバイスとの送信・受信時に、通信開始から終了までを管理する必要があります。ワーカーオブジェクトを利用する記述について内容を記述します。

if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
	Serial << "{I2C SHTC3 connected.";
	wrt << SHTC3_TRIG_H;
	wrt << SHTC3_TRIG_L;
	Serial << " end}";
}

periph_twowire::writer クラスの抜粋です。streamインタフェースを実装するために mwx::stream<writer> を継承しています。steamインタフェースを利用するために write()vOutput()メソッドの実装を行っています。

コンストラクタでは2線シリアルの通信開始を、デストラクタで通信終了のメソッドを呼び出しています。また、operator bool()演算子では、2線シリアルのデバイスの通信開始に成功した場合 true を返すようになっています。

class periph_twowire {
public:
	class writer : public mwx::stream<writer> {
		friend class mwx::stream<writer>;
		periph_twowire& _wire;

	public:
		writer(periph_twowire& ref, uint8_t devid) : _wire(ref) {
	  	_wire.beginTransmission(devid); // コンストラクタで通信開始
		}

		~writer() {
			_wire.endTransmission(); // デストラクタで通信終了
		}

		operator bool() {
			return (_wire._mode == periph_twowire::MODE_TX);
		}

	private: // stream interface
		inline size_t write(int n) {
			return _wire.write(val);
		}

		// for upper class use
		static void vOutput(char out, void* vp) {
			periph_twowire* p_wire = (periph_twowire*)vp;
			if (p_wire != nullptr) {
				p_wire->write(uint8_t(out));
			}
		}
	};

public:
	writer get_writer(uint8_t address) {
		return writer(*this, address);
	}
};
class periphe_twowire Wire; // global instance

// ユーザコード
if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
	Serial << "{I2C SHTC3 connected.";
	wrt << SHTC3_TRIG_H;
	wrt << SHTC3_TRIG_L;
	Serial << " end}";
}

get_writer()メソッドによりオブジェクトwrtを生成します。この時にオブジェクトのコピーは通常発生しません。C++コンパイラのRVO(Return Value Optimization)という最適化により、writerwrtに直接生成されるためコピーは発生せず、コンストラクタで実行されているバスの初期化を多重に行ったりすることはありません。ただしRVOはC++の仕様では保証されておらず、念のためMWXライブラリ中ではコピー、代入演算子の削除、moveコンストラクタを定義しています(moveコンストラクタが評価される可能性はないと考えられますが)。

if節の中の wrtは、まずコンストラクタにより初期化され同時に通信開始します。通信開始でエラーがなければ、条件判定時のbool演算子がtrueを返し、if節スコープ内の処理が行われます。スコープを脱出するとデストラクタにより、2線シリアルバスの利用終了処理を行います。通信の相手先がない場合は falseが戻り、wrtオブジェクトは破棄されます。

Wire, SPI特有の定義としてoperator << (int)の定義をオーバーライドしています。ストリームのデフォルトの振る舞いは、数値を文字列に変換して出力するのですが、WireSPIで数値文字列をバスに書き込むことは稀で、反対に設定値など数値型のリテラルをそのまま入力したいことが多いのですが、数値型リテラルは多くの場合int型として評価されるため、この振る舞いを変更します。

			writer& operator << (int v) {
				_wire.write(uint8_t(v & 0xFF));
				return *this;
			}

ここではint型の値については8bitに切り詰めて、その値を出力しています。

2 - 定義

ライブラリ中で共通的に読み込まれる定義

ライブラリ中で共通的に読み込まれる定義について、定義内容を引用します。

mwx_common.h

##include <cstdint> // for type name
typedef char char_t;
typedef uint8_t byte;
typedef uint8_t boolean;

##ifndef NULL
##define NULL nullptr
##endif

3 - クラスオブジェクト

ネットワークやペリフェラルを操作するために定義されたオブジェクト

クラスオブジェクトは、MWXライブラリであらかじめ定義されたオブジェクトで、TWENETを取り扱うthe_twelite、ペリフェラルの利用のためのオブジェクトが定義されています。

各オブジェクトは.setup(), .begin()メソッドの呼び出しを行って初期化する必要があります。

(UART0を利用するSerialオブジェクトのみ初期化は必要ありません)

3.1 - the_twelite

TWENET 利用の中核クラス (mwx::twenet)
the_tweliteオブジェクトは、TWENETの利用手続きをまとめたもので、無線の基本設定やスリープ等の手続きなど無線マイコンを操作するための手続きが含まれます。

概要

the_twelitesetup()関数内で設定と開始the_twelite.begin()を行います。setup()以外では設定は行えません。

void setup() {
  ...
 	the_twelite
		<< TWENET::appid(APP_ID)
		<< TWENET::channel(CHANNEL)
		<< TWENET::rx_when_idle();
  ...
  the_twelite.begin();
}

上記の例では、アプリケーションIDの設定、通信チャネルの設定、受信回路の設定を行っています。

様々な手続きが含まれます。

// シリアル番号を得る
uint32_t u32hwser = the_twelite.get_hw_serial();

// チャネルを 11 に設定する
the_twelite.change_channel(11);

// 1秒のスリープを行う
the_twelite.sleep(1000);

// リセットを行う
the_twelite.reset_system();

また無線ネットワークを取り扱うクラスやボード対応をまとめたクラス、ユーザ記述のイベントドリブン処理を行うクラスを登録できるようになっています。このクラスを登録することにより、専用化した機能を手軽に利用できるようになります。これらのクラスを本解説中では「ビヘイビア」と呼称します。

void setup() {
	/*** SETUP section */
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_AMB>();

	...

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID);

上記の例では環境センサーパル<PAL_AMB>と、シンプル中継ネットワーク<NWK_SIMPLE>の2種類を登録しています。これらを登録することにより環境センサーパル上のセンサーなどハードウェアを簡易に取り扱うことが出来ます。また煩雑な無線パケットの取り扱いについて中継の処理や重複で届いたパケットの自動破棄などの機能を暗黙に持たせることが出来ます。

メソッド

<<演算子 (設定)

オブジェクトthe_tweliteの初期設定を行うために<<演算子を用います。

以下に挙げる設定用のクラスオブジェクトを入力とし、設定をしなければデフォルト値が適用されます。

TWENET::appid(uint32_t id)

パラメータidに指定したアプリケーションIDを設定します。これは必須指定です。

設定の読み出しは uint32_t the_twelite.get_appid() で行います。

TWENET::channel(uint8_t ch)

パラメータchに指定したチャネル番号(11..26)を設定します。

設定の読み出しはuint8_t the_twelite.get_channel()で行います。

TWENET::tx_power(uint8_t pw)

パラメータpwに指定した出力設定を(0..3)を設定します。デフォルトは(3:出力減衰無し)です。

設定値の読み出しはuint8_t the_twelite.get_tx_power()で行います。

TWENET::rx_when_idle(uint8_t bEnable)

パラメータbEnable1であれば常に受信回路を動作させ、他からの無線パケットを受信できるようになります。デフォルトは0で、もっぱら送信専用となります。

設定値の読み出しはuint8_t the_twelite.get_rx_when_idle()で行います。

TWENET::chmgr(uint8_t ch1 = 18, uint8_t ch2 = 0, uint8_t ch3 = 0)

チャネルマネージャを有効にします。チャネルを複数指定すると複数チャネルでの送受信を行います。ch2,ch3に0を指定すると、その指定は無効になります。

STG_STD

インタラクティブモードの設定値を反映します。

auto&& set = the_twelite.settings.use<STG_STD>();
...
set.reload();       // 設定値をロード
the_twelite << set; // インタラクティブモードの設定値を反映

反映される項目は以下です。

  • app_id
  • channel
  • tx_power
  • MAC ack 使用時の再送回数

begin()

void begin()

事前に設定(<<演算子参照)や、ビヘイビアの登録を済ませた後に実行します。通常はsetup()関数内の一番最後に記述します。

  • the_twelite 設定完了
  • ビヘイビアの初期化

void setup() {
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_AMB>();

	// settings
 	the_twelite
		<< TWENET::appid(APP_ID)
		<< TWENET::channel(CHANNEL)
		<< TWENET::rx_when_idle();

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID);

	// somo others

	// begin the TWENET!
	the_twelite.begin();
}

change_channel()

inline bool change_channel(uint8_t u8Channel)

チャネル設定を変更します。失敗時にはチャネルは変更されずfalseを戻します。

get_channel_phys()

uint8_t get_channel_phys()

現在設定中のチャネル番号(11..26)を取得する。MAC層のAPIより取得します。

get_hw_serial()

inline uint32_t get_hw_serial()

モジュールのシリアル番号を取得します。

sleep()

inline void sleep(
        uint32_t u32Periodms,
        bool_t bPeriodic = true,
        bool_t bRamOff = false,
        uint8_t u8Device = TWENET::SLEEP_WAKETIMER_PRIMARY)

モジュールをスリープさせる。

パラメータ解説
u32Periodmsスリープ時間[ms]
bPeriodic前回の起床時間をもとに次の起床時間を再計算する。
※次の起床タイミングが迫っているなどの理由で、現在のタイミングからになる場合もあります。
bRamofftrueに設定すると、RAMを保持しないスリープになる(起床後はwakeup()ではなくsetup()から再初期化する必要がある)
u8Deviceスリープに用いるウェイクアップタイマーの指定。TWENET::SLEEP_WAKETIMER_PRIMARYまたは TWENET::SLEEP_WAKETIMER_SECONDARYを指定する。

is_wokeup_by_dio()

bool is_wokeup_by_dio(uint8_t port)

スリープからの復帰要因が指定したディジタルピンである場合にtrueを返します。

is_wokeup_by_wktimer()

bool is_wokeup_by_wktimer()

スリープからの復帰要因がウェイクアップタイマーである場合にtrueを返します。

reset_system()

inline void reset_system()

システムをリセットします。リセット後はsetup()からの処理となります。

stop_watchdog()

inline void stop_watchdog()

ウォッチドッグタイマーを停止します。長時間のポーリング待ちを行うような場合はタイマーを停止します。

restart_watchdog()

inline void restart_watchdog()

ウォッチドッグタイマーを再開します。

ビヘイビア

twe_tweliteには3つのビヘイビアを登録でき、これらを格納する以下のクラスオブジェクトを定義されています。

  • network : ネットワークを実装するビヘイビアです。通常は<NWK_SIMPLE>を登録します。
  • network2 : ネットワークを実装するビヘイビアです。最初に networkでペイロードのデータ構造などの判定により受理しなかったパケットを、別のネットワーク ビヘイビアで処理させたい場合に使用します。(参考: NWK_LAYERED と NWK_SIMPLEの併用)
  • board: ボード対応のビヘイビアです。ボード上の各デバイス利用手続きが付加されます。
  • app: ユーザアプリケーションを記述したビヘイビアです。割り込みやイベント記述、ステートマシンによる状態遷移によるふるまいの記述が可能です。また複数のアプリケーション記述を定義しておいて、起動時に全く振る舞いの違うアプリケーションを選択する記述が容易に行えます。
  • settings: 設定(インタラクティブモード)を実行するためのビヘイビアです。<SET_STD>を登録します。

use<B>()

// 例
auto&& brd = the_twelite.board.use<PAL_AMB>();

ビヘイビア<B>を登録します。登録はsetup()内で行います。戻り値は登録したビヘイビアに対応するオブジェクトの参照です。

登録後は登録時と同じ書式でオブジェクトの取得を行います。

クラスオブジェクト

the_tweliteには上述のboard, network, appの3つのクラスオブジェクトが定義されていますが他に以下が定義されています。

tx_status

送信完了状態を通知する。

is_complete()

bool is_complete(uint8_t cbid)

指定したIDのパケットが送信完了したときにtrueを返す。

is_success()

bool is_success(uint8_t cbid)

指定したIDのパケットが送信完了し、かつ送信成功したときにtrueを返す。

receiver

受信パケットを取得する。

available()

bool available()

まだ読み出していない受信パケットが存在する場合にtrueを返す。

read()

packet_rx& read()

パケットを読み出します。

3.2 - Analogue

ADC (mwx::periph_analogue.hpp)
Analogueは、ADCの実行と値の取得を行います。一度に複数のチャネルを連続取得でき、またこれをタイマーなどの周期に合わせて逐次実行可能です。

定数

ピンの定義

定数種別標準アプリでのピン名
uint8_t PIN_ANALOGUE::A1 = 0ADC1ピンAI1
uint8_t PIN_ANALOGUE::A2 = 1ADC2ピンAI3
uint8_t PIN_ANALOGUE::A3 = 2``uint8_t PIN_ANALOGUE::D0 = 2ADC3ピン (DIO0) *1AI2
uint8_t PIN_ANALOGUE::A4 = 3``uint8_t PIN_ANALOGUE::D1 = 3ADC4ピン (DIO1) *1AI4
uint8_t PIN_ANALOGUE::VCC = 4Vcc 電源電圧

メソッド

setup()

void setup(
        bool bWaitInit = false,
        uint8_t kick_ev = E_AHI_DEVICE_TICK_TIMER,
        void (*fp_on_finish)() = nullptr)

ADCの初期化を行います。setup()では、半導体内部のレギュレータの始動、周期実行するためのタイマーデバイスの指定、指定チャネルすべてのADCが終了したときに呼び出されるコールバック関数の指定します。

パラメータ解説
bWaitInittrueを指定すると、半導体内部のレギュレータの初期化を待つ。
kick_ev周期実行に指定するタイマーデバイスを指定する。指定可能なデバイスは、以下の5種類で、初回以外は割り込みハンドラ内でADが開始される。E_AHI_DEVICE_TICK_TIMER (TickTimer)``E_AHI_DEVICE_TIMER0 .. 4 (Timer0 .. 4)
fp_on_finish指定されたポートすべてのADCが終了後に、割り込みハンドラ内から呼び出されるコールバック関数。ADC計測値をFIFOキューなどに別途格納したい場合に利用する。

begin()

void begin(uint8_t bmPorts, uint8_t capt_tick = 1)

1番目のパラメータにはADCを行いたいポートを指定します。ポートの指定はピンの定義で述べたポート番号に対応するビットをセットしたビットマップになります。例えば PIN_ANALOGUE::A2PIN_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ごとの処理になります。

void begin()

デフォルトのADCピン(PIN_ANALOGUE::A1,PIN_ANALOGUE::A2)を指定してADC処理を開始します。end()では中断したADC処理を再開します。

end()

void end()

ADC処理を終了し、半導体内部のレギュレータを停止します。

available()

inline bool available()

ADCの値が取得後にtrueになります。本関数により確認した後は次のADC完了まではfalseです。

read(), read_raw()

inline int16_t read(uint8_t s)
inline int16_t read_raw(uint8_t s)

ADC値を読み出します。パラメータには読み出したいADCピンを指定します。read()はmVに変換した読み値、read_raw()はADCの値(0..1023)を戻します。

ADC割り込みハンドラ

ADCの割り込みハンドラはsetup()の呼び出し時にperiph_analogue::ADC_handler()に設定されます。

半導体のペリフェラルライブラリで別途ハンドラを指定すると正常に動作しなくなります。

スリープ時の振る舞い

ADCがbegin()により周期実行状態であれば、スリープ復帰後もADC処理を再開します。

3.3 - Buttons

デジタル入力管理クラス (mwx::periph_buttons)
デジタル入力の変化を検出します。このクラスは、同じ検出値が複数回得られたときに変化を検出します。メカ式のボタンのチャタリングの影響を小さくするのに有効です。

メソッド

setup()

void setup(uint8_t max_history);

パラメータのmax_historyは、begin()で設定可能な参照回数の最大値です。ここではメモリーの確保と初期化を行います。

begin()

void begin(uint32_t bmPortMask,
				   uint8_t u8HistoryCount,
				   uint16_t tick_delta);

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のイベントハンドラで行っています。割り込みハンドラではないので、処理等の遅延の影響を受けますが、メカ式ボタン等のチャタリング抑制には十分です。

end()

void end()

Buttonsの動作を終了します。

available()

inline bool available()

変化が検出されたときにtrueを返します。read()を実行するとクリアされます。

read()

bool read(uint32_t& u32port, uint32_t& u32changed)

availableになったとき呼び出します。u32portは現在の入力DIOのビットマップ、u32changedは変化が検出されたDIOのビットマップです。

Buttonsが動作していない場合はfalseを返します。

動作について

初回の値確定

Buttonsが動作を開始した時点では、DIOの入力状態は未確定です。値が確定した時点でavailableになります。このときread()で読み出すビットマップのMSB(bit31)が1にセットされます。

動作確定を要するため、入力値が定常的に変化するポートを監視する目的では利用できません。

スリープ

スリープ前にButtonsが稼働状態であれば、復帰後に再開します。再開後、初回確定を行います。

3.4 - EEPROM

内蔵EEPROMに対して読み書きを実行

TWELITE 無線マイコンの内蔵EEPROMに対して読み書きを実行します。

内蔵EEPROMはアドレス0x000~0xEFFまでの3480バイトが利用可能です。

先頭部分は設定(インタラクティブモード)に利用されるため、併用する場合は後半のアドレスの利用を推奨します。設定(インタラクティブモード)でどの程度の領域を消費するかは、その実装に依存します。最小限度の設定であっても先頭から256バイトまでは利用されるため、それ以降の利用を推奨します。

メソッド

read()

uint8_t read(uint16_t address)

EEPROMからaddressに対応するデータを読み出します。

write()

void write(uint16_t address, uint8_t value)

EEPROMからaddressに対してvalueを書き込みます。

update()

void update(uint16_t address, uint8_t value)

write()と同じく書き込みを行いますが、先にaddressにあるデータを読み出してvalueと違う場合のみ、書き込みを行います。EEPROMの書き換え寿命を考慮し、書換回数を減らしたいときに用います。

get_stream_helper()

auto&& get_stream_helper()
// 戻り値型は長くなるためauto&&と省略しています。

後述のmwx::streamを用いた読み書きを行うために、ヘルパーオブジェクトを取得します。

mwx::streamインタフェースを用いた入出力

stream_helper ヘルパーオブジェクトを経由して、mwx::streamによる演算子やメソッドを用います。mwx::streamを用いるとuint16_tuint32_t型といった整数型の読み書き、uint8_tの固定長配列型の読み書き、format()オブジェクトによる書式整形などが可能になります。

auto&& strm = EEPROM.get_stream_helper();
// ヘルパーオブジェクトの型名は長くなるためauto&&により解決しています。

このオブジェクトに対して<<演算子などmwx::streamで定義されたインタフェースを利用できます。

strm.seek(1024); // 1024バイト目に移動

strm << format("%08x", 12345678); // 12345678を16進数の8文字で記録
strm << uint32_t(0x12ab34cd);     // 0x12ab34cd の4バイトを記録
uint8_t msg_hello[16] = "HELLO WORLD!";
strm << msg_hello;                // バイト列 "HELLO WORLD!" を記録(終端なし)

// 結果
// 0400: 30 30 62 63 36 31 34 65 12 ab 34 cd 48 45 4c 4c
//        0  0  b  c  6  1  4  e  0x12ab34cd  H  E  L  L
// 0410: 4f 20 57 4f 52 4c 44 21 00 00 00 00 ff ff ff ff
//        O SP  W  O  R  L  D  !

.seek()を用いてEEPROMのアドレスを1024に移動しています。

上記では8バイト文字列(00bc614e)、4バイト整数(0x12ab34cd)、16バイトバイト列(HELLO WORLD!...)、1バイト終端文字を書き込んでいます。

strm.seek(1024);

uint8_t msg1[8];
strm >> msg1;
Serial << crlf << "MSG1=" << msg1;
// MSG1=00bc614e

uint32_t var1;
strm >> var1;
Serial << crlf << "VAR1=" << format("%08x", var1);
// VAR1=12ab34cd

uint8_t msg2[16]; // "HELLO WORLD!"の文字数
strm >> msg2;
Serial << crlf << "MSG2=" << msg2;
// MSG2=HELLO WORLD!

.seek()を用いてEEPROMのアドレスを1024に移動しています。

先ほど書き出したデータ列を読み出します。順番に8バイト文字、4バイト整数、16バイト文字列を>>演算子を用いて読み出します。

3.5 - PulseCounter

パルスカウンタ (mwx::periph_pulse_counter)
パルスカウンタは、マイコンのCPUが稼働していない時もパルスを読み取り計数する回路です。

パルスカウンターは2系統あります。PC0はPulseCounter0, PC1はPulseCounter1に割り当てられます。また**PulseCounterPulseCounter1**の別名です。

メソッド

begin()

void begin(uint16_t refct = 0,
           E_PIN_INT_MODE edge = PIN_INT_MODE::FALLING,
           uint8_t debounce = 0)

オブジェクトを初期化し、計数を開始します。1番目のパラメータrefctは割り込みやavailable判定の基準となるカウント数です。この数を超えたときにアプリケーションに報告されます。またrefctには0を指定することもできます。この場合は、スリープ起床要因にはなりません。

2番目のパラメータedgeは割り込みが立ち会がり(PIN_INT_MODE::RISING)か立下り(PIN_INT_MODE::FALLING)を指定します。

3番目のdebounceは、0,1,2,3の値をとります。1,2,3の設定はノイズの影響を小さくするため値の変化の検出に連続した同じ値を要する設定です。

設定連続サンプル数最大検出周波数
0-100Khz
123.7Khz
242.2Khz
381.2Khz

end()

void end()

検出を中止します。

available()

inline bool available()

指定カウント数(begin()refct)が0の場合は、カウントが1以上でtrueを返します。

指定カウント数(begin()refct)が1以上の場合は、検出回数が指定カウント数を超えた場合にtrueとなります。

read()

uint16_t read()

カウント値を読み出します。読み出し後にカウント値を0にリセットします。

3.6 - Serial

UART0 ポート (mwx::serial_jen)
mwx::stream を実装し TWELITE の UART0 で入出力します。
  • Serialオブジェクトはシステム起動時に UART0, 115200 bps で初期化され、ライブラリ内で初期化処理が行われます。ユーザコード上は、setup()から利用できます。
  • Serial1オブジェクトは、ライブラリ内で用意されていますが、初期化処理は行っていません。UART1を有効化するためには、必要な初期化手続き Serial1.setup(), Serial1.begin() を行ってください。

setup()

void setup(uint16_t buf_tx, uint16_t buf_rx)

オブジェクトの初期化を行う。

  • TX/RX用のFIFOバッファのメモリ確保
  • TWE_tsFILE 構造体のメモリ確保
パラメータ解説
buf_txTX用のFIFOバッファサイズ
buf_rxRX用のFIFOバッファサイズ

begin()

void begin(unsigned long speed = 115200, uint8_t config = 0x06)

ハードウェアの初期化を行う。

パラメータ解説
speedUART のボーレートを指定する。
configserial_jen::E_CONF::PORT_ALTビットを指定したときは、UART1をDIO14,15で初期化します。指定しない場合はDIO11(TxD),9(RxD)で初期化します。

end()

(未実装)ハードウェアの使用を停止する。

get_tsFile()

TWE_tsFILE* get_tsFile();

Cライブラリで利用する TWE_tsFILE* 形式での構造体を得る。

3.7 - SerialParser

シリアルポート用書式入力 (mwx::serial_parser)
この組み込みクラスはシリアルポートでの書式入力に利用することを想定して組み込みオブジェクトとして定義しています。

初期化時(begin())にヒープから内部で使用するバッファ領域を確保するmwx::serial_parser<mwx::alloc_heap<uint8_t>>型として定義されています。

詳細はクラス serparser を参照してください。

3.8 - SPI

SPIバス (コントローラ側) の読み書き
SPIバス (コントローラ側) の読み書きを行います。

注意事項

定数

定数意味
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 に設定する

初期化と終了

SPIバスの利用手続きはbegin()メソッドによります。

begin()

void begin(uint8_t slave_select, SPISettings settings)
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode)

ハードウェアの初期化を行います。

パラメータ解説
slave_select対象のペリフェラルのセレクトピンを指定する。0 : DIO19``1 : DIO0 (DIO 19 は予約されます)``2 : DIO1 (DIO 0,19 は予約されます)
settingsSPIのバス設定を指定します。

clock[hz]でSPIバスの周波数を指定します。指定した周波数に近いディバイザが選択されます。16Mhzまたは16Mhzを偶数で割った値になります。
bitOrderSPI_CONF::MSBFIRSTSPI_CONF::LSBFIRSTを指定します。
dataModeSPI_CONF::SPIMODE0..3を指定します。

void setup() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

void wakeip() {
  ...
  SPI.begin(0, SPISettings(2000000, SPI_CONF::MSBFIRST, SPI_CONF::SPI_MODE3));
  ...
}

end()

void end()

SPIのハードウェアの利用を終了します。

読み書き

読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。

使用例

次のサンプルコードは、Analog Devices の温度センサ ADT7310 から1秒おきに温度を取得し、シリアルポートへ出力します。

#include <TWELITE>
#include <SM_SIMPLE>

enum class STATE : uint8_t {
    INTERACTIVE = 255,
    INIT = 0,
    INIT_WAIT,
    SENSOR,
    LOOP_WAIT
};
SM_SIMPLE<STATE> step;

struct SensorData {
    uint8_t highByte;
    uint8_t lowByte;
    uint16_t rawValue;
    int32_t tempValue16th;
    div_result_i32 temperature;
} sensorData;

void setup() {
    step.setup(); // 状態マシンの初期化
}

void loop() {
    do {
        switch (step.state()) {
        case STATE::INIT: // 初期状態
            SPI.begin(0 /* DIO19をチップセレクトとして使用 */
                      , { 400000UL /* クロック周波数 */
                    , SPI_CONF::MSBFIRST
                    , SPI_CONF::SPI_MODE3
            }
                      );

            // ソフトウェアリセット
            SPI.beginTransaction();
            for (int i = 0; i < 4; i++) {
                SPI.transfer(0xFF);
            }
            SPI.endTransaction();

            // Continuous Readモード開始
            SPI.beginTransaction();
            SPI.transfer(0x54);
            SPI.endTransaction();

            step.set_timeout(300); // 待機時間の設定
            step.next(STATE::INIT_WAIT);
            break;

        case STATE::INIT_WAIT: // 待機
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        case STATE::SENSOR: // センサーデータの読み取り
            SPI.beginTransaction();
            sensorData.highByte = SPI.transfer(0x00);  // ダミーデータを送信してクロック信号を生成
            sensorData.lowByte = SPI.transfer(0x00);   // ダミーデータを送信してクロック信号を生成
            SPI.endTransaction();

            sensorData.rawValue = (((uint16_t)sensorData.highByte << 8) | sensorData.lowByte) >> 3;
            if (sensorData.rawValue & 0x1000) {
                sensorData.tempValue16th = int32_t(sensorData.rawValue) - 0x2000;
            } else {
                sensorData.tempValue16th = sensorData.rawValue;
            }

            // div100()を使用して温度計算
            sensorData.temperature = div100((sensorData.tempValue16th * 100) / 16);

            // 結果をシリアル出力
            Serial << crlf << sensorData.temperature.format() << "°C";

            step.set_timeout(1000); // 次のキャプチャまで待機
            step.next(STATE::LOOP_WAIT);
            break;

        case STATE::LOOP_WAIT: // 待機
            if (step.is_timeout()) {
                step.next(STATE::SENSOR);
            }
            break;

        default:
            break;
        }
    } while (step.b_more_loop());
}

ここでは、メンバ関数版のインタフェースを利用しています。

3.8.1 - SPI (メンバ関数版)

SPI (メンバ関数を使用する方法)
begin()メソッドによりハードウェアの初期化を行った後、beginTransaction()によりバスの読み書きができるようになります。beginTransaction()を実行するとSPIのセレクトピンが選択されます。読み書きはtransfer()関数を用います。SPIは読み出しと書き込みを同時に実行します。

beginTransaction()

void beginTransaction()
void beginTransaction(SPISettings settings)

バスの利用開始を行います。SPIのセレクトピンをセットします。

settingsパラメータを与えて呼び出した場合は、バスの設定を行います。

endTransaction()

void endTransaction()

バスの利用を終了します。SPIのセレクトピンを解除します。

transfer(), transfer16(), transfer32()

inline uint8_t transfer(uint8_t data)
inline uint16_t transfer16(uint16_t data)
inline uint32_t transfer32(uint32_t data)

バスの読み書きを行います。trasnfer()は8bit、transfer16()は16bit、transfer32()は32bitの転送を行います。

3.8.2 - SPI (ヘルパークラス版)

SPI (ヘルパークラスを使用する方法)
ヘルパークラス版はより抽象度の高い実装です。読み書きを行うオブジェクト transceiver を生成することでバスの利用を開始し、オブジェクトを破棄することでバスの利用を終了します。

if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。

uint8_t c;
if (auto&& trs = SPI.get_rwer()) { // オブジェクトの生成とデバイスの通信判定
   // このスコープ(波かっこ)内が trs の有効期間。
   trs << 0x00; // 0x00 を mwx::stream インタフェースで書き出し
   trs >> c;    // 読み出したデータをcに格納。
}
// if 節を抜けるところで wrt は破棄され、バスの利用終了

また、読み書きオブジェクトは、mwx::streamインタフェースを実装しているため<<演算子などを利用できます。

  • バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぎます
  • mwx::streamインタフェースによる読み書き手続きを統一します

読み書き

読み込み処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法。

inline uint8_t _spi_single_op(uint8_t cmd, uint8_t arg) {
    uint8_t d0, d1;
    if (auto&& x = SPI.get_rwer()) {
        d0 = x.transfer(cmd); (void)d0;
        d1 = x.transfer(arg);
        // (x << (cmd)) >> d0;
        // (x << (arg)) >> d1;
    }

    return d1;
}

上記では get_rwer() メソッドにより生成された x オブジェクトを用いて1バイトずつ読み書きを行っています。

  1. if(...) 内で x オブジェクトを生成します。同時にSPIバスのセレクトピンをセットします。(型は、型推論によるユニバーサル参照 auto&& で解決しています。)
  2. 生成した x オブジェクトには operator bool () が定義されており、判定式の評価として利用される。SPIバスの場合は常に true となる。
  3. x オブジェクトには uint8_t transfer(uint8_t) メソッドが定義されていて、これを呼び出すことでSPIに対して8bitの読み書き転送を行。
  4. if() { ... } スコープの末尾で x のデストラクタが呼び出され、SPIバスのセレクトピンを解除します。

get_rwer()

periph_spi::transceiver get_rwer()

SPIバスの読み書きに用いるワーカーオブジェクトを取得します。

ワーカーオブジェクト

transfer(), transfer16(), transfer32()

uint8_t transfer(uint8_t val)
uint16_t transfer16(uint16_t val)
uint32_t transfer32(uint32_t val)

それぞれ8bit,16bit,32bitの転送を行い、読み取り結果を書き込んだデータ幅と同じデータ幅で返す。

<<演算子

operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)

int型,uint8_t型は8bitの転送を行います。

uint16_t型、uint32_t型は、それぞれ16bitの転送、32bitの転送を行います。

転送結果は最大16バイトの内部FIFOキューに格納され >> 演算子により読み出します。バッファが大きくないので、転送都度読み出すことを想定します。

>>演算子

operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)

null_stream(size_t i = 1)
operator >> (null_stream&& p)

直前の転送と同じデータ幅の変数を指定します。

読み出した結果が不要の場合はnull_stream()オブジェクトを使用します。iで指定したデータバイト分だけ読み飛ばします。

3.9 - TickTimer

システムタイマー (mwx::periph_ticktimer)
TickTimerはTWENETの内部制御用に利用され、暗黙に実行されています。タイマーの周期は1msです。loop()中でTickTimerイベントにより1msごとの処理を記述する目的でavailable()メソッドのみを定義しています。

メソッド

available()

inline bool available()

TickTimer割り込み発生後にセットされ、その直後のloop()trueになります。loop()終了後にクリアされます。

3.10 - Timer0 .. 4

タイマー, PWM (mwx::periph_timer)
タイマーでは、指定周期でのソフトウェア割り込みを発生させる目的、指定周期でPWM出力を行う2つの機能があります。TWELITE無線モジュールには0..4まで合計5つのタイマーが利用可能です。

組み込みオブジェクト名は Timer0..4 ですが、このページでは TimerXと記載します。

メソッド

setup()

void setup()

タイマーを初期化します。この呼び出しにより必要なメモリ領域の確保を行います。

begin()

void begin(uint16_t u16Hz, bool b_sw_int = true, bool b_pwm_out = false)

タイマーを開始します。1番目のパラメータは、タイマーの周期でHzで指定します。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。3番目のパラメータをtrueにするとPWM出力を有効にします。

end()

void end()

タイマーの動作を停止します。

available()

inline bool available()

タイマー割り込みが発生した直後のloop()trueになり、loop()が終了すればfalseになります。

change_duty()

void change_duty(uint16_t duty, uint16_t duty_max = 1024)

デューティー比の設定です。1番目のパラメータにデューティ比を指定します(小さい値を指定すると波形の平均はGNDレベルに近づき、大きい値を指定するとVccレベルに近づく)。2番目のパラメータはデューティ比の最大値を指定します。

change_hz()

void change_hz(uint16_t hz, uint16_t mil = 0)

タイマーの周波数を設定します。2番目のパラメータは周波数の小数点3桁分の値を整数で指定します。例えば 10.4 Hz としたい場合は hz=10, mil=400 と指定します。

3.11 - Wire

2線シリアル (I2C) master (controller) としての読み書き (mwx::periph_wire)
2線シリアル(I2C) master (controller) としての読み書きを行います。

定義

別名定義

using TwoWire = mwx::periph_twowire<MWX_TWOWIRE_RCVBUFF>;

mwx::periph_wire<MWX_TWOWIRE_RCVBUFF>TwoWireとして参照可能です。

型定義

以下の定義型で引数や戻り値の型を記載します。

typedef uint8_t size_type;
typedef uint8_t value_type;

注意事項

初期化と終了

Wire インスタンスの生成

ライブラリ内でインスタンスの生成と必要な初期化は行われます。ユーザコードでは Wire.begin() を呼び出すことで利用可能となります。

requestFrom() メソッドを用いる場合、データを一時保管するための FIFO キューのサイズを指定できます。コンパイル時にマクロMWX_TWOWIRE_BUFF に必要なバイト数を指定してコンパイルする。デフォルトは 32 バイトです。

例: -DMWX_TWOWIRE_BUFF=16

begin()

void begin(
    const size_type u8mode = WIRE_100KHZ,
    bool b_portalt = false)

ハードウェアの初期化を行います。

パラメータ解説
u8modeバス周波数を指定する。デフォルトは100Khz(WIRE_CONF::WIRE_100KHZ)
周波数はWIRE_CONF::WIRE_??KHZで指定し??には50,66,80,100,133,160,200,266,320,400を指定できる。
b_portaltハードウェアのピン割り当てを変更する。

void setup() {
    ...
    Wire.begin();
    ...
}

void wakeup() {
    ...
    Wire.begin();
    ...
}

読み書き

読み書きの手続きは、以下の2種類あります。いずれかを選択して利用します。

その他

プローブ(デバイスの存在判定)

bool probe(uint8_t address)

address で指定したデバイスが応答するかを確認します。デバイスが存在する場合は true が戻ります。

setClock()

void setClock(uint32_t speed)

本来はバス周波数を変更するための手続きですが、何も処理をしません。

3.11.1 - Wire (メンバ関数版)

Wire (メンバ関数を使用する方法)

メンバ関数を利用した方法は、抽象度が比較的低く、C言語ライブラリで提供されるような一般的なAPI体系に倣っています。二線シリアルバスの操作手続きがより直感的です。

ただしバスの利用の開始と終了を明示的に意識して記述する必要があります。

読み込み

requestFrom()

size_type requestFrom(
    uint8_t u8address,
    size_type length,
    bool b_send_stop = true)

指定バイト数分を一括で読み出します。読みだした結果はキューに保存されるため、直後にキューが空になるまで .read() メソッドを呼び出すようにしてください。

パラメータ解説
u8address読み出し対象のI2Cアドレス
length読み出しバイト数
b_send_stop=truetrue の時、読み込み終了時にSTOPビットを設定する。
戻り値型 size_type読み出したバイト数。 0 は読み出しの失敗。

コード例

int len = Wire.requestFrom(0x70, 6);
for (int i = 0; i < 6; i++) {
  if (Wire.available()) {
		  au8data[i] = Wire.read();
    Serial.print(buff[i], HEX);
  }
}
// skip the rest (just in case)
// while (Wire.available()) Wire.read(); // normally, not necessary.

書き出し

書き出し処理は、beginTransmission() を実行後、write() メソッドにより行います。一連の書き出しが終了したら endTranmission() を呼びます。

	#define DEV_ADDR (0x70)
	const uint8_t msg[2] =
	  {SHTC3_SOFT_RST_H, SHTC3_SOFT_RST_L};

	Wire.beginTransmission(DEV_ADDR);
	Wire.write(msg, sizeof(msg));
	Wire.endTransmission();

beginTransmission()

void beginTransmission(uint8_t address)

書き出しの転送を初期化する。書き出し処理終了後、速やかに endTransmission() を呼び出す。

パラメータ解説
u8address書き出し対象のI2Cアドレス

write(value)

size_type write(const value_type value)

1バイトの書き出しを行う。

パラメータ解説
value書き込むバイト
戻り値 size_type書き込んだバイト数。0はエラー。

write(*value, quantity)

size_type write(
  const value_type* value,
  size_type quantity)

バイト列の書き出しを行います。

パラメータ解説
*value書き込むバイト列
size_typeバイト数
戻り値 size_type書き込んだバイト数。0はエラー。

endTransmission()

uint8_t endTransmission(bool sendStop = true)

書き出しの終了処理を行います。

パラメータ解説
sendStop = trueSTOPビットを発行します。
戻り値 uint8_t0: 成功 4: 失敗

3.11.2 - Wire (ヘルパークラス版)

Wire (ヘルパークラスを使用する方法)
ヘルパークラス版はより抽象度が高い実装です。読み書きに対応するオブジェクト reader, writer を生成することがバスの利用開始となり、オブジェクトを破棄するとバス利用の終了手続きを行います。

if文の判定式内でオブジェクトの生成を行うことで、オブジェクトの有効期間はif節内のスコープに限定され、if節を抜けた時点でオブジェクトは破棄され、その時点でバスの利用終了の手続きを行います。

if (auto&& wrt = Wire.get_writer(...)) { // オブジェクトの生成とデバイスの通信判定
   // このスコープ(波かっこ)内が wrt の有効期間。
   wrt << 0x00; // 0x00 を mwx::stream インタフェースで書き出し
}
// if 節を抜けるところで wrt は破棄され、バスの利用終了

また読み書きオブジェクトはmwx::streamインタフェースを実装しているため<<演算子などを利用することができます。

  • バスの利用開始と終了をオブジェクトの有効期間と一致させることで、ソースコードの見通しを良くし、また終了手続きの記述漏れなどを防ぐ
  • mwx::streamインタフェースによる読み書き手続きの統一

読み込み

読み込み処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法です。

const uint8_t DEV_ADDR = 0x70;
uint8_t au8data[6];
if (auto&& rdr = Wire.get_reader(DEV_ADDR, 6)) {
		for (auto&& c: au8data) {
			c = rdr();
		}
}

// same above
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(SHTC3_ADDRESS, 6)) {
		rdr >> u16temp;
		rdr >> u8temp_csum;
		rdr >> u16humd;
		rdr >> u8humd_csum;
}

上記では get_readr() メソッドにより生成された rdr オブジェクトを用いて1バイトずつ読み出しします。 メソッドのパラメータには読み込みたい二線シリアル ID を指定します。

  1. if(...) 内で rdr オブジェクトを生成。(型は、型推論によるユニバーサル参照 auto&& で解決しています。)
  2. 生成した rdr オブジェクトには operator bool () が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true となる。
  3. rdr オブジェクトには int operator () (void) 演算子が定義されていて、これを呼び出すことで2線シリアルバスから1バイトのデータを読み出す。読み込みに失敗したときは -1 が戻り、成功した場合は読み込んだバイト値が戻る。
  4. if() { ... } スコープの末尾で rdr のデストラクタが呼び出され、二線シリアルバスの STOP を行う。

get_reader(addr, read_count=0)

periphe_wire::reader
get_reader(uint8_t addr, uint8_t read_count = 0)

I2C 読み出しに用いるワーカーオブジェクトを取得します。

パラメータ解説
addr読み込み用のI2Cアドレス
read_count読み出しバイト数(この値を指定すると最後の転送で STOP ビットを発行する)。0を指定した場合は STOP ビットなしとなる(デバイスによっては動作するものもあります)

書き出し (writer)

書き出し処理とその終了手続きをスコープ内 if() { ... } で行うためのヘルパークラスを用いた読み込み方法です。

const uint8_t DEV_ADDR = 0x70;
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
	wrt(SHTC3_TRIG_H);
	wrt(SHTC3_TRIG_L);
}

// same above
if (auto&& wrt = Wire.get_writer(DEV_ADDR)) {
	wrt << SHTC3_TRIG_H; // int type is handled as uint8_t
	wrt << SHTC3_TRIG_L;
}

上記では get_writer() メソッドにより生成された wrt オブジェクトを用いて1バイトずつ書き出す。 メソッドのパラメータには読み出したい二線シリアル ID を指定します。

  1. if(...) 内で wrt オブジェクトを生成する。(型名は長くなるため auto で解決)
  2. 生成した wrt オブジェクトには operator bool () が定義されており、判定式の評価として利用される。指定された ID により通信が可能であれば true となる。
  3. wrt オブジェクトには int operator () (void) 演算子が定義されていて、これを呼び出すことで2線シリアルバスに1バイトのデータを書き出しす。失敗したときは -1 が戻り、成功した場合は書き込んだバイト値が戻る。
  4. if() { ... } スコープの末尾で wrt のデストラクタが呼び出され、二線シリアルバスの STOP を行う。

get_writer()

periph_wire::writer
get_writer(uint8_t addr)

I2C書き出しに用いるワーカーオブジェクトを取得します。

パラメータ解説
addr書き出し用のI2Cアドレス

ワーカーオブジェクト (writer)

<<演算子

operator << (int c)
operator << (uint8_t c)
operator << (uint16_t c)
operator << (uint32_t c)

int型,uint8_t型は8bitの転送を行います。データ並び順はビッグエンディアン形式(上位バイトが先に転送される)です。

()演算子

operator() (uint8_t val)
operator() (int val)

1バイト書き出す。

ワーカーオブジェクト (reader)

>>演算子

operator >> (uint8_t& c)
operator >> (uint16_t& c)
operator >> (uint32_t& c)
operator >> (uint8_t(&c)[N]) // Nバイトの固定配列

それぞれのデータ型のサイズ分だけ読み出します。データ並び順はビッグエンディアン形式(先に転送されたほうが上位バイトに格納される)です。

()演算子

int operator() (bool b_stop = false)

//例
uint8_t dat[6];
if (auto&& rdr = Wire.get_reader(0x70)) {
  for(uint8_t& x : dat) {
    x = rdr();
  }
}

1バイト読み出します。エラーがある場合は-1を戻し、正常時は読み出したバイト値を戻します。

b_stoptrueにすると、その読み出しにおいてSTOPビットを発行します。

以下の例は、環境センサーパルの温湿度センサーSHTC3の計測例です。

Wire.begin();
// reset (may not necessary...)
if (auto&& wrt = Wire.get_writer(0x70)) {
	wrt << 0x80; // SHTC3_SOFT_RST_H
	wrt << 0x05; // SHTC3_SOFT_RST_L
}

delay(5); // wait some

// start read
if (auto&& wrt = Wire.get_writer(0x70)) {
	wrt << 0x60; // SHTC3_TRIG_H
	wrt << 0x9C; // SHTC3_TRIG_L
}

delay(10); // wait some

// read result
uint16_t u16temp, u16humd;
uint8_t u8temp_csum, u8humd_csum;
if (auto&& rdr = Wire.get_reader(0x70, 6)) {
	rdr >> u16temp;
	rdr >> u8temp_csum;
	rdr >> u16humd;
	rdr >> u8humd_csum;
}

// checksum 0x31, init=0xFF
if (CRC8_u8CalcU16(u16temp, 0xff) != u8temp_csum) {
	Serial << format("{SHTC3 T CKSUM %x}", u8temp_csum); }
if (CRC8_u8CalcU16(u16humd, 0xff) != u8humd_csum) {
	Serial << format("{SHTC3 H CKSUM %x}", u8humd_csum); }

// calc temp/humid (degC x 100, % x 100)
int16_t i16Temp = (int16_t)(-4500 + ((17500 * int32_t(u16temp)) >> 16));
int16_t i16Humd = (int16_t)((int32_t(u16humd) * 10000) >> 16);

Serial << "temp=" << int(i16Temp)
	   << ",humid=" << int(i16Humd) << mwx::crlf;

4 - クラス

各種クラス

4.1 - MWX_APIRET

戻り値クラス
32bit型をラップしたAPI戻り値クラス。MSB(bit31)は失敗、成功のフラグ。bit0..30は戻り値を格納するために使用します。
class MWX_APIRET {
	uint32_t _code;
public:
	MWX_APIRET() : _code(0) {}
	MWX_APIRET(bool b) {
	  _code = b ? 0x80000000 : 0;
  }
	MWX_APIRET(bool b, uint32_t val) {
		_code = (b ? 0x80000000 : 0) + (val & 0x7fffffff);
	}
	inline bool is_success() const { return ((_code & 0x80000000) != 0); }
	inline bool is_fail() const { return ((_code & 0x80000000) == 0); }
	inline uint32_t get_value() const { return _code & 0x7fffffff; }
	inline operator uint32_t() const { return get_value(); }
	inline operator bool() const { return is_success(); }
};

コンストラクタ

MWX_APIRET()
MWX_APIRET(bool b)
MWX_APIRET(bool b, uint32_t val)

デフォルトコンストラクタはfalse,0の組み合わせで構築します。

またbool型とuint32_t型をパラメータとする明示的な構築も可能です。

bool型のコンストラクタを実装しているため、以下のようにtrue/falseを用いることができます。

MWX_APIRET myfunc() {
  if (...) return true;
  else false;
}

メソッド

is_success(), operator bool()

inline bool is_success()
inline operator bool()

MSBに1がセットされていればtrueを返す。

inline bool is_fail()

MSBが0の場合にtrueを返す。

inline uint32_t get_value()
inline operator uint32_t()

bit0..30の値部を取得する。

4.2 - alloc

メモリ確保
コンテナクラス(smplbuf, smplque)のテンプレート引数として指定し、内部で利用するメモリの確保または領域指定します。
クラス名内容
alloc_attach<T>すでにあるバッファを指定する
alloc_local<T, int N>Nバイトのバッファを内部に静的確保する
alloc_heap<T>指定したサイズをヒープに確保する

alloc_attachalloc_heapではメモリ確保クラスに応じた初期化メソッド (init_???())を実行する必要があります。

初期化

void attach(T* p, int n) // alloc_attach
void init_local()        // alloc_local
void init_heap(int n)    // alloc_heap

バッファーp・サイズnで初期化します。

メソッド

alloc_size()

uint16_t alloc_size()

バッファのサイズを返す。

_is_attach(), _is_local(), _is_heap()

想定したallocクラスと異なるメソッド呼び出し記述に対して、static_assertのように、コンパイルエラーを発生させるためのメソッドです。

4.3 - axis_xyzt

加速度データのコンテナクラス
3軸の加速度センサーの値を格納するための構造体ですが、格納したデータの利便性を向上するための手続きを追加しています。
struct axis_xyzt {
    int16_t x;
    int16_t y;
    int16_t z;
    uint16_t t;
};

get_axis_{x,y,z}_iter()

/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x_iter(Iter p)
auto&& get_axis_y_iter(Iter p)
auto&& get_axis_z_iter(Iter p)

axis_xyztを格納したコンテナクラスのイテレータをパラメータとして、X, Y, Z 軸のいずれかの要素にアクセスするイテレータを生成します。

以下の例では、buf.begin(), buf.end()をX軸用のイテレータとしてアルゴリズムstd::minmax_elementに用いています。

##include <algorithm>

void myfunc() {
  // コンテナクラス
  smplbuf_local<axis_xyzt, 10> buf;

  // テスト用にデータを投入
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...

  // 最大、最小値を得るアルゴリズム
  auto&& minmax = std::minmax_element(
    get_axis_x_iter(buf.begin()),
    get_axis_x_iter(buf.end()));

  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

get_axis_{x,y,z}()

/*戻り型は長いテンプレート型名なのでauto&&と記載します*/
auto&& get_axis_x(T& c)
auto&& get_axis_y(T& c)
auto&& get_axis_z(T& c)

axis_xyztを格納したコンテナクラスのXYZ軸のいずれかの軸を取り出した仮想的なコンテナクラスを生成する関数です。この生成したクラスにはbegin()end()メソッドのみ実装されています。このbegin()end()メソッドで取得できるイテレータは前節get_axis_{x,y,z}_iter()のイテレータと同じものになります。

##include <algorithm>

void myfunc() {
  // コンテナクラス
  smplbuf_local<axis_xyzt, 10> buf;

  // テスト用にデータを投入
  buf[0] = { 1, 2, 3, 4 };
  buf[1] = { 2, 3, 4, 5 };
  ...

  // キューの中の X 軸を取り出す
  auto&& vx = get_axis_x(que);

  // 範囲for文の利用
  for (auto&& e : vx) { Serial << int(e) << ','; }

  // 最大、最小値を得るアルゴリズム
  auto&& minmax = std::minmax_element(
      vx.begin(), vx.end());

  Serial << "min=" << int(*minmax.first)
        << ",max=" << int(*minmax.second) << mwx::crlf;
}

4.4 - packet_rx

受信パケット
このクラスはTWENETのtsRxDataApp構造体のラッパークラスです。

このクラスオブジェクトは、ビヘイビアのコールバック関数またはon_rx_packets()により取得できます。

packet_rxでは、特にパケットのデータペイロードをsmplbufコンテナで取り扱えるようにし、expand_bytes()などのユーティリティ関数によりペイロードの解釈記述を簡素化しています。

メソッド

get_payload()

smplbuf_u8_attach& get_payload()

パケットのデータペイロードを取得する。

get_psRxDataApp()

const tsRxDataApp* get_psRxDataApp()

TWENET Cライブラリの受信構造体を得る。

get_length()

uint8_t get_length()

ペイロードのデータ長を返す。.get_payload().size()と同じ値になる。

get_lqi()

uint8_t get_lqi()

LQI値 (Link Quality Indicator)を得る。

get_addr_src_long(), get_addr_src_lid()

uint32_t get_addr_src_long()
uint8_t get_addr_src_lid()

送信元のアドレスを得る。

get_addr_src_long()は送信元のシリアル番号で、MSB(bit31)が必ず1になります。

get_addr_src_lid()は送信元の論理IDで0x00-0xFEまでの値をとります(<NWK_SIMPLE>で指定する論理IDです)。

get_addr_dst()

uint32_t get_addr_dst()

宛先アドレスを得ます。

宛先アドレスは、送信元で指定され、宛先の種別によって値の範囲が変わります。

解説
MSB(bit31)がセットされている宛先としてシリアル番号を指定しています。
0x00-0xFF宛先として論理ID(8bit)が指定されています。

is_secure_pkt()

bool is_secure_pkt()

暗号化パケットの場合は true を返し、平文の時はfalse を返します。

get_network_type()

uint8_t get_network_type()

ネットワークビヘイビアで識別されるパケットのネットワークタイプを返す。

解説
mwx::NETWORK::LAYERED<NWK_LAYERED> からのパケット
mwx::NETWORK::SIMPLE<NWK_SIMPLE> からのパケット
mwx::NETWORK::NONEネットワークを介さないパケット (App_Tweliteなど)
その他エラーまたは識別できないパケット

4.5 - packet_tx

送信パケット
このクラスはTWENET CライブラリのtsTxDataApp構造体のラッパクラスで、このクラスをベースとした派生クラスのオブジェクトをネットワークビヘイビアまたはon_tx_comp()により取得します。
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	pkt << tx_addr(0x00)
		<< tx_retry(0x1)
		<< tx_packet_delay(0,50,10);

	pack_bytes(pkt.get_payload()
		, make_pair("APP1", 4)
		, uint8_t(u8DI_BM)
	);

  pkt.transmit();
}

オブジェクトの生成

ネットワークビヘイビアの .prepare_tx_packet() によって行います。

if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
  ...
}

上記の例ではthe_twelite.network.use<NWK_SIMPLE>()によってネットワークビヘイビアのオブジェクトを取り出します。このオブジェクトの.prepare_tx_packet() によってオブジェクトpktが生成されます。型名はauto&&で推論されていますがpacket_txの派生クラスになります。

このpktオブジェクトは、まず、()内の条件判定にてtruefalseを返します。falseが返ってくるのは、送信用のキューが一杯でこれ以上要求が追加できない時です。

送信設定

無線パケットには宛先情報など相手に届けるための様々な設定を行います。設定には設定内容を含むオブジェクトを«演算子の右辺値に与えます。

pkt << tx_addr(0x00)
	  << tx_retry(0x1)
  	<< tx_packet_delay(0,50,10);

以下に設定に用いるオブジェクトについて記載します。

tx_addr

tx_addr(uint32_t addr)

宛先アドレスaddrを指定します。宛先アドレスの値については、ネットワークビヘイビアの仕様を参照してください。

  • <NWK_SIMPLE> MSB(bit31=0x80000000)がセットされるアドレスは、無線モジュールのシリアル番号宛という意味になります。 0x00..0xEFは、8bitの論理IDを意味します。0xFEは子機宛(0x01..0xEF)の同報通信(ブロードキャスト)、0xFFは親機子機関係なく同報通信(ブロードキャスト)します。

tx_retry

tx_retry(uint8_t u8count, bool force_retry = false)

再送回数の指定を行います。再送回数はu8countで指定します。force_retryは、送信が成功しようがしまいが、指定回数の再送を行う設定です。

  • <NWK_SIMPLE> ネットワークビヘイビア<NWK_SIMPLE>では、同じ内容のパケットをu8count+1回送信します。 force_retryの設定は無視されます。

tx_packet_delay

tx_packet_delay(uint16_t u16DelayMin,
                uint16_t u16DelayMax,
                uint16_t u16RetryDur)

パケットを送信するまでの遅延と再送間隔を設定します。u16DelayMinu16DelayMaxの2つの値をミリ秒[ms]で指定します。送信要求をしてからこの間のどこかのタイミングで送信を開始します。再送間隔をu16RetryDurの値[ms]で指定します。再送間隔は一定です。

  • <NWK_SIMPLE> この指定は有効です。 最初の送信から1秒を超えて再送され到達した同一パケットについては、新たなパケットが到達したとして重複除外が為されません。再送間隔を長く設定したり、中継でのパケット到達遅延により1秒を超えて同じパケットを受信する場合があります。 重複パケットの処理の設定は<NWK_SIMPLE>ビヘイビアの初期化で設定できます。

tx_process_immediate

tx_process_immediate()

パケット送信を「できるだけ速やかに」実行するように要求する設定です。TWENETでのパケット送信処理は、1msごとに動作するTickTimer起点で行われています。この設定をすることで、要求後速やかにパケット送信要求が処理されます。もちろん、tx_packet_delay(0,0,0)以外の設定では意味がない指定になります。

他のパケット送信処理が行われている場合は、通常の処理になります。

  • <NWK_SIMPLE> この指定は有効です。

tx_ack_required

tx_ack_required()

無線パケット通信では、送信完了後、送信相手先からACK(アック)という短い無線電文を得て、送信成功とする送信方法があります。このオプションを設定することで、ACK付き送信を行います。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>は、シンプルに動作する中継ネットワークの実装を目的としており、ACK付きの通信は行いません。

tx_addr_broadcast

tx_addr_broadcast()

ブロードキャストの指定を行います。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 替わりに宛先アドレスtx_addr(0xFF) (ブロードキャスト)またはtx_addr(0xFE)(子機宛のブロードキャスト)を指定します。

tx_packet_type_id

tx_packet_type_id(uint8_t)

0..7の指定ができるTWENET無線パケットのタイプIDを指定します。

  • <NWK_SIMPLE> <NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。 <NWK_SIMPLE>ではタイプIDを内部的に使用しています。ユーザは使用できません。

4.6 - serparser

シリアル書式入出力 (mwx::serial_parser)
シリアル書式の入出力のために用います。内部に解釈済みのバイナリ系列を保持するバッファを持ち、入力時は1バイトずつ系列を読み出し書式に従い内部バッファに格納し、系列の解釈が完了した時点で完了状態になるものです。反対に出力時は内部バッファから所定の出力書式に従いバッファを出力します。

メモリバッファ取り扱い方法(alloc)に応じて3種類のクラス名が定義されています。

// serparser_attach : 既存のバッファを用いる
serparser_attach

// serparser : Nバイトのバッファを内部に確保する
serparser_local<N>

// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap

定数(書式種別)

begin()の初期化のパラメータで渡す書式の種別です。ここではアスキー形式とバイナリー形式の2種類があります。

定数種別
uint8_t PARSER::ASCII = 1アスキー形式
uint8_t PARSER::BINARY = 2バイナリー形式

形式について

アスキー形式

アスキー形式は、バイナリで構成されたデータ列を文字列で表現する方法です。

例えばバイト列で 00A01301FF123456 をアスキー形式で表現すると、以下のようになります。先頭は :B1 がチェックサム、終端は [CR:0x0d][LF:0x0a] となります。

:00A01301FF123456B1[CR][LF]

終端のチェックサムを省略できます。チェックサムからCRLFの系列をXに置き換えます。文字化けによる誤ったデータ系列には弱くなりますが、実験などでデータを送付したいときに便利です。

:00A01301FF123456X

定義

======元データのバイト数バイト数解説
ヘッダ1:(0x3A) コロンを指定します。
データ部N2N元データの各バイトをアスキー文字列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) を指定する。

バイナリ形式

バイナリ形式は、バイナリで構成されたデータ列にヘッダとチェックサムを付加して送付する方法です。

例えば 00A01301FF123456をバイナリ形式で表現すると、以下のようになります。

0xA5 0x5A 0x80 0x08 0x00 0xA0 0x13 0x01 0xFF 0x12 0x34 0x56 0x3D

定義

======元データのバイト数形式におけるバイト数解説
ヘッダ20xA5 0x5A を指定します。
データ長2データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。
例えばデータ部の長さが 8 バイトなら0x80 0x08 を指定します。
データ部NN元データを指定します。
チェックサム1データ部の各バイトの XOR を計算します。
例えばデータ部が 00A01301FF123456なら 0x00 xor 0xA0 xor … 0x56 = 0x3D となります。
フッタ(1)チェックサムが事実上の終端です。無線モジュールからの出力では 0x04 (EOT) が付加されます。

メソッド

宣言, begin()

// serparser_attach : 既存のバッファを用いる
serparser_attach p1;

uint8_t buff[128];
p1.begin(ARSER::ASCII, buff, 0, 128);


// serparser : Nバイトのバッファを内部に確保する
serparser p2<128>;
p2.begin(PARSER::ASCII);


// serparser_heap : ヒープ領域にバッファを確保する
serparser_heap p3;
p3.begin(PARSER::ASCII, 128);

宣言にはメモリの確保クラスを指定します。この指定は煩雑であるため、上述のように別名定義を行っています。

クラス名(別名定義)
メモリ確保
内容
serparser_attachすでにあるバッファをbegin()にて指定する
serparser_local<N>Nバイトのバッファを内部に確保する
serparser_heapbegin()メソッドのパラメータで指定したサイズをヒープに確保する

メモリ確保クラスに応じたbegin()メソッドを呼び出します。

serparser_attach

void begin(uint8_t ty, uint8_t *p, uint16_t siz, uint16_t max_siz)

tyで指定する形式で、pで指定したバッファを用います。バッファの最大長はmax_sizで、バッファの有効データ長をsizで指定します。

この定義は、特に、データ列を書式出力したい場合に用います(>> 演算子参照)

serparser_local<N> - 内部にバッファを確保する

void begin(uint8_t ty)

tyで指定する形式で初期化を行います。

serparser_heap - ヒープに確保

void begin(uint8_t ty, uint16_t siz)

tyで指定する形式で、sizで指定したサイズをヒープに確保して初期化します。

get_buf()

BUFTYPE& get_buf()

内部バッファを返す。バッファは smplbuf<uint8_t, alloc> 型となります。

parse()

inline bool parse(uint8_t b)

入力文字を処理する。書式入力の入力文字列を1バイト受け取り書式に従い解釈します。例えばASCII書式では:00112233Xのような系列を入力として受け取りますが : 0 0 ... X の順で1バイトずつ入力し、最後の X を入力した時点で書式の解釈を完了し、完了済みと報告します。

parse()のパラメータは入力バイト、戻り値は解釈完了であればtrueを戻します。

while (Serial.available()) {
    int c = Serial.read();

    if (SerialParser.parse(c)) {
        // 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();

        // 以下は得られたデータ列に対する処理を行う
        if (b[0] == 0xcc) {
          // ...
        }
    }
}

operator bool()

operator bool()

trueならparse()により読み出しが完了した状態で、falseなら解釈中となります。

例 (parse()の例は以下のように書き換えられる)

while (Serial.available()) {
    int c = Serial.read();

    SerialParser.parse(c);

    if(SerialParser) {
        // 書式解釈完了、b に得られたデータ列(smplbuf<uint8_t>)
        auto&& b = SerialParser.get_buf();
        // ...
    }
}

<< 演算子

内部バッファを書式形式でストリーム(Serial)に対して出力します。

uint8_t u8buf[] = { 0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc };

ser_parser pout;
pout.begin(ARSER::ASCII, u8buf, 6, 6); // u8bufの6バイトを指定

Serial << pout;// Serialに書式出力 -> :112233AABBCC??[CR][LF]

4.7 - pktparser

パケットパーサ
pktparser(parser_packet)は、serparserで変換したバイト列に対して、内容の解釈を行います。
serparser_heap parser_ser;

void setup() {
    // init ser parser (heap alloc)
    parser_ser.begin(PARSER::ASCII, 256);
}

void loop() {
    int c;
    while ((c = Serial.read()) >= 0) {
        parser_ser.parse(c);
        if (parser_ser.available()) {
            // get buffer object
            auto&& payl = parser_ser.get_buf();
            // identify packet type
            auto&& typ = identify_packet_type(payl.begin(), payl.end());

            // if packet type is TWELITE standard 0x81 message
            if (typ == E_PKT::PKT_TWELITE) {
                pktparser pkt; // packet parser object
                // analyze packet data
                typ = pkt.parse<TwePacketTwelite>(payl.begin(), payl.end());

                if (typ != E_PKT::PKT_ERROR) { // success!
                    // get data object
                    auto&& atw = pkt.use<TwePacketTwelite>();

                    // display packet inforamtion
                    Serial << crlf << format("TWELITE: SRC=%08X LQI=%03d "
                        , app.u32addr_src, app.u8lqi);
	                  Serial << " DI1..4="
	                      << atw.DI1 ? 'L' : 'H' << atw.DI2 ? 'L' : 'H'
                        << atw.DI3 ? 'L' : 'H' << atw.DI4 ? 'L' : 'H';
                }
            }
        }
    }
}

上記の例は、標準アプリケーションの0x81メッセージの解釈を行っています。parser_serオブジェクトによりSerialより入力された電文をバイト列に変換します。このバイト列をまずidentify_packet_type()により電文の種別E_PKTを特定します。電文の種別が判定できたら次に.parse<TwePacketTwelite>()により電文を解析します。解析結果はTwePacketTwelite型になりますが、このオブジェクトを取り出す手続きが.use<TwePacketTwelite>()です。TwePacketTwelite型はクラスですが構造体として直接メンバー変数を参照します。

parse<T>

template <class T>
E_PKT parse(const uint8_t* p, const uint8_t* e)

バイト列を解析します。

Tには解析対象のパケット型を指定します。例えば標準アプリケーションの0x81メッセージならTwePacketTweliteを指定します。

peはバイト列の先頭と終端の次を指定します。

戻り値はE_PKT型です。エラーの場合はE_PKT::PKT_ERRORが戻ります。

user<T>

template 
T& use()

解釈したバイト列に対応するパケット型に対応するオブジェクトの参照を返します。事前にparse<T>を実行しエラーがなかった場合に呼び出すせます。

Tparse<T>で実行した型と同じもの、または基本的な情報のみ取得できるTwePacketを指定します。

4.7.1 - E_PKT

パケット種別定義

以下のパケットに対応します。

名前解説
PKT_ERRORパケット解釈前やパケット種別が特定できないなど、TwePacketには意味のあるデータが格納されていない
PKT_TWELITE標準アプリ App_Twelite の 0x81 コマンドを解釈したもの
PKT_PALTWELITE PALのシリアル形式を解釈したもの
PKT_APPIOリモコンアプリ App_IO UARTメッセージを解釈したもの
PKT_APPUARTシリアル通信アプリ App_UART 拡張書式を解釈したもの。
PKT_APPTAG無線タグアプリApp_TagのUARTメッセージを解釈したもの。センサ固有部分は解釈されずpayloadとしてバイト列を報告します。
PKT_ACT_STDアクト(Act)のサンプルなどで使用される出力書式。

4.7.2 - identify_packet_type()

パケット種別の判定

idenify_packet_type()

パケットデータのバイト列を入力として、パケットの種別を判定します。戻り値はE_PKTです。

E_PKT identify_packet_type(uint8_t* p, uint8_t u8len)

特定のパケットであると解釈できなかった場合はE_PKT::PKT_ERRORが戻ります。

4.7.3 - TwePacket

パケット型
パケット型の基底クラスですが、メンバー構造体commonにはアドレス情報など共通情報が含まれます。
class TwePacket {
	public:
		static const E_PKT _pkt_id = E_PKT::PKT_ERROR;

		struct {
			uint32_t tick;     // 解釈実行時のシステム時刻[ms]
			uint32_t src_addr; // 送信元アドレス(シリアル番号)
			uint8_t src_lid;   // 送信元アドレス(論理アドレス)
			uint8_t lqi;       // LQI
			uint16_t volt;     // 電圧[mV]
		} common;
};

4.7.3.1 - TwePacketTwelite

App_Tweliteからのパケット
TwePacketTweliteクラスは、標準アプリApp_Tweliteの0x81コマンドを解釈したものです。
class TwePacketTwelite : public TwePacket, public DataTwelite { ... };

パケットデータ内の諸情報はparse<TwePacketTwelite>()実行後にパケット情報がDataTweliteに格納されます。

DataTwelite構造体

struct DataTwelite {
		//送信元のシリアル#
		uint32_t u32addr_src;

		// 送信元の論理ID
		uint8_t u8addr_src;

		// 宛先の論理ID
		uint8_t u8addr_dst;

		// 送信時のタイムスタンプ
		uint16_t u16timestamp;

		// 低レイテンシ送信時のフラグ
		bool b_lowlatency_tx;

		// リピート中継回数
		uint16_t u8rpt_cnt;

		// LQI値
		uint16_t u8lqi;

		// DIの状態 (true がアクティブ Lo,GND)
		bool DI1, DI2, DI3, DI4;
		// DIの状態ビットマップ (LSBから順にDI1,2,3,4)
		uint8_t DI_mask;

		// DIアクティブならtrue (過去にアクティブになったことがある)
		bool DI1_active, DI2_active, DI3_active, DI4_active;
		// DIのアクティブビットマップ(LSBから順にDI1,2,3,4)
		uint8_t DI_active_mask;

		// モジュールの電源電圧[mV]
		uint16_t u16Volt;

		// AD値 [mV]
		uint16_t u16Adc1, u16Adc2, u16Adc3, u16Adc4;
		// ADがアクティブ(有効)なら 1 になるビットマップ (LSBから順にAD1,2,3,4)
		uint8_t Adc_active_mask;
};

4.7.3.2 - TwePacketIO

App_IOからのパケット
TwePacketAppIOクラスは、標準アプリApp_IOのシリアルメッセージ(0x81)を解釈したものです。
class TwePacketAppIO : public TwePacket, public DataAppIO { ... };

パケットデータ内の諸情報はparse<TwePacketIO>()実行後にDataTweliteに格納されます。

DataAppIO構造体

struct DataAppIO {
		//送信元のシリアル#
		uint32_t u32addr_src;

		// 送信元の論理ID
		uint8_t u8addr_src;

		// 宛先の論理ID
		uint8_t u8addr_dst;

		// 送信時のタイムスタンプ
		uint16_t u16timestamp;

		// 低レイテンシ送信時のフラグ
		bool b_lowlatency_tx;

		// リピート中継回数
		uint16_t u8rpt_cnt;

		// LQI値
		uint16_t u8lqi;

		// DIの状態ビットマップ (LSBから順にDI1,2,3,4,...)
		uint8_t DI_mask;

		// DIのアクティブ(使用なら1)ビットマップ(LSBから順にDI1,2,3,4,...)
		uint8_t DI_active_mask;

		// DIが割り込み由来かどうかのビットマップ(LSBから順にDI1,2,3,4,...)
		uint16_t DI_int_mask;
};

4.7.3.3 - TwePacketUART

App_UARTからのパケット
TwePacketAppUartクラスは、App_UARTの拡張書式を親機・中継機アプリApp_Wingsで受信したときの形式です。
class TwePacketAppUART : public TwePacket, public DataAppUART

パケットデータ内の諸情報はparse<TwePacketUART>()実行後にDataAppUARTに格納されます。

DataAppUART構造体

struct DataAppUART {
		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_src;

		/**
		 * source address (Serial ID)
		 */
		uint32_t u32addr_dst;

		/**
		 * source address (logical ID)
		 */
		uint8_t u8addr_src;

		/**
		 * destination address (logical ID)
		 */
		uint8_t u8addr_dst;

		/**
		 * LQI value
		 */
		uint8_t u8lqi;

		/**
		 * Response ID
		 */
		uint8_t u8response_id;

		/**
		 * Payload length
		 */
		uint16_t u16paylen;

		/**
		 * payload
		 */
##if MWX_PARSER_PKT_APPUART_FIXED_BUF == 0
		mwx::smplbuf_u8_attach payload;
##else
		mwx::smplbuf_u8<MWX_PARSER_PKT_APPUART_FIXED_BUF> payload;
##endif
	};

payloadはデータ部分ですが、マクロ定義によってデータ格納の方法が変わります。

MWX_PARSER_PKT_APPUART_FIXED_BUFの値が0としてコンパイルした場合は、payloadはパケット解析を行うバイト列を直接参照します。元のバイト列の値が変更されるとpayload中のデータは破壊されます。

MWX_PARSER_PKT_APPUART_FIXED_BUFの値を0より大きい値として定義した場合は、payloadにはその値(バイト数)のバッファが確保されます。ただしシリアル電文のデータがバッファサイズを超えた場合はparse<TwePacketAppUART>()は失敗しE_PKT::PKT_ERRORを戻します。

4.7.3.4 - TwePacketPAL

App_PALからのパケット
TwePacketPalクラスは、TWELITE PALのパケットデータを解釈したものです。このクラスはTWELITE PAL(センサーデータなど上り方向)共通に取り扱います。
class TwePacketPal : public TwePacket, public DataPal { ... };

PAL共通データはDataPalに定義されています。

PALの各センサー基板特有のデータを取り出すためのジェネレータ関数を用意しています。

DataPal構造体

PALは接続されるセンサーなどによってパケットデータ構造が異なりますが、DataPalでは共通部のデータ構造を保持します。

struct DataPal {
	uint8_t u8lqi;        // LQI値

	uint32_t u32addr_rpt; // 中継器のアドレス

	uint32_t u32addr_src; // 送信元のアドレス
	uint8_t u8addr_src;   // 送信元の論理アドレス

	uint16_t u16seq;      // シーケンス番号

	E_PAL_PCB u8palpcb;		// PAL基板の種別
	uint8_t u8palpcb_rev;	// PAL基板のレビジョン
	uint8_t u8sensors;		// データに含まれるセンサーデータの数 (MSB=1はエラー)
	uint8_t u8snsdatalen; // センサーデータ長(バイト数)

	union {
		const uint8_t *au8snsdata; // センサーデータ部への参照
		uint8_t _pobj[MWX_PARSER_PKT_APPPAL_FIXED_BUF]; // 各センサーオブジェクト
	};
};

PALのパケットデータ構造は大まかに2つのブロックからなり、全てのPAL共通部と個別のデータ部になります。個別のデータ部は、パケットの解釈を行わずそのまま格納しています。取り扱いを単純化するため32バイトを超えるデータは動的に確保するuptr_snsdataに格納します。

個別のデータ部は、PalBaseをベースクラスに持つ構造体に格納されます。この構造体は、TwePacketPalに定義されるジェネレータ関数により生成されます。

parse<TwePacketPAL>()実行時にMWX_PARSER_PKT_APPPAL_FIXED_BUFに収まるサイズであれば、センサー個別のオブジェクトを生成します。

収まらない場合はau8snsdataに解析時のバイト列の参照が保存されます。この場合、解析に用いたバイト列のデータが書き換えられた場合は、センサー個別のオブジェクトは生成できなくなります。

PalBase

PALの各センサーのデータ構造体はすべてPalBaseを継承します。センサーデータの格納状況u32StoredMaskが含まれます。

	struct PalBase {
		uint32_t u32StoredMask; // 内部的に利用されるデータ取得フラグ
	};

PalEvent

PALイベントは、センサーなどの情報を直接送るのではなく、センサー情報を加工し一定の条件が成立したときに送信される情報です。例えば加速度センサーの静止状態から一定以上の加速度が検出された場合などです。

	struct PalEvent {
		uint8_t b_stored;       // 格納されていたら true
		uint8_t u8event_source; // 予備
		uint8_t u8event_id;     // イベントID
		uint32_t u32event_param;// イベントパラメータ
	};

イベントデータが存在する場合はTwePacketPal.is_PalEvent()trueになることで判定でき、.get_PalEvent()によりPalEventデータ構造を得られます。

ジェネレータ関数

センサーPALの各種データを取り出すためのジェネレータ関数です。

void print_pal(pktparser& pkt) {
	auto&& pal = pkt.use<TwePacketPal>();
	if (pal.is_PalEvent()) {
		PalEvent obj = pal.get_PalEvent();
	} else
	switch(pal.u8palpcb) {
	case E_PAL_PCB::MAG:
	  {
		  // generate pal board specific data structure.
		  PalMag obj = pal.get_PalMag();
	  } break;
  case E_PAL_PCB::AMB:
	  {
		  // generate pal board specific data structure.
		  PalAmb obj = pal.get_PalAmb();
	  } break;
	  ...
	default: ;
	}
}

ジェネレータ関数を利用するには、まずpktがイベントかどうか判定(.is_PalEvent())します。イベントの場合はget_PalEvent()を持ちます。それ以外はu8palpcbに応じてオブジェクトを生成します。

get_PalMag()

PalMag get_PalMag()

// MAG
struct PalMag : public PalBase {
    uint16_t u16Volt;         // モジュール電圧[mV]
    uint8_t u8MagStat;        // 磁気スイッチの状態 [0:磁石なし,1,2]
    uint8_t bRegularTransmit; // MSB flag of u8MagStat
};

.u8palpcb==E_PAL_PCB::MAGの場合、開閉センサーパルのデータPalMagを取り出します。

get_PalAmb()

PalAmb get_PalAmb()

// AMB
struct PalAmb : public PalBase {
    uint16_t u16Volt;       // モジュール電圧[mV]
    int16_t i16Temp;        // 温度(100倍値)
    uint16_t u16Humd;       // 湿度(100倍値)
    uint32_t u32Lumi;       // 照度(Lux相当)
};

.u8palpcb==E_PAL_PCB::AMBの場合、環境センサーパルのデータPalAmbを取り出します。

get_PalMot()

PalMot get_PalMot()

// MOT
struct PalMot : public PalBase {
    uint16_t u16Volt;  // モジュール電圧[mV]
    uint8_t u8samples; // サンプル数
    uint8_t u8sample_rate_code; // サンプルレート (0: 25Hz, 4:100Hz)
    int16_t i16X[16]; // X 軸
    int16_t i16Y[16]; // Y 軸
    int16_t i16Z[16]; // Z 軸
};

.u8palpcb==E_PAL_PCB::MOTの場合、動作センサーパルのデータPalMotを取り出します。

get_PalEvent()

PalEvent get_PalEvent()

// PAL event
struct PalEvent {
    uint8_t b_stored;        // trueならイベント情報あり
    uint8_t u8event_source;  // イベント源
    uint8_t u8event_id;      // イベントID
    uint32_t u32event_param; // 24bit、イベントパラメータ
};

.is_PalEvent()trueの場合PalEvent(PALイベント)を取り出します。

4.8 - smplbuf

配列構造のコンテナクラス
内部が配列構造のコンテナクラスです。初期化時にバッファの最大サイズを決定しますが、その最大サイズまでの範囲で可変長の配列として振る舞います。
template <typename T, int N> smplbuf_local
template <typename T> smplbuf_attach
template <typename T> smplbuf_heap

smplbufは要素の型Tメモリの確保方法allocで指定したメモリ領域に対して配列の操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。

オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、サイズは0です。必要に応じてサイズを拡張しながら使用します。

// 配列領域は、クラスメンバー変数の固定配列
smplbuf_local<uint8_t, 128> b1;

// 配列領域は、すでにある領域を参照
uint8_t buf[128];
smplbuf_attach<uint8_t> b2(;

// 配列領域は、ヒープに確保
smplbuf_heap<uint8_t> b3;

// 初期化(グローバル定義の場合はsetup()で行う)
void setup() {
    b1.init_local();
    b2.attach(buf, 0, 128);
    b3.init_heap(128);
}

// 処理関数内
void some_func() {
    smplbuf_local<uint8_t, 128> bl;
    // bl.init_local(); // smplbuf_localがローカル定義の場合は省略可能

    bl.push_back('a');
}

上記のuint8_t型に限り別名定義があります。

template <int N>
smplbuf_u8
// smplbuf<uint8_t, alloc_local<uint8_t, N>>

smplbuf_u8_attach
// smplbuf<uint8_t, alloc_attach<uint8_t>>

smplbuf_u8_heap
// smplbuf<uint8_t, alloc_heap<uint8_t>>

通常の配列のように[]演算子などを用いて要素にアクセスできますし、イテレータを用いたアクセスも可能です。

void begin() { // begin()は起動時1回だけ動作する
  smplbuf_u8<32> b1;
  b1.reserve(5); // 5バイト分利用領域に初期化(b1[0..5]にアクセスできる)

  b1[0] = 1;
  b1[1] = 4;
  b1[2] = 9;
  b1[3] = 16;
  b1[4] = 25;

  for(uint8_t x : b1) { // 暗黙に .begin() .end() を用いたループ
    Serial << int(x) << ",";
  }
}

push_back()メソッドを定義しています。末尾にデータを追記するタイプのアルゴリズムが使用可能になります。

宣言・初期化

smplbuf_local<T,N>()
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>(T* buf, uint16_t size, uint16_t N)
smplbuf_attach<T>::attach(T* buf, uint16_t size, uint16_t N)

smplbuf_heap<T>()
smplbuf_heap<T>::init_heap(uint16_t N)

// 例
// 内部に固定配列
smplbuf_local<uint8_t, 128> b1;
b1.init_local();

// すでにある配列を利用する
uint8_t buf[128];
smplbuf_attach<uint8_t> b2;
b2.attach(buf, 0, 128);

// ヒープに確保する
smplbuf_heap<uint8_t> b3;
b3.init_heap(128);

TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。

smplbuf_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。

smplbuf_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。

smplbuf_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。

初期化子リスト

void in_some_func() {
    smplbuf_local<uint8_t, 5> b1;
    b1.init_local();

    b1 = { 0, 1, 2, 3, 4 };

    smplbuf_local<uint8_t, 5> b2{0, 1, 2, 3, 4};
}

初期化子リスト(イニシャライザリスト){ ... } によるメンバーの初期化をできます。smplbuf_localのローカル宣言でのコンストラクタでの利用を除き、初期化のメソッドを呼び出した後に有効です。

  • 代入演算子の右辺値 (smplbuf_local, smplbuf_attach, smplbuf_heap)
  • コンストラクタ(smplbuf_localのローカル宣言、グローバル宣言は不可)

メソッド

append(), push_back(), pop_back()

inline bool append(T&& c)
inline bool append(const T& c)
inline void push_back(T&& c)
inline void push_back(const T& c)
inline void pop_back()

末尾にメンバーcを追加します。append()の戻り値はboolで、バッファが一杯で追加できないときにfalseが返ります。

pop_back()は末尾のエントリを抹消します。ただしエントリのクリアはしません。

empty(), size(), capacity()

inline bool empty()
inline bool is_end()
inline uint16_t size()
inline uint16_t capacity()

empty()は配列に要素が格納されていない場合にtrueを戻します。is_end()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。

size()は配列の要素数を返します。

capacity()は配列の最大格納数を返します。

reserve(), reserve_head(), redim()

inline bool reserve(uint16_t len)
inline void reserve_head(uint16_t len)
inline void redim(uint16_t len)

reserve()は配列のサイズを拡張します。配列が格納されていない領域はデフォルトで初期化されます。

reserve_hear()は配列の先頭部に指定したサイズの領域を確保します。コンテナオブジェクトからは参照できない領域となります。例えばパケットペイロードのヘッダ部分を読み飛ばした部分配列にアクセスするようなコンテナとして利用できるようにします。確保した領域を戻しすべてアクセスできるようにコンテナを戻すには確保時と同じ負の値を与えます。

redim()は利用領域のサイズを変更します。reserve()と違い、未使用領域の初期化を行いません。

operator []

inline T& operator [] (int i)
inline T operator [] (int i) const

要素にアクセスします。

iに負の値を与えるとバッファー末尾からの要素となります。-1の場合は末尾の要素、-2は末尾から一つ手前となります。

mwx::streamへの出力

uint8_t型の配列オブジェクト(smplbuf<uint8_t, *>)は、mwx::streamの派生オブジェクトに対して、そのまま出力できます。

<< 演算子

template <class L_STRM, class AL>
	mwx::stream<L_STRM>& operator << (
			mwx::stream<L_STRM>& lhs, mwx::_smplbuf<uint8_t, AL>& rhs)

//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf;
// 出力: abc

Serialなどmwx::streamの派生オブジェクトに対して、バイト列を出力します。

to_stream()

inline std::pair<T*, T*> to_stream()

//例
smplbuf_u8<128> buf;
buf.push_back('a');
buf.push_back('b');
buf.push_back('c');

Serial << buf.to_stream();
// 出力:0123

ストリームへの出力目的で利用します。«演算子の実装に用いられています。

mwx::streamでデータ生成

mwx::streamでは<<演算子やprintfmt()メソッドと行ったストリームに対してバイト列を出力するための関数・演算子が定義されています。uint8_t型のsmplbufの配列を出力先と見立ててストリーム出力手続きを行えます。

方法は2種類あります。

4.8.1 - get_stream_helper()

mwx::stream を使用するためのヘルパーオブジェクト
uint8_t型のsmplbuf配列を参照したstream_helper を経由して、mwx::streamによる演算子やメソッドを用います。
smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // ヘルパーオブジェクト

// データ列の生成
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Serialへの出力は smplbuf_u8<32> クラス経由で

//結果: ABCD;0123;99

ヘルパーオブジェクトの型名は長くなるためauto&&により解決しています。このオブジェクトに対して<<演算子などmwx::streamで定義されたインタフェースを利用できます。

生成されたヘルパーオブジェクトbsは生成時に大本の配列bの先頭位置から読み書きを始めます。配列の末尾の場合はappend()によりデータを追加します。読み書きを行うたびに位置は次に移動していきます

ヘルパー関数では読み出し用の>>演算子が利用できます。

//..上例の続き
// ABCD;0123;99 <- bに格納されている

//読み出しデータ格納変数
uint8_t FOURCHARS_READ[4];
uint32_t u32val_read;
uint8_t c_read[2];

// >>演算子で読み出す
bs.rewind();                //ポジションを先頭に巻き戻す
bs >> FOURCHARS_READ;      //4文字
bs >> mwx::null_stream(1); //1文字スキップ
bs >> u32val_read;         //32bitデータ
bs >> mwx::null_stream(1); //1文字スキップ
bs >> c_read;              //2文字

// 結果表示
Serial << crlf << "4chars=" << FOURCHARS_READ;
Serial << crlf << format("32bit val=0x%08x", u32val_read);
Serial << crlf << "2chars=" << c_read;

// 4chars=ABCD
// 32bit val=0x30313233
// 2chars=99

4.8.2 - smplbuf_strm_u8

直接ストリーム用メソッドを使用することのできる型
uint8_t型のsmplbuf_strm_u8???ストリーム(stream)インタフェースも有しているため、いくつかのストリーム用のメソッドを使用することができます。
// smplbuf_strm_u8<N> : ローカル確保
template <int N> using smplbuf_strm_u8
  = _smplbuf_stream<uint8_t, mwx::alloc_local<uint8_t, N>>;

// smplbuf_strm_u8_attach : 既存バッファへのアタッチ版
using smplbuf_strm_u8_attach
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_attach<uint8_t>>;

// smplbuf_strm_u8_heap : HEAP確保
using smplbuf_strm_u8_heap
  = mwx::_smplbuf_stream<uint8_t, mwx::alloc_heap<uint8_t>>;

// << 演算子の定義
template <class L_STRM, class ALOC>
mwx::stream<L_STRM>& operator << (
        mwx::stream<L_STRM>& lhs,
        mwx::_smplbuf_stream<uint8_t, ALOC>& rhs)
{
		lhs << rhs.to_stream();
		return lhs;
}

smplbuf_strm_u8<128> sb1;

sb1 << "hello";
sb1 << uint32_t(0x30313233);
sb1 << format("world%d",99);
sb1.printfmt("Z!");

Serial << sb1;
// hello0123world99Z!

4.9 - smplque

FIFOキュー構造のコンテナクラス
FIFOキュー構造のコンテナクラスです。
template <typename T, int N, class Intr> smplbuf_local
template <typename T, class Intr> smplbuf_attach
template <typename T, class Intr> smplbuf_heap

smplqueは要素の型Tメモリの確保方法allocで指定したメモリ領域に対してFIFOキューの操作を提供するコンテナクラスです。allocの指定は煩雑であるためusingを用いた別名定義が行っています。

宣言時に割り込み禁止設定を行うクラスIntrを登録することが出来ます。このクラスは指定しない場合は、割り込み禁止制御を行わない通常の動作となります。

オブジェクトの宣言例です。宣言の直後に初期化用のメソッド呼び出しを行います。いずれも初期化直後の最大サイズは128バイトで、初期サイズは0で何も格納されていません。最大サイズは変更できません。

void some_func() {

// 内部に固定配列
smplque_local<uint8_t, 128> q1;

// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;

// ヒープに確保する
smplque_heap<uint8_t> q3;

void setup() {
  // グローバル定義のオブジェクトは setup() で初期化
  q1.init_local();
  q2.attach(buf, 128);
  q3.init_heap(128);
}

void some_func() {
  // ローカル定義の smplque_local は init_local() は省略できる
  smplque_local<uint8_t, 128> q_local;
  ..
}

FIFOキューですのでpush(),pop(),front()といったメソッドを用いて操作します。

void begin() { // begin() は起動時1回のみ動作する
	smplque_local<int, 32> q1;

	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);

	while(!q1.empty()) {
		Serial << int(q1.front()) << ',';
		q1.pop();
	}
	// output -> 1,4,9,16,25,
}

イテレータによるアクセスも可能です。

void begin() { // begin() は起動時1回のみ動作する
	smplque_local<int, 32> q1;
	q1.init_local();

	q1.push(1);
	q1.push(4);
	q1.push(9);
	q1.push(16);
	q1.push(25);

	// イテレータを利用
	for(int x : q1) {
		Serial << int(x) << ',';
	}

	// STLアルゴリズムの適用
	auto&& minmax = std::minmax_element(q1.begin(), q1.end());
	Serial <<  "min=" << int(*minmax.first)
		     << ",max=" << int(*minmax.second);
	// output -> 1,4,9,16,25,min=1,max=25[]
}

宣言・初期化

smplbuf_local<T,N>
smplbuf_local<T,N>::init_local()

smplbuf_attach<T>
smplbuf_attach<T>::attach(T* buf, uint16_t N)

smplbuf_heap<T>
smplbuf_heap<T>::init_heap(uint16_t N);

//例
// 内部に固定配列
smplque_local<uint8_t, 128> q1;
q1.init_local();

// すでにある配列を利用する
uint8_t buf[128];
smplque_attach<uint8_t> q2;
q2.attach(buf, 128);

// ヒープに確保する
smplque_heap<uint8_t> q3;
q3.init_heap(128);

TでサイズNのコンテナを宣言します。宣言後に初期化のメソッドを呼び出します。

smplque_localは、内部に固定配列により領域を確保します。コンストラクタによる初期化も可能です。

smplque_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。コンストラクタによる初期化も可能です。

smplque_heapは、HEAP領域(解放は不可能だが随時確保可能なメモリ領域)にメモリを確保します。一度確保したら開放できない領域ですので通常はグローバル領域に定義します。領域確保はinit_heap()で行います。コンストラクタによるメモリ確保はできません。必ずinit_heap()を呼び出して利用してください。

メソッド

push(), pop(), front(), back()

inline void push(T&& c)
inline void push(T& c)
inline void pop()
inline T& front()
inline T& back()

inline T& pop_front()

push()はエントリをキューに追加します。

pop()はエントリをキューから抹消します。

front()は先頭のエントリ(一番最初に追加されたもの)を参照します。

back()は末尾のエントリ(一番最後に追加されたもの)を参照します。

pop_front()は先頭のエントリを戻り値として参照し、同時にそのエントリをキューから抹消します。

empty(), size(), is_full()

inline bool empty()
inline bool is_full()
inline uint16_t size()
inline uint16_t capacity()

empty()は配列に要素が格納されていない場合にtrueを戻します。is_full()は反対に配列サイズ一杯まで要素が格納されているときにtrueを戻します。

size()はキューに格納されている要素数を返します。

capacity()はキューの最大格納数を返します。

clear()

inline void clear()

キューのすべての要素を抹消します。

operator []

inline T& operator[] (int i)

要素にアクセスします。0が最初に追加した要素です。

イテレータ

inline smplque::iterator begin()
inline smplque::iterator end()

begin()end()によるイテレータを取得できます。イテレータの先頭はキューの最初に登録した要素です。イテレータを用いることで範囲for文やアルゴリズムが利用できます。

応用としてaxis_xyzt構造体の特定のメンバーに注目したイテレータによるアクセスがあります。

4.10 - 入出力ストリーム

入出力ストリームを処理する上位クラス
入出力ストリームを処理する上位クラスです。
  • CRTP (Curiously Recurring Template Pattern) 手法を用いたポリモーフィズムにより、いくつかのクラス(Serial, Wire, SPI, smplbuf) にインタフェースを提供します。
    • CRTP では下位クラスは template class Derived : public stream<Derived>;のように定義し、上位クラスからも下位クラスのメソッドを参照します。
  • 本クラスでは print メソッド、<< 演算子などの共通処理の定義を行い、下位クラスで実装した write() メソッドなどを呼び出すことで、仮想関数を用いるのと近い実装を行っています。

インタフェース(下位クラスで実装)

下位クラスでは、以下に列挙する関数を実装します。

available()

int available()

// example
while(Serial.available()) {
  int c = Serial.read();
  // ... any
}

入力が存在する場合は 1、存在しない場合は 0 を返します。

パラメータ解説
戻り値 int0: データなし 1:データあり

flush()

void flush()

// example
Serial.println("long long word .... ");
Serial.flush();

出力をフラッシュ(出力完了まで待つ)します。

read()

int read()

// example
int c;
while (-1 != (c = read())) {
    // any
}

ストリームより1バイトデータを入力します。データが存在しない場合は -1 を戻します。

write()

size_t write(int c)

// example
Serial.write(0x30);

ストリームに1バイト出力します。

パラメータ解説
n出力したい文字。
戻り値 size_t出力が成功すれば 1、失敗すれば 0。

vOutput()

static void vOutput(char out, void* vp)

1バイト出力を行うスタティック関数です。クラスメソッドではないため、メンバー変数等の情報は利用できません。替わりにパラメータとして渡される vp にクラスインスタンスへのポインタを渡します。

このスタティック関数は内部的に利用されfctprintf()の1バイト出力関数として関数ポインタが渡ります。これを用いてprintメソッドなどを実装しています。

パラメータ解説
out出力したい文字
vpクラスインスタンスへのポインタ
通常は、元のクラスにキャストして write() メソッドを呼び出す

インタフェース

putchar()

void mwx::stream::putchar(char c)

// example
Serial.putchar('A');
// result -> A

1バイト出力します。

size_t print(T val, int base = DEC) // T: 整数型
size_t print(double val, int place = 2)
size_t print(const char*str)
size_t print(std::initializer_list<int>)

// example
Serial.print("the value is ");
Serial.print(123, DEC);
Serial.println(".");
// result -> the value is 123.

Serial.print(123.456, 1);
// result -> 123.5

Serial.print({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

各種整形出力を行います。

パラメータ解説
val整形出力したい数値型
base出力形式BIN 二進数 / OCT 8進数 / DEC 10進数 / HEX 16進数
place小数点以下の桁数
戻り値 size_t書き出したバイト数

printfmt()

size_t printfmt(const char* format, ...);

// example
Serial.printfmt("the value is %d.", 123);
// result -> the value is 123.

printf 形式での出力を行います。

TWESDK/TWENET/current/src/printf/README.md 参照

operator <<

// examples
Serial << "this value is" // const char*
       << int(123)
       << '.';
       << mwx::crlf;
// result -> this value is 123.

Serial << fromat("this value is %d.", 123) << twe::crlf;
// result -> this value is 123.

Serial << mwx::flush; // flush here

Serial << bigendian(0x1234abcd);
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << int(0x30) // output 0x30=48, "48"
       << '/'
       << uint8_t(0x31); // output '1', not "48"
// result -> 48/1

smplbuf<char,16> buf = { 0x12, 0x34, 0xab, 0xcd };
Serail << but.to_stream();
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Seiral << make_pair(buf.begin(), buf.end());
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.

Serial << bytelist({ 0x12, 0x34, 0xab, 0xcd });
// will output 4byte of 0x12 0x34 0xab 0xcd in binary.
引数型解説
char1バイト出力 (数値としてフォーマットはしない)
int整数出力 (printf の “%d”)
double数値出力 (printf の “%.2f”)
uint8_t1バイト出力する(char型と同様)
uint16_t2バイト出力する(ビッグエンディアン順)
uint32_t4バイト出力する(ビッグエンディアン順)
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 により生成できる。Tuint8_t 型を想定する。(右辺値)
bytelist()std::initializer_list を用いるバイト列の出力
smplbuf<uint8_t,AL>&uint8_t型の配列クラスの内容を出力する。ALCメモリ確保手段
smplbuf<uint8_t, AL>::to_stream()smplbuf&#x3C;T> のデータを出力する
Tuint8_t型、ALメモリ確保手段

set_timeout(), get_error_status(), clear_error_status()

uint8_t get_error_status()
void clear_error_status()
void set_timeout(uint8_t centisec)

// example
Serial.set_timeout(100); // 1000msのタイムアウトを設定
uint8_t c;
Serial >> c;

>>演算子を用いた入力タイムアウトとエラーを管理します。

set_timeout() によりタイムアウト時間を指定し、>>演算子により入力処理を行います。所定時間内までに入力が得られない場合は get_error_status() によりエラー値を読み出せます。clear_error_status()によりエラー状況をクリアします。

引数型解説
centisec1/10秒単位でタイムアウト時間を設定します。0xffを指定した場合は、タイムアウトを無効とします。

エラー値

意味
0エラーなし
1エラー状況

operator >>

inline D& operator >> (uint8_t& v)
inline D& operator >> (char_t& v)
template <int S> inline D& operator >> (uint8_t(&v)[S])
inline D& operator >> (uint16_t& v)
inline D& operator >> (uint32_t& v)
inline D& operator >> (mwx::null_stream&& p)

//// 例
uint8_t c;

the_twelite.stop_watchdog(); // ウォッチドッグの停止
Serial.set_timeout(0xFF); // タイムアウト無し

// 1バイト読み出す
Serial >> c;
Serial << crlf << "char #1: [" << c << ']';

// 読み捨てる
Serial >> null_stream(3); // 3バイト分読み捨てる
Serial << crlf << "char #2-4: skipped";

// 4バイト分読み出す (uint8_t 型固定長配列限定)
uint8_t buff[4];
Serial >> buff;
Serial << crlf << "char #5-8: [" << buff << "]";

入力処理を行います。

以下に読み出し格納できる型を列挙します。

引数型解説
uint8_t, char_t1バイト入力
uint16_t2バイト入力(ビッグエンディアン順)
uint32_t4バイト入力(ビッグエンディアン順)
uint8_t[S]Sバイト分入力(Sは固定配列のサイズ指定)
null_stream(int n)nバイト読み捨てる

4.10.1 - mwx::mwx_format

printf の書式入力
mwx::stream の « 演算子に対してフォーマット書式を書き出すヘルパークラスです。

ライブラリ内では Using format=mwx::mwx_format; として別名定義しています。

Serial << format("formatted print: %.2f", (double)3123 / 100.) << mwx::crlf;

// formatted print: 31.23[改行]
  • コンストラクタで受け取った引数リストを、パラメータパックの展開機能を用いてクラス内部変数に格納する
  • operator << が呼び出された時点で、fctprintf() を呼び出し、ストリームにデータを書き出す

コンストラクタ

format(const char *fmt, ...)

コンストラクタでは、書式のポインタとパラメータを保存します。続く <<演算子による呼び出しでフォーマットを解釈して出力処理を行います。

パラメータ解説
fmtフォーマット書式。TWESDK/TWENET/current/src/printf/README.md 参照
...フォーマット書式に応じたパラメータ。
※ 最大数は4で、5つ以上のパラメータではコンパイルエラーとなる。※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。

4.10.2 - mwx::bigendian

ビッグエンディアン順のデータ出力
mwx::stream<< 演算子に対して数値型をビッグエンディアンのバイト列で出力するヘルパークラスです。
Serial << mwx::bigendian(0x1234abcdUL);

// output binary -> 0x12 0x34 0xab 0xcd

コンストラクタ

template <typename T>
bigendian::bigendian(T v)
パラメータ解説
vuint16_t または uint32_t の型の値

4.10.3 - mwx::crlf

改行コード出力
mwx::stream<< 演算子に対して改行コード (CR LF) を出力するためのヘルパークラスのインスタンスです。
Serial << "hello world!" << mwx::crlf;

4.10.4 - mwx::flush

出力バッファ強制出力
mwx::stream の出力バッファをフラッシュします。

flush() メソッドを呼び出すヘルパークラスへのインスタンスです。

for (int i = 0; i < 127; ++i) {
    Serial << "hello world! (" << i << ")" << twe::endl << twe::flush;
}
  • シリアルポートの場合は出力完了までポーリング待ちを行う
  • mwx::simpbuf バッファの場合は 0x00 を末尾に出力する(サイズは変更しない)

4.10.5 - stream_helper

ヘルパーオブジェクト
stream_helperは、mwx::streamインタフェースを付与するヘルパーオブジェクトです。データクラスを参照するヘルパーオブジェクトを生成し、ヘルパーオブジェクト経由でデータの入出力を行います。

以下にはsmplbufの配列bからヘルパーオブジェクトbsを生成しmwx::stream::operator <<()演算子によるデータ入力を行っています。

smplbuf_u8<32> b;
auto&& bs = b.get_stream_helper(); // ヘルパーオブジェクト

// データ列の生成
uint8_t FOURCHARS[]={'A', 'B', 'C', 'D'};
bs << FOURCHARS;
bs << ';';
bs << uint32_t(0x30313233); // "0123"
bs << format(";%d", 99);

Serial << b << crlf; // Serialへの出力は smplbuf_u8<32> クラス経由で

//結果: ABCD;0123;99

概要

stream_helper はデータ配列をストリームに見立てて振舞います。

内部にはデータ配列中の読み書き位置を保持しています。次のようにふるまいます。

  • 読み出しまたは書き込みをすると次の読み書き位置に移動します。
  • 最期のデータを読み出した後、またはデータを末尾に追記した後には、読み書き位置は終端となります。
  • 読み書き位置が終端の場合、
    • available()falseになります。
    • 読み出しは出来ません。
    • 書き込みは書き込み可能範囲であれば追記します。

stream_helperの生成

stream_helper は、データクラス (smplbuf, EEPROM) のメンバー関数より生成します。

auto&& obj_helper = obj.get_stream_helper()
// obj はデータクラスのオブジェクト、obj_helperの型は長くなるのでauto&&で受けています。

メソッド

rewind()

void rewind()

読み書き位置を先頭に移動します。

seek()

int seek(int offset, int whence = MWX_SEEK_SET)

読み書き位置を設定します。

whence設定位置
MWX_SEEK_SET先頭位置から設定します。offset0を指定するとrewind()と同じ意味になります。
MWX_SEEK_CUR現在位置を基準にoffset分移動しまします。
MWX_SEEK_END終端位置にします。offset0にすると終端に設定します。-1を設定すると最後の文字に移動します。

tell()

int tell()

読み書き位置を返します。終端位置の場合は-1を返します。

available()

int available()

読み書き位置が終端であれば0を返します。終端でなければそれ以外の値を返します。

4.11 - SM_SIMPLE ステートマシン

状態管理
SM_SIMPLEは、サンプルコード中の状態遷移、タイムアウト待ち、送信完了などの処理待ちを行うために用意しています。

SM_SIMPLEの基本的なコードを示します。

##include <SM_SIMPLE>

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

begin() {
  ...
  step.init(); //初期化
}

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT:
      ...
      step.next(STATE::SENSOR);
    break;

    case STATE::SENSOR:
      ...
      step.next(STATE::TX);
    break;

    case STATE::TX:
      if (/*送信要求成功*/) {
        step.set_timeout(100); // タイムアウトの設定
        step.clear_flag(); //処理待ち

        step.next(STATE::TX_WAIT_COMP);
      }
    break;

    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) the_twelite.reset_system(); // タイムアウト
      if (step.is_flag_ready()) sleepNow(); // flagがセットされた
    break;

    ...
    }
  } while(step.b_more_loop());
}

void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus);
}

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

解説

SM_SIMPLEを利用するには状態一覧としてのenum class定義が必要です。上記ではSTATEとして定義しています。このステージをパラメータとしてSM_SIMPLE<STATE> step;のようにクラスオブエクトを生成します。生成したクラスオブジェクトは.setup()により初期化しておきます。

enum class STATE : uint8_t {
	INIT = 0,
	SENSOR,
	TX,
	TX_WAIT_COMP,
	GO_SLEEP
};

SM_SIMPLE<STATE> step;

void setup() {
  step.init();
}

SM_SIMPLEの初期状態は値が0で、上記の例ではSTATE::INITが対応します。現在の状態を取得するには.state()を用、上記例のように_do while_文中の_switch_節の判定式に用います。

loop() {
  do {
    switch(step.state()) {
    case STATE::INIT: // 値0の状態
    ...

状態の遷移には.next()を呼び出します。状態が変更された場合、b_more_loop()trueになり_do while_節のループがもう一度実行されます。例ではSTATE::SENSOR状態から.next(STATE::TX)を呼び出すことで、ループがもう一度実行されcase STATE::TX:節も実行されることになります。状態を変更しない場合は_do while_ループを脱出しloop()を一旦終了します。次のloop()の呼び出しまで一旦待ちます。

  do {
    switch(step.state()) {
    ...
    case STATE::SENSOR:
      ...
      step.next(STATE::TX); // (1)状態遷移
    break;

    case STATE::TX: // (3) 2回めのループで呼び出される
      if (/*送信要求成功*/) {
      ...
    }
  } while (b_more_loop()); // (2) ループ継続判定 true

送信完了などの処理待ちをしたい場合は.clear_flag()を呼び出し、別のコールバック関数などで.set_flag(uint32_t)により処理完了を知らせます。ここで指定したuint32_t型のパラメータをは.get_flag_value()から読み出せます。

またタイムアウトの処理を行いたい場合は.set_timeout(uint32_t)を呼び出した時刻を記録し、.is_timeout()によりタイムアウト時間が経過したかを調べることができます。

    case STATE::TX:
      if (/*送信要求成功*/) {
        step.set_timeout(100); // タイムアウトの設定
        step.clear_flag(); //処理待ち

        step.next(STATE::TX_WAIT_COMP);
      }
    break;

    case STATE::TX_WAIT_COMP:
      if (step.is_timeout()) ...; // タイムアウト
      if (step.is_flag_ready()) ...; // flagがセットされた
    break;
...

// 送信完了イベント
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	step.set_flag(ev.bStatus); // flag を設定する
}

スリープからの復帰で再びSM_SIMPLEを利用することになりますが、スリープ前に必ず.on_sleep(bool)を呼び出すようにします。パラメータにfalseを入れると復帰後に0状態から開始し、trueを入れるとスリープ直前の状態から再開します。

void sleepNow() {
	step.on_sleep(false); // reset state machine.
  the_twelite.sleep(10000); // 10sec
}

ソースコード

以下にSM_SIMPLEのソースコードを示します。

// very simple class to control state used in loop().
template <typename STATE>
class SM_SIMPLE {
	uint32_t _u32_flag_value;  // optional data when flag is set.
	uint32_t _ms_start;		// system time when start waiting.
	uint32_t _ms_timeout;	// timeout duration

	STATE _step;			  // current state
	STATE _step_prev;		// previous state
	bool_t _b_flag; 		// flag control.
public:
	// init
	void setup() { memset(this, 0, sizeof(SM_SIMPLE)); }
	// call befoer sleeping (save state machine status)
	void on_sleep(bool b_save_state = false) {
		STATE save = _step;
		setup();
		if(b_save_state) _step = _step_prev = save;
	}

	// state control
	void next(STATE next) { _step = next; } // set next state
	STATE state() { return _step; } // state number
	bool b_more_loop() { // if state is changed during the loop, set true
		if (_step != _step_prev) { _step_prev = _step; return true; }
		else return false;
	}

	// timeout control
	void set_timeout(uint32_t timeout) {
		_ms_start = millis();
		_ms_timeout = timeout;
	}
	bool is_timeout() { return (millis() - _ms_start) >= _ms_timeout; }

	// flag control
	void clear_flag() { _b_flag = false; _u32_flag_value = 0; }
	void set_flag(uint32_t u32_flag_value = 0) {
		_b_flag = true;
		_u32_flag_value = u32_flag_value; }
	uint32_t get_flag_value() { return _u32_flag_value; }
	bool is_flag_ready() { return _b_flag; }
};
  • バージョンによって内容が変化する場合があり。
  • 本体は MWX ライブラリソースフォルダのSM_SIMPLE.hppに格納されます。

5 - コールバック関数

各種コールバック関数
アプリケーションの記述を行うコールバック関数です。コールバックはシステム(ライブラリ)から呼び出されるという意味です。ユーザがいくつかのコールバック関数を定義することでシステムの振る舞いを記述します。

以下のコールバック関数は必須定義です。

  • setup()
  • loop()

それ以外の関数は定義しない場合は、何も実行しない空の関数が替わりにリンクされます。

通常のコールバック呼び出し順序

init_coldboot()
  ↓ (TWENET内部処理:初期化1)
setup()
  ↓(TWENET内部処理:初期化2)
begin() --- 初回のみ
  ↓
loop() <--+
  ↓       |イベント処理、ビヘイビア処理
CPU DOZE -+

スリープ復帰時のコールバック呼び出し順序

the_twelite.sleep()
  ↓ sleeping...


init_warmboot()
  ↓ (TWENET内部処理:初期化3)
wakeup()
  ↓(TWENET内部処理:初期化4)
loop() <--+
  ↓       |イベント処理、ビヘイビア処理
CPU DOZE -+

5.1 - setup()

初期化処理
コード実行の初期に呼び出され、初期化コードを記述します。

5.2 - begin()

初期化処理(TWENET初期化後)
loop()関数の初回コールの手前で一度だけ呼び出されます。TWENET の初期化は終了しているのでsetup()のような制約を考慮する必要はありません。

主に下記のような場面で使用します。

  • 始動メッセージの表示
  • テスト用のコードを記述
  • 始動直後のスリープ遷移
  • setup()で不都合がある処理(無線パケット処理・タイマー動作など)

5.3 - loop()

ループ処理
アプリケーションのメインループです。ループ終了後はCPUがDOZEモードに遷移し低消費電流で次の割り込みを待ちます。

アクトでは、ほとんどの処理がこのループ内に記述されます。

5.4 - wakeup()

スリープ起床後の処理
スリープから起床したときにloop()に移行する前に呼ばれ、スリープ復帰後の初期化処理や復帰状態によって処理を分岐するための手続きを含めます。

5.5 - init_coldboot()

初期化処理(ペリフェラル初期化前)
ペリフェラルAPIも初期化もされていない、コード実行の再初期に呼び出されます。

5.6 - init_warmboot()

スリープ復帰後の処理(ペリフェラル初期化前)
スリープ復帰後、ペリフェラルAPIが初期化されない再初期に呼び出されます。

5.7 - on_rx_packet()

パケット受信時の処理
受信パケットを受け取った際の処理を記述します。
void on_rx_packet(mwx::packet_rx& pkt, bool_t &b_handled)

無線パケットを受信したときにpacket_rxとして pkt にデータが格納された状態で、本関数が MWX ライブラリ内から呼び出されます。アプリケーション中で本関数が定義されない場合は何もしない weak 関数がリンクされます。

本関数中で b_handledtrue をセットすると、MWX ライブラリに受信パケットがアプリケーション内で処理されたことを伝達します。処理済みとした場合、不要な処理を抑制します。(the_twelite.receiver の処理を行わない)

5.8 - on_tx_comp()

パケット送信完了時の処理
送信完了時に行う処理を記述します。
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled)

無線パケットの送信が終了したときにpacket_ev_txとして ev にデータが格納された状態で、本関数が MWX ライブラリ内から呼び出されます。アプリケーション中で本関数が定義されない場合は何もしない weak 関数がリンクされます。

ev.u8CbIdは送信時のID、ev.bStatusは送信の成功(1)または失敗(0)を示すフラグです。

本関数中で b_handledtrue をセットすると、MWX ライブラリに受信パケットがアプリケーション内で処理されたことを伝達します。処理済みとした場合、不要な処理を抑制します。(the_twelite.app, .board, .settings に対してはイベントコールバック関数を呼び出さない)

6 - ビヘイビア

複雑なアプリケーションを実装するための高度なフレームワーク
ビヘイビアでは、指定の方法でクラスを定義することで、the_tweliteクラスオブジェクトに登録できるようになります。登録したビヘイビアはTWENETに組み込まれて動作し、ユーザコードではアプリケーションの振る舞いを記述できます。ループでの記述とは異なり、TWENETからの割り込みハンドラやイベントのコールバック関数を定義できます。記述量が多くなりますが、より複雑なアプリケーションの構築に適しています。

クラス定義 (.hpp)

ビヘイビアの定義には下記に示すようなクラス定義が必要です。

class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)
{
public:
    static const uint8_t TYPE_ID = 0x01;

    // load common definition for handlers
    #define __MWX_APP_CLASS_NAME MY_APP_CLASS
    #include "_mwx_cbs_hpphead.hpp"
    #undef __MWX_APP_CLASS_NAME

public:
    // constructor
    MY_APP_CLASS() {}

    void _setup() {}
    void _begin() {}

public:
    // TWENET callback handler (mandate)
    void loop() {}
    void on_sleep(uint32_t & val) {}
    void warmboot(uint32_t & val) {}
    void wakeup(uint32_t & val) {}

    void on_create(uint32_t& val) { _setup();  }
    void on_begin(uint32_t& val) { _begin(); }
    void on_message(uint32_t& val) { }

public:
    void network_event(mwx::packet_ev_nwk& pEvNwk) {}
    void receive(mwx::packet_rx& rx) {}
    void transmit_complete(mwx::packet_ev_tx& evTx) {}
};

上記の例ではMY_APP_CLASSという名前でビヘイビアクラスを定義しています。いくつかの箇所にMY_APP_CLASSの記述が必要です。

class MY_APP_CLASS: MWX_APPDEFS_CRTP(MY_APP_CLASS)

クラス名の定義と、ベース(親)クラスの定義をします。MWX_APPDEFS_CRTP()はマクロです。

    #define __MWX_APP_CLASS_NAME MY_APP_CLASS
    #include "_mwx_cbs_hpphead.hpp"
    #undef __MWX_APP_CLASS_NAME

ここでは必要な定義を #include により取り込んでいます。

MY_APP_CLASS() {}

コンストラクタの定義です。

メソッド

loop()

メインループで、グローバル定義のloop()と同じ役割の関数です。

on_create()

on_create()はオブジェクト生成時(use<>()メソッド)に呼び出されます。valは将来の拡張のためのパラメータです。

on_begin()

on_begin()setup()終了後に呼び出されます。valは将来の拡張のためのパラメータです。

on_sleep()

スリープ前に呼び出されます。valは将来の拡張のためのパラメータです。

warmboot()

スリープ復帰時の初期段階で呼び出されます。valは将来の拡張のためのパラメータです。

この時点でまだペリフェラルが初期化されていません。スリープ起床要因の確認ができます。

wakeup()

スリープ復帰時に呼び出されます。valは将来の拡張のためのパラメータです。

receive()

void receive(mwx::packet_rx& rx)

パケットが受信されたとき、受信したパケット情報をrxとして呼び出されます。

transmit_complete()

void transmit_complete(mwx::packet_ev_tx& evTx)

パケット送信完了時に送信情報をevTxとして呼び出されます。evTx.u8CbIdが送信時のIDでevTx.bStatusが送信の成功(1)失敗(0)を示すフラグです。

ハンドラの定義 (.cpp)

ビヘイビアのハンドラ(割り込み、イベント、状態定義)はcppファイルに定義します。ファイルは分割できず、全てのハンドラ定義を一つのファイル中に記述します。

cppファイルの冒頭と末尾にはMWXライブラリの必要な定義(#include "_mwx_cbs_cpphead.hpp")をインクルードする必要があります。

##include <TWELITE>
##include "myAppClass.hpp" // ビヘイビア定義ファイル

/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
##define __MWX_APP_CLASS_NAME MY_APP_CLASS
##include "_mwx_cbs_cpphead.hpp" // 冒頭の定義
/*****************************************************************/

ファイルの冒頭には、上記のようにビヘイビア定義の.hppファイルをインクルードします。__MWX_APP_CLASS_NAMEにはビヘイビアのクラス名を指定します。上記ではMY_APP_CLASSです。

/*****************************************************************/
// common procedure (DO NOT REMOVE)
##include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
##undef __MWX_APP_CLASS_NAME
/*****************************************************************/

ファイルの末尾では必要な定義(#include "_mwx_cbs_cpptail.cpp")をインクルードします。

ハンドラ定義は以下の例のように記述します。定義の種別については後述します。定義用のマクロを用いて利用したいハンドラの定義を記述します。利用しないハンドラは記述しないようにしてください。

MWX_???_INT()は割り込みハンドラの定義、MWX_???_EVENT()はイベントハンドラの定義、MWX_STATE()はステートマシンの状態定義です。

// TickTimer割り込み
MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
	// blink LED
	digitalWrite(PAL_AMB::PIN_LED,
	      ((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}

// PAL_AMB::PIN_BIN(12)のイベント
MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
	Serial << "Button Pressed" << mwx::crlf;

	static uint32_t u32tick_last;
	uint32_t tick = millis();

	if (tick - u32tick_last > 100) {
		PEV_Process(E_ORDER_KICK, 0UL);
	}

	u32tick_last = tick;
}

// 状態 STATE_0 の動作定義
MWX_STATE(E_MWX::STATE_0, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_START_UP) {
		Serial << "[STATE_0:START_UP]" << mwx::crlf;
	} else
	if (ev == E_ORDER_KICK) {
		PEV_SetState(E_MWX::STATE_1);
	}
}

// 状態 STATE_1 の動作定義
MWX_STATE(E_MWX::STATE_1, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_1]" << mwx::crlf;
	} else
	if (ev == E_ORDER_KICK) {
		PEV_SetState(E_MWX::STATE_2);
	} else
	if (ev == E_EVENT_TICK_SECOND) {
		Serial << "<1>";
	}
}

割り込み・イベントハンドラ

割り込みハンドラは、マイコンの割り込みが発生したときに現在実行中のコードを中断して実行されます。このため、可能な限り短い処理を記述することが望ましく、また、変数等の操作に対しても細心の注意が必要です。

割り込みハンドラのパラメータにはuint8_t& handledがあり、この値をtrueにセットすることで、続くイベント呼び出しを行いません。

handledfalseのまま割り込みハンドラを終了した場合、アプリケーションループ(通常コード)の中でイベントハンドラが呼び出されます。イベントハンドラにはhandledのパラメータはありません。イベントハンドラは通常コードですので、比較的大きな処理を行うことが出来ます。イベントハンドラのオーバーヘッドも発生するため、頻繁な割り込み都度呼び出されるような処理の場合、処理しきれなくなる可能性があります。また、イベントの発生はシステム内部のFIFOキューにより処理されるため、一定時間内に処理できない場合はイベントが消失する場合もあります。

以下にハンドラ関数定義用のマクロの解説を行います。

DIO

MWX_DIO_INT(N, uint32_t arg, uint8_t& handled)
MWX_DIO_EVENT(N, arg)

DIO(ディジタルIO)割り込み・イベントです。Nは対象DIOの番号を指定します。argは将来の拡張のための定義です。

割り込みを発生させるためにはpinMode()による適切な入力設定, attachDioInt()による割り込み開始の設定が必要です。

TICKTIMER

MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled)
MWX_TICKTIMER_EVENT(uint32_t arg)

TickTimer割り込み・イベントです。argは将来の拡張のための定義です。

TIMER

MWX_TIMER_INT(N, uint32_t arg, uint8_t& handled)
MWX_TIMER_EVENT(N, uint32_t arg)

タイマー割り込み・イベントです。Nは対象タイマーの番号を指定します。argは将来の拡張のための定義です。

割り込みを発生させるためには、Timerオブジェクトをソフトウェア割り込みを有効にして開始します。

その他

その他の割り込み・イベントは以下のハンドラ関数で受けることが出来ます。これらは将来的に専用のハンドラが定義された場合、利用できなくなります。

MWX_MISC_INT(uint32_t arg, uint32_t arg2, handled)
MWX_MISC_EVENT(auint32_t rg, uint32_t arg2)

ペリフェラル (AHI) の割り込みハンドラのu32DeviceIdargu32ItemBitmaparg2に対応します。

状態マシン

状態マシン(ステートマシン)は、メッセージを受け取り、そのメッセージに応じて状態を遷移させながら動作させるアプリケーションの記述方法です。

PAL_AMB-behaviorサンプルでは、センサーの動作開始からセンサーの値取得、無線パケット送信から送信完了まで、スリープ遷移といったアプリケーションの動作の流れを記述しています。実例として参考にしてください。

受け取るイベントは以下となります。

イベント名内容
E_EVENT_START_UPシステム始動時に呼び出される。電源投入直後はパラメータが0で呼び出されます。実行初期であるため、通常処理を行う状態に遷移する場合は一旦begin()メソッドからPEV_Process()を呼び出し動作を開始させます。

スリープ復帰後も呼び出されるがパラメータは0以外です。この状態からは通常処理を行えます。
E_EVENT_NEW_STATE状態遷移直後に新しい状態で呼び出されます。ある状態に遷移したときに最初に実行される処理を記述します。
E_EVENT_TICK_TIMER1msごとのTickTimerで呼び出されます。
E_EVENT_TICK_SECOND1秒毎に呼び出されます。

PEV_SetState()

void PEV_SetState(uint32_t s)

状態をsに設定します。

状態ハンドラを抜けると次の状態に遷移し、続けてE_EVENTS_NEW_STATEイベントで状態ハンドラが呼び出されます。

PEV_u32Elaspsed_ms()

uint32_t PEV_u32Elaspsed_ms()

状態遷移してからの経過時間[ms]を返します。タイムアウトを管理するような目的で使用します。

MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg) {
  ...

	if (PEV_u32Elaspsed_ms() > 100) {
		// does not finish TX!
		Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
		the_twelite.reset_system();
	}
}

上記の例では100ms経過した時点でシステムリセットを行います。

PEV_Process()

void PEV_Process(uint32_t ev, uint32_t u32evarg) {

状態ハンドラ外から呼び出します。状態ハンドラをイベントevパラメータu32evargで実行します。

void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}

送信完了イベントを状態マシンに伝えます。つまり状態ハンドラの呼び出しを行います。

PEV_KeepStateOnWakeup()

void PEV_KeepStateOnWakeup()

スリープ直前に設定します。スリープ復帰後に、直前の状態を維持します。つまり、スリープを開始した状態でE_EVENT_START_UPで状態ハンドラが呼び出されます。

PEV_is_coldboot()

bool PEV_is_coldboot(uint32_t ev, uint32_t u32evarg)

イベントが起床時のE_EVENT_START_UPかどうか判定します。

PEV_is_warmboot()

bool PEV_is_warmboot(uint32_t ev, uint32_t u32evarg)

イベントがスリープ復帰時のE_EVENT_START_UPかどうか判定します。

6.1 - PAL_AMB-behavior

環境センサパルを使用したビヘイビアの例

環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。

  • ビヘイビアを用いた親機子機の記述を行っています。
  • センサーを値を得るのにボードビヘイビアの機能を使わずWireを用いて直接記述しています。
  • 子機はステートマシンによる状態遷移により記述しています。

アクトの機能

  • 環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。
  • コイン電池で動作させるための、スリープ機能を利用します。

アクトの使い方

TWELITEの準備

役割
親機MONOSTICK BLUEまたはRED
子機BLUE PAL または RED PAL +環境センサーパル AMBIENT SENSE PAL

ファイル構成

  • 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>です。

初期化 setup()

// now read DIP sw status can be read.
u8ID = (brd.get_DIPSW_BM() & 0x07);

// Register App Behavior (set differnt Application by DIP SW settings)
if (u8ID == 0) {
	// put settings to the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL)  // set channel (pysical channel)
		<< TWENET::rx_when_idle();   // open RX channel

	the_twelite.app.use<MY_APP_PARENT>();
} else {
	// put settings to the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	the_twelite.app.use<MY_APP_CHILD>();
}

DIP SWの読み値が0の場合は親機用のビヘイビア<MY_APP_PARENT>を、それ以外の場合は子機用のビヘイビア<MY_APP_CHILD>を登録します。

親機のビヘイビア

親機はスリープをしない受信機としてふるまい、子機からのパケットを受信したときにシリアルポートにパケットの情報を出力します。

MY_APP_PARENT::receive()

void MY_APP_PARENT::receive(mwx::packet_rx& rx) {
	uint8_t msg[4];
	uint32_t lumi;
	uint16_t u16temp, u16humid;

	// expand packet payload (shall match with sent packet data structure, see pack_bytes())
	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end(), msg);

	// if PING packet, respond pong!
	if (!strncmp((const char*)msg, (const char*)FOURCHARS, 4)) {
		// get rest of data
		expand_bytes(np, rx.get_payload().end(), lumi, u16temp, u16humid);

		// print them
		Serial << format("Packet(%x:%d/lq=%d/sq=%d): ",
							rx.get_addr_src_long(), rx.get_addr_src_lid(),
							rx.get_lqi(), rx.get_psRxDataApp()->u8Seq)
			   << "temp=" << double(int16_t(u16temp)/100.0)
			   << "C humid=" << double(int16_t(u16humid)/100.0)
			   << "% lumi=" << int(lumi)
			   << mwx::crlf << mwx::flush;
    }
}

親機用がパケットを受信したときは、パケットの先頭4文字が照合(FOURCHARS)できれば、そのパケット内容を表示します。

MY_APP_PARENT::MWX_TICKTIMER_INT()

MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled) {
  // blink LED
  digitalWrite(PAL_AMB::PIN_LED,
    ((millis() >> 9) & 1) ? PIN_STATE::HIGH : PIN_STATE::LOW);
}

親機の割り込みハンドラはLEDの点滅を行います。

MY_APP_PARENT::MWX_DIO_EVENT(PAL_AMB::PIN_BTN)

MWX_DIO_EVENT(PAL_AMB::PIN_BTN, uint32_t arg) {
	Serial << "Button Pressed" << mwx::crlf;

	static uint32_t u32tick_last;
	uint32_t tick = millis();

	if (tick - u32tick_last > 100) {
		PEV_Process(E_ORDER_KICK, 0UL);
	}

	u32tick_last = tick;
}

PAL上のボタン(5)が押されたときには、状態マシンに対してE_ORDER_KICKイベントを発行します。

MY_APP_PARENT::MWX_STATE(E_MWX::STATE_0 .. 3)

状態マシンは、状態遷移の参考として記述したもので、アプリケーションの動作上意味のあるものではありません。ボタンから送付されるE_ORDER_KICKイベントによる状態遷移や、タイムアウトなどを実行しています。

子機のビヘイビア

子機の動作の流れはPAL_AMB-usenapと同じです。初回スリープから「起床→センサー動作開始→短いスリープ→起床→センサー値取得→無線送信→無線送信完了待ち→スリープ」を繰り返します。

MY_APP_CHILD::on_begin()

void _begin() {
    // sleep immediately.
    Serial << "..go into first sleep (1000ms)" << mwx::flush;
    the_twelite.sleep(1000);
}

on_begin()から呼び出される_begin()関数では、初回スリープを実行しています。

(※_begin()関数で本処理を記述せずon_begin()に直接記述してもかまいません)

MY_APP_CHILD::wakeup()

void wakeup(uint32_t & val) {
    Serial << mwx::crlf << "..wakeup" << mwx::crlf;
    // init wire device.
    Wire.begin();

    // turn on LED
    digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::LOW);

    // KICK it!
    PEV_Process(E_ORDER_KICK, 0); // pass the event to state machine
}

スリープからの起床処理を記述しています。

ここで初回のWire.begin()を実行しています。2回目以降のスリープ起床時では冗長な記述です。この処理はon_begin()に移動してもかまいません。

MY_APP_CHILD::transmit_complete()

void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}

送信完了時に状態マシンに対してE_ORDER_KICKメッセージを処理します。

MY_APP_CHILD::transmit_complete()

static const uint8_t STATE_IDLE = E_MWX::STATE_0;
static const uint8_t STATE_SENSOR = E_MWX::STATE_1;
static const uint8_t STATE_TX = E_MWX::STATE_2;
static const uint8_t STATE_SLEEP = E_MWX::STATE_3;

状態名を定義しています。

MY_APP_CHILD::shtc3_???()

MWX_APIRET MY_APP_CHILD::shtc3_start()
MWX_APIRET MY_APP_CHILD::shtc3_read()

SHTC3用のセンサー取得実装例です。送付コマンド等の詳細はSHTC3のデータシートなどを参考にしてください。

MY_APP_CHILD::ltr308als_???()

MWX_APIRET MY_APP_CHILD::ltr308als_read()
MWX_APIRET MY_APP_CHILD::ltr308als_start()
static MWX_APIRET WireWriteAngGet(uint8_t addr, uint8_t cmd)

LTR308ALSのセンサー取得実装例です。送付コマンド等の詳細はLTR308ALSのデータシートなどを参考にしてください。

WireWriteAndGet()addrのデバイスに対してcmdを1バイト送信してから、1バイト受信して値を返します。

MY_APP_CHILD::STATE_IDLE (0)

MWX_STATE(MY_APP_CHILD::STATE_IDLE, uint32_t ev, uint32_t evarg) {
	if (PEV_is_coldboot(ev,evarg)) {
		Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
		// then perform the first sleep at on_begin().
	} else
	if (PEV_is_warmboot(ev,evarg)) {
		Serial << "[STATE_IDLE:START_UP(" << int(evarg) << ")]" << mwx::crlf;
		PEV_SetState(STATE_SENSOR);
	}
}

0番の状態は特別な意味を持ちます。起動直後またはスリープ復帰後の状態です。

起動直後PEV_is_coldboot(ev,evarg)判定がtrueになって呼び出されます。on_begin()から、そのままスリープしてしまうため、状態遷移するようなコードも含まれません。**この時点では主要な初期化がまだ終わっていませんので、無線パケットの送信など複雑な処理を行うことが出来ません。**そのような処理を行うための最初の状態遷移を行うためにはon_begin()からイベントを送り、そのイベントに従って状態遷移を行います。

スリープ復帰後はPEV_is_warmboot(ev,evarg)trueになる呼び出しが最初にあります。PEV_SetState()を呼び出しSTATE_SENSOR状態に遷移します。

MY_APP_CHILD::STATE_SENSOR

MWX_STATE(MY_APP_CHILD::STATE_SENSOR, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_SENSOR:NEW] Start Sensor." << mwx::crlf;

		// start sensor capture
		shtc3_start();
		ltr308als_start();

		// take a nap waiting finish of capture.
		Serial << "..nap for 66ms" << mwx::crlf;
		Serial.flush();
		PEV_KeepStateOnWakeup(); // stay this state on waking up.
		the_twelite.sleep(66, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
	} else
	if (PEV_is_warmboot(ev,evarg)) {
		// on wakeup, code starts here.
		Serial << "[STATE_SENSOR:START_UP] Wakeup." << mwx::crlf;

		PEV_SetState(STATE_TX);
	}
}

スリープ復帰後STATE_IDLEから遷移したとき、STATE_SENSORの状態ハンドラが続けて呼び出されます。この時のイベントevE_EVENT_NEW_STATEです。

ここではSHTC3, LTR308ALSの2センサーの動作開始を行います。一定時間経過すれば、センサーはデータ取得可能な状態になります。この時間待ちを66ms設定のスリープで行います。スリープ前にPEV_KeepStateOnWakeup()が呼ばれている点に注意してください。この呼び出しを行うと、スリープ復帰後の状態はSTATE_IDLEではなく、スリープしたときの状態、つまりSTATE_SENSORとなります。

短いスリープから復帰するとPEV_is_warmboot(ev,evarg)判定がtrueとなる呼び出しが最初に発生します。この呼び出し時点で、無線パケットの送信などを行うことが出来ます。STATE_TXに遷移します。

MY_APP_CHILD::STATE_TX

MWX_STATE(MY_APP_CHILD::STATE_TX, uint32_t ev, uint32_t evarg)
	static int u8txid;

	if (ev == E_EVENT_NEW_STATE) {
		Serial << "[STATE_TX:NEW]" << mwx::crlf;
		u8txid = -1;

		auto&& r1 = shtc3_read();
		auto&& r2 = ltr308als_read();

		Serial << "..shtc3 t=" << int(i16Temp) << ", h=" << int(i16Humd) << mwx::crlf;
		Serial << "..ltr308als l=" << int(u32Lumi) << mwx::crlf;

		if (r1 && r2) {
			if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

ここではE_EVENT_NEW_STATEイベントの時に、センサーデータ読み出し、無線パケットの送信手続きに入ります。送信手続きの詳細は他のアクトサンプル例を参考にしてください。

void transmit_complete(mwx::packet_ev_tx& txev) {
    Serial << "..txcomp=" << int(txev.u8CbId) << mwx::crlf;
    PEV_Process(E_ORDER_KICK, txev.u8CbId); // pass the event to state machine
}

    // ↓ ↓ ↓ メッセージ送付

} else if (ev == E_ORDER_KICK && evarg == uint32_t(u8txid)) {
		Serial << "[STATE_TX] SUCCESS TX(" << int(evarg) << ')' << mwx::crlf;
		PEV_SetState(STATE_SLEEP);
}

送信完了まちの処理はループでのアクト記述と違い、transmit_complete()からのPEV_Process()によるメッセージを待つことで完了確認としています。メッセージを受け取った時点でスリープします。スリープ処理はSTATE_SLEEPに遷移することで行っています。

	if (PEV_u32Elaspsed_ms() > 100) {
		// does not finish TX!
		Serial << "[STATE_TX] FATAL, TX does not finish!" << mwx::crlf << mwx::flush;
		the_twelite.reset_system();
	}

最後にタイムアウト処理を行っています。万が一送信パケットの完了メッセージが戻ってこなかった場合を想定します。PEV_u32Elaspsed_ms()はその状態に遷移してからの経過時間を[ms]で返します。時間経過した場合は、上記では(このタイムアウトは余程のことだとして)システムリセットthe_twelite.reset_system()を行います。

MY_APP_CHILD::STATE_SLEEP

MWX_STATE(MY_APP_CHILD::STATE_SLEEP, uint32_t ev, uint32_t evarg) {
	if (ev == E_EVENT_NEW_STATE) {
		Serial << "..sleep for 5000ms" << mwx::crlf;
		pinMode(PAL_AMB::PIN_BTN, PIN_MODE::WAKE_FALLING_PULLUP);
		digitalWrite(PAL_AMB::PIN_LED, PIN_STATE::HIGH);
		Serial.flush();

		the_twelite.sleep(5000); // regular sleep
	}
}

スリープを行います。前の状態から遷移した直後のE_EVENT_NEW_STATEに記述します。スリープ直前に他のイベントが呼ばれる可能性がありますので、必ず1回だけ実行される判定式の中でthe_twelite.sleep()を実行してください。

7 - 関数

グローバル関数

7.1 - システム関数

時間や乱数を扱う関数

7.1.1 - millis()

システム時刻の取得
システム時刻[ms]を得ます。
uint32_t millis()

システム時刻はTickTimerの割り込みで更新されます。

7.1.2 - delay()

待ち処理
ポーリングによる時間待ちを行います。
void delay(uint32_t ms)

msにて与えられた期間待ち処理を行います。

時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。

7.1.3 - delayMicroseconds()

時間待ち(マイクロ秒)
ポーリングによる時間待ちを行います(μ秒指定)。
void delayMicroseconds(uint32_t microsec)

microsecにて与えられた期間待ち処理を行います。

時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。

7.1.4 - random()

乱数生成
乱数を生成します。
uint32_t random(uint32_t maxval)
uint32_t random(uint32_t minval, uint32_t maxval)

1行目は0..(maxval-1)の値を戻します。maxvalの値が最大値ではないことに注意してください。

2行目はminval..maxval-1の値を戻します。

7.2 - 汎用デジタルIO

DIOポートを扱う関数

汎用デジタルIO(DIO)の操作には以下の関数を利用します。

  • pinMode()
  • digitalWrite()
  • digitalRead()
  • attachIntDio()
  • detachIntDio()

定数

ピン名と番号

定義名称
const uint8_t PIN_DIGITAL::DIO0 .. 19DIOピン0~19
const uint8_t PIN_DIGITAL::DO0 .. 1DOピン0,1

ピンのモード(DIO0..19)

以下の列挙値は型名 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入力状態に戻す

ピンのモード(DO0,1)

以下の列挙値は型名 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::HIGH1HIGHレベル(=Vccレベル)
PIN_STATE::LOW0LOWレベル(=GNDレベル)

ピンの立ち上がり、立ち下がり

以下の列挙値は型名 E_PIN_INT_MODEで取り扱われます。

定義名称
PIN_INT_MODE::FALLING立ち下り
PIN_INT_MODE::RISING立ち上がり

7.2.1 - pinMode()

初期化関数
DIO(汎用デジタルIO)ピンの設定を行います。
void pinMode(uint8_t u8pin, E_PIN_MODE mode)

この関数では DIO0..19 と、DO0,1のピンの状態を変更できます。設定内容は E_PIN_MODE の列挙値のDIOの解説DOの解説を参照してください。

7.2.2 - digitalWrite()

デジタル出力関数
デジタル出力ピンの設定を変更します。
static inline void digitalWrite(uint8_t u8pin, E_PIN_STATE ulVal)

事前にpinMode()にて設定対象のピンを出力用に設定しておきます。1番目のパラメータは、設定対象のピン番号を指定します。2番目のパラメータはHIGHLOWのいずれかを指定します。

7.2.3 - digitalRead()

デジタル入力関数
入力設定のポートの値を読み出します。
static inline E_PIN_STATE digitalRead(uint8_t u8pin)

事前に入力に設定したピンの入力値をLOWまたはHIGHで得ることができます。

7.2.4 - attachIntDio()

DIO割り込みハンドラを登録する関数
DIO割り込みハンドラを登録します。
void attachIntDio(uint8_t u8pin, E_PIN_INT_MODE mode)

事前に入力設定したピンに対して、1番目のパラメータは割り込みを有効にしたいピン番号で、2番目は割り込み方向(立ち上がり、立ち下がり)を指定します。

DIO5のピンがHIGHからLOWに変化したときに割り込みが発生する設定を行います。

void setup() {
  the_twelite.app.use<myAppClass>();

  pinMode(PIN_DIGITAL::DIO5, PIN_MODE::INPUT_PULLUP);
  attachIntDio(PIN_DIGITAL::DIO5, PIN_INT_MODE::FALLING);
}

void loop() {
  ;
}

myAppClass.hpp

class myAppClass: public mwx::BrdPal, MWX_APPDEFS_CRTP(myAppClasslMot)
{

};

ビヘイビアmyAppClassの基本定義。詳細は省略しています。

myAppClass.cpp

/*****************************************************************/
// MUST DEFINE CLASS NAME HERE
##define __MWX_APP_CLASS_NAME myAppClass
##include "_mwx_cbs_cpphead.hpp"
/*****************************************************************/

MWX_DIO_INT(PIN_DIGITAL::DIO5, uint32_t arg, uint8_t& handled) {
  static uint8_t ct;
  digitalWrite(PIN_DIGITAL::DIO12, (++ct & 1) ? HIGH : LOW);
	handled = false; // if true, no further event.
}

MWX_DIO_EVENT(PIN_DIGITAL::DIO5, uint32_t arg) {
  Serial << '*';
}

/*****************************************************************/
// common procedure (DO NOT REMOVE)
##include "_mwx_cbs_cpptail.cpp"
// MUST UNDEF CLASS NAME HERE
##undef __MWX_APP_CLASS_NAME
} // mwx
/*****************************************************************/

ビヘイビアmyAppClassの割り込みハンドラの記述。DIO5の割り込み発生時にDIO12の出力設定を反転させ、割り込みハンドラが終了してから発生するイベントではシリアルポートSerial*を表示します。

7.2.5 - detachIntDio()

DIO割り込みハンドラの登録を解除する関数
割り込みハンドラの登録を解除します。
void detachIntDio(uint8_t u8pin)

7.2.6 - digitalReadBitmap()

デジタル入力関数(一括)
入力設定のポートの値を一括で読み出します。
uint32_t digitalReadBitmap()

LSB側から順にDIO0 … DIO19 の順に値が格納されます。

HIGH側のピンには 1 が、LOW側のピンには 0 が設定されます。

7.3 - ユーティリティ関数

その他のユーティリティ関数

7.3.1 - printfの実装

C言語標準の printf に近い機能
MWX ライブラリでは、C言語で常用されるprintf()に近い実装を用意しています。
int mwx_printf(const char* format, ...)
int mwx_snprintf(char* buffer, size_t count, const char* format, ...)

mwx_printf()Serialオブジェクトに対してprintf出力を行います。Serial.printfmt()と同じ処理になります。

mwx_snprintf()はバッファに対してsnprintfを行います。

7.3.2 - pack_bits()

指定したビット位置に1をセット
指定したビット位置に1をセットします。
constexpr uint32_t pack_bits(...)

パラメータは可変数引数で指定でき、各パラメータはビット位置を指定する0..31の整数を指定する。例えばpack_bits(1,3,6)と指定すると((1UL<<1)|(1UL<<3)|(1UL<<6))を返します。

背景

IOポート(DI,DO)の状態など各種ビットマップに値を参照・設定する場面があり、その記述を簡素化するため。

7.3.3 - collect_bits()

指定したビット位置の値からビットマップを作成
整数から指定したビット位置の値を取得し、指定した順番のビットマップを作成します。
constexpr uint32_t collect_bits(uint32_t bm, ...)

パラメータbmに指定する値から、その後の可変数パラメータで指定する0..31のビット位置に対応する値を取り出します。取り出した値はパラメータ順に並べビットマップとして戻り値になります。

ビットマップの並び順は、最初のパラメータを上位ビットとし末尾のパラメータがbit0になります。

uint32_t b1 = 0x12; // (b00010010)
uint32_t b2 = collect_bits(b1, 4, 2, 1, 0);
  // bit4->1, bit2->0, bit1->1, bit0->0
  // b2=0x10 (b1010)

例ではb1のビット4,2,1,0を取り出すと (1,0,1,0) になります。これをb1010として0x10のように計算されます。

背景

IOポート(DI,DO)の状態など各種ビットマップに値を参照・設定する場面があり、その記述を簡素化するため。

7.3.4 - Byte array utils

バイト配列と16/32ビット整数の相互変換
バイト配列から16/32ビット整数を生成、または16/32ビット整数からバイト配列を生成します。

読み出し

バイト配列から、uint8_t ビッグエンディアン並びとして、uint16_t, uint32_t の値を取得する。

	inline uint8_t G_BYTE(const uint8_t*& p) {
		return *(p)++;
	}
	inline uint16_t G_WORD(const uint8_t*& p) {
		uint32_t r = *p++;
		r = (r << 8) + *p++;
		return r;
	}
	inline uint32_t G_DWORD(const uint8_t*& p) {
		uint32_t r = *p++;
		r = (r << 8) + *p++;
		r = (r << 8) + *p++;
		r = (r << 8) + *p++;
		return r;
	}

p は読み出したバイト数分だけインクリメントされる。

書き込み

ポインタqで指定するバイト配列にuint8_t,ビッグエンディアンでuint16_t,uint32_tの値を書き込む。

	inline uint8_t& S_OCTET(uint8_t*& q, uint8_t c) {
		*q++ = c;
		return *q;
	}
	inline uint8_t& S_WORD(uint8_t*& q, uint16_t c) {
		*(q) = ((c) >> 8) & 0xff; (q)++;
		*(q) = ((c) & 0xff); (q)++;
		return *q;
	}
	inline uint8_t& S_DWORD(uint8_t*& q, uint32_t c) {
		*(q) = ((c) >> 24) & 0xff; (q)++;
		*(q) = ((c) >> 16) & 0xff; (q)++;
		*(q) = ((c) >>  8) & 0xff; (q)++;
		*(q) = ((c) & 0xff); (q)++;
		return *q;
	}

q は書き込んだバイト数分だけインクリメントされる。

背景

無線パケットのデータペイロードの生成・分解時の操作を簡略化するため。

より簡略化したpack_bytes(), expand_bytes()も利用できます。

7.3.5 - pack_bytes()

要素データを並べてバイト列を生成
要素データを並べてバイト列を生成します。
uint8_t* pack_bytes(uint8_t* b, uint8_t* e, ...)

pack_bytesはコンテナクラスのbegin(),end()イテレータをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。

可変引数パラメータに与えるデータは以下に示すとおりです。

データ型バイト数解説
uint8_t1
uint16_t2ビッグエンディアン並びで格納される
uint32_t4ビッグエンディアン並びで格納される
uint8_t[N]Nuint8_t 型の固定長配列
std::pair<char*,N>Nchar*,uint8_t*型の配列と配列長のペア。make_pair()で生成できる。
smplbuf_u8& pack_bytes(smplbuf_u8& c, ...)

pack_bytesはコンテナオブジェクトをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。コンテナの.push_back()メソッドで末尾に追加します。

可変引数パラメータに与えるデータは以下に示すとおりです。

データ型バイト数解説
uint8_t1
uint16_t2ビッグエンディアン並びで格納される
uint32_t4ビッグエンディアン並びで格納される
uint8_t[N]Nuint8_t 型の固定長配列
std::pair<char*,N>Nchar*,uint8_t*型の配列と配列長のペア。make_pair()で生成できる。
smplbuf_u8?.size()uint8_t型のsmplbuf<>コンテナ。コンテナ長(.size())のデータを格納する。

auto&& rx = the_twelite.receiver.read();

smplbuf<uint8_t, 128> buf;
mwx::pack_bytes(buf
	, uint8_t(rx.get_addr_src_lid())	// src addr (LID)
	, uint8_t(0xCC)							      // cmd id (0xCC, fixed)
	, uint8_t(rx.get_psRxDataApp()->u8Seq)	// seqence number
	, uint32_t(rx.get_addr_src_long())		// src addr (long)
	, uint32_t(rx.get_addr_dst())			// dst addr
	, uint8_t(rx.get_lqi())					  // LQI
	, uint16_t(rx.get_length())				// payload length
	, rx.get_payload() 						    // payload
);

この例では受信パケットの各属性やペイロードを別のバッファbufに再格納しています。

背景

無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t型のバイト配列の記述を簡素化するため。

auto&& rx = the_twelite.receiver.read();

uint8_t data[128];
data[0] = rx.get_addr_src_lid();
data[1] = 0xCC;
data[2] = rx.get_psRxDataApp()->u8Seq;
data[4] = rx.get_addr_src_long() & 0x000000FF;
data[5] = (rx.get_addr_src_long() & 0x0000FF00) >> 8;
data[6] = (rx.get_addr_src_long() & 0x00FF0000) >> 16;
data[7] = (rx.get_addr_src_long() & 0xFF000000) >> 24;
...

上記はもっとも単純な記述だが、以下のようにByte array utilsを用いてバイト配列を生成できる。

auto&& rx = the_twelite.receiver.read();

uint8_t data[128], *q = data;
S_OCTET(q, rx.get_addr_src_lid());
S_OCTET(q, 0xCC);
S_OCTET(q, rx.get_psRxDataApp()->u8Seq);
S_DWORD(q, rx.get_addr_src_long());
S_DWORD(q, rx.get_addr_dst());
S_OCTET(q, rx.get_lqi());
S_WORD(q, rx.get_length());
for (auto x : rx.get_payload()) {
  S_OCTET(q, x);
}

7.3.6 - expand-bytes()

“バイト列を分解し変数に格納”
バイト列を分解し変数に格納します。
const uint8_t* expand_bytes(
        const uint8_t* b, const uint8_t* e, ...)

expand_bytes()は、パラメータにuint8_t*型のイテレータの組み合わせを指定します。これは解析対象の先頭と末尾の次のイテレータの指定となります。eの位置まで解析が進んだ場合はエラーとなりnullptrを返します。

展開にエラーがない場合は、次の読み出しを行うイテレータを戻します。

可変数パラメータには以下を指定します。

バイト数データ長解説
uint8_t1
uint16_t2ビッグエンディアン並びとして展開する
uint32_t4ビッグエンディアン並びとして展開する
uint8_t[N]Nuint8_t 型の固定長配列
std::pair<char*,N>Nchar*,uint8_t*型の配列と配列長Nのペアmake_pair()で生成できる

auto&& rx = the_twelite.receiver.read();

char fourchars[5]{};
auto&& np =
	expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((uint8_t*)fourchars, 4)
    );

// read rest of payload
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];
expand_bytes(np, rx.get_payload().end()
	, u8DI_BM_remote
	, au16AI_remote[0]
	, au16AI_remote[1]
	, au16AI_remote[2]
	, au16AI_remote[3]
	, au16AI_remote[4]
);

この例では、まず4バイトの文字列を読み出しています。ここではmake_pair()を用いて明示的に4バイト分のデータを読み出します。

戻されたイテレータnpをもとに、次のデータを読み出します。次のデータはuint8_t型、あとはuint16_t型が5つ続いています。

背景

無全パケットのデータペイロードの生成やデータの取り出しで用いられるuint8_t型のバイト配列の記述を簡素化するため。

auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];

uint8_t *p = rx.get_payload().begin();
fourchars[0] = p[0];
fourchars[1] = p[1];
fourchars[2] = p[2];
fourchars[3] = p[3];
fourchars[4] = 0;
p += 4;

u8DI_BM_remote = (p[0] << 8) + p[1]; p+=2;
au16AI_remote[0] = (p[0] << 8) + p[1]; p+=2;
...

上記はもっとも単純な記述だが、以下のようにByte array utilsを用いてバイト配列から読み出せる。

auto&& rx = the_twelite.receiver.read();
char fourchars[5]{};
uint8_t u8DI_BM_remote = 0xff;
uint16_t au16AI_remote[5];

uint8_t *p = rx.get_payload().begin();
fourchars[0] = G_BYTE(p);
fourchars[1] = G_BYTE(p);
fourchars[2] = G_BYTE(p);
fourchars[3] = G_BYTE(p);
fourchars[4] = 0;

u8DI_BM_remote = G_WORD(p);
au16AI_remote[0] = G_WORD(p);
...

7.3.7 - CRC8, XOR, LRC

チェックサム計算
チェックサムの計算で良く用いられる値を計算します。
uint8_t CRC8_u8Calc(uint8_t *pu8Data, uint8_t size, uint8_t init=0)
uint8_t CRC8_u8CalcU32(uint32_t u32c, uint8_t init=0)
uint8_t CRC8_u8CalcU16(uint16_t u16c, uint8_t init=0)
uint8_t XOR_u8Calc(uint8_t *pu8Data, uint8_t size)
uint8_t LRC_u8Calc(uint8_t* pu8Data, uint8_t size)

CRC8, XOR, LRC(アスキー形式で使用)の計算を行います。

CRC8_u8CalcU16(), CRC8_u8CalcU32()u16c, u32cをビッグエンディアン並びとして、CRC8を計算します。

背景

無線パケットのデータ列、アスキー形式のチェックサム(LRC)、各種センサーのデータチェック用に利用されるため、ライブラリ手続きとして追加した。

7.3.8 - div10(), div100(), div1000()

10, 100 または 1000 で割った商と剰余を計算
10, 100 または 1000 で割った商と剰余を計算します。
struct div_result_i32 {
		int32_t quo; // quotient
		int16_t rem; // remainder
		uint8_t b_neg;  // true if negative
		uint8_t digits_rem; // digits of remainder
};

div_result_i32 div10(int32_t val);
div_result_i32 div100(int32_t val);
div_result_i32 div1000(int32_t val);

センサー値などで100倍した値をuint16_t型にして受け渡しする場合がありますが、除算回路がないマイコンでの計算処理には相応の時間がかかるため、加算・減算・乗算とビットシフトを用いた近似計算と補正により計算を行います。

valに計算したい値、remは余りの格納変数、negは符号を格納する変数を渡します。

戻り値は商の値(常に正)、remには余りの値(常に正)、negは負ならtrueが格納されます。

計算アルゴリズムの制約(桁あふれ)からdiv100()div1000()での計算可能な値域が決まっています。div100()は-99999~99999までの値に対応し、div1000()は-999999~999999までの値に対応します。

使用例

auto d1 = div100(sns_val.u16temp_object);
auto d2 = div100(sns_val.u16temp_object);

Serial
	<< crlf << format("..Object  = %c%2d.%02d"
									, d1.b_neg ? '-' : '+', d1.quo, d1.rem)
	        << format("  Ambient = %c%2d.%02d"
									, d2.b_neg ? '-' : '+', d2.quo, d2.rem);

計算速度

10分の1程度になります。

結果の出力

// 変換オプション
struct DIVFMT {
  static const int STD = 0; // displays with minimul digits (no padding, no positive sign)
  static const int PAD_ZERO = 1; // set padding character as '0' instead of ' '.
  static const int SIGN_PLUS = 2; // put '+' sign if value is positive or 0.
  static const int PAD_ZERO_SIGN_PLUS = 3; // PAD_ZERO & SIGN_PLUS
  static const int SIGN_SPACE = 4; // put ' ' sign if value is positive or 0.
  static const int PAD_ZERO_SIGN_SPACE = 5; // PAD_ZERO & SIGN_SPACE
};

// 文字列変換結果を格納するためのクラス
class _div_chars {
  ...
  const char* begin() const {...}
  const char* end() const {...}
  const char* c_str() const { return begin(); }
  operator const char*() const { return begin(); }
};

// format()メソッド
_div_chars div_result_i32::format(
    int dig_quo = 0, uint32_t opt = DIVFMT::STD) const;

// Serialへのインタフェースの実装
template <class D> class stream {
...
		inline D& operator << (const mwx::_div_chars&& divc);
		inline D& operator << (const mwx::div_result_i32&&);
		inline D& operator << (const mwx::div_result_i32&);
};

割り算の結果を格納するdiv_result_i32クラスにはformat()メソッドを用い_div_charsクラスオブジェクトを得ることが出来る。_div_chars()クラスオブジェクトは文字列バッファを内包していてconst char*型として文字列バッファにアクセスするメソッドが用意されている。また、Serialオブジェクトに対する<<演算子も実装している。

format()メソッドのパラメータの1番目dig_quoは出力桁数(符号部を含まず)を指定します。出力桁数に足りない場合(以下、不足桁)は空白または0で埋めます。2番目のパラメータoptは書式を指定します。

optパラメータ内容
DIVFMT::STD標準的な出力で、不足桁は空白で埋め、負の値に限り-を付加します。
DIVFMT::PAD_ZERO不足桁は0で埋めます。
DIVFMT::SIGN_PLUS正の値にも+符号を付加します。
DIVFMT::PAD_ZERO_SIGN_PLUS不足桁は0で埋め、正の値にも+符号を付加します。
DIVFMT::SIGN_SPACE正の値の場合は+符号の替わりに空白を付加します。
DIVFMT::PAD_ZERO_SIGN_SPACE不足桁は0で埋め、正の値の場合は+符号の替わりに空白を付加します。

//// div_result_i32オブジェクトから直接出力
Serial << div100(-1234) << crlf;
// 結果: -12.34

//// 3桁で出力します
Serial << div100(3456).format(3, DIVFMT::PAD_ZERO_SIGN_PLUE) << crlf;
// 結果: +034.56

//// c_str()を使ってconst char*を得る
char str1[128];
auto x = div100(-5678);
mwx_snprintf(str1, 16, "VAL=%s", x.format.c_str()); // const char*
Serial << str1;
// 結果: VAL=-56.78

背景

TWELITE BLUE/RED では除算はコストが高い演算であるため、目的を限定した除算アルゴリズムを追加した。

ライブラリ内では温度・湿度といった一部のセンサー値、100倍の値(25.12℃なら2512)を用いて表現しているため、100で割った商と余りを得るための簡素な手続きを定義した。

dev_result_i32::format()については、書式出力を行う際の煩雑さを避けるためである。

7.3.9 - Scale utils

最適化された値のスケーリング処理
8bit値(0..255など)の値とユーザが取り扱いやすい0..1000(千分率, ‰)値でスケール(拡縮)する。低い演算コストで実施するため、除算(x*1000/255)の替わりに乗算とビットシフトによって近似計算します。
static inline uint8_t scale_1000_to_127u8(uint16_t x)
static inline uint16_t scale_127u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_255u8(uint16_t x)
static inline uint16_t scale_255u8_to_1000(uint8_t x)
static inline uint8_t scale_1000_to_256u8(uint16_t x)
static inline uint16_t scale_256u16_to_1000(uint16_t x)

scale_1000_to_127u8()

0..1000 を 0..127 にスケールします。(16646*x+65000)>>17を用いて近似計算します。

scale_127u8_to_1000()

0..127 を 0..1000 にスケールします。(2064000UL*x+131072)>>18を用いて近似計算します。

scale_1000_to_255u8()

0..1000 を 0..255 にスケールします。(33423*x+65000)>>17を用いて近似計算します。

scale_255u8_to_1000()

0..255 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18を用いて近似計算します。

scale_1000_to_256u8()

0..1000 を 0..256 にスケールします。(33554*x+66000) >> 17を用いて近似計算します。

注: x=999,1000は計算値が256になりますがuint8_tの範囲として255を返します。

scale_256u16_to_1000()

0..256 を 0..1000 にスケールします。(1028000UL*uint32_t(x)+131072)>>18を用いて近似計算します。

背景

ハードウェアに設定すべき値は0..255といった2進数が前提になることが多く、ユーザアプリケーションで取り扱う数は0..1000といった10進数基準のほうが扱いやすい。これらのスケール変換に除算を行わない式を定義した。

7.3.10 - pnew()

配置new の記述を簡素化する
配置new(placement new)の記述を簡素化します。
template <class T, class... Args>
T* pnew(T& obj, Args&&... args) {
    return (T*)new ((void*)&obj) T(std::forward<Args&&>(args)...);
}

例えば以下のように利用します。コンストラクタ引数を与えることもできます。

class my_class {
    int _a;
public:
    my_class(int a = -1) : _a(a) {}
};

my_class obj_1; // このコンストラクタは呼ばれない!
my_class obj_2; // このコンストラクタは呼ばれない!

void setup() {
    mwx::pnew(obj_1);    // my_class obj_1; に相当
	mwx::pnew(obj_2, 2); // my_class obj_2(2); に相当
    ...
}

背景

コンパイラの制約のためグローバルオブジェクトのコンストラクタが呼び出されないため、これを初期化する方法は配置newを使用する方法が挙げられます。しかしながら、配置new(placement new)の構文は煩雑に見えるため。

他にstd::unique_ptr(または eastl::unique_ptr)を用いる方法もある。

std::unique_ptr<my_class> obj_3;

void setup() {
    obj_3.reset(new my_class(3));
    		// TWELITE マイコンでは new は1回だけ確保し delete はできないため、
            // 事実上グローバルオブジェクトと同等です。
}

8 - 外部ライブラリ

外部ライブラリ
MWX と併用可能な外部ライブラリを紹介します。
  • EASTL - EASTL は Electronic Arts 社による C++ 標準ライブラリで、固定長メモリで動作するコンテナが用意されており、組み込みマイコンで利用しやすい特徴があります。TWELITE STAGE SDK では特別な設定なしでビルド可能できます。

8.1 - EASTL

軽量テンプレートライブラリ
EASTL は Electronic Arts 社が整備した標準テンプレートライブラリ(コンテナ・アルゴリズム)で、C++ の STL (Standard Template Library) に倣い実装されていますが、制約の多いゲーム機の開発で整備されてきた経緯があり、メモリの取り扱いに制約が大きい環境を意識したコンテナやアルゴリズムが用意されています。

本ライブラリは EASTL を TWENET 内で利用できるようにしています。

以下はバッファ最大長が固定の配列とソートアルゴリズムの適用です。

以下にEASTLの特徴を記載します。

  • メモリ固定確保のコンテナ (fixed_) : 動的確保を行わず、固定長の要素数を持つコンテナの宣言が可能です。グローバル宣言すれば、コンパイル時に固定的にメモリ領域が確保され、ローカル宣言すればスタックエリアに確保されそのスコープ内で利用できます。
  • Intrusive コンテナ:通常のコンテナは任意のデータ構造を格納できますが、Intrusiveコンテナはデータ構造に対して専用の基底クラスを継承することで、コンテナ内のリンク構造などを維持するためのリンク情報などを保持します。コンテナ内の各要素はそのコンテナ専用になりますが、リストやマップ構造では非常にメモリ利用効率の良くなります。(参考: Intrusive and non-intrusive containers

2007年の記事EASTL (open-std.org)に開発動機などが記されています。(関連記事:EASTL から垣間見るゲームソフトウェア開発現場の現状 その 1, その 2)

TWENETでの利用

以下に留意してください。

当社ではライブラリの動作については包括的な検証は行っておりません。お客様での動作検証をお願いいたします。また EASTL の利用方法についてのお問い合わせについても当社では対応できません。配布元の開設資料・ライブラリソースコードなどの情報を参照してください。

  • EASTL3.07 (2018/1/31) のバージョンを利用します。(C++11でコンパイルできる一番最後のバージョン)
  • 以下のライブラリは組み込んでいません。
    • test/packages/EAAssert, source/assert.cpp
    • test/packages/EATest
    • test/packages/EAThread, source/thread_support.cpp
  • テストコード test/source の動作移植はしていません。
  • _sprintf_関連ではEA::StdC::Vsnprintf(char8_t*, ...) のみを printf.h ライブラリ中の vsnprintf_() を呼び出すことで解決しています。

組み込み・コンパイル方法

EASTL はアクト Act の記述の際に利用できます。

TWELITE 向けの開発環境で必要なインクルードパスの追加、ライブラリの追加は行います。作成するコード中でライブラリヘッダのインクルードを行ってください。

#include 
#include 

using namespace eastl;
using tstr128 = fixed_string;

void setup() {
    tstr128 s1;
    s1 = "Hello World";
    Serial << s1.c_str();
}
void loop() {
    ;
}

組み込み方法(詳細)

ライブラリのコンパイルやインクルードパスの設定は、MWSDK/TWENET 以下のディレクトリで実施済みですが、内部的な設定を以下に記載します。

  • EASTL/source 内のコードをコンパイルして、ライブラリアーカイブとしておく (libEASTL.a)。リンク時にはこのライブラリの参照が必須です。
  • コンパイル時に以下のインクルードパスを追加しておく。

$(PATH_EASTL)を EASTL ディレクトリとした場合、インクルードパスは以下となります。

-I$(PATH_EASTL)/include
-I$(PATH_EASTL)/test/packages/EAAssert/include
-I$(PATH_EASTL)/test/packages/EABase/include/Common
-I$(PATH_EASTL)/test/packages/EAMain/include
-I$(PATH_EASTL)/test/packages/EAStdC/include
-I$(PATH_EASTL)/test/packages/EATest/include
-I$(PATH_EASTL)/test/packages/EAThread/include

コーディングについて

std::eastl:: について

MWXライブ内部でも std:: 名前空間の標準ライブラリを利用しています。

標準ライブラリ (std::) と EASTL(eastl::)では同じ名前で同じ機能を持つものが定義されています。これらは混在できる場合もありますが、使用するとエラーになる場合もあります。つまりEASTLで使用するものは、通常はEASTL内の定義を用います(例:std::unique_ptreastl::fixed_string を格納しようとするとコンパイラエラーになります)。

またusing namespace std;といったような記述を行う場合は、名前の衝突に注意してください。

グローバルオブジェクトの初期化1 (配置new)

TWENET の開発では、コンパイラの制約により、グローバル宣言のオブジェクトのコンストラクタが実行されません。グローバル宣言宣言したオブジェクトのメモリ領域がゼロクリアされるだけです。そのまま、コードを実行すると大抵の場合 null pointer access によりハングアップします。

このオブジェクトを初期化するためには*placement new (配置new)*を用います。

##include 
##include 

using namespace eastl;
using tstr128 = fixed_string;

tstr128 g_str1; // constructor is NOT called! needs to be initialized before use.

void setup() {
    (void) new ((void*)&g_str1) tstr128("Hello World");
    Serial << g_str1.c_str();
}

placement new のコードは少し乱雑に見えるため、補助関数mwx::pnew() を用意しています。先ほどの例を以下のように書き換えることができます。

(void) new ((void*)&g_str1) tstr128("Hello World");
// ↓
mwx::pnew(g_str1, "Hello World");

※ 2番目の引数以降は可変数で、コンストラクタにそのまま渡されます。

グローバルオブジェクトの初期化2 (unique_ptr)

グローバルオブジェクトの初期化方法として unique_ptr(std::unique_ptrの解説)を用いる方法もあります。unique_ptrstd:: にも eastl:: にもありますが、EASTLのクラスではeastl::のものを使用します。

以下のように初期化のタイミングで .reset() を呼び出します。

##include 
##include 
##include 

using namespace eastl;
using tstr128 = fixed_string;

eastl::unique_ptr uq_str1;

void setup() {
	uq_str1.reset(new tstr128("Hello World"));
    if (uq_str1) { // true: object is stored.
        Serial << uq_str1->c_str();
    }
}

intrusive コンテナについて

以下の例は intrusive_list の要素定義例です。メンバーは int mX のみです。

struct IntNode : public eastl::intrusive_list_node {
    int mX;
    IntNode(int x = 0) : mX(x) { }
        // no need to call super class's constructor eastl::intrusive_list_node()
};

inline bool operator<(const IntNode& a, const IntNode& b) { return a.mX < b.mX; }
inline bool operator>(const IntNode& a, const IntNode& b) { return a.mX > b.mX; }

intrusive_listの要素は、必ず intrusive_list_node を基底クラスに持っている必要があります。基底クラス内にはリストを維持するためのリンクポインタが含まれます。ここではさらに sortなどで使用する比較演算子の定義も行います。

using tiList = intrusive_list;

void setup() {
    IntNode nodeA(5);
    IntNode nodeB(1);
    IntNode nodeC(9);
    IntNode nodeD(2);
    IntNode nodeE(4);

    tiList l; // intrusive_list body

    l.push_front(nodeA); // forming list strucure
                         //   by updating link info in intrusive_list_node.
    l.push_front(nodeB);
    l.push_front(nodeC);
    l.push_front(nodeD);
    l.push_front(nodeE);

    l.sort(); // sort, using < operator
    l.sort(eastl::greater()); // sort, using > operator
}

参考資料

本サンプルについて

EASTLのライセンス記述は以下です。

Modified BSD License (3-Clause BSD license) see the file LICENSE in the project root.

/*
Copyright (C) 2015 Electronic Arts Inc.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
3.  Neither the name of Electronic Arts, Inc. ("EA") nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

サンプルコードは MWSLA-1J/E を適用します。

コード例

fixed_vector

最大長が固定された(つまり拡張しない)配列。 (※ mwx::smplbufも同様に最大長固定の配列ですが、MWXライブラリの内部処理に一部特化しています)

##include <TWELITE>
##include <EASTL/fixed_vector.h>
##include <EASTL/sort.h>

using tvct = eastl::fixed_vector<uint16_t, 64, false>;
tvct v;

void setup() {
    mwx::pnew(v); // initialize
    v = { 3, 1, 2 ,4 }; // set initial list.

    // push and pop
    v.pop_back();   // 3, 1, 2
    v.push_back(5); // 3, 1, 2, 5

    // sort
    eastl::sort(v.begin(), v.end(), eastl::less<tvct::value_type>());
    				// 1, 2, 3, 5

    // disp all
    for (auto x : v) { Serial << format(" %d", x); }
    Serial << crlf;
    // using [] operator
    for (int i = 0; i < v.size(); i++) { Serial << format(" %d", v[i]); }
}

fixed_vector のテンプレート引数は3つあり、1番目が型、2番目が最大数、3番目は_false_にします。 配列の操作については、一般の std::vector と類似した .puch_back().pop_back()[]演算子などが利用できます。

また、ソートアルゴリズムなどの適用も可能です。上記の例では eastl::sort を昇順 eastl::less にて行っています。

fixed_list

最大エレメント数が固定されたリスト構造(intrusive_list についても要参照)。

##include <TWELITE>
##include <EASTL/fixed_list.h>
##include <EASTL/sort.h>

using tdata = eastl::pair<uint8_t, void (*)(uint8_t)>; // element data type
using tlst = eastl::fixed_list<tdata, 3, false>; // fixed_list with 3 elements.
tlst l; // list object

void setup() {
    mwx::pnew(l); // initialize (call constructor)
    // add
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('A', [](uint8_t v){ Serial << format("(1:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('B', [](uint8_t v){ Serial << format("(2:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('C', [](uint8_t v){ Serial << format("(3:%c)", v); } ));
    if (!l.full()) l.insert(l.begin(), eastl::make_pair('D', [](uint8_t v){ Serial << format("(4:%c)", v); } )); // fails
    Serial << crlf << "init: "; for(auto &x: l) x.second(x.first);
    // find & erase
    auto p = eastl::find_if(l.begin(), l.end(), [](tdata& x) { return (x.first == 'B'); } );
    if (p != l.end()) l.erase(p);
    Serial << crlf << "find&erase: "; for(auto &x: l) x.second(x.first);
    // append
    if (!l.full()) l.insert(l.end(), eastl::make_pair('D', [](uint8_t v){ Serial << format("(4:%c)", v); } ));
    Serial << crlf << "append: "; for(auto &x: l) x.second(x.first);
    // sort
    eastl::sort(l.begin(), l.end(), eastl::less<tlst::value_type>());
    Serial << crlf << "sort:"; for(auto &x: l) x.second(x.first);
    // sort reverse
    eastl::sort(l.begin(), l.end(), eastl::greater<tlst::value_type>());
    Serial << crlf << "sort(rev):"; for(auto &x: l) x.second(x.first);
}

fixed_list のテンプレート引数は3つあり、1番目が型、2番目が最大数、3番目は_false_にします。リストの操作については、一般の std::list に類似した .insert(), .erase() などが利用できます。

上記のコードでは、リストにはペア eastl::pair の要素を格納し、ペアの first を uint8_t 型の整数、second を void (*)(uint8_t) の関数ポインタとしています。コード中はラムダ式を直接記述しています。コード中の x.second(x.first); は second から得られる関数に対して first の値を与えるという意味合いになります。

このリストに対して eastl::find_if による要素の検索を行ったり、bubble_sortによるソートが可能です。

intrusive_list

通常のリストは任意のデータ構造を要素とできますが、intrusive_list は要素に特定のデータを付与し、そのデータを用いることでデータ構造を構築します。

以下の例では、intruslve_list データ構造の要素となるには eastl::intrusive_list_node を継承したデータ要素型であることを要件とします。eastl::intrusive_list_nodeには前後の要素に対するポインタを格納できるような拡張です。

##include <TWELITE>
##include <EASTL/fixed_vector.h>
##include <EASTL/intrusive_list.h>
##include <EASTL/unique_ptr.h>

// list element of intrusive_list.
struct IntNode : public eastl::intrusive_list_node {
    int mX;
    IntNode(int x = 0) : mX(x) { }
};
inline bool operator>(const IntNode& a, const IntNode& b) { return a.mX > b.mX; } // for sort

using tpool = eastl::fixed_vector<IntNode, 16, false>;
using tlst = eastl::intrusive_list<IntNode>;

tpool pool; // instance pool.
tlst l; // list object

void setup() {
    mwx::pnew(pool); // prepare isntances
    mwx::pnew(l); // initialize (call constructor)

    pool.resize(5); // create 4 instances into pool

    // insert an IntNode element into List.
    int i = 0;
    pool[i].mX = 5; l.push_front(pool[i]); i++;
    pool[i].mX = 1; l.push_front(pool[i]); i++;
    pool[i].mX = 2; l.push_front(pool[i]); i++;
    pool[i].mX = 4; l.push_front(pool[i]); i++;
    pool[i].mX = 3; l.push_front(pool[i]); i++;
    Serial << crlf << "init: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }

    l.remove(pool[2]);
    Serial << crlf << "remove: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }

    l.sort(eastl::greater<tlst::value_type>());
    Serial << crlf << "sort: ";
    for(auto& x : l) { Serial << format(" %d", x.mX); }
}

この例で eastl::fixed_vector<> が使用されているのは、IntNode の要素を必要数確保する目的で、fixed_vectorが必要だったわけではありません。5つの要素にテストの値を格納して intrusive_list を構築します。例では l.push_pront() を呼び出し要素をひとつずつリストに格納しています。実際は格納するのではなく、各要素 IntNode が持つポインタの繋ぎ変えです。

ソートの記述は l.sort() のようにメンバ関数の呼び出しで行います。

ring_buffer

リングバッファ ring_buffer は、他のコンテナ(例では fixed_vector) との組み合わせで構築されます。

##include <TWELITE>
##include <EASTL/fixed_vector.h>
##include <EASTL/bonus/ring_buffer.h>

const size_t N_RING_ELE = 4; // element max for RING BUFFER.
using tvec = eastl::fixed_vector<uint8_t, N_RING_ELE + 1, false>; // One extra element is required.
using tring = eastl::ring_buffer<uint8_t, tvec>;
tring rb;

void setup() {
    mwx::pnew(rb, N_RING_ELE);

    rb.push_front(5);
    rb.push_front(1);
    rb.push_front(4);
    rb.push_front(2);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(3);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(8);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);
    rb.push_front(9);
    Serial << crlf; for (auto x : rb) Serial << format(" %d", x);

    Serial << crlf << format("back=%d", rb.back()) << crlf;
    rb.pop_back();
    for (auto x : rb) Serial << format(" %d", x);
}

ring_buffer の定義は、要素型とそのコンテナ型の組み合わせです。要素型は余分に一つ要素を持たせておきます。

上記の例では .push_front() で先頭に要素を挿入します。オーバーフローすると末尾は消えてしまいます。 .back()により一番古い要素を取り出します。pop_back()で一番古い要素を削除します。

intrusive_hash_map

マップ構造は、キーと値をもつデータ構造で、キー値により効率よく要素を抽出できるように設計されたデータ構造です。 intrusive_hash_map は intrusive 構造とハッシュ値を用いて実装しています。いくばくか定義は煩雑ですが、メモリ消費量は抑制できます。

intrusive_list と同様独自の要素型 IntNodeeastl::intrusive_hash_node_key<要素型> を継承した形で定義する必要があります。またハッシュを用いるためハッシュ最大値 (N_BUCKET_CT) を定めておく必要があります。この値は、想定される格納要素数に応じて適切に素数の値を設定します。

##include <TWELITE>
##include <EASTL/internal/intrusive_hashtable.h>
##include <EASTL/intrusive_hash_map.h>

static const unsigned N_BUCKET_CT = 7;

// intrusive element type
struct IntNode : public eastl::intrusive_hash_node_key<uint8_t> {
    using SUP = intrusive_hash_node_key;
    void (*m_func)(); // member variable is func pointer.
    IntNode(uint8_t key = 0) { SUP::mKey = key; } // key will be passed by the constructor.
};

// intrusive map type
using tmap = eastl::intrusive_hash_map<uint8_t, IntNode, N_BUCKET_CT>;

tmap mp;
IntNode nd_a, nd_b, nd_c, nd_d;

void setup() {
    mwx::pnew(mp); // initialize (call constructor)

    mwx::pnew(nd_a, 'A')->m_func = []() { Serial << "FuncA"; };
    mwx::pnew(nd_b, 'B')->m_func = []() { Serial << "FuncB"; };
    mwx::pnew(nd_c, 'C')->m_func = []() { Serial << "FuncC"; };
    mwx::pnew(nd_d, 'D')->m_func = []() { Serial << "FuncD"; };

    mp.insert(nd_a);
    mp.insert(nd_b);
    mp.insert(nd_c);
    mp.insert(nd_d);
}

void loop() {
    int c = Serial.read();
    if(c != -1) {
        Serial << crlf << '[' << uint8_t(c) << ']';
        auto&& it = mp.find(uint8_t(c));
        if (it != mp.end()) it->m_func();
    }
}

上記の例は、マップのキーを uint8_t 型の1文字とし、マップの値部分を関数ポインタとします。loop()ではキーの入力に応じた関数を実行するといった処理を行います。

最初に、グローバルオブジェクトとしてテーブルと要素を定義したため、setup()中で mwx::pnew() を呼び出すことでデータ要素(nd_a, nd_b, nd_c, nd_d)の初期化、ハッシュマップの初期化 (mp) を行っておきます。mwx::pnew() の戻り値は、構築したオブジェクトへのポインタですので、初期化後直接メンバ変数に値(ラムダ式)を書き込んでいます。

要素(nd_a, nd_b, nd_c, nd_d)の初期と値の設定が終わったら mp.insert(nd_a) のようにマップに要素を挿入していきます。

loop()ではシリアルから文字を入力するたびに、ハッシュマップの検索を行います。検索は mp.find() メソッドを呼び出し、戻り値はイテレータで、検索失敗時は mp.end() が戻ります。検索が成功したら (*it) により検索出来た要素を参照できます。

9 - ボードビヘイビア (BRD)

ハードウェアの抽象化レイヤ
ボードビヘイビアは、TWELITE へ接続したハードウェアを取り扱うための手続きをまとめたものです。
  • 定数の定義(ピン番号など)
  • ハードウェアの初期化
  • センサー等の取り扱い

9.1 - <BRD_APPTWELITE>

超簡単!標準アプリと同様の配線を想定したボードビヘイビア
超簡単!標準アプリ (App_Twelite) と同様の配線を想定したボードビヘイビアです。定数定義と、M1-M3,BPSピンの読み出し機能があります。

定数

以下の定数を定義しています。

static const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO12;
static const uint8_t PIN_DI2 = mwx::PIN_DIGITAL::DIO13;
static const uint8_t PIN_DI3 = mwx::PIN_DIGITAL::DIO11;
static const uint8_t PIN_DI4 = mwx::PIN_DIGITAL::DIO16;

static const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18;
static const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19;
static const uint8_t PIN_DO3 = mwx::PIN_DIGITAL::DIO4;
static const uint8_t PIN_DO4 = mwx::PIN_DIGITAL::DIO9;

static const uint8_t PIN_M1 = mwx::PIN_DIGITAL::DIO10;
static const uint8_t PIN_M2 = mwx::PIN_DIGITAL::DIO2;
static const uint8_t PIN_M3 = mwx::PIN_DIGITAL::DIO3;
static const uint8_t PIN_BPS = mwx::PIN_DIGITAL::DIO17;

static const uint8_t PIN_AI1 = mwx::PIN_ANALOGUE::A1;
static const uint8_t PIN_AI2 = mwx::PIN_ANALOGUE::A3;
static const uint8_t PIN_AI3 = mwx::PIN_ANALOGUE::A2;
static const uint8_t PIN_AI4 = mwx::PIN_ANALOGUE::A4;

BRD_APPTWELITE::PIN_DI1のようにアクセスできます。

メソッド

DIP SW (M1 M2 M3 BPS) ピンの値を取得するためのメソッドが用意されています。

inline uint8_t get_M1()
inline uint8_t get_M2()
inline uint8_t get_M3()
inline uint8_t get_BPS()
inline uint8_t get_DIPSW_BM()

戻り値はHIGH, LOWではなく、0がセットされていない(HIGH側)、1がスイッチがセットされる(LOW側)という意味です。

get_DIPSW_BM()は、bit0から順にM1,M2,M3,BPSピンの値を返します。

9.2 - <MONOSTICK>

MONOSTICK用
MONOSTICK用のボードビヘイビアです。内蔵ウォッチドッグタイマーの制御とLED点灯用の手続きが含まれます。

定数

以下の定数を定義しています。

const uint8_t PIN_LED = mwx::PIN_DIGITAL::DIO16;  // LED

const uint8_t PIN_WDT = mwx::PIN_DIGITAL::DIO9;     // WDT (shall tick < 1sec)
const uint8_t PIN_WDT_EN = mwx::PIN_DIGITAL::DIO11; // WDT (LO as WDT enabled)

const uint8_t PIN_LED_YELLOW = mwx::PIN_DIGITAL::DO1; // YELLOW LED

MONOSTICK::PIN_LEDのようにアクセスできます。

ハードの初期化

pinMode(PIN_LED, OUTPUT_INIT_HIGH);
pinMode(PIN_WDT, OUTPUT_INIT_LOW);
pinMode(PIN_WDT_EN, OUTPUT_INIT_LOW);
pinMode(PIN_LED_YELLOW, OUTPUT);

上記のコードのように各ピンが初期化されます。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

メソッド

set_led()

void set_led_red(uint8_t mode, uint16_t tick)
void set_led_yellow(uint8_t mode, uint16_t tick)

LED(赤、黄)の制御を行います。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定意味
LED_TIMER::BLINKLEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RXパケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP送信完了時にtickに与える時間[ms]だけ点灯します。

9.3 - PAL 共通定義

TWELITE PAL シリーズ向けの共通定義
TWELITE PALのハードには共通部分があり、ボードビヘイビアも共通ハードについては、共通のインタフェースを定義しています。

定数

以下の定数を定義しています。

static const uint8_t PIN_BTN = 12; // button (as SET)
static const uint8_t PIN_LED = 5;  // LED
static const uint8_t PIN_WDT = 13; // WDT (shall tick every 60sec)

static const uint8_t PIN_D1 = 1; // DIP SW1
static const uint8_t PIN_D2 = 2; // DIP SW2
static const uint8_t PIN_D3 = 3; // DIP SW3
static const uint8_t PIN_D4 = 4; // DIP SW4

static const uint8_t PIN_SNS_EN = 16;
static const uint8_t PIN_SNS_INT = 17;

PAL_AMB::PIN_BTNのようにアクセスできます。

ハードの初期化

pinMode(PIN_BTN, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT_INIT_HIGH);
pinMode(PIN_WDT, OUTPUT_INIT_HIGH);

pinMode(PIN_D1, INPUT_PULLUP);
pinMode(PIN_D2, INPUT_PULLUP);
pinMode(PIN_D3, INPUT_PULLUP);
pinMode(PIN_D4, INPUT_PULLUP);

上記のコードのように各ピンが初期化されます。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

メソッド

set_led()

void set_led(uint8_t mode, uint16_t tick)

LED(D1)の制御を行います。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定意味
LED_TIMER::BLINKLEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RXパケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP送信完了時にtickに与える時間[ms]だけ点灯します。

led_one_shot()

void led_one_shot(uint16_t tick)

指定期間だけLEDを点灯します。set_led()の機能と同時には使えません。

get_D1() .. D4(), get_DIPSW_BM()

inline uint8_t get_D1()
inline uint8_t get_D2()
inline uint8_t get_D3()
inline uint8_t get_D4()
inline uint8_t get_DIPSW_BM()

get_D1() .. get_D4()はDIP SWがHIGH(スイッチが上)の時0、LOW(スイッチが下)のとき1を返します。

get_DIPSW_BM()はDIP SWの設定値を0..15で返します。SW1==LOW1, SW2 == LOW2, SW3 == LOW4, SW4 == LOW8とした和を返します。

9.3.1 - <PAL_AMB>

環境センサーパル用
環境センサーパル AMBIENT SENSE PAL のボードビヘイビアです。

共通定義に加えボード上のセンサーを取り扱えるようになっています。

  • 温湿度センサー SHTC3
  • 照度センサー LTR308ALS
void setup() {
  auto&& brd = the_twelite.board.use<PAL_AMB>();
}

メンバーオブジェクト

sns_SHTC3

SHTC3センサーのオブジェクトです。

sns_LTR308ALS

LTR-308ALSセンサーのオブジェクトです。

9.3.2 - <PAL_MAG>

開閉センサーパル用
開閉センサーパル OPEN-CLOSE SENSE PAL のボードビヘイビアです。
void setup() {
  auto&& brd = the_twelite.board.use<PAL_MAG>();
}

開閉センサーパルのセンサーは磁気センサーで、2本の信号線の割り込みの入力のみです。

const uint8_t PAL_MAG::PIN_SNS_NORTH = 16;
const uint8_t PAL_MAG::PIN_SNS_OUT1 = 16;
const uint8_t PAL_MAG::PIN_SNS_SOUTH = 17;
const uint8_t PAL_MAG::PIN_SNS_OUT2 = 17;

PAL_MAG::PIN_SNS_NORTHはセンサーがN極を検出したとき、PAL_MAG::PIN_SNS_SOUTHはセンサーがN極を検出したときに割り込みが入ります。

スリープ前に以下の設定をしておきます。

pinMode(PAL_MAG::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
pinMode(PAL_MAG::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

起床時に起床要因のIOを確認します。

uint8_t b_north =
  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_NORTH);
uint8_t b_south =
  the_twelite.is_wokeup_by_dio(PAL_MAG::PIN_SNS_SOUTH);

9.3.3 - <PAL_MOT>

動作センサーパル用
動作センサーパル MOTION SENSE PAL のボードビヘイビアです。

共通定義に加えボード上のセンサーを取り扱えるようになっています。

  • 加速度センサー MC3630
void setup() {
  auto&& brd = the_twelite.board.use<PAL_MOT>();
}

メンバーオブジェクト

sns_MC3630

MC3630センサーのオブジェクトです。

9.3.4 - <PAL_NOTICE>

通知パル用
通知パル NOTICE PAL のボードビヘイビアです。

共通定義に加えボード上のセンサーを取り扱えるようになっています。

  • LEDドライバ PCA9632
  • 加速度センサー MC3630
void setup() {
  auto&& brd = the_twelite.board.use<PAL_NOTICE>();
}

メンバーオブジェクト

sns_PCA9632

PCA9632デバイスのオブジェクトです。ボード定義ではWireの初期化、デバイスの初期化を実施します。原則として後述のPCA9632操作メソッドを用いて制御します。

sns_MC3630

MC3630センサーのオブジェクトです。ボード定義ではSPIの初期化、MC3630デバイスの初期化、MC3630の割り込み処理などを行っています。諸処理はsns_MC3630定義の手続きを用います。

PCA9632 定義

static const uint8_t LED_OFF = SnsPCA9632::LED_OFF;
static const uint8_t LED_ON = SnsPCA9632::LED_PWM;
static const uint8_t LED_BLINK = SnsPCA9632::LED_BLINK;
static const uint8_t LED_NOP = SnsPCA9632::LED_NOP;

static const uint8_t LED_R = SnsPCA9632::LED1;
static const uint8_t LED_G = SnsPCA9632::LED2;
static const uint8_t LED_B = SnsPCA9632::LED3;
static const uint8_t LED_W = SnsPCA9632::LED4;

static const uint8_t LED_REG_MAX_PWM = 127;
static const uint8_t LED_REG_BOOST_PWM = 255;

点灯状態

定義意味
PAL_NOTICE::LED_OFF消灯
PAL_NOTICE::LED_ON点灯(PWM照度制御)
PAL_NOTICE::LED_BLINK点滅
PAL_NOTICE::LED_NOP変更しない

LED識別子

定義意味
PAL_NOTICE::LED_RLED赤
PAL_NOTICE::LED_GLED緑
PAL_NOTICE::LED_BLED青
PAL_NOTICE::LED_WLED白

レジスタ設定定義

定義意味
PAL_NOTICE::LED_REG_MAX_PWM標準照度のPMWレジスタ設定値(全灯の1/2を標準とする)
PAL_NOTICE::LED_REG_BOOST_PWMブースト時のPWMレジスタ設定値

PCA9632 操作メソッド

マスタースイッチ

void set_led_master_sw_on() { digitalWrite(PIN_SNS_EN, LOW); }
void set_led_master_sw_off() { digitalWrite(PIN_SNS_EN, HIGH); }

NOTICE PAL では、PCA9632の出力後段にFETスイッチを設けています。このスイッチをONにしない限りLEDは点灯しません。

点灯状態変更

void set_led_r_blink()
void set_led_r_on()
void set_led_r_off()
void set_led_g_on()
void set_led_g_blink()
void set_led_g_off()
void set_led_b_on()
void set_led_b_blink()
void set_led_b_off()
void set_led_w_on()
void set_led_w_blink()
void set_led_w_off()

個別のLEDを消灯、点灯、点滅に設定します。

void set_leds(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
void set_leds_off()

set_leds()は全てのLEDの点灯状態を変更します。パラメータは点灯状態PAL_NOTICE::LED_OFF PAL_NOTICE::LED_ON PAL_NOTICE::LED_BLINK PAL_NOTICE::LED_NOP のいずれかを指定します。

点灯照度制御

void set_led_brightness_r_reg(uint8_t duty)
void set_led_brightness_g_reg(uint8_t duty)
void set_led_brightness_b_reg(uint8_t duty)
void set_led_brightness_w_reg(uint8_t duty)
void set_leds_brightness_reg(uint8_t r, uint8_t g, uint8_t b, uint8_t w)

void set_led_brightness_r1000(uint16_t duty, bool boost = false)
void set_led_brightness_g1000(uint16_t duty, bool boost = false)
void set_led_brightness_b1000(uint16_t duty, bool boost = false)
void set_led_brightness_w1000(uint16_t duty, bool boost = false)
void set_leds_brightness1000(
    uint16_t r, uint16_t g, uint16_t b, uint16_t w, bool boost = false)

PWMのデューティ比(LEDの点灯明るさ)を指定します。

set_led_brightness_?_reg()set_leds_brightness_reg()はレジスタ値を直接指定します。0..255を指定し点灯は比duty/256となります。

set_led_brightness_?1000()set_leds_brightness1000()は、デューティ比を0..1000で指定します。0は消灯相当で値が大きくなるほど明るく(点灯区間が長くなる)なります。boostfalseにすると1000を指定したときのレジスタ値が127となります。trueでは255となります。

点滅制御

void set_blink_cycle_ms(uint16_t x)
void set_blink_duty1000(uint16_t x)

PAL_NOTICE::LED_BLINK を指定したLEDは、指定した周期・点灯期間比で点滅します。

  • 個別に点滅パターンを指定することは出来ません。
  • 上記の点灯照度設定で指定したPWMデューティ比で点灯するため、明るさの制御も可能です。

set_blink_cycle_ms()xで指定した期間[ms]を周期として点滅します。

set_blink_duty1000()xを0..1000で指定し周期*x/1000を点灯期間として点滅します。

テスト点灯

void test_led()

ごく短い間4つのLEDを点灯します。点灯後はマスタースイッチがON(set_led_master_sw_on())になります。

9.4 - <CUE>

TWELITE CUE 用
TWELITE CUE上のペリフェラルに対応するボードビヘイビアです。

ボード上の加速度センサー、磁気センサー、LEDを取り扱えるようになっています。

  • 加速度センサー
  • 磁気センサー
  • LED
void setup() {
  auto&& brd = the_twelite.board.use<CUE>();
}

加速度センサー

MC3630センサーのメンバーオブジェクト (sns_MC3630) が定義されています。

磁気センサー

開閉センサーパルのセンサーは磁気センサーで、2本の信号線の割り込みの入力のみです。

const uint8_t CUE::PIN_SNS_NORTH = 16;
const uint8_t CUE::PIN_SNS_OUT1 = 16;
const uint8_t CUE::PIN_SNS_SOUTH = 8;
const uint8_t CUE::PIN_SNS_OUT2 = 8;

CUE::PIN_SNS_NORTH はセンサーがN極を検出したとき、CUE::PIN_SNS_SOUTH はセンサーがN極を検出したときに割り込みが入ります。

スリープ前に以下の設定をしておきます。

pinMode(CUE::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
pinMode(CUE::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

起床時に起床要因のIOを確認します。

uint8_t b_north =
  the_twelite.is_wokeup_by_dio(CUE::PIN_SNS_NORTH);
uint8_t b_south =
  the_twelite.is_wokeup_by_dio(CUE::PIN_SNS_SOUTH);

LED

set_led()

void set_led(uint8_t mode, uint16_t tick)

LED(D1)の制御を行います。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定意味
LED_TIMER::BLINKLEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RXパケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP送信完了時にtickに与える時間[ms]だけ点灯します。

led_one_shot()

void led_one_shot(uint16_t tick)

指定期間だけLEDを点灯します。set_led()の機能と同時には使えません。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

9.5 - <ARIA>

TWELITE ARIA用
TWELITE ARIA上のペリフェラルに対応するボードビヘイビアです。

ボード上の加速度センサー、磁気センサー、LEDを取り扱えるようになっています。

  • 温湿度センサー
  • 磁気センサー
  • LED
void setup() {
  auto&& brd = the_twelite.board.use<ARIA>();
}

温湿度センサー

SHT4xセンサーのメンバーオブジェクト (sns_SHT4x) が定義されています。

磁気センサー

開閉センサーパルのセンサーは磁気センサーで、2本の信号線の割り込みの入力のみです。

const uint8_t CUE::PIN_SNS_NORTH = 16;
const uint8_t CUE::PIN_SNS_OUT1 = 16;
const uint8_t CUE::PIN_SNS_SOUTH = 8;
const uint8_t CUE::PIN_SNS_OUT2 = 8;

ARIA::PIN_SNS_NORTH はセンサーがN極を検出したとき、ARIA::PIN_SNS_SOUTH はセンサーがN極を検出したときに割り込みが入ります。

スリープ前に以下の設定をしておきます。

pinMode(CUE::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
pinMode(CUE::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

起床時に起床要因のIOを確認します。

uint8_t b_north =
  the_twelite.is_wokeup_by_dio(CUE::PIN_SNS_NORTH);
uint8_t b_south =
  the_twelite.is_wokeup_by_dio(CUE::PIN_SNS_SOUTH);

LED

set_led()

void set_led(uint8_t mode, uint16_t tick)

LED(D1)の制御を行います。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定意味
LED_TIMER::BLINKLEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。
LED_TIMER::ON_RXパケットの受信時にtickに与える時間[ms]だけ点灯します。
LED_TIMER::ON_TX_COMP送信完了時にtickに与える時間[ms]だけ点灯します。

led_one_shot()

void led_one_shot(uint16_t tick)

指定期間だけLEDを点灯します。set_led()の機能と同時には使えません。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

10 - センサーデバイス (SNS)

センサーや各種デバイス向けの定型化された手続き
センサーや各種デバイスの手続きを定型化したクラスを用意しています。

センサー取り扱いのための手続き

温度センサーなど、センサー稼働開始→待ち時間→センサー値の読み出しといった手続きが共通のものもあります。

I2Cセンサーの取り扱い前にWire.begin()を実施しておいてください。スリープ復帰後は、Wireの再初期化は自動で行われるため特別な記述は必要ありません(注:ユーザコード上から明示的に Wire.end() を呼び出した場合は、再初期化を wakeup() に記述します)

void setup() {
  auto&& brd = the_twelite.board.use<PAL_AMB>();
  ..
  Wire.begin();
	brd.sns_SHTC3.begin();
	brd.sns_LTR308ALS.begin();
}

読み出し開始後の手続きはセンサーの種類ごとに違いますが例えば<PAL_AMB>のセンサーは2つとも時間経過を管理します。時間経過をセンサーオブジェクトに伝えるには process_ev() メソッドを用います。

void loop() {
	auto&& brd = the_twelite.board.use<PAL_AMB>();

	// mostly process every ms.
	if (TickTimer.available()) {
		//  wait until sensor capture finish
		if (!brd.sns_LTR308ALS.available()) {
			brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
		}

		if (!brd.sns_SHTC3.available()) {
			brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
		}
..

上記の例では1msおきのTickTimerを起点にして時間経過を伝えています。E_EVENT_TICK_TIMERはセンサーオブジェクトに1msの経過を伝えるものです。

スリープ復帰などで十分な時間が経過したときは替わりにE_EVENT_START_UPを渡します。センサーオブジェクトは速やかに読み出し可能として処理されます。

センサー共通メソッド

setup()

void setup(uint32_t arg1 = 0, uint32_t arg2 = 0)

センサーの初期化を行います。

begin(), end()

void begin(uint32_t arg1 = 0, uint32_t arg2 = 0)
void end()

センサーの取得を開始, 終了する。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

(対応しているセンサーのみ)センサーが接続されているときにtrueを返します。

10.1 - SHTC3

温湿度センサー
I2Cバスを利用する温湿度センサーです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .begin(): センサーの動作開始
  3. 時間待ち数ms
  4. .available()trueになる
  5. .get_temp(), .get_humid(): 値の読み出し

動作に必要な手続き

Wireバス

begin()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

メソッド

get_temp(), get_temp_cent()

double get_temp()
int16_t get_temp_cent()

温度を読み出す。get_temp()は℃で、get_temp_cent()は℃の100倍の値を整数値で返します。

エラー時は-32760から-32768の値が返ります。

get_humid(), get_humid_per_dmil()

double get_humid()
int16_t get_humid_per_dmil()

湿度を読み出す。get_humid()は%で、get_humid_per_dmil()は%の100倍の値を整数値で返します。

エラー時は-32760から-32768の値が返ります。

共通メソッド

setup()

void setup()

センサー用のメモリ領域の確保や初期化を行います。

begin(), end()

void begin()
void end()

センサーの取得を開始します。センサーの値を読み出すまで約5ms待ち時間が必要です。

end()には対応しません。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

10.2 - SHT3x

温湿度センサー
I2Cバスを利用する温湿度センサーです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .setup(): センサーの初期化
  3. .begin(): センサーの動作開始
  4. 時間待ち数ms
  5. .available()trueになる
  6. .get_temp(), .get_humid(): 値の読み出し

動作に必要な手続き

Wireバス

setup()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

コード例

##include <TWELITE>
##include <SNS_SHT3X>

SNS_SHT3X sns_sht3x; // オブジェクトの宣言

#include <SNS_SHT3X>SNS_SHT3Xクラスオブジェクトの宣言が必要です。

初期化

void setup() {
    Wire.begin();
    sns_sht3x.setup();
}

センサー値の取得開始

void loop() {

  if(eState == E_STATE::INIT) {
    sns_sht3x.begin();
    eState = E_STATE::CAPTURE;
  }

}

センサー値の取得開始には.begin()を呼び出します。完了まで数msかかります。

※ 上記 loop()内は状態変数eStateにより処理が分岐する設計とします。(参考

センサー値の取得待ち

void loop() {

  if(eState == E_STATE::CAPTURE) {
    if (sns_sht3x.available()) {
      // センサー値読み出し可能
    }
  }

}

センサー値が準備できたかどうかは.available()により判定できます。

センサー値の読み出し

void loop() {

  if(eState == E_STATE::CAPTURE) {
    if (sns_sht3x.available()) {
      Serial << crlf << "SHT3X:"
            << " T=" << sns_sht3x.get_temp() << 'C'
						<< " H=" << sns_sht3x.get_humid() << '%';
    }
  }

}

センサー値が準備出来次第、値を読み出すことが出来ます。

.get_temp(), get_humid()は浮動小数点演算が含まれます。100倍整数値を取得することもできます。

auto temp = div100(sns_sht3x.get_temp_cent());
auto humd = div100(sns_sht3x.get_humid_per_dmil);

Serial << crlf << "SHT3X:"
  << format(" T=%c%d.%02d", temp.neg ? '-' : ' ', temp.quo, temp.rem)
  << format(" T=%c%d.%02d", humd.neg ? '-' : ' ', humd.quo, humd.rem);

ここではdiv100()を用いて100倍値を整数部と小数部に分解しています。

メソッド

get_temp(), get_temp_cent()

double get_temp()
int16_t get_temp_cent()

温度を読み出す。get_temp()は℃で、get_temp_cent()は℃の100倍の値を整数値で返します。

エラー時は-32760~-32768の値が返ります。

get_humid(), get_humid_per_dmil()

double get_humid()
int16_t get_humid_per_dmil()

湿度を読み出す。get_humid()は%で、get_humid_per_dmil()は%の100倍の値を整数値で返します。

エラー時は-32760-32768の値が返ります。

共通メソッド

setup()

void setup(uint32_t arg1 = 0UL)

センサー用のメモリ領域の確保や初期化を行います。

arg1のLSBから8bitには、I2Cアドレスを格納することが出来ます。指定しない場合は0としておきます。

##include <SNS_SHT3X>
SNS_SHT3X sns_sht3x;
bool b_found_sht3x = false;

void setup() {
 	sns_sht3x.setup();
	if (!sns_sht3x.probe()) {
		delayMicroseconds(100); // just in case, wait for devices to listen furthre I2C comm.
		sns_sht3x.setup(0x45); // alternative ID
		if (sns_sht3x.probe()) b_found_sht3x = true;
	} else {
		b_found_sht3x = true;
	}
}

上記の例では、まずデフォルトのI2C IDで初期化を試み、応答が無ければ0x45のアドレスでの初期化を試みています。

begin(), end()

void begin()
void end()

センサーの取得を開始します。センサーの値を読み出すまで数ms必要でavailable()trueになるまで待つ必要があります。

end()には対応しません。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailable()trueになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

sns_stat()

uint32_t sns_stat()

センサーデバイスの諸情報が格納されます。

  • 本デバイスでは格納値は未定義です。

sns_opt()

uint32_t& sns_opt()

setup(uint32_t arg1)で渡した値が格納されています。

  • 下位8bitには指定したI2Cデバイスのアドレスが格納されます。

10.3 - LTR-308ALS

照度センサー
I2Cバスを利用する照度センサーです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .begin(): センサーの動作開始
  3. 時間待ち50ms
  4. .available()trueになる
  5. .get_luminance(): 値の読み出し

動作に必要な手続き

Wireバス

.begin()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

メソッド

get_luminance()

uint32_t get_luminance()

照度[lx]を整数値で返します。

エラーの時は-1が返ります。

共通メソッド

setup()

void setup()

センサー用のメモリ領域の確保や初期化を行います。

begin(), end()

void begin()
void end()

センサーの取得を開始します。センサーの値を読み出すまで約50ms待ち時間が必要です。

end()には対応しません。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

10.4 - MC3630

加速度センサー
SPIバスを用いる加速度センサーです。

動作の流れ

  1. .begin(): センサーの動作開始
  2. PIN_SNS_INT割り込み または available(): FIFOキューが規定数に達する
  3. .get_que(): FIFOキューからのデータを取得する

動作に必要な手続き

SPI バス

特にありません。

スリープ手続き

PIN_SNS_INT 割り込みによる起床を行うため、スリープ前に以下の設定行います。

pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);

スリープ復帰時の手続き

.wakeup()メソッドの呼び出しが必要です。この処理は<PAL_MOT>ボードビヘイビア中で実行されています。

データ構造

各サンプルはaxis_xyzt構造体を要素とするキューsmplqueに格納されます。メンバーx, y, zはそれぞれ X, Y, Z 軸に対応します。

struct axis_xyzt {
  int16_t x;
  int16_t y;
  int16_t z;
  uint16_t t;
};

各軸の値は1Gを1000とした値として格納されます。tはサンプルの番号で0から順番にサンプルごとに割り振られます。

メソッド

read()

uint8_t read()

半導体のFIFOキューからデータを読み出します。読みだしたバイト数が戻りますが.get_que()で参照するキューのサイズに格納されるデータ数を読み出すようにしてください。

get_que()

smplque<axis_xyzt>& get_que()

加速度のサンプルを取得します。キューはaxis_xyztを要素としたsmplqueです。availableになってから速やかにキューを空にする必要があります。

共通メソッド

setup()

void setup()

このセンサーではsetup()を使用しません。

begin(), end()

void begin(uint32_t conf)
void end()

confで指定した設定で初期化します。

conf[0:15](bit0-15) : サンプリングモード、conf[16:23] (bit16-23): 加速度のレンジ、conf[24:31] (bit24-31) : 割り込み発生までのサンプル数を設定します。

conf[0:15] サンプルモード内容
MODE_LP_1HZ_UNOFFICIAL1Hz Low Power (非公式設定)
MODE_LP_2HZ_UNOFFICIAL2Hz Low Power (非公式設定)
MODE_LP_7HZ_UNOFFICIAL7Hz Low Power (非公式設定)
MODE_LP_14HZ14Hz Low Power (デフォルト)
MODE_LP_28HZ28Hz Low Power
MODE_LP_54HZ54Hz Low Power
MODE_LP_105HZ105Hz Low Power
MODE_LP_210HZ210Hz Low Power
MODE_LP_400HZ400Hz Low Power
MODE_ULP_25HZ25Hz Ultra Low Power
MODE_ULP_50HZ50Hz Ultra Low Power
MODE_ULP_100HZ100Hz Ultra Low Power
MODE_ULP_190HZ190Hz Ultra Low Power
MODE_ULP_380HZ380Hz Ultra Low Power
conf[16:23] 加速度レンジ内容
RANGE_PLUS_MINUS_8G±8G (デフォルト)
RANGE_PLUS_MINUS_4G±4G
RANGE_PLUS_MINUS_2G±2G
RANGE_PLUS_MINUS_1G±1G

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

このセンサーではprocess_ev()を使用しません。

available()

bool available()

センサーにデータが読み出され内部のキューにデータが保存されているとtrueを戻します。

probe()

bool probe()

このセンサーではprobe()は使用できません。

wakeup()

void wakeup()

スリープ復帰後のSPIバスの再初期化を行い、加速度データを読み出します。

10.5 - BMx280

環境センサー
I2Cバスを利用する気圧・温度・湿度(湿度はBME280のみ)センサーです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .setup(): センサーの初期化
  3. .begin(): センサーの動作開始
  4. 時間待ち数ms
  5. .available()trueになる
  6. .get_press(), .get_temp(), .get_humid(): 値の読み出し

動作に必要な手続き

Wireバス

setup()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

コード例

##include <TWELITE>
##include <SNS_BME280>

SNS_BME280 sns_bme280; // オブジェクトの宣言

#include <SNS_SHT3X>SNS_SHT3Xクラスオブジェクトの宣言が必要です。

初期化

void setup() {
    Wire.begin();
    sns_bme280.setup();
}

センサー値の取得開始

void loop() {

  if(eState == E_STATE::INIT) {
    sns_bme280.begin();
    eState = E_STATE::CAPTURE;
  }

}

センサー値の取得開始には.begin()を呼び出します。完了まで数msかかります。

※ 上記 loop()内は状態変数eStateにより処理が分岐する設計とします。(参考

センサー値の取得待ち

void loop() {

  if(eState == E_STATE::CAPTURE) {
    if (sns_bme280.available()) {
      // センサー値読み出し可能
    }
  }

}

センサー値が準備できたかどうかは.available()により判定できます。

センサー値の読み出し

void loop() {

  if(eState == E_STATE::CAPTURE) {
    if (sns_bme280.available()) {
      Serial << crlf << "BMx280:"
            << " P=" << int(sns_bme280.get_press()) << "hP";
            << " T=" << sns_bme280.get_temp() << 'C'
						<< " H=" << sns_bme280.get_humid() << '%';
    }
  }

}

センサー値が準備出来次第、値を読み出すことが出来ます。

.get_temp(), get_humid()は浮動小数点演算が含まれます。100倍整数値を取得することもできます。

auto temp = div100(sns_bme280.get_temp_cent());
auto humd = div100(sns_bme280.get_humid_per_dmil);

Serial << crlf << "BMx280:"
  << " P=" << int(sns_bme280.get_press()) << "hP";
  << format(" T=%c%d.%02d", temp.neg ? '-' : ' ', temp.quo, temp.rem)
  << format(" T=%c%d.%02d", humd.neg ? '-' : ' ', humd.quo, humd.rem);

ここではdiv100()を用いて100倍値を整数部と小数部に分解しています。

メソッド

get_press()

int16_t get_press()

気圧を読み出します。単位はヘクトパスカル(hectopascal)で、通常は1000前後の値を示します。

get_temp(), get_temp_cent()

double get_temp()
int16_t get_temp_cent()

温度を読み出す。get_temp()は℃で、get_temp_cent()は℃の100倍の値を整数値で返します。

エラー時は-32760から-32768の値が返ります。

get_humid(), get_humid_per_dmil()

double get_humid()
int16_t get_humid_per_dmil()

湿度を読み出す。get_humid()は%で、get_humid_per_dmil()は%の100倍の値を整数値で返します。

エラー時は-32760から-32768の値が返ります。

共通メソッド

setup()

void setup(uint32_t arg1 = 0UL)

センサー用のメモリ領域の確保や初期化を行います。

arg1のLSBから8bitには、I2Cアドレスを格納することが出来ます。指定しない場合は0としておきます。

##include <SNS_BME280>
SNS_BME280 sns_bme280;
bool b_found_bme280 = false;

void setup() {
  ...
  sns_bme280.setup();
	if (!sns_bme280.probe()) {
			delayMicroseconds(100); // device needs small time for further I2C comm.
			sns_bme280.setup(0x77); // alternative ID
			if (sns_bme280.probe()) b_found_bme280 = true;
	} else {
			b_found_bme280 = true;
	}
	...

上記のコードではまずデフォルトのI2C IDでデバイスが応答するかを試し、応答が無ければ 0x77で試みます。

begin(), end()

void begin()
void end()

センサーの取得を開始します。センサーの値を読み出すまで数ms必要でavailable()trueになるまで待つ必要があります。

end()には対応しません。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailable()trueになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

sns_stat()

uint32_t sns_stat()

センサーデバイスの諸情報が格納されます。

  • 下位8bitにはBME280/BMP280のチップモデルが格納されます。0x60ならBME280, 0x58ならBMP280となります。

sns_opt()

uint32_t& sns_opt()

setup(uint32_t arg1)で渡した値が格納されています。

  • 下位8bitには指定したI2Cデバイスのアドレスが格納されます。

10.6 - PCA9632

LEDドライバ
通知パル (NOTICE PAL) に使用されている LED ドライバです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .setup(): クラスオブジェクトの初期化
  3. .reset(): ドライバの初期化
  4. 各種手続き

PCA9632について

4chのLEDドライバです。

  • 各chは消灯・全点灯・PWM点灯・点滅の4つの状態を指定できる
  • 各chで独立して照度制御(PWM)できる
  • 点滅指定したchはすべて同じ点滅パターンとなる
  • 点滅時はPWMによる各ch個別の照度制御ができる

動作に必要な手続き

Wireバス

setup()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

コード例

##include <TWELITE>
##include <SNS_PCA9632>

SNS_PCA9632 pca;

#include <SNS_PCA9632>SNS_PCA9632クラスオブジェクトの宣言が必要です。

初期化&リセット

void setup() {
    Wire.begin();
    pca.setup();
    pca.reset();
}

点灯

...
   pca.set_led_duty_all(
      127,
      127,
      127,
      127
   );

   pca.set_led_status(
      SNS_PCA9632::LED_PWM,
      SNS_PCA9632::LED_NOP,
      SNS_PCA9632::LED_PWM,
      SNS_PCA9632::LED_NOP);

上記の例ではLED1,3をPWM制御により点灯します。

メソッド

コンストラクタ, setup()

SnsPCA9632(uint8_t i2c_addr = DEFAULT_I2C_ADDRESS)
void setup(uint8_t i2c_addr = DEFAULT_I2C_ADDRESS)

コンストラクタではi2c_addrを指定します。

グローバル宣言でクラスオブジェクトを定義した場合、コンストラクタが呼びだされませんので、setup()を呼び出すようにしてください。

reset()

bool reset()

デバイスを初期化します。 レジスタアドレス 0x0 から順に {0x81, 0x35, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x0B, 0x00} を書き込みます。

set_mode2()

bool set_mode2(uint8_t u8var = 0x35)

MODE2レジスタに値を書き込みます。

set_power_mode()

bool set_power_mode(bool b_pow_on)

b_pow_ontrueにすると通常運用、falseにするとスリープします。

bool set_blink_cycle(uint8_t u8var)
bool set_blink_cycle_ms(uint16_t u16ms)

点滅(グループPWM)周期を決めます。

u8varを指定すると周期は(u8var+1)/24[秒]となります。

u16msは周期を[ms]で指定します。

bool set_blink_duty(uint8_t u8duty);

点滅(グループPWM)のデューティ比を決めます。点灯期間はu8duty/256となり、0は消灯相当、255は全灯相当になります。

set_led_duty()

bool set_led_duty(uint8_t port, uint8_t duty)

明るさ(PMW制御のデューティ比)を指定します。

portは対象のLED(SNS_PCA9632::LED1..4)を指定します。

dutyは0..255を指定し、比率duty/256で点灯します。

set_led_duty_all()

bool set_led_duty_all(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4)

全てのLEDに対して明るさ(PMW制御のデューティ比)を指定します。

p1,p2,p3,p4はLED1..4のデューティで0..255を指定します。比率duty/256で点灯します。

set_led_status()

bool set_led_status(uint8_t u8led1, uint8_t u8led2, uint8_t u8led3, uint8_t u8led4)

全てのLEDの点灯状態を変更します。

u8led1..4は順にLED1..4の状態を指定します。

指定できる状態は、以下の通りです。

内容
SNS_PCA9632::LED_OFF消灯
SNS_PCA9632::LED_ON全灯
SNS_PCA9632::LED_PWM照度制御(PWM)
SNS_PCA9632::LED_BLINK点滅制御(グループPWM)
SNS_PCA9632::LED_NOP状態を変更しない

probe()

bool probe()

I2Cバス上にデバイスが存在すればtrueを返します。

show_registers()

void show_registers()

レジスタ(0x0-0x8)の値を表示します。

10.7 - SHT4x

温湿度センサー
I2Cバスを利用する温湿度センサーです。

処理の流れ

  1. Wire.begin(): バスの初期化
  2. .begin(): センサーの動作開始
  3. 時間待ち数ms
  4. .available()trueになる
  5. .get_temp(), .get_humid(): 値の読み出し

動作に必要な手続き

Wireバス

begin()メソッド呼び出し前にWire.begin()によりWireが動作状態にしておきます。

スリープ復帰時の手続き

スリープ直前もWireバスが動作状態にしておきます(スリープ復帰後自動でWireを回復します)。

メソッド

get_temp(), get_temp_cent()

double get_temp()
int16_t get_temp_cent()

温度を読み出す。get_temp()は℃で、get_temp_cent()は℃の100倍の値を整数値で返します。

エラー時は-32760~-32768の値が返ります。

get_humid(), get_humid_per_dmil()

double get_humid()
int16_t get_humid_per_dmil()

湿度を読み出す。get_humid()は%で、get_humid_per_dmil()は%の100倍の値を整数値で返します。

エラー時は-32760~-32768の値が返ります。

共通メソッド

setup()

void setup()

センサー用のメモリ領域の確保や初期化を行います。

begin(), end()

void begin()
void end()

センサーの取得を開始します。センサーの値を読み出すまで約5ms待ち時間が必要です。

end()には対応しません。

process_ev()

void process_ev(uint32_t arg1, uint32_t arg2 = 0)

待ち時間処理のセンサーの場合はarg1E_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

11 - ネットワークビヘイビア (NWK)

TWELITE NET による無線通信の抽象化レイヤ
ネットワーク ビヘイビアは、IEEE802.15.4のMAC層のパケットの送受信に対して、アドレス定義、配送制御などを行います。
  • <NWK_SIMPLE> - 非常にシンプルな簡易中継ネットワークです。
  • <NWK_LAYERED> - レイヤー補助を用いた簡略化された木構造ネットワーク。(注:MWXは受信のみのパラントノードしかサポートしていません。)

11.1 - シンプル中継ネット <NWK_SIMPLE>

シンプルな中継ネットワーク
シンプルな中継ネットワークを実装したネットワークビヘイビアです。
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0xFE)
        << NWK_SIMPLE::repeat_max(3);

上記はネットワークの利用宣言と設定例です。詳細は後述しますが、ネットワークのアドレスについての考え方など基本的な内容をまず解説します。

このネットワークでの各無線局は8bitの論理IDで識別されます。この値は起動時に各無線局が独自に設定します。論理IDは重複しても構いませんが、重複したことを前提とした通信を行う必要があります。

各無線局のIDを設定します。通常はネットワークには親機の役割の無線局と子機の役割の無線局を配置します。子機のみのネットワークも運用できます。

また子機は中継機の役割にもなります。

無線局の識別ID役割
0x00親局
0x01..0xEF子局
0xFEIDを割り振らない子局

論理IDを宛先として指定できますが、0xFE,0xFFは特別な意味を持ちます。下表に宛先指定についてまとめます。

宛先ID意味
0x00子局から親局を指定する。親局からの指定は無効。
0x01..0xEF特定の子局を指定する。
0xFEすべての子局を指定する同報通信(ブロードキャスト)。
0xFFすべての無線局を指定する同報通信(ブロードキャスト)。

また、無線局を特定するのに32bitで指定するシリアル番号も利用できます。

パケットの配送は、IEEE802.15.4のブロードキャストを用います。ACKを用いないため、配送の成功が送信元では判別できませんが、替わりに要求に合った成功率が得られる適当な再送回数を設定したうえ、到達確認が必要な場合は通常パケットの通信を用います。

大規模で頻繁な通信を行う場合は非効率に見えるかもしれませんが、もっぱらデータ収集のみを行い、比較的中継段数が少ないネットワークの場合などより効率的になる場合もあります。

またネットワークを構築するための通信を必要としないため、障害等例外的な状況においても全く通信が止まってしまうといったことが原理的に少なくなります。親機が受信状態かつ子機からの無線到達範囲にあって、子機がパケットを送信しさえすれば、多くの場合親機は受信できます。ネットワークの構築のために通信が必要なネットワークでは、一旦、設定情報などが失われた後は、再度親機と子機間の通信確立のための通信が完了しなければデータが送れません。ネットワークビヘイビア<NWK_SIMPLE>がその命名にシンプルとしているのは、こういった理由があります。

このシンプルネットワークを動作させるためには、多くの場合複数回届く再送パケット(同一パケット)を無視する必要があります。<NWK_SIMPLE>での同一パケットの識別は送信元のシリアル番号と送信時のパケットの続き番号によって行います(重複チェッカと呼びます)。続き番号は0..63として、順番に割り当てられ、近い時間に届くパケットの続き番号は近い番号であるという仮定を置いています。一定時間以上経過した番号的に遠い(今10番を受信したとしたら40番台のパケットはしばらく前に送信されたと考えられる)続き番号はタイムアウトとして重複対象から除外します。

重複チェッカでの考慮すべきことは以下になります。

  • チェック可能な要素数(数を増やせばメモリ消費とチェックのための処理時間が増える)
  • タイムアウト時間の設定

デフォルトではタイムアウトは1秒で、チェックする無線局の数は16です。つまり中継パケットがまわりまわって1秒以上経過した場合、重複パケットとみなされななくなります。また短期的に16を超える無線局からのパケットが到達した場合、超過した無線局については重複チェックが出来なくなります。

中継段数や段数が少なくとも中継局の数が多い場合、再送を非常に長い間隔で行う場合は、設定を考慮すべき場合もあります。

宣言・登録

ネットワークビヘイビア<NWK_SIMPLE>を利用例を挙げます。

##include <TWELITE>
##include <NWK_SIMPLE>

void setup() {
  ...

  auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
}

2行目で<NWK_SIMPLE>の定義情報をインクルードします。7行目でthe_twelite<NWK_SIMPLE>を登録します。

設定

<NWK_SIMPLE>登録後に設定を行います。

void setup() {
  auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
  nwksmpl << NWK_SIMPLE::logical_id(0xFE);
}

設定は<<演算子で行います。

<<演算子 (設定)

オブジェクトthe_tweliteの初期設定を行うために<<演算子を用います。

以下に挙げる設定用のクラスオブジェクトを入力とし、設定をしなければデフォルト値が適用されます。

NWK_SIMPLE::logical_id(uint8_t id)

パラメータidに指定した論理デバイスIDを設定します。デフォルトは0xFE(ID未設定子機)です。

NWK_SIMPLE::retry_default(uint8_t val)

パラメータvalに指定した回数を送信時の再送回数のデフォルト値とします。

NWK_SIMPLE::repeat_max(uint8_t val)

パラメータvalに指定した回数を最大中継回数とします。デフォルトは2です。

中継をさせたくない場合は0を指定します。

NWK_SIMPLE::dup_check(uint8_t maxnodes, uint16_t timeout_ms, uint8_t tickscale)

重複パケットの検出アルゴリズムのパラメータです。

  • maxnodesは履歴を保持するため無線局(ノード)の数です。ノード数を少なく設定した場合、短期間に設定以上のノードからのパケットが来た場合、重複除外できないノードが出てきます。重複除外できない場合、受信時に複数回データが表示される、必要以上に再中継してしまうといった問題が出ます。デフォルトは16です。1ノード当たり21バイトメモリを消費します。
  • timeout_msは履歴を抹消するまでのタイムアウト時間[ms]です。タイムアウトは続き番号のブロック単位で管理されていて、ブロック単位でタイムアウト処理が行われます。デフォルトは1000[ms]です。
  • tickscaleはタイムアウトを管理するための時間単位で2^tickscale[ms]となります。時間は7bitで管理されるため、127*(2^tickscale) > timeout_msになるように設定します。デフォルトは5(32ms)です。

NWK_SIMPLE::secure_pkt(const uint8_t *pukey, bool b_recv_plain_pkt = false)

暗号化パケットの有効化します。

  • pukey は暗号化の鍵を 16バイト (128bit) で指定します。
  • b_recv_plain_pkttrueを指定すると、同じアプリケーションID、チャネルの平文パケットを受信します。

STG_STD

インタラクティブモードの設定値を反映します。以下の値を反映します。

auto&& set = the_twelite.settings.use<STG_STD>();
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
...
set.reload(); // 設定値をロード
nwk << set;   // インタラクティブモードの設定値を反映
  • logical_id
  • retry_default

メソッド

prepare_tx_packet()

// 型名はpacket_tx_nwk_simple<NWK_SIMPLE>ですがauto&&と記載します。
auto&&  preare_tx_packet()

//例
if (auto&& pkt =
  the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
  ...
  pkt.transmit();
}

送信オブジェクトを取得します。オブジェクトはpacket_txの派生クラスになります。このオブジェクトに送信アドレスやペイロードを格納し.transmit()メソッドで送信を行います。

このオブジェクトにはbool演算子が定義されています。オブジェクト生成時にTWENETが送信要求を受け付けられない場合はfalseを返します。

送信オブジェクト

.prepare_tx_packet()メソッドにて取得した送信オブジェクトのメソッドです。

bool 演算子

operator bool()

// 例
if (auto&& pkt =
  the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

オブジェクト生成時にTWENETが送信要求を受け付けられない場合はfalseを返します。

transmit()

MWX_APIRET transmit()

// 例
uint8_t txid;

if (auto&& pkt =
  the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {

  ...

  MWX_APIRET ret = pkt.transmit();
  if (ret) {
    txid = pkt.get_value();
  }
}

パケットの送信処理を行います。MWX_APIRETtrueの場合に送信要求が成功ですが、この要求時点では送信処理が始まりません。

パケットの送信IDはMWX_APIRET.get_value()で得られる値部に格納されます。the_twelite.tx_status.is_complete()またはtransmit_complete()より送信完了を確認できます。

パケット最大長と構造

パケットの最大長を以下に示します。宛先をLID(論理デバイスID)とした場合は、暗号化無しで91バイトまで含めることが可能です。

ネットワーク層暗号化ペイロードの最大
<NWK_SIMPLE>なし91
<NWK_SIMPLE>あり89

※ 将来のための予備として2バイト分は予備としています。ユーザの判断でこの予備バイトを利用することも可能です。

パケットの構造は以下のようになっています。

|MacH:XX[........................................]MacF:2|
         TWENET:3[.....................]TwenetF:1
                  NWK_SIMPLE:11|PAYLOAD
                                        (:nはバイト数)

1: MacH は IEEE802.15.4 MAC のヘッダ情報
2: TwenetH は TWENET 識別のための情報
3: NWK_SIMPLE は NWK_SIMPLEネットワークの制御情報
  |Type:1|Src LID:1|Src Addr:4|Dest LID:1|Dst Addr:4|Repeat:1|
4: PAYLOAD はペイドード
5: TwenetF は CRC8 のチェックサム(TWENETパケットの弁別を目的とする)
6: MacF は CRC16 のMAC 層のチェックサム

11.2 - <NWK_LAYERED>

レイヤーツリーネット
シンプルな中継ネットワークを実装したネットワークビヘイビアです。

以下のように setup() 中で初期化します。NWK_LAYERED::ROLE_PARENTとして親機としてのロールを割り当てます。

##include <NWK_LAYERED>
void setup() {
  ...
  auto&& nwk_ly = the_twelite.network.use<NWK_LAYERED>();
  nwk_ly << NWK_LAYERED::network_role(NWK_LAYERED::ROLE_PARENT);
            // set a role as parent.
}

パケットの受信が行われたときは、NWK_SIMPLEと同様に on_rx_packet() が呼び出されます。

void on_rx_packet(packet_rx& rx, bool_t &handled) {
  auto type = rx.get_network_type();

  if (type == mwx::NETWORK::LAYERED) {
    ; // レイヤーツリーネットのパケット
    handled = true; // 処理済みにする
  }
}

rxはパケット情報をラップしたクラスです。内部的には _get_network_type()の処理用の内部フラグを設定する以外はパケット情報等の加工は行っていません。

つまりtsRxDataApp* を返すrx.get_psRxDataApp()を参照すれば、TWENET C ライブラリと同様のパケット情報が得られます。packet_rxはこの情報にアクセスするためのいくつかの手続きが定義されていますが、得られる情報に変わりはありません。

NWK_SIMPLEとの併用

NWK_SIMPLEと併用する場合は、the_twelite.networkNWK_LAYEREDを、the_twelite.newwork2NWK_SIMPLEを割り当てます。

##include <NWK_LAYERED>
##include <NWK_SIMPLE>

void setup() {
    ...
	auto&& nwk_ly = the_twelite.network.use<NWK_LAYERED>();
	auto&& nwk_sm = the_twelite.network2.use<NWK_SIMPLE>();
}

void on_rx_packet(packet_rx& rx, bool_t &handled) {
  auto type = rx.get_network_type();

  if (type == mwx::NETWORK::LAYERED) {
      ; // レイヤーツリーネットのパケット
  }
  else if (type == mwx::NETWORK::SIMPLE) {
      ; // NWK_SIMPLE のパケット
  }
  else if (type == mwx::NETWORK::NONE) {
      ; // 通常のアプリ (App_Twelite など)
  }
  else {
      ; // 解釈できなかったもの
  }

  // パケットを処理済みにし、これ以上 MWX ライブラリの介入はしない。
  handled = true;
}

各パケット種別は上記のように .get_network_type() により判別します。

  • mwx::NETWORK::LAYERED : そのままパケット情報を参照します。
  • mwx::NETWORK::SIMPLE : NWK_SIMPLE の処理に倣います。
  • mwx::NETWORK::NONE : ネットワーク処理や重複パケットの処理など一切が行われません。例えば App_Twelite 標準アプリケーションの場合、例えば1送信ごとに再送を含め3パケットずつ送出されます。この際、すべてのパケットの受信が成功した場合on_rx_packt()が3回呼び出されることになります。通常は、3回受信できたからと言って2回目、3回目のデータは必要ありません。こういった重複パケットの処理を追加する必要があります。

実例はAct_SamplesのRcv_Univslを参照してください。TWELITE PAL, Act_samples, App_Twelite といった無線チャネルとアプリケーションIDが同一だが、種別の違うパケットの受信処理しています。さらに App_Twelite のために重複チェッカーの処理も用意しています。

12 - 設定ビヘイビアによる設定インタフェース

インタラクティブモードの抽象化レイヤ
設定ビヘイビアは、インタラクティブモードによる設定CUIを利用するためのビヘイビアです。設定のためのインタフェースはシリアルポートの入出力により行います。TWELITE STAGE APP / TeraTerm / screen などのターミナルソフトウェアを利用した対話型の設定が可能です。

12.1 - <STG_STD>

最小限の設定ビヘイビア
<STG_STD> は、最小限の設定項目を有した設定ビヘイビアです。
               設定画面例
[CONFIG/MY_APP:0/SID=8102ECE3]
a: (0x1234ABCD) Application ID [HEX:32bit]
i: (         1) Device ID [1-100,etc]
c: (        13) Channel [11-26]
o: (0x00000000) Option Bits [HEX:32bit]

 [ESC]:Back [!]:Reset System [M]:Extr Menu

使用法

登録

// use twelite mwx c++ template library
##include <TWELITE>
##include <NWK_SIMPLE>
##include <STG_STD> // interactive mode

上記のように #include <STG_STD> を追加します。

setup() による読み出し

uint32_t APP_ID;
uint8_t CHANNEL;
uint8_t LID;

void setup() {
   ...
   auto&& set = the_twelite.settings.use<STG_STD>();

   // call reload() before reading values.
   set.reload();

   // read value
   APP_ID = set.u32appid();
   CHANNEL = set.u8ch();
   LID = set.u8devid();

   // apply them
	 the_twelite
		<< TWENET::appid(APP_ID)
		<< TWENET::channel(CHANNEL);

   auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
   nwk	<< NWK_SIMPLE::logical_id(LID);
}

多くの場合、設定の読み出しはsetup() 中の早い段階で行います。

上記の例はでは、まずthe_twelite.settings.use<STG_STD>()により設定ビヘイビアを登録します。

つぎにset.reload()を呼び出し、実際にEEPROMからデータを読み出し、これを解釈します。自動で読み出さないことに注意してください。

set.u32appid(), set.u8ch(), set.u8devid()は各々アプリケーションIDの設定値、チャネルの設定値、論理デバイスIDの設定値を取得しています。ここでは変数に各設定値を格納しています。

あとは、設定値を利用してアプリケーションIDやチャネルなどの値を反映します。

設定一覧

以下が設定IDの一覧(enum class E_STGSTD_SETID)定義です。

設定ID内容
APPIDアプリケーションID
LOGICALID論理デバイスID (8bit)
CHANNELチャネル
CHANNELS_3チャネル(3chまで)
POWER_N_RETRY出力とリトライ回数
OPTBITSOption 1
OPT_DWORD2Option 2
OPT_DWORD3Option 3
OPT_DWORD4Option 4
ENC_MODE暗号化モード
ENC_KEY_STRING暗号化キー(文字列入力)

<STG_STD>では、代表的な設定と自由に使える32bit値を4つ定義されています。これらは、ユーザが自由に利用できます。

設定ビヘイビアのカスタマイズ

設定ビヘイビアのカスタマイズは.reload()を行う前に全項目を行っておきます。

アプリケーション名

	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::appname("MY_APP");
	...
	set.reload();

アプリケーション名はインタラクティブモードの先頭行に表示されます。

[CONFIG/MY_APP:0/SID=8102ECE3]

文字列ポインタを指定してください。内部でコピーを作らないようにしているため、ローカル変数を指定できません。

デフォルト値

	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::appid_default(0x13579be);
	set << SETTINGS::ch_default(18);
	set << SETTINGS::lid_default(7);
		...
	set.reload();

アプリケーションID、周波数チャネル、論理デバイスID(LID)については、デフォルト値を変更できます。

複数チャネル設定メニュー

	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::ch_multi();
	...
	set.reload();

SETTINGS::ch_multi() を指定すると、チャネル設定が複数指定になります。複数設定を行う場合、設定値の読み出しは.u32chmask()を用います。

すぐに設定画面を表示する

auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::open_at_start();
	...
	set.reload();

アプリケーションID、チャネル、論理IDについては、デフォルト値を変更できます。

項目名、詳細の記述内容の変更

const TWESTG_tsMsgReplace SET_MSGS[] = {
	{ int(E_STGSTD_SETID::OPTBITS),    "オプション1",
			"オプション1を設定してください" },
	{ int(E_STGSTD_SETID::OPT_DWORD2), "オプション2",
			"オプション2を設定してください\r\nオプション2は云々" },
	{ 0xFF } // terminator
};

setup() {
  auto&& set = the_twelite.settings.use<STG_STD>();
	set.replace_item_name_desc(SET_MSGS);
	...

項目名を別のモノに変更することが出来ます。上記の例ではUTF-8による日本語にしていますが、ターミナルの表示など条件がそろわないと適切には表示されません。

この配列は最後に { 0xFF } で終端します。

1番目のエントリは設定ID、2番目が項目名、3番目が設定入力時に表示される解説になります。\rにより改行できます。

現在設定画面かどうか判定

		auto&& set = the_twelite.settings.use<STG_STD>();
		if (!set.is_screen_opened()) {
		   // 設定画面が出ていないときの処理
		}

設定画面出力中にシリアルへの出力を行うと画面が崩れたりする原因になります。設定画面でないことを確認するには.is_screen_opened()で確認します。

項目の削除

auto&& set = the_twelite.settings.use<STG_STD>();
set.hide_items(E_STGSTD_SETID::OPT_DWORD3, E_STGSTD_SETID::OPT_DWORD4);

...
if(set.is_hidden(E_STGSTD_SETID::OPT_DWORD3) {
  ; // OPT_DWORD3は非表示
}

不要な項目の削除を行います。.hide_itemsは項目IDをパラメータとして(可変引数で複数指定可能)不要な項目を非表示にします。非表示項目かどうかは.is_hidden()により確認できます。

メソッド

reload()

	auto&& set = the_twelite.settings.use<STG_STD>();
	set << SETTINGS::appname(APP_NAME)
		  << SETTINGS::appid_default(DEF_APP_ID)
   		<< SETTINGS::open_at_start();

	set.reload();

設定を読み込みます。すべてのカスタマイズが終わってから実行します。

メソッド (データ読み出し)

データの読み出しは以下のメソッドを呼び出します。

メソッド内容
uint32_t u32appid()アプリケーションID
uint8_t u8devid()論理デバイスID
uint8_t u8ch()設定チャネル (11..26)
uint32_t u32chmask()チャネル設定マスク (ビットマスクで指定、13 なら 1UL « 13 にビットを設定する)
uint8_t u8power()出力設定 (0..3)
uint8_t u8retry()リトライ数
uint32_t u32opt1()オプション1
uint32_t u32opt2()オプション2
uint32_t u32opt3()オプション3
uint32_t u32opt4()オプション4
uint8_t u8encmode()暗号化モード (0: なし 1: 有効 2: 有効、平文パケットも表示する)
const uint8_t * u8enckeystr()暗号化キーの取得

設定の反映

the_twelite<NWK_SIMPLE>オブジェクトに対して、本オブジェクトを用いて直接設定を反映できます。

auto&& set = the_twelite.settings.use<STG_STD>();
...
set.reload(); //ここで実際に設定がEEPROMより読み出される

the_twelite << set; // 設定値の反映 (APPIDなど)

auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

nwk << set; // 設定値の反映 (LIDなど)

反映される設定値は以下となります。.hide_items()により非表示になっている項目は反映しません。

対象項目ID内容
the_tweliteAPPIDTWENET::appid(値)に反映されます。
CHANNELTWENET::channel(値)に反映されます。※ SETTINGS::ch_multi() を指定したときは反映されません
CHANNELS_3TWENET::chmask(値)による設定を行います。※ SETTINGS::ch_multi() を指定したときのみ、チャネルマスクとして反映されます。
POWER_N_RETRYTWENET::tx_power(値)TWENET::mac_retry_count(値)による設定を行います。注: &#x3C;NWK_SIMPLE>での再送設定も同じ値を参照します。
<NWK_SIMPLE>LOGICALIDNWK_SIMPLE::logical_id(LID)による設定を行います。
POWER_N_RETRYNWK_SIMPLE::repeat_max(LID)による設定を行います。

項目ID

.hide_items()などで項目IDを指定する場合があります。この項目IDはenum class E_STGSTD_SETIDで定義されてます。

E_STGSTD_SETID::内容
APPIDアプリケーションID
LOGICALID論理ID(0..100)
CHANNELチャネル
CHANNELS_3チャネル(複数指定)
POWER_N_RETRY出力とリトライ設定
OPTBITSオプションビット
UARTBAUDUARTのボーレート設定
OPT_DWORD2オプションビット2
OPT_DWORD3オプションビット3
OPT_DWORD4オプションビット4
ENC_MODE暗号化モード
ENC_KEY_STRING暗号化キー

Extra Menu

[ROOT MENU/BAT1/SID=810B645E]
0: CONFIG
1: EEPROM UTIL
2: H/W UTIL

 [ESC]:Back [!]:Reset System

Mキーを入力すると追加メニューにアクセスできます。

  • CONFIG : 設定画面に戻ります
  • EEPROM UTIL : EEPROMのメンテナンスを行うためのメニューです
  • H/W UTIL : ハードウェアの状態を調べるためのメニューです

EEPROM UTIL

[EEPROM UTIL/BAT1/SID=810B645E]
r: Read sector.
R: Read ALL sectors.
e: Erase sector.
E: Erase ALL sectors.

 [ESC]:Back [!]:Reset System [M]:Extr Menu

セクターの読み出し、削除を行います。全読み出し、全消去を行うときは大文字で “YES” の3文字を入力します。

H/W UTIL

[H/W UTIL/BAT1/SID=810B645E]

現バージョンでは機能は提供されません。