このページのみ全てのページ
GitBook提供
101ページのPDFを生成できませんでした、100で生成が停止しました。
さらに50ページで拡張
1 / 100

v0.1.5

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

クラス

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

関数

システム関数

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

ユーティリティ関数

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

用語

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

用語の解説は、規格などで定められる定義に沿っていない場合があります。

一般的な用語

SDK (TWELITE SDK, MWSDK)

ソフトウェア開発環境

TWELITE無線マイコンのソフトウェア開発用のSDKをTWELITE SDK (またはMWSDK)と呼称します。

IEEE802.15.4

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

パケット

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

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

ペイロード

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

ノード

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

MWXライブラリ特有の用語

アクト

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

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

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

  • アプリケーションビヘイビア:イベントドリブンでのアプリケーション記述を行い、ユーザが定義するクラス。

  • ボードビヘイビア:TWELITE無線モジュールを実装するボードの機能利用を簡素化するためのクラス。

  • ネットワークビヘイビア:無線ネットワークの手続きを簡素化するためのクラス。

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

クラスオブジェクト

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

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

C++に関する用語

一般の用語です。C言語の知識を前提に解説します。

C++

C++言語のこと。

MWXライブラリはC++とC言語によって記述されています。

C++11

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

クラス

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

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

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++特有の機能性を追加するなどして、利用の便を図ったものです。解説中でも「~構造体をラップした」といった記述をする場合があります。

MWXライブラリは、TWENETの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の配列

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

nullptr

C++11ではNULLポインタをnullptrと記述するようになりました。

参照型

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;
}

MWXライブラリのプログラミングインタフェースは、原則といてポインタ型の利用をせず、参照型を用いています。

型推論

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

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

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

コンテナ

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

MWXライブラリでは、配列クラスsmplbufとFIFOキュークラスsmplqueを用意しています。

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

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

イテレータを取得するため.begin(),.end() のメソッドが用いられます。コンテナの先頭を指すイテレータを.begin()で取得します。末尾の次を指すイテレータを.end()で取得します。末尾ではなく、末尾の次である理由にはforやwhile文のループ記述の明快さ、コンテナに格納される要素数が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です。このアルゴリズムは、イテレータの示す要素同士で<,>,==といった演算子での比較が出来れば最大と最小を計算してくれます。戻り型もイテレータの型から導かれます。

MWXライブラリはC++11で追加された機能や構文を用いて実装されています。はC++11までとなります。

MWXライブラリでは、コンパイラの制限や性能上の理由で仮想関数を使用しません。ポリモーフィズムを実現するのにを用いています。

TWELITE向けのC/C++コンパイラのから、利用できる機能はごく一部です。

ビヘイビア
制約

License

保証・ライセンス

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

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

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

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

# Copyright (C) 2019 Mono Wireless Inc. All Rights Reserved.
# Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
# AGREEMENT)

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&& と記載します。

auto はC言語ではローカル変数(自動変数)を宣言する際のキーワードとなっていますが、ここでは型推論により宣言を行う意味です。C++のテンプレート構文では非常に煩雑な型名になることが多く、同時に型名を明示的に記述しなくても実装できる場面で便利な記法です。

下記の例では、vの型に対する最大値最小値を発見する標準ライブラリのアルゴリズム std::minmax_element を用いた例で、結果の戻り値を autoにより宣言しています。この場合、autoで推論された型はstd::pair<int, int>になります。

#include <algorithm>
int v[] = { 1, 3, -1, 5, 10 };
auto&& result = std::minmax_element(std::begin(v), std::end(v));
Serial << "min=" << int(result.first)
       << ",max=" << int(result.second);

auto &&の&&ついては、厳格な意味合いは専門書籍などを紐解いていただく必要がありますが、ここでは「戻りが参照型(C言語でいうポインタ渡しに近い)であっても値であっても、気にせず宣言できる」とお考え下さい。

名前空間について

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++標準ライブラリの一部のみの利用となります。

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

標準ライブラリについては利用可否、また利用できそうなものについての包括的な検証は行っておりません。動作の不都合が確認できた場合は、別の方法で実装するようにしてください。

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

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

  • {MWSDKインストールディレクトリ}/TWENET/current/src/mwx

本パッケージ内で、ライセンス上特別な記述のないものは、(MW-SLA)を適用します。

モノワイヤレスソフトウェア使用許諾契約書
https://github.com/monowireless/mwx

環境 (OSなど)

開発環境を構築するためには、ソフトウェア群のインストール、またこれらの利用許諾に同意する必要があります。また、PC、ワークステーション上でセキュリティ設定等が必要になる場合があります。

  • 配布時には十分注意しておりますが、ウィルスなどの確認はお客様のほうでも留意いただくようお願いいたします。

  • お客様のセキュリティの考え方や運用(例:外部アプリケーションのインストールの可否)については、お客様の環境の管理者にご確認ください。

また、開発環境をインストールまた動作するにあたり、OSが介在し設定等必要になる場合があります(例:開発元が不明なアプリケーションの実行。開発環境または紹介するツール群の多くは、アプリケーションは開発元を証明する仕組みが組み込まれせん)。設定方法については、一般の情報を参考いただくようお願いいたします。

MWXライブラリを用いてアプリケーションを記述するには以下が必要です。

  • MWSDK(ソフトウェア開発環境)

  • 開発用エディタ(Microsoft社のVisualStudio Codeを紹介します)

コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、サポート中のWindows10バージョンを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。

以下、開発で使用しているバージョンを挙げます。

  • Windows10 #1809, #1903

  • .NET CLR v4.0.30319

  • FTDI社のドライバが動作していること (MONOSTICK, TWELITE Rを動作させるため)

WSL (Windows Subsystem Linux) 環境下でもコンパイラを動作させることが出来ます。ただしコンパイルのみでファームウェアの書き換え等はWindows10上のユーティリティから実施してください。

WSL環境は必須ではありません。

コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。

以下、開発で使用しているバージョンを挙げます。

  • Ubuntu 16.04 LTS 64bit

  • Ubuntu 18.04 LTS 64bit

*32bitのシステムはサポートしません。

コンパイラのツールチェインなどは比較的環境への依存度が低いため、多くの環境で動作することが期待できますが、現在サポート中のディストリビューションを推奨します。動作環境の差異により動作しないような場合は、当社で確認している環境を参考に別途環境を用意してください。

以下、開発で使用しているバージョンを挙げます。

  • macOS 14.06

Visual Studio Code など開発環境について

開発環境を動作させるための環境や使用方法については、その開発元やコミュニティの情報を参照ください。

コード記述作業の効率面で Visual Studio Code (VSCode) の利用を推奨します。

MWXライブラリでは、C言語の開発に比べ、読み込むヘッダファイルが多くなるため、VSCode上でのコード解釈等にはより多くのPCのリソースを要求します。

ビルド環境による差異

Linux/WSL環境下/macOSのビルド結果はWindows10の結果と異なります。通常系の動作で差異が見られることは当社が把握する限りありませんが、特にgccのLTOを無効にしているためバイナリサイズが数%程度大きくなる傾向にあります。

動作等に疑問を感じた際は、必ず Windows10 上のビルドを実施し再現することを確認してから、お問い合わせください。

The MWX Library (v0.1.5)

Mono Wireless C++ Library for TWELITE.

このページには 0.1.5 (MWSDK2020_08_UNOFFICIAL) の情報が含まれます。

MWX ライブラリは、TWELITE 無線モジュールのコード表記を簡素化することを目的としています。MWXで作成されたプログラムをアクト act と呼びます。アクトにはループによる記述と、イベントによる記述(ビヘイビア behavior と呼びます)の二種類があります。

本ページではアクトの主な特徴を紹介します。

  • ループによる記述 (setup(), loop())。小規模な機能を記述するのに向いています。

#include <TWELITE>
const uint8_t PIN_LED = 5;

void setup() {
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
  if (TickTimer.available()) {
    uint32 t_now = millis();
    
    // blink LED every 1024ms
    digitalWrite(PIN_LED, (t_now >> 10) & 1 ? HIGH : LOW);
  }
}	

  • イベントドリブンのアプリケーション記述。各種イベント・割り込みハンドラの定義、アプリケーションの複雑な振る舞いを記述するのに向いたステートマシンをクラス内に定義して見通しの良いコードを記述できます。この記述をビヘイビアと呼びます。

// myApp.hpp
...
class myApp : MWX_APPDEFS_CRTP(myApp) {
...
  void loop() {
    // main loop
  }
  
  void receive(mwx::packet_rx& rx) {
    // on receive
  }
};

// myApp.cpp
...
MWX_DIO_EVENT(12, uint32_t arg) {
		// on event from DIO12
}

  • ペリフェラルの手続きを簡素化。よく使われる UART, I2C, SPI, ADC, DIO, タイマー, パルスカウンタを取り扱うクラスオブジェクトを定義しています。

void loop() {
  while(Serial.available() { 
    auto x = Serial.read(); ... } // serial message
  if (Analogue.available() {
    auto x = Analogue.read(...); } // adc values
  if (Buttons.available() { 
    Buttons.read(...); } // DIO changes
  if (the_twelite.receiver.available()) { 
    auto&& rx = the_twelite.receiver.read(); } // on rx packet
}

  • シンプルな中継ネットワークを定義。この中継ネットワークは TWELITE 標準アプリケーションと同等の実装で、個体アドレスの管理は 8bit の論理IDで行うこと、ネットワーク構築のための通信を行わないため電源投入後すぐにネットワーク宛に無線パケットを送ることができる点が特徴です。

#include <TWELITE>
#include <NWK_SIMPLE>

void setup() {
  ...
  auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0xFE) 
	           // set Logical ID. (0xFE means a child device with no ID)
	        << NWK_SIMPLE::repeat_max(3);
	           // can repeat a packet up to three times.
}

void loop() {
  ...
  vTransmit();
  ...
}

void vTransmit() {
  if (auto&& pkt =
    the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet(); 
  pkt << tx_addr(0x00)  // to parent 
	  	<< tx_retry(0x3); // set retry
	
	pack_bytes(pkt.get_payload() // prepare payload data
	    , uint8_t(0x01)
	    , uint16_t(analogRead(PIN_ANALOGUE::A1))
	    , uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)));
	
	pkt.transmit(); // transmit!
}

TWELITE 標準アプリケーションとのパケットの相互通信はできませんが、以下の点で自由度が上がっています。

  • 論理IDは 0 が親機である点は同様ですが、子機アドレスとして 0x01..0xEF を利用できるため、識別数を 200 以上とすることができます。

  • 原則3回までとしていた中継回数について、最大数を64回までを設定できるようにしています。(※ パケットが遠回りして一定時間経過後に戻ってきた場合、重複パケットの管理テーブルがクリアされ中継済みのパケットであっても、再中継が発生する場合があります。中継回数を大きくする設定は注意してください)

  • PAL や MONOSTICK 向けのボード定義。ボード上のセンサーなどを容易に取り扱えます。

#include <TWELITE>
#include <PAL_AMB> // include the board support of PAL_AMB

void setup() {
	auto&& brd = the_twelite.board.use<PAL_AMB>(); // use PAL AMB
	uint8_t u8dip = brd.get_DIP_SW();   // check DIP s/w status
	brd.set_led(LED_TIMER::BLINK, 100); // LED switchs on/off every 100ms
	...
	
	// start capture of sensors
	brd.sns_SHTC3.begin();
}

void loop() {
	if (TickTime.available()) { // check every ms
		auto&& brd = the_twelite.board.use<PAL_AMB>();

		if (brd.sns_LTR308ALS.available()) {
		  Serial << brd.sns_SHTC3..get_temp();
		} else {
		  // notify sensor that 1ms passed.
			brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
		}
	}
}

をご参照ください。 お気付きの点がありましたら、当サポート窓口にご連絡いただければ幸いです。

資料の取り扱いについて

アクトのビルド

MWXライブラリで記述したアプリケーションプログラムをアクト(Act)と呼びます。まずは、これをビルドして書き込みます。

  • ビルドディレクトリ構成について

  • ビルドスクリプトについて

  • VS Code でのビルドについて

ビルドディレクトリ構成について

MWSDKをインストールしたディレクトリMWSDK_ROOT (例 C:\MWSDK)を開きます。以下のような構成になっています。

MWSDK_ROOT
  |
  +-ChipLib      : 半導体ライブラリ
  +-License      : ソフトウェア使用許諾契約書
  +-MkFiles      : makefile
  +-Tools        : コンパイラ等のツール一式
  +-TWENET       : TWENET/MWXライブラリ
  +-Act_samples  : アクトサンプル
  ...

アクトファイルはAct_samples 以下に格納しています。(以下は一部割愛しています)

Act_samples
  |
  +-CoreAppTwelite    : App_TweLiteと同じ構成のボード用のアクト
  +-PAL_AMB           : 環境センス PAL 用のアクト
  +-PAL_MAG           : 開閉センス PAL 用のアクト
  +-PAL_MOT           : 動作センス PAL 用のアクト
  ..
  +-Parent-MONOSTICK  : 親機アクト、MONOSTICK用
  +-PingPong          : PingPong アクト
  +-PulseCounter      : パルスカウンタを利用したアクト
  +-act0              : スクラッチ(とりあえず書いてみる)用アクト

これらのアクトは、MWXライブラリの記述の参考となるシンプルな例ですが、多くのアクトは以下の機能を有しています。

  • センサー値を取得する

  • センサー値取得後、無線パケットを親機宛に送信する

  • 送信完了後、一定時間スリープする(または割り込みを待つ)

実際に動作させてみるときは、以下の組み合わせを試してみてください。

親

子

解説

親機はM1ピンをLOW(GNDレベル)にして起動する。通常モード(常時稼働)にて、App_TweLiteのような動作を確認できます。

子機同士2台使って動作します。片方から Ping パケットを送ると、相手方から Pong パケットが戻ってきます。

その他

子機用のアクトのパケット送信を確認できます。

では、アクトの中から PingPong のディレクトリの中を見てみましょう。

Act_samples にある他のアクトもビルドできます。その場合、ディレクトリ名・ファイル名は読み替えるようにしてください。

Act_samples
  +-PingPong
    +-PingPong.cpp   : アクトファイル
    +-build          : ビルドディレクトリ
    +-.vscode        : VS Code 用の設定ファイル

必ずディレクトリ直下にディレクトリと同名の .cpp ファイルが必要です。

PingPong ディレクトリ直下にアクトファイル PingPong.cpp があります。ディレクトリ名を変更した場合は、必ず .cpp ファイルの名前もディレクトリ名と同名にします。

次にビルドディレクトリを開きます。

Act_samples
  +-PingPong
    +-build
      +-Makefile        : makefile
      +-build-BLUE.cmd  : TWELITE BLUE 用ビルドスクリプト
      +-build-RED.cmd   : TWELITE RED 用ビルドスクリプト
      +-build-clean.cmd : obj_* ファイル削除

ビルドに必要なスクリプトとMakefileが格納されています。

ビルドスクリプト(Windows10)

ビルドスクリプトは、エラーがあまり出ることのない、完成済みのアクト、サンプル提供のアクトなどに向いています。

以下は Windows10用のスクリプト(バッチファイル)です。

バッチファイル名

内容

build-BLUE.cmd

TWELITE BLUE用

build-RED.cmd

TWELITE RED用

実行後にbinファイルが生成されPingPong_BLUE_???.bin またはPingPong_RED_???.bin のファイル名になります。???はバージョン番号やライブラリのバージョン文字に置き換わります。

BINファイルが出来上がればビルド成功です。

objs_BLUEディレクトリはビルド中に生成された中間ファイルです。削除してもかまいません。

ビルドを迅速にするためパラレルビルドのオプションを設定しています。このためエラーが出た場合はエラーメッセージを視認しづらくなっています。

エラーメッセージを効率的に参照したい場合は VS Code などの開発ツールの利用を推奨します。

クリーン(中間ファイルの削除)

build-clean.cmdを実行すれば、objs_ で始まるディレクトリを消去します。BINファイルは消去しません。

ビルドがうまくいかない場合は、まずエラーメッセージを確認して下さい。errorという文字列が含まれる行中のメッセージから、エラー原因が容易に特定できる場合も少なくありません。

objs_??? ディレクトリにある中間ファイルを削除してから再実行してみてください。(他の環境でビルドした中間ファイルが残っているとmake cleanを含めすべての操作が失敗します)

コマンドライン(Linux/macOS/WSK)でのビルド

コマンドライン環境でのビルドについて補足します。

Windows10 では特別な理由がない限りWSL (Windows Subsystem for Linux)は、MWSDKでアクトをビルドするのに必須ではありません。WSLについて、既に知見があり特に利用したい方のみ本節をご覧ください。ここではWSLのインストール方法や使用方法については紹介しません。

WSL のディストリビューション Ubuntu 18.04 を前提として記述します。

WSL で動作するプログラム書き込みツールは用意していません。Windows10上のTWE-Programmerを利用ください。

コマンドライン(bash)についての利用の知識が必要です。

OS環境によっては各実行プログラムの動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)

コマンドラインでのビルドは、bash(Bourne-again shell)が動作するウインドウでmakeを実行します。事前に環境変数MWSDK_ROOTが正しく設定されていることを確認してください。例えばC:/MWSDKにインストールした場合は ~/.profile に以下のような設定を行います。

MWSDK_ROOT=/mnt/c/MWSDK
export MWSDK_ROOT

コマンドライン(bash)よりmakeを実行します。makeがない場合はパッケージをインストールする必要があります。

$ make

Command 'make' not found, but can be installed with:

sudo apt install make
sudo apt install make-guile

$ sudo apt install make
...
  • Linux/WSL環境ではmakeまたはbuild-essentialパッケージをインストールします。

  • macOS環境ではXcodeでCommand Line Toolsをインストールします。

ビルドは以下のようになります。

$ cd $MWSDK_ROOT
$ cd Act_samples/PingPong/build
$ pwd
/mnt/c/MWSDK/Act_samples/PingPong/build

$ ls
... ファイル一覧の表示

$ rm -rfv objs_*
... 念のため中間ファイルを削除

$ make TWELITE=BLUE
... BLUE用に通常ビルド

$ make -j8 TWELITE=BLUE
... BLUE用にパラレルビルド(同時に8プロセス)

中間ファイルについて

ビルドが行われると objs_??? ディレクトリが作成され、その中に中間ファイルが生成されます。このファイルはコンパイルした環境に依存しているため、他の環境のファイルが残っているとmakeがエラーとなりビルドが失敗します。

makeがエラーとなった場合は直接objs_???ディレクトリを削除してください。

コマンド例

コマンド例

解説

make TWELITE=BLUE

TWELITE BLUE用にビルド

make TWELITE=RED

TWELITE RED用にビルド

make cleanall

中間ファイルの削除

VS Code でのビルドについて

OS環境によっては各実行プログラムの動作時にセキュリティ警告が出る場合があります。警告を抑制する設定が必要になります。(警告を抑制してプログラムを動作する運用を行うかは、お客自身またはシステム管理者に相談の上、ご判断ください)

VS Code では、Act_samplesディレクトリ直下にあるワークスペースファイルを開くか、Act_samples以下のアクトディレクトリを開きます。

以下の例では英語インタフェースの画面例で、ワークスペースを開いています。

ワークスペースを開き [Terminal>Run Task...] を選択します。

ビルドするTWELITE無線モジュールの種別(BLUE/RED)とアクト名を選択します。以下の例ではBuild for TWELITE BLUE PingPong (TWELITE BLUE用/PingPongアクト) を選択しています。選択後すぐにビルドが始まります。

ビルド中の経過は画面下部の TERMINAL 部分に出力されます。

正しくビルドできた場合、上記画面例の反転表示部のように .elf ファイルが生成されるメッセージがサイズ情報(text data bss dec hex filenameと記載がある部分)とともに出力されます。

またBINファイル(上記では PingPong_BLUE_???.bin)ファイルがbuildディレクトリ下に出来上がっているはずです。確認してみてください。

ビルド定義には Windows10 のファイルシステムに適合しないディレクトリ名 (例:/c/User/...)を変換する(例:C:/User/...) 定義を追加しています。

変換は完全ではありませんが、コンパイルメッセージからエラーが発生しているファイル名と行番号を抽出できます。

.vscode/tasks.json 中の実行コマンドは sh -c "make ... | sed -E -e s#..." のようにコマンド呼び出しすることで、出力メッセージ中のドライブ名相当部の文字列を書き換えています。

...
"windows": {
    "command": "sh",
    "args": [
        "-c", "make TWELITE=BLUE 2>&1 | sed -E -e s#\\(/mnt\\)?/\\([a-zA-Z]\\)/#\\\\\\2:/#g"
    ],

ビルドがうまくいかない場合は、まずエラーメッセージを確認して下さい。errorという文字列が含まれる行中のメッセージから、エラー原因が容易に特定できる場合も少なくありません。

念のため、クリーン(objs_??? ディレクトリにある中間ファイルの削除)を行い、ビルドを再実行してみてください。(他の環境でビルドした中間ファイルが残っているとmake cleanを含めすべての操作が失敗します)

WSLでのビルド

事前にコマンドライン(bash)でビルドが出来ることを確認しておいてください。

WSL環境でビルドしたい場合は、.vscode/tasks.jsonを編集してください。以下の例の4行目 "windows": 始まる行を追加(またはコメントアウト)します。

// .vscode/tasks.json
{
    "version": "2.0.0",
    "windows": { "options": { "shell": { "executable": "bash.exe", "args": ["-c", "-l"] } } },
    "tasks": [
    ...

本ページでは、いくつかのビルド方法を記載していますが、いずれの方法も最終的にはmakeコマンドを実行しています。詳細はを参照ください。

Parent-MONOSTICKのアクトによりパケットの受信と表示を行っています。この親機用のアクトは、アスキー形式で出力しています。 (:00112233AABBCC...FF[CR][LF] のような : で始まり、途中は16進数のバイトをアスキー文字2字で表現する形式です。末尾の??は同様に2字のバイトとなりますがLRCというチェックサムバイトになります。参考:)

小規模なアクトならこの.cppファイル内に記述します。規模が大きくなってきたときはを参考にして複数のファイルに分割してビルドすることが出来ます。

macOS, Linux ではまたはによるビルドを行います。

詳細はをご覧ください。

Makefileの解説
アスキー形式
Makefileの解説
Makefileの解説
VS Code
コマンドライン
BRD_APPTWELITE
BRD_APPTWELITE
PingPong
PingPong
Parent-MONOSTICK

インストール・ビルド

Install and Build

MWXライブラリを用いてアプリケーションを記述(本書ではアクトと呼びます)し、実行するために開発環境のセットアップが必要です。

サンプルアクト

Sample Acts

アクトの動作を理解するため、いくつかのサンプルを用意しています。

サンプルは MWSDK をインストールしたディレクトリにある Act_samples にあります。

最新版の入手

最新版のコードや MWSDK バージョン間の修正履歴を確認する目的で Github にソース一式を置いています。以下のリンク先を参照してください。

共通の記述

アクトのサンプル中で以下の項目は共通の設定項目になり、以下で解説します。

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const char APP_FOURCHAR[] = "BAT1";

サンプルアクト共通として以下の設定をしています。

  • アプリケーションID 0x1234abcd

  • チャネル 13

アプリケーションIDとチャネルはともに他のネットワークと混在しないようにする仕組みです。

アプリケーションIDが異なる者同士は、チャネルが同じであっても混信することはありません。ただし、別のアプリケーションIDのシステムが頻繁に無線送信しているような場合はその無線送信が妨害となりますので影響は出ます。

チャネルは通信に使う周波数を決めます。TWELITE無線モジュールでは原則として16個のチャネルが利用でき、通常のシステムでは実施しないような極めて例外的な場合を除き、違うチャネルとは通信できません。

サンプルアクト共通の仕様として、パケットのペイロード(データ部)の先頭には4バイトの文字列(APP_FOURCHAR[])を格納しています。種別の識別性には1バイトで十分ですが、解説のための記述です。こういったシステム特有の識別子やデータ構造を含めるのも混信対策の一つです。

最初にとの解説に目を通すようにしてください。

BRD_APPTWELITE
Parent_MONOSTICK
https://github.com/monowireless/Act_samples

ビルド定義 Makefile

Makefileはbuild/Makefileに格納されています。makeコマンドを実行することで、アクトをビルドするよう予め定義されています。

MWSDK 2020-04 では、プロジェクトディレクトリ中の .cpp ファイルを自動で検出するため、通常は Makefile の修正は不要です。

ソースファイルをサブディレクトリに格納するような場合は、編集が必要になります。

MWSDK 2019-12では、.cpp ファイルが複数ある場合は、Makefile の編集が必要です。

プロジェクトディレクトリを他の環境からコピーしたあとには、必ずbuild/objs_???ディレクトリを削除してください。他の環境での中間ファイルが残っているとmakeがエラーになります。

(MWSDK 2020-04) USE_APPDEPS=0 を付加して clean してから、改めて make コマンドを実行することでエラーを回避できます。

$ make USE_APPDEPS=0 TWELITE=BLUE clean ... $ make TWELITE=BLUE

makeのパラメータ

TWELITE=

ビルド対象をBLUEまたはREDで指定します。TWELITE BLUEならmake TWELITE=BLUEと指定します。

all

ビルドを実行します。通常は省略してmake TWELITE=BLUEのように実行します。

clean

 ビルドの中間ファイルを削除します。make TWELITE=BLUE cleanのように実行します。

cleanall

すべての中間ファイルを削除します。make cleanallのように実行します。buildディレクトリのobjs_???ディレクトリをすべて削除するのと同じです。

USE_APPDEPS=0 または 1

1 (デフォルト) を設定すると、ファイルの依存関係をもとに、ビルドファイルを決定します。例えば、ヘッダファイルに変更があった場合に関連するソースファイルが再コンパイル対象となります。

0 では依存関係を評価しません。0 に設定した場合、矛盾ある中間ファイルが残っていても makefile がエラーになりません。

Makefile 定義

アクトの規模に応じて、また、ビヘイビアの定義をする場合には、通常はソースファイルを分割してビルドします。

ビルドファイルの一つは{プロジェクトフォルダ名.cpp}です。

他にファイルを定義する場合は、プロジェクトフォルダのbuild/Makefileを編集します。

##############################################################################
# Copyright (C) 2019 Mono Wireless Inc. All Rights Reserved.
# Released under MW-SLA-*J,*E (MONO WIRELESS SOFTWARE LICENSE
# AGREEMENT). 
##############################################################################
# USER PROJECT BUILD DEFINITION.
##############################################################################

#####################################################################
### set TWELITE model
TWELITE ?= BLUE
#TWELITE = RED

#####################################################################
### set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB  = 1
VERSION_VAR  = 0

#####################################################################
### set an additional source file
###   the default file name is dirname.

### for C++ files compiled with g++ (must have .cpp suffix)
APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cpp

### for C files compiled with gcc (must have .c suffix)
#APPSRC += my_c_file.c

### Additional Src/Include Path
# if set, find source files from given dirs.
#
APP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Child
#APP_COMMON_SRC_DIR_ADD3 = 
#APP_COMMON_SRC_DIR_ADD4 =

#####################################################################
### set misc option for compiler

### C++ flags passed to g++
# e.g. CXXFLAGS += -DMY_DEFS
#CXXFLAGS +=

### C++/C flags passed to g++/gcc
# e.g. CFLAGS += -DMY_DEFS
#CFLAGS +=

### include opts
# e.g. INCFLAGS += -I../my_common_src/
#INCFLAGS +=

### optimize flag (default is -Os, normally no need to change)
#OPTFLAG=-O2

#####################################################################
### must include mwx.mk (the makefile body part.)
MWSDK_PATH?=$(realpath $(MWSDK_ROOT))
include $(MWSDK_PATH)/MkFiles/mwx.mk
#####################################################################

VERSION_???

### set application version (MUST SET THIS.)
VERSION_MAIN = 0
VERSION_SUB  = 1
VERSION_VAR  = 0

バージョン番号を指定します。ビルド結果ファイル名に反映されます。

コンパイル中は -DVERSION_MAIN=0 -DVERSION_SUB=1 -DVERSION_VAR=0のように定義として渡されます。

ソースファイルの追加

(MWSDK 2020-04) サブディレクトリにファイルを配置しない場合は、追加指定は不要になりました。プロジェクトファイルにある .c .cpp ファイルがすべて追加されます。

ソースファイルを追加する際に必要なのはAPPSRC_CXXとAPP_COMMON_SRC_DIR_ADD?です。

サブディレクトリにソースファイルを配置する場合は必ずディレクトリ APP_COMMON_SRC_DIR_ADD? の指定が必要です。

ソースファイル名をAPPSRC_CXXに追記します。このファイル名にはディレクトリ名が含まれてはいけません。サブディレクトリにあるものもディレクトリなしで指定します(つまり同じファイル名がサブディレクトリにある場合は、ビルドが失敗します)

APPSRC_CXX += myAppBhvParent.cpp
APPSRC_CXX += myAppBhvParent-handlers.cpp
APPSRC_CXX += myAppBhvChild.cpp
APPSRC_CXX += myAppBhvChild-handlers.cpp

次にソースファイルがプロジェクトディレクトリ以外の場所に格納されている場合の検索パスを指定します。最大4つまで設定できます。

APP_COMMON_SRC_DIR_ADD1 = ../Parent
APP_COMMON_SRC_DIR_ADD2 = ../Child

ディレクトリの指定はMakefileからの相対パスになります。

コンパイル・リンカオプション

その他にもいくつかのオプションをコンパイラ・リンカに渡すことができます。

指定

内容

CXXFLAGS

C++ソースファイルに対してコンパイルオプションを指定します。

CFLAGS

C/C++ソースファイルに対してコンパイルオプションを指定します。

INCFLAGS

ヘッダファイルのインクルードファイル指定をします。

OPTFLAGS

特別な理由があって-Os以外のコンパイルオプションを適用したい場合に定義します。

LDFLAGS

リンカオプションを指定します。(上記Makefileのコメントには記述はありませんが指定は可能です)

VS Codeのインストール

MWSDKでは、アクト(ソースコード)記述をより容易に行うため、VisualStudio Code(VS Code)を紹介しています。添付のアクトには、VS Codeで適切にコード解釈が行われるように設定したファイルが含まれます。

VS Codeはソースファイルやヘッダファイルを読み込み、ソースコードを解釈し、これによりソースコードの記述の助けとなる関数定義情報や、関数・メソッド名の補完などを行います。C言語の開発に比べて、MWXライブラリでは読み込まれるヘッダファイルの分量が多くなります。環境によってはエディタの動作が重く感じる場合があります。

VSCode のインストール

当サポートでは VSCode のインストール方法、使用方法のお問い合わせについては対応いたしません。一般で得られる情報を参照ください。

環境によっては、インストールのためにセキュリティ設定などが必要になる場合があります。インストールの可否はシステム管理者にご確認の上、手順は配布元や一般の情報を参考にしてください。

VSCode では、以下のことができます。

  • ソースコードの編集

  • GIT への接続(お客様が独自にソース管理を GIT 上で行う場合)

  • ソースコード解釈に基づく intellisense(*全ての定義が正確に解釈されることを保証するわけではありません)

VSCode はリンク先より入手してください。

プラグインの導入

Visual Studio Code が C/C++ 言語の記述を解釈できるようにするために、プラグインをインストールします。

  • C/C++ for Visual Studio Code

注記事項

MWXライブラリのサンプルには .vscode の定義を含めています。この定義は MWSDK_ROOT 環境変数を用い、ライブラリのソースコード({MWSDK_ROOT}/TWENET/current以下)を特定しています。

VS Code のソースコードの解釈はコンパイラでの解釈とは完全には一致しません。またソースコードの編集状況によっては解釈がより不完全な状態になる場合もあります。

上記はでのMakefileの例です。

サンプルPAL_AMB-bihavior

tweterm.py

  • TWELITE 用ファームウェアの書き込み (TWELITE R/MONOSTICK)

  • シリアルポートでの動作振る舞いの確認

本スクリプトの OS X での実行には Python3 インタプリタが必要です。コマンドライン環境ならびに Python インタプリタの取り扱いに慣れた方を対象とします。

本スクリプトを Linux で動作させるには同等のパッケージ (libusb-dev, pyserial, pyftdi) を用意します。コマンドライン環境ならびに Python インタプリタの取り扱いに慣れた方を対象とします。

  • 参考環境:Ubuntu 18.04 (x86-64 64bit), Python 3.6.5

動作環境・必要パッケージ

  • Mac OS X または Linux

  • python3.5 以降

  • libusb

  • pyserial

  • pyftdi

開発環境

以下の環境で開発、動作確認を実施しました。ただし、これら環境で動作を保証するものではありません。

環境

Mac OS X 10.11.6, Python3.5.1 (2018/05)

Ubuntu 18.04, Python3.6.7 (2018/05)

Mac OS X 10.14.2, Python3.7.2 (2019/01)

インストール

パッケージのインストール

以下の手順はパッケージ管理システムのバージョンアップや仕様変更で、そのまま適用できない場合があります。一般の情報などを参考にしてください。

以下では、Homebrew を用いた新しく全パッケージをインストールする例をご紹介します。

Homebrew

Homebrew をインストールします。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Python3

$ brew install python3

libusb

$ brew install libusb

pyserial

$ pip3 install pyserial

pyftdi

$ pip3 install pyftdi

お使いのディストリビューションのパッケージ導入法を調べてください。以下のパッケージが必要です。

  • python3.5 以降 (多くの場合導入されています)

  • libusb-dev

  • pyserial

  • pyftdi

パッケージ導入コマンド例。

$ sudo apt-get install libusb-dev
$ sudo apt-get install python3-pip
$ pip3 install pyserial
$ pip3 install pyftdi

実行

TWELITE SDK をインストールしたディレクトリを ${TWELITESDK} と記載します。

スクリプトは以下になります。

${TWELITESDK}/Tools/tweterm/tweterm.py

スクリプトに実行許可を与えます。

$ chmod +x ${TWELITESDK}/Tools/tweterm/tweterm.py

必要に応じて環境変数 PATH に追加しておきます。

$ PATH=${TWELITESDK}/Tools/tweterm:$PATH

利用方法

USBドライバのアンロード

libusb と OS のドライバが競合するため、ドライバをアンロード(無効に)しておきます。

FTDI 関連のドライバをアンロードします。

$ sudo kextunload -b com.apple.driver.AppleUSBFTDI

ドライバのアンロードは不要です。

エラーが出る場合は、ドライバをアンロードを試してみてください。

$ sudo rmmod ftdi_sio
$ sudo rmmod usbserial

コマンドパラメータ

パラメータ

解説

-p ftdi:///? または -p

デバイス一覧を表示します。

-p [デバイス名]

デバイスを指定します。

ftdi:///1 他にデバイスがない時。

ftdi://::MW19ZZUB/1 シリアル番号による指定。`

-b [ボーレート]

ボーレートを指定します。

-b 115200 115200bps を指定。

-F [ファームウェア]

ファームウェアを書き込みます。

-F App_Twelite.bin ファイル名が App_Twelite.binを書き込みます。

--no-color

文字のカラー出力を抑制します。

--no-term

ファームウェアの書き込みのみを実施しターミナルを開きません。

キーボード操作

Ctrl+C キーを入力することで制御プロンプトを表示し、いくつかの特別な操作が可能です。それ以外の場合は、直接 TWELITE 無線モジュールに入力文字列が送付されます。

入力キー

解説

Ctrl+C Ctrl+C

ターミナルを終了します。

Ctrl+C Ctrl+R または Ctrl+C r

TWELITE 無線マイコンをリセットします。

Ctrl+C Ctrl+I または Ctrl+C i

インタラクティブモードへ遷移する + + + コマンドを入力します。インタラクティブモード対応ファームウェアのみ有効です。

Ctrl+C A

書式の解釈を開始します。TWELITE無線マイコンからの出力とキー入力に対して、アスキー形式の解釈を行います。

Ctrl+C B

書式の解釈を開始します。TWELITE無線マイコンからの出力に対してバイナリ形式の解釈を行います。キー入力はアスキー形式で解釈します。

Ctrl+C N

書式の解釈を停止します。

書式解釈中は TWELITE からの電文は解釈できた電文のみ表示し、キーボードの入力はエコーバックされますが、アスキー形式の電文が完成した時に TWELITE に送付されます。

[実行例]

実行例中では、適宜改行を挟んでいます。

エラーが発生する場合は、シリアルポートの権限の問題かもしれません。root権限(sudo等)で実行します。

<最初にデバイスがリストされるかを確認します>

$ tweterm.py -p ftdi:///?
Available interfaces:
  ftdi://ftdi:232:MW19ZZUB/1   (MONOSTICK)
  Please specify the USB device

<ファームウェアを書き込んでターミナルを起動します>

以下の例では App_UART (UART 通信アプリ) を書き込み、起動メッセージを確認しています。

$ tweterm.py -p ftdi://ftdi:232:MW19ZZUB/1 -b 115200 -F ../App_Uart_Master_RED_L1101_V1-2-15.bin 
*** TWE Wrting firmware ... ../App_Uart_Master_RED_L1101_V1-2-15.bin
MODEL: TWEModel.TWELite
SER: 102eebd

 file info: 0f 03 000b
erasing sect #0..#1..#2..
0%..10%..20%..30%..40%..50%..60%..70%..80%..90%..done - 10.24 kb/s
Entering minicom mode

!INF TWE UART APP V1-02-15, SID=0x8102EEBD, LID=0x78
8102EEBD:0> 

<Ctrl+Cを入力すると制御プロンプトが表示されます>

*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>

<続けて i を入力します。インタラクティブモードに入ります>

*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[+ + +]
--- CONFIG/TWE UART APP V1-02-15/SID=0x8102eebd/LID=0x00 -E ---
 a: set Application ID (0x67720103) 
 i: set Device ID (121=0x79) 
... <省略> 

通常通り + + + と3回入力しても同じ結果になります。

<書式の解釈の例:App_UART をバイナリ形式に設定しておきます>

インタラクティブモードで m B Enter S と順に入力します。

--- CONFIG/TWE UART APP V1-02-15/SID=0x8102eebd/LID=0x00 -E ---
 a: set Application ID (0x67720103) 
 i: set Device ID (121=0x79) 
 c: set Channels (18) 
 x: set RF Conf (3) 
 r: set Role (0x0) 
 l: set Layer (0x1) 
 b: set UART baud (38400) 
 B: set UART option (8N1) 
 m: set UART mode (B)*
 h: set handle name [sadkasldja] 
 C: set crypt mode (0) 
 o: set option bits (0x00000000) 
---
 S: save Configuration
 R: reset to Defaults
 
!INF Write config Success
!INF RESET SYSTEM...

<書式の解釈の例>

以下の例ではApp_UARTがバイナリ形式で入出力を行ないます。Ctrl+C B を入力します。

*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>
[FMT: console ASCII, serial BINARY]

この状態では入出力は書式形式となります。キーボードの入力はアスキー形式、 TWELITE からの電文はバイナリ形式として解釈します。

TWELITE からの電文を受け取る例を示します。一番簡単な方法は TWELITE をリセットします。Ctrl+C r を入力します。

*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[RESET TWE]
[dbf1677201030001020f008102eebd0000]

出力された [dbf...] がTWELITEからの電文で実際は0xdb 0xf1 0x67... と続くバイナリ列になります。

反対に TWELITE に電文を送る場合はアスキー書式で入力します。:7800112233AABBCCDDX と入力します。 ここでは ペイロードが 0x7800112233AABBCCDD のデータをバイナリ形式で TWELITE に送付しています。直後に応答として [dba18001] が戻ってきています。

:7800112233AABBCCDDX[dba18001]

<終了します>

Ctrl+C Ctrl+C を入力します。

*** r:reset i:+++ A:ASCFMT B:BINFMT x:exit>[
[EXIT]
Bye

TWELITE SDK のインストール

MWSDK 2020-04 以降は TWELITE STAGE アーカイブに含まれます。

TWELITE STAGEアプリからビルドする場合は、MWSDK_ROOT 環境変数の設定が不要になります。

MWSDK 2019-12 と同じの方法でビルドする場合は、TWELITE STAGE アーカイブ中の MWSDK ディレクトリの構成は大きく変わっておりませんので、読み替えて利用ください。

TWELITE STAGE については以下をご覧ください。

TWELITE SDK (MWSDK) をインストールします。本書ではMWSDKと記載します。

TWELITE SDKはTWENET MWX C++ ライブラリ対応版をダウンロードしてください。

1. MWSDK 圧縮アーカイブファイルのダウンロード

2. 圧縮アーカイブの展開

圧縮アーカイブファイルを展開してください。

展開先のディレクトリ名にはスペース、日本語名が含まれてはいけません。

3. 環境変数の設定

MWSDK_ROOT, MWSDK_ROOT_WINNAME(Windows10のみ) の設定が必要です。

ここでは展開後のディレクトリ名を C:\MWSDK とします。別のディレクトリにインストールした場合は、読み替えてください。

C:\MWSDK\SET_ENV.CMD を実行してください。以下の環境変数を設定します。

  • MWSDK_ROOT

  • MWSDK_ROOT_WINNAME

例えば以下のような設定になります。

インストールしたPC上からMWSDKをアンインストールするには以下を行ってください。

  • UNSET_ENV.cmdを実行してください。環境変数の設定を解除します。

  • MWSDKディレクトリを削除してください。

開発環境やシェルに MWX_ROOT環境変数を反映されるように設定してください。

方法はいくつかありますが、ホームディレクトリの.profile(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。

MWSDK_ROOT=/foo/bar/MWSDK/ export MWSDK_ROOT

エディタを使用せずに追加するには以下のようにコマンド入力します。$はプロンプトで環境によって表示が違います。/foo/bar/MSWSDKの部分はインストールしたディレクトリに応じて書き換えてください。

開発環境やシェルに MWX_ROOT環境変数を反映されるように設定してください。

方法はいくつかありますが、ホームディレクトリの.profile(ファイルがなければ新しく作成してください)に以下の設定を追加します。この設定でVSCodeのビルドまで可能です。

MWSDK_ROOT=/foo/bar/MWSDK/ export MWSDK_ROOT

エディタを使用せずに追加するには以下のようにコマンド入力します。$はプロンプトで環境によって表示が違います。/foo/bar/MSWSDKの部分はインストールしたディレクトリに応じて書き換えてください。

環境全体にMWSDK_ROOTを適用にするにはLaunchDを用います。

VS Codeの一部の設定で環境変数を参照していますが、ビルドには必須ではありません。

4. ライブラリの修正の適用

PingPong アクトのディレクトリを開く
build-BLUE.cmd をダブルクリック(実行)
ビルド結果
BINファイル(PingPong_BLUE_L1203_V0-1-0.bin) が出来ている
ビルドタスクの一覧表示
ビルドタスクの選択
ビルド経過

本プログラムは pyftdi () ライブラリサンプルスクリプト pyterm.py に TWELITE 用のファームウェア書き込みスクリプトを組み込んだものです。以下の機能があります。

MWX 対応 SDK の圧縮アーカイブファイルをしてください。

SDKに収録された時点から、ライブラリソースコードに修正がある場合があります。を参照の上、必要に応じでライブラリソースコードを差し替えてください。

https://github.com/eblot/pyftdi
MWSDK_ROOT=C:/MWSDK/
MW_ROOT_WINNAME=C:\MWSDK\
$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profile
$ cd $HOME
$ echo MWSDK_ROOT=/foo/bar/MWSDK>>.profile
$ echo export MWSDK_ROOT>>.profile
インストール・使用方法
インストール補足情報
ダウンロード
改版履歴

PAL_AMB-behavior

Parent_MONOSTICK

親機アプリケーション(MONOSTICK用)

MONOSTICKを親機として使用するアクトです。子機からのパケットのデータペイロードをシリアルポートに出力します。

アクトの機能

  • サンプルアクトの子機からのパケットを受信して、アスキー形式で出力する。

アクトの使い方

必要なTWELITEと配線

役割

例

親機

子機

アクトの解説

インクルード

// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <MONOSTICK>

setup()

void setup() {
	/*** SETUP section */
	auto&& brd = the_twelite.board.use<MONOSTICK>();
	brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
	brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0x00); // set Logical ID. (0x00 means parent device)

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- MONOSTICK_Parent act ---" << mwx::crlf;
}

ボードサポートの使用とLEDの動作を設定しています。

auto&& brd = the_twelite.board.use<MONOSTICK>();
brd.set_led_red(LED_TIMER::ON_RX, 200); // RED (on receiving)
brd.set_led_yellow(LED_TIMER::BLINK, 500); // YELLOW (blinking)

2行目では赤色のLEDを無線パケットを受信したら200ms点灯する設定をしています。最初のパラメータはLED_TIMER::ON_RXが無線パケット受信時を意味します。2番目は点灯時間をmsで指定します。

3行目はLEDの点滅指定です。1番目のパラメータはLED_TIMER::BLINKが点滅の指定で、2番目のパラメータは点滅のON/OFF切り替え時間です。500msごとにLEDが点灯、消灯(つまり1秒周期の点滅)を繰り返します。

auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0x00);

ここではNWK_SIMPLE::logical_id(0x00)を設定し親機(論理ID=0x00)であることを指定します。

loop()

パケットを受信したとき、その内容を表示します。ここでは2種類の表示を行っています。

void loop() {
	// receive RF packet.
    while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();

		Serial << ".. coming packet (" << int(millis()&0xffff) << ')' << mwx::crlf;

		// output type1 (raw packet)
		//   uint8_t  : 0x01
		//   uint8_t  : src addr (LID)
		//   uint32_t : src addr (long)
		//   uint32_t : dst addr (LID/long)
		//   uint8_t  : repeat count
		//     total 11 bytes of header.
		//     
		//   N        : payload
		{
		  serparser_attach pout;
			pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, 
			    rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);

			Serial << "RAW PACKET -> ";
			pout >> Serial;
			Serial << mwx::flush;
		}

		// output type2 
		{
			smplbuf_u8<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
			);

			serparser_attach pout;
			pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
			
			Serial << "FMT PACKET -> ";
			pout >> Serial;
			Serial << mwx::flush;
		}
	}
}

最初の出力は<NWK_SIMPLE>の制御データを含めたデータをすべて表示します。制御データは11バイトあります。通常は制御情報を直接参照することはありませんが、あくまでも参考です。

serparser_attach pout;
pout.begin(PARSER::ASCII, rx.get_psRxDataApp()->auData, 
    rx.get_psRxDataApp()->u8Len, rx.get_psRxDataApp()->u8Len);

Serial << "RAW PACKET -> ";
pout >> Serial;
Serial << mwx::flush;

// 参考:制御部のパケット構造
// uint8_t  : 0x01 固定
// uint8_t  : 送信元のLID
// uint32_t : 送信元のロングアドレス(シリアル番号)
// uint32_t : 宛先アドレス
// uint8_t  : 中継回数

1行目は出力用のシリアルパーサをローカルオブジェクトとして宣言しています。内部にバッファを持たず、外部のバッファを流用し、パーサーの出力機能を用いて、バッファ内のバイト列を書式出力します。

2行目はシリアルパーサーのバッファを設定します。すでにあるデータ配列、つまり受信パケットのペイロード部を指定します。serparser_attach poutは、既にあるバッファを用いたシリアルパーサーの宣言です。pout.begin()の1番目のパラメータは、パーサーの対応書式をPARSER::ASCIIつまりアスキー形式として指定しています。2番目はバッファの先頭アドレス。3番目はバッファ中の有効なデータ長、4番目はバッファの最大長を指定します。出力用で書式解釈に使わないため4番目のパラメータは3番目と同じ値を入れています。

6行目でシリアルポートへ>>演算子を用いて出力しています。

7行目のSerial << mwx::flushは、ここで出力が終わっていないデータの出力が終わるまで待ち処理を行う指定です。(Serial.flush()も同じ処理です)

2番目の出力はより実践的です。ユーザが定義した並び順で書式を構成します。

smplbuf_u8<128> buf;
mwx::pack_bytes(buf
	, uint8_t(rx.get_addr_src_lid())		   // 送信元の論理ID
	, uint8_t(0xCC)											   // 0xCC
	, uint8_t(rx.get_psRxDataApp()->u8Seq) // パケットのシーケンス番号
	, uint32_t(rx.get_addr_src_long())		 // 送信元のシリアル番号
	, uint32_t(rx.get_addr_dst())			     // 宛先アドレス
	, uint8_t(rx.get_lqi())					       // LQI:受信品質
	, uint16_t(rx.get_length())				     // 以降のバイト数
	, rx.get_payload() 						         // データペイロード
);

serparser_attach pout;
pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());

Serial << "FMT PACKET -> ";
pout >> Serial;
Serial << mwx::flush;

1行目はアスキー書式に変換する前のデータ列を格納するバッファをローカルオブジェクトとして宣言しています。

2行目はpack_bytes()を用いてデータ列を先ほどのbufに格納します。データ構造はソースコードのコメントを参照ください。pack_bytes()のパラメータにはsmplbuf_u8 (smplbuf<uint8_t, ???>)形式のコンテナを指定することもできます。

パケットのシーケンス番号は、<NWK_SIMPLE>で自動設定され、送信パケット順に割り振られます。この値はパケットの重複検出に用いられます。

LQI (Link Quality Indicator)は受信時の電波強度に相当する値で、値が大きければ大きいほどより強い電界強度で受信できていることになります。ただしこの値と物理量との厳格な関連は定義されていませんし、環境のノイズと相対的なものでLQIがより大きな値であってもノイズも多ければ通信の成功率も低下することになります。

13,14,17行目は、シリアルパーサーの宣言と設定、出力です。

PAL_AMB-usenap

アクトの解説

begin()

begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

void begin() {
	sleepNow(); // the first time is just sleeping.
}

setup()終了後に初回スリープを実行します。setup()中にセンサーデータ取得を開始していますが、この結果は評価せず、センサーを事前に一度は動かしておくという意味あいで、必ずしも必要な手続きではありません。

wakeup()

起床後の手続きです。以下の処理を行います。

  • まだセンサーデータの取得開始をしていない場合、センサーデータ取得を行い、短いスリープに入る。

  • 直前にセンサーデータ取得開始を行ったので、データを確認して無線送信する。

void wakeup() {
	if (!b_senser_started) {
		// delete/make shorter this message if power requirement is harder.	
		Serial	<< mwx::crlf
				<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
				<< mwx::crlf
				<< "..start sensor capture again."
				<< mwx::crlf;

		startSensorCapture();
		b_senser_started = true;

		napNow(); // short period sleep.
	} else {
		Serial << "..wake up from short nap.." << mwx::crlf;

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

		b_senser_started = false;

		// tell sensors waking up.
		brd.sns_LTR308ALS.process_ev(E_EVENT_START_UP);
		brd.sns_SHTC3.process_ev(E_EVENT_START_UP);
	}
}

上記の分岐をグローバル変数のb_sensor_startedにより制御しています。!b_sensor_startedの場合はセンサー取得開始(startSensorCapture())を行い、napNow()により短いスリープに入ります。時間は100msです。

napNow()によるスリープ復帰後、b_sensor_started==trueの節が実行されます。ここでは、2つのセンサーに対してE_EVENT_START_UPイベントを通知しています。このイベントは、センサーの取得が終了するのに十分な時間が経過したことを意味します。この通知をもとにsns_LTR308ALSとsns_SHTC3はavailableになります。この後loop()に移行し、無線パケットが送信されます。

センサーに通知するイベントは必要な時間待ちが終わったかどうかを判定するために使われます。実際時間が経過しているかどうかはnapNow()で正しい時間を設定したかどうかで決まります。短い時間で起床した場合は、必要とされる時間経過に足りないため、続く処理でセンサーデータが得られないなどのエラーが出ることが想定されます。

napNow()

ごく短いスリープを実行する。

void napNow() {
	uint32_t u32ct = 100;
	Serial << "..nap " << int(u32ct) << "ms." << mwx::crlf;
	the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}

sleepのパラメータの2番目をtrueにすると前回のスリープ復帰時刻をもとに次の復帰時間を調整します。常に5秒おきに起床したいような場合設定します。

3番目をtrueにするとメモリーを保持しないスリープになります。復帰後はwakup()は呼び出されじ、電源再投入と同じ処理になります。

4番目はウェイクアップタイマーの2番目を使う指定です。ここでは1番目は通常のスリープに使用して、2番目を短いスリープに用いています。このアクトでは2番目を使う強い理由はありませんが、例えば上述の5秒おきに起床したいような場合、短いスリープに1番目のタイマーを用いてしまうとカウンター値がリセットされてしまい、経過時間の補正計算が煩雑になるため2番目のタイマーを使用します。

あまり短いスリープ時間を設定してもスリープ復帰後のシステムの再初期化などのエネルギーコストと釣り合いません。目安として最小時間を30-50ms程度とお考え下さい。

アクトの実行

ここではアクト(BINファイル)の書き込みと実行について解説します。

この節はWindows10環境のユーティリティTWE-Programmerを用いた解説です。

TWE-Programmer, tweterm.pyはBINファイルの書き込み機能とターミナル機能がありますが、ターミナル機能については各システム用のターミナルソフトを利用することもできます。

  • Windows10: TeraTerm

  • Linux/macOS: screen など

BINファイルの書き込みと実行

BINファイルが出来上がれば、実機で動作させることになります。繊細な電子部品ですので、取り扱いには十分注意してください。以下に代表的な注意事項を記載します。

特にTWELITE Rを用いている場合は、多くの場合電子基板がケースなどを介さず直接外部に触れる状態で使用するため、意図しないショートやノイズなどUSBデバイスが正常に動作しない状態になる場合があります。

この場合は、アプリケーションを終了させUSBデバイスを抜き差しすることで通常は回復します。最悪、USBデバイスの破損やPCの破損も考えられます。

電子基板の取り扱いには十分注意してください。

  • 回路の間違い

    • 電源を入れる前にはもう一度回路を確認してください。

    • 電池の逆差しや過大電圧には注意してください。

  • 静電気

    • 人感がない電圧であっても半導体の故障になりえます。大掛かりな対応をしなくとも、金属部に触れてから作業する・リストバンド・専用マットなど簡易にできる対応でも相応の効果はあります。

  • 金属物などが触れることでのショート

    • 電子基板の近くには金属物がないようにしてください。クリップなどが散らかっているとこれが原因でショートすることもありますし、電池が大放電して加熱する危険な状況も考えられます。

1. 事前に MONOSTICK や TWELITE R をPCに差し込んでドライバのセットアップを終了しておく。

デバイスが認識されれば、新たにCOMポートが追加されます。複数あってわかりにくい場合は、抜いた状態でのCOMポートと刺した状態でのCOMポートの様子を観察してみてください。

2. TWE-Programmer.exe を起動する。

{MWSDKインストールディレクトリ}/Tools/TWE-Programmerディレクトリを開き、TWE-Programmer.exe をダブルクリックします。

TWE-Programmer.exe を起動すると以下のような画面になります。画面例では、起動前に MONOSTICK を差し込んでいて COM6 に割り当てられています。

MONOSTICK や TWELITE R が表示されない場合は以下を試してください。

  • TWE-Programmer を終了する。

  • MONOSTICK や TWELITE RをPCから抜く(取り外す)。

  • もう一度MONOSTICK や TWELITE Rを差し込む。

  • TWE-Programmer を起動する。

これでもうまくいかない場合は以下も試してください。

  • PCから可能な限り他のUSBデバイスを取り外す。

  • PCを再起動してからMONOSTICK や TWELITE Rを差し込んでみる。

  • FTDI社のドライバを再導入してみる。

3. TWE-Programmer 上で BIN ファイルを指定する。

ファイルダイアログから BIN ファイルを指定します。

4. BINファイルの書き込み

BINファイルの書き込みは、BINファイルを指定すると自動で行われます。

5-1. 書き込み成功時

書き込みが成功するとTWELITEは自動でリセットして、ターミナルが表示されます(ターミナル接続するにチェックが入っている場合)。

MWSDK添付のアクトファイルは始動時にメッセージを表示します。何か表示されれば書き込みは無事終了し、TWELITE 無線モジュール上のアクトが無事に動作していることが確認できます。

ターミナルを開いたままで、アクトを編集して再度書き込みたいときはCtrl+Alt+Wキーを押します。

5-2. 書き込み失敗時

書き込みに失敗したときは、ターミナルは開かず、TWE-Programmerのウインドウ背景がピンク色になります。

書き込みが失敗したときは (再)書き込みボタンを押して、もう一度書き込みを実行してください。

TWE-Programmer が反応しないなど、うまくいかない場合は、以下を実行した上、USBへの接続からやり直してください。

  • TWE-Programmer を終了する

  • MONOSTICK や TWELITE Rを取り外す

場合によってはPCの再起動を行ってください。

TWELITE PAL での書き込み中に、ハードウェア・ウォッチドッグタイマーが作動し書き込みが中断する場合があります。再度、書き込みを行ってください。

BRD_APPTWELITE

IO通信(標準アプリケーションApp_Tweliteの基本機能)

App_TweLite で必要な配線と同じ配線想定したボードサポート <BRD_APPTWELITE> を用いたサンプルです。

このサンプルは App_TweLite と通信できません。

アクトの機能

  • M1を読み取り、親機か子機を決める。

  • DI1-DI4 の値を読み取ります。Buttons クラスにより、チャタリングの影響を小さくするため、連続で同じ値になったときにはじめて変化が通知されます。変化があったときには通信を行います。

  • AI1-AI4 の値を読み取ります。

  • DIの変化または1秒おきに、DI1-4, AI1-4, VCC の値を、自身が親機の場合は子機へ、子機の場合は親機宛に送信します。

  • 受信したパケットの値に応じで DO1-4, PWM1-4 に設定する。

アクトの使い方

必要なTWELITEと配線例

アクトの解説

インクルード

宣言部

  • サンプルアクト共通宣言

  • 長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)

  • アプリケーション中のデータ保持するための変数

セットアップ setup()

大まかな流れは、各部の初期設定、各部の開始となっています。

the_twelite

このオブジェクトはTWENETの中核としてふるまいます。

ボードの登録(このアクトでは<BRD_APPTWELITE>を登録しています)。以下のように use の後に <> で登録したいボードの名前を指定します。

ユニバーサル参照(auto&&)にて得られた戻り値として、参照型でのボードオブジェクトが得られます。このオブジェクトにはボード特有の操作や定義が含まれます。

ここではボードオブジェクトを用いbrd.get_M1()を呼び出すことでM1ピンの設定を読み出します。1がスイッチをセットしGND側になっていることを示します。M1 がセットされた場合にu8devidを0x00に、そうでなければ0xFEを指定しています。これはネットワーク上での役割が親機(0x00)であるか子機(0xFE)あるかを決定します。この値は <NWK_SIMPLE>の設定に使用します。

the_twelite に設定を反映するには << を用います。

  • TWENET::appid(APP_ID) アプリケーションIDの指定

  • TWENET::channel(CHANNEL) チャネルの指定

  • TWENET::rx_when_idle() 受信回路をオープンにする指定

<<, >>演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。

次にネットワークを登録します。

1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。

2行目は、<NWK_SIMPLE>の設定です。先ほどボードから得たM1ピンの状態によって親機アドレス(0x00)か子機アドレス(0xFE)を格納したu8devidを指定します。

setup() 関数の末尾で the_twelite.begin() を実行しています。

Analogue

ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。

初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。2番目のパラメータは、ADCの開始をTimer0に同期して行う指定です。

ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。

ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。

パラメータと指定されるBRD_APPTWELITE::にはPIN_AI1..4が定義されています。App_Tweliteで用いるAI1..AI4に対応します。AI1=ADC1, AI2=DIO0, AI3=ADC2, AI4=DIO2 と割り当てられています。PIN_ANALOGUE::にはADCで利用できるピンの一覧が定義されています。

初回を除き ADC の開始は、割り込みハンドラ内で行います。

Buttons

DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。

初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。

開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_DI1-4 (DI1-DI4) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。4を指定しているので4msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。

ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。

Timer0

App_Twelite ではアプリケーションの制御をタイマー起点で行っているため、このアクトでも同じようにタイマー割り込み・イベントを動作させます。もちろん1msごとに動作しているシステムのTickTimerを用いても構いません。

上記の例の1番目のパラメータはタイマーの周波数で32Hzを指定しています。2番目のパラメータをtrueにするとソフトウェア割り込みが有効になります。

Timer0.begin()を呼び出したあと、タイマーが稼働します。

Serial

Serial オブジェクトは、初期化や開始手続きなく利用できます。

上記の例では "--- BRD_APPTWELITE(254) ---\r\n" といった文字列を表示します。ここでは10進数の数値文字列として表示するためにint(u8devid)としています。int型でない場合は出力の意味合いが変わりバイトの書き出しになるので注意してください。

ループ loop()

ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。

TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。

したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。

Buttons

DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。

1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば bp & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。

初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。

次にビットマップから値を取り出してu8DI_BMに格納しています。ここではMWXライブラリで用意したcollect_bits()関数を用いています。

collect_bits() は、上述のpack_bits()と同様のビット位置の整数値を引数とします。可変数引数の関数で、必要な数だけパラメータを並べます。上記の処理では bit0 は DI1、bit1 は DI2、bit2 は DI3、bit3 は DI4の値としてu8DI_BMに格納しています。

App_Twelite では、DI1から4に変化があった場合に無線送信しますので、Buttons.available()を起点に送信処理を行います。transmit()処理の内容は後述します。

Analogue

ADCのアナログディジタル変換が終了した直後のloop()で available になります。次の ADC が開始するまでは、データは直前に取得されたものとして読み出すことが出来ます。

ADC値を読むには Analogue.read() または Analogue.read_raw() メソッドを用います。read()はmVに変換した値、read_raw()は 0..1023 のADC値となります。パラメータにはADCのピン番号を指定します。ADCのピン番号はPIN_ANALOGUE::やBRD_APPTWELITE::に定義されているので、こちらを利用しています。

周期的に実行されるADCの値は、タイミングによってはavailable通知前のより新しい値が読み出されることがあります。

このアクトでは32Hzと比較的ゆっくりの周期で処理しているため、available判定直後に処理すれば問題にはなりませんが、変換周期が短い場合、loop()中で比較的長い時間のかかる処理をしている場合は注意が必要です。

Analogueには、変換終了後に割り込みハンドラ内から呼び出されるコールバック関数を指定することが出来ます。例えば、このコールバック関数からFIFOキューに値を格納する処理を行い、アプリケーションループ内ではキューの値を逐次読み出すといった非同期処理を行います。

Timer0

Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。

AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。

u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。

the_twelite.receiver

受信パケットがある場合の処理です。

無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。

TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。

内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。

transmit()

無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。

関数プロトタイプ

MWX_APIRETはuint32_t型のデータメンバを持つ戻り値を取り扱うクラスです。MSB(bit31)が成功失敗、それ以外が戻り値として利用するものです。

ネットワークオブジェクトとパケットオブジェクトの取得

ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。

ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。

パケットの送信設定

パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。

  • tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。

  • tx_retry() パラメータに再送回数を指定します。例の1は再送回数が1回、つまり合計2回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。

  • tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ0msから50msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから10ms置きに再送を行うという意味です。

パケットのデータペイロード

ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。

送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。

データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。

IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。

上記の例は、解説のためある程度の妥協をしています。節約を考える場合 00: の識別子は1バイトの簡単なものにすべきですし、Vccの電圧値は8ビットに丸めてもかまわないでしょう。また各AI1..AI4の値は10bitで、合計40bit=5バイトに対して6バイト使用しています。

上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。

上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。

pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。

そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。

3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。

4行目はuint8_t型でDI1..DI4のビットマップを書き込みます。

7-9行目ではau16AI配列の値を順に書き込んでいます。この値はuint16_t型で2バイトですが、ビッグエンディアンの並びで書き込みます。

7行目のfor文はC++で導入された範囲for文です。サイズのわかっている配列やbegin(), end()によるイテレータによるアクセスが可能なコンテナクラスなどは、この構文が使用できます。au16AIの型もコンパイル時に判定できるため auto&& (ユニバーサル参照)で型の指定も省略してます。

通常のfor文に書き換えると以下のようになります。

これでパケットの準備は終わりです。あとは、送信要求を行います。

パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。戻り値としてMWX_APIRET型を返していますが、このアクトでは使っていません。

戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。

receive()

パケットの受信が確認できた(つまりthe_twelite.receiver.available()がtrueになった)ときの処理です。ここでは、相手方から伝えられたDI1..DI4の値とAI1..AI4の値を、自身のDO1..DO4とPWM1..PWM4に設定します。

まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。

次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。

<NWK_SIMPLE>では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。

MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。

1行目ではデータ格納のためのchar型の配列を宣言しています。サイズが5バイトなのは末尾にヌル文字を含め、文字出力などでの利便性を挙げるためです。末尾の{}は初期化の指定で、5バイト目を0にすれば良いのですが、ここでは配列全体をデフォルトの方法で初期化、つまり0にしています。

2行目でexpand_bytes()により4バイト文字列を取り出しています。パラメータにコンテナ型を指定しない理由は、この続きを読み出すための読み出し位置を把握する必要があるためです。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。

3番目に読み出す変数を指定しますが、ここでもmake_pairによって文字列配列とサイズのペアを指定します。

このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()の戻り値により判定してください。

expand_bytes()の戻り値は uint8_t* ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)を戻します。

読み出した4バイト文字列の識別子が、このアクトで指定した識別子と異なる場合は、このパケットを処理しません。

TWENETではアプリケーションIDと物理的な無線チャネルが合致する場合は、どのアプリケーションもたとえ種別が違ったとしても、受信することが出来ます。他のアプリケーションで作成したパケットを意図しない形で受信しない目的で、このような識別子やデータペイロードの構造などのチェックを行い、偶然の一致が起きないように対処することを推奨します。

シンプルネットワーク<NWK_SIMPLE>でのパケット構造の要件も満足する必要があるため、シンプルネットワークを使用しない他のアプリケーションが同じ構造のパケットを定義しない限り(非常にまれと思われます)、パケットの混在受信は発生しません。

続いて、データ部分の取得です。DI1..DI4の値とAI1..AI4の値を別の変数に格納します。

先ほどのexpand_bytes()の戻り値npを1番目のパラメータにしています。先に読み取った4バイト文字列識別子の次から読み出す指定です。2番目のパラメータは同様です。

3番目以降のパラメータはデータペイロードの並びに一致した型の変数を、送り側のデータ構造と同じ順番で並べています。この処理が終われば、指定した変数にペイロードから読み出した値が格納されます。

確認のためシリアルポートへ出力します。

数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)

1行目の "DI:%04b" は"DI:0010"のようにDI1..DI4のビットマップを4桁で表示します。3行目の"/%04d"は"/3280/0010/0512/1023/1023"のように Vcc/AI1..AI4の値を順に整数で出力します。5行目のmwx::crlfは改行文字列を出力します。

これで必要なデータの展開が終わったので、あとはボード上のDO1..DO4とPWM1..PWM4の値を変更します。

digitalWrite()はディジタル出力の値を変更します。1番目のパラメータはピン番号で、2番目はHIGH(Vccレベル)かLOW(GNDレベル)を指定します。

Timer?.change_duty()はPWM出力のデューティ比を変更します。パラメータにデューティ比 0..1024 を指定します。最大値が1023でないことに注意してください(ライブラリ内で実行される割り算のコストが大きいため2のべき乗である1024を最大値としています)。0にするとGNDレベル、1024にするとVccレベル相当の出力になります。

PAL_AMB

アクトの機能

  • 環境センサーパル AMPIENT SENSE PAL を用い、センサー値の取得を行います。

  • コイン電池で動作させるための、スリープ機能を利用します。

アクトの使い方

必要なTWELITE

アクトの解説

インクルード

setup()

ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

このアクトではもっぱら無線パケットを送信しますので、TWENET の設定では動作中に受信回路をオープンにする指定(TWENET::rx_when_idle())は含めません。

ボード上のセンサーはI2Cバスを用いますので、バスを利用開始しておきます。

loop()

このアクトでは、1ms周期で呼び出させる(アプリケーションループの呼び出しタイミングは不定期な遅延が発生します)TickTimerのイベントを起点としてセンサーの読み出しや送信要求を行います。TickTimer.available()がtrueになったときに処理を行います。これによりおよそ1msごとにif節内の処理を行います。

ボード上のセンサーは .sns_LTR308ALS または .sns_SHTC3 という名前でアクセスでき、このオブジェクトに操作を行います。センサーの完了待ちを行います。まだセンサーの取得が終わっていない場合(.available()がfalse)はセンサーに対して時間経過のイベント(.process_ev(E_EVENT_TICK_TIMER))を送付します。

上記2つのセンサーがavailableになった時点で、無線送信などの処理を行います。

送信手続きについては他のアクトのサンプルと同様です。ここでは、再送1回、再送遅延を最小にする設定になっています。

パケットにセンサーデータを書き込み際に、センサーデータを取得しています。センサーの値を取得するメソッドはセンサーごとに違います。

照度センサーは.get_luminance() : uint32_tで得られます。

温湿度センサーは以下のように取得できます。

  • .get_temp_cent() : int16_t : 1℃を100とした温度 (25.6 ℃なら 2560)

  • .get_temp() : float : float値 (25.6 ℃なら 25.6)

  • .get_humid_dmil() : int16_t : 1%を100とした湿度 (56.8%なら 5680)

  • .get_temp() : float : float値 (56.8%なら 56.8)

得られた値のうち温度値は int16_t ですが、送信パケットのデータ構造は符号なしで格納するため、uint16_tにキャストしています。

送信が成功すれば b_tansmit を trueにし、u8txidに送信パケットの識別IDを格納し、完了待ちします。送信が失敗すれば sleepNow() によりスリープします。

loop() 中 b_transmit が true になっている場合は、送信完了チェックを行い、完了すれば sleepNow() によりスリープします。

送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。

startSensorCapture()

ボード上のセンサーの取得を開始します。多くのセンサーは、取得を開始してから完了するまで数msから数十msの時間を必要とします。

5行目で、温湿度センサー SHTC3 のデータ取得を開始します。

6行目で、照度センサー LTR308ALS のデータ取得を開始します。

7行目は、送信完了フラグをクリアします。

sleepNow()

スリープに入る手続きをまとめています。

ここでは、起床までの時間を乱数により 1750ms から 2250ms の間に設定しています。これにより他の同じような周期で送信するデバイスのパケットとの連続的な衝突を避けます。

周期が完全に一致すると、互いのパケットで衝突が起き通信が困難になります。通常は時間の経過とともにタイマー周期が互いにずれるため、しばらくすると通信が回復し、また時間がたつと衝突が起きるという繰り返しになります。

3行目、この例ではシリアルポートからの出力を待ってスリープに入ります。通常は消費エネルギーを最小化したいため、スリープ前のシリアルポートの出力は最小限(または無し)にします。

スリープ前にflushを行うと、出力が不安定になる場合があります。

スリープに入るには the_twelite.sleep() を呼びます。この呼び出しの中で、ボード上のハードウェアのスリープ前の手続きなどが行われます。たとえばLEDは消灯します。

パラメータとしてスリープ時間をmsで指定しています。

TWELITE PAL では、必ず60秒以内に一度起床し、ウォッチドッグタイマーをリセットしなければなりません。スリープ時間は60000を超えないように指定してください。

wakeup()

スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理が行われます。例えばLEDの点灯制御を再始動します。

ここではセンサーのデータ取得を開始しています。

応用編

より安全な実装

より安全な実装として、このアクトでは以下の例外処理を強化します。

  • センサー取得部分でavailableにならなかった場合の例外処理が記述されてません。

  • 送信完了待ちで送信完了が通知されない場合の例外処理が記述されていません。

上記いずれもタイムアウト処理を行います。現在の時間は millis() により取得できます。

消費エネルギーの削減

PingPong

2台のシリアル接続しているTWELITEの片方からPING(ピン)の無線パケットを送信すると、他方からPONG(ポン)の無線パケットが返ってきます。

アクトの使い方

必要なTWELITE

いずれかを2台。

アクトの解説

インクルード

宣言部

  • サンプルアクト共通宣言

  • 長めの処理を関数化しているため、そのプロトタイプ宣言(送信と受信)

  • アプリケーション中のデータ保持するための変数

セットアップ setup()

大まかな流れは、各部の初期設定、各部の開始となっています。

the_twelite

このオブジェクトはTWENETを操作するための中核クラスオブジェクトです。

the_twelite に設定を反映するには << を用います。

  • TWENET::appid(APP_ID) アプリケーションIDの指定

  • TWENET::channel(CHANNEL) チャネルの指定

  • TWENET::rx_when_idle() 受信回路をオープンにする指定

<<, >>演算子は本来ビットシフト演算子ですが、その意味合いと違った利用とはなります。MWXライブラリ内では、C++標準ライブラリでの入出力利用に倣ってライブラリ中では上記のような設定やシリアルポートの入出力で利用しています。

次にネットワークを登録します。

1行目は、ボードの登録と同じ書き方で <> には <NWK_SIMPLE>を指定します。

2行目は、<NWK_SIMPLE>の設定で、0xFE(ID未設定の子機)という指定を行います。

3行目は、中継回数の最大値を指定しています。この解説では中継には触れませんが、複数台で動作させたときにパケットの中継が行われます。

setup() 関数の末尾で the_twelite.begin() を実行しています。

Analogue

ADC(アナログディジタルコンバータ)を取り扱うクラスオブジェクトです。

初期化Analogue.setup()で行います。パラメータのtrueはADC回路の安定までその場で待つ指定です。

ADCを開始するにはAnalogue.begin()を呼びます。パラメータはADC対象のピンに対応するビットマップです。

ビットマップを指定するのにpack_bits()関数を用います。可変数引数の関数で、各引数には1を設定するビット位置を指定します。例えばpack_bits(1,3,5)なら2進数で 101010の値が戻ります。この関数はconstexpr指定があるため、パラメータが定数のみであれば定数に展開されます。

パラメータにはPIN_ANALOGUE::A1(ADC0)とPIN_ANALOGUE::VCC(モジュール電源電圧)が指定されています。

2番目のパラメータには50が指定されています。ADCの動作はデフォルトではTickTimerで開始されていて、

初回を除き ADC の開始は、割り込みハンドラ内で行います。

Buttons

DIO (ディジタル入力) の値の変化を検出します。Buttonsでは、メカ式のボタンのチャタリング(摺動)の影響を軽減するため、一定回数同じ値が検出されてから、値の変化とします。

初期化は Buttons.setup()で行います。パラメータの 5 は、値の確定に必要な検出回数ですが、設定可能な最大値を指定します。内部的にはこの数値をもとに内部メモリの確保を行っています。

開始は Buttons.begin() で行います。1番目のパラメータは検出対象のDIOです。BRD_APPTWELITE::に定義されるPIN_BTN (12) を指定しています。2番めのパラメータは状態を確定するのに必要な検出回数です。3番めのパラメータは検出間隔です。10を指定しているので10msごとに5回連続で同じ値が検出できた時点で、HIGH, LOWの状態が確定します。

ButtonsでのDIO状態の検出はイベントハンドラで行います。イベントハンドラは、割り込み発生後にアプリケーションループで呼ばれるため割り込みハンドラに比べ遅延が発生します。

Serial

Serial オブジェクトは、初期化や開始手続きなく利用できます。

シリアルポートへの文字列出力を行います。mwx::crlfは改行文字です。

ループ loop()

ループ関数は TWENET ライブラリのメインループからコールバック関数として呼び出されます。ここでは、利用するオブジェクトが available になるのを待って、その処理を行うのが基本的な記述です。ここではアクトで使用されているいくつかのオブジェクトの利用について解説します。

TWENET ライブラリのメインループは、事前にFIFOキューに格納された受信パケットや割り込み情報などをイベントとして処理し、そののちloop()が呼び出されます。loop()を抜けた後は CPU が DOZE モードに入り、低消費電流で新たな割り込みが発生するまでは待機します。

したがってCPUが常に稼働していることを前提としたコードはうまく動作しません。

Serial

Serial.available()がtrueの間はシリアルポートからの入力があります。内部のFIFOキューに格納されるためある程度の余裕はありますが、速やかに読み出すようにします。データの読み出しはSerial.read()を呼びます。

ここでは't'キーの入力に対応してvTransmit()関数を呼び出しPINGパケットを送信します。

Buttons

DIO(ディジタルIO)の入力変化を検出したタイミングで available になり、Buttons.read()により読み出します。

1番目のパラメータは、現在のDIOのHIGH/LOWのビットマップで、bit0から順番にDIO0,1,2,.. と並びます。例えば DIO12 であれば btn_state & (1UL << 12) を評価すれば HIGH / LOW が判定できます。ビットが1になっているものがHIGHになります。

初回のIO状態確定時は MSB (bit31) に1がセットされます。スリープ復帰時も初回の確定処理を行います。

初回確定以外の場合かつPIN_BTNのボタンが離されたタイミングでvTransmit()を呼び出しています。押したタイミングにするには(!(btn_state && (1UL << PIN_BTN)))のように条件を論理反転します。

Timer0

Timer0は32Hzで動作しています。タイマー割り込みが発生直後の loop() で available になります。つまり、秒32回の処理をします。ここでは、ちょうど1秒になったところで送信処理をしています。

AppTweliteでは約1秒おきに定期送信を行っています。Timer0がavailableになったときにu16ctをインクリメントします。このカウンタ値をもとに、32回カウントが終わればtransmit()を呼び出し無線パケットを送信しています。

u8DI_BMとau16AI[]の値判定は、初期化直後かどうかの判定です。まだDI1..DI4やAI1..AI4の値が格納されていない場合は何もしません。

the_twelite.receiver

受信パケットがある場合の処理です。

無線パケットを受信後のloop()ではthe_twelite.receiver.available()がtrueを返します。受信パケットの取り扱いについては receive() 関数の解説で行います。

TWENETがパケットを受信してから時間をたってからの受信パケットの参照は安全ではありません。

内部のデータが新しい受信パケットの内容に上書きされ、この新しい内容を参照することになります。availableになってから速やかに処理するようにloop()を記述してください。内部的には、1パケット分余分に保持できる余裕はあります。

まず受信パケットのデータrxを取得します。rxからアドレス情報やデータペイロードにアクセスします。

次の行では、受信パケットデータには、送信元のアドレス(32bitのロングアドレスと8bitの論理アドレス)などの情報を参照しています。

<NWK_SIMPLE>では、8bitの論理IDと32bitのロングアドレスの2種類が常にやり取りされます。送り先を指定する場合はロングアドレスか論理アドレスのいずれかを指定します。受信時には両方のアドレスが含まれます。

MWXライブラリにはtransmit()の時に使ったpack_bytes()の対になる関数expand_bytes()が用意されています。

1行目から3行目までは、データを格納する変数を指定しています。

6行目でexpand_bytes()によりパケットのペイロードのデータを変数に格納します。1番目のパラメータでコンテナの先頭イテレータ(uint8_t*ポインタ)を指定します。.begin()メソッドにより取得できます。2番目のパラメータはコンテナの末尾の次を指すイテレータで.end()メソッドで取得できます。2番目はコンテナの末尾を超えた読み出しを行わないようにするためです。

3番目以降のパラメータに変数を列挙します。列挙した順番にペイロードの読み出しとデータ格納が行われます。

このアクトでは、パケット長が間違っていた場合などのエラーチェックを省いています。チェックを厳格にしたい場合は、expand_bytes()の戻り値により判定してください。

expand_bytes()の戻り値は uint8_t* ですが、末尾を超えたアクセスの場合はnullptr(ヌルポインタ)を戻します。

msgに読み出した4バイト文字列の識別子が"PING"の場合はPONGメッセージを送信する処理です。

続いて到着したパケット情報を表示します。

数値のフォーマット出力が必要になるのでformat()を用いています。>>演算子向けにprintf()と同じ構文を利用できるようにしたヘルパークラスですが、引数の数が4つまでに制限されています。(Serial.printfmt()には引数の数の制限がありません。)

mwx::crlfは改行文字(CR LF)を、mwx::flushは出力完了待ちを指定します。

transmit()

無線パケットの送信要求をTWENETに行う関数です。本関数が終了した時点では、まだ無線パケットの処理は行われません。実際に送信が完了するのは、送信パラメータ次第ですが、数ms後以降になります。ここでは代表的な送信要求方法について解説します。

ネットワークオブジェクトとパケットオブジェクトの取得

ネットワークオブジェクトをthe_twelite.network.use<NWK_SIMPLE>()で取得します。そのオブジェクトを用いて.prepare_tx_packet()によりpktオブジェクトを取得します。

ここではif文の条件判定式の中で宣言しています。宣言したpktオブジェクトはif節の終わりまで有効です。pktオブジェクトはbool型の応答をし、ここではTWENETの送信要求キューに空きがあって送信要求を受け付ける場合にtrue、空きがない場合にfalseとなります。

パケットの送信設定

パケットの設定はthe_tweliteの初期化設定のように<<演算子を用いて行います。

  • tx_addr() パラメータに送信先アドレスを指定します。0x00なら自分が子機で親機宛に、0xFEなら自分が親機で任意の子機宛のブロードキャストという意味です。

  • tx_retry() パラメータに再送回数を指定します。例の3は再送回数が3回、つまり合計4回パケットを送ります。無線パケット1回のみの送信では条件が良くても数%程度の失敗はあります。

  • tx_packet_delay() 送信遅延を設定します。一つ目のパラメータは、送信開始までの最低待ち時間、2番目が最長の待ち時間です。この場合は送信要求を発行後におよそ100msから200msの間で送信を開始します。3番目が再送間隔です。最初のパケットが送信されてから20ms置きに再送を行うという意味です。

パケットのデータペイロード

ペイロードは積載物という意味ですが、無線パケットでは「送りたいデータ本体」という意味でよく使われます。無線パケットのデータにはデータ本体以外にもアドレス情報などいくつかの補助情報が含まれます。

送受信を正しく行うために、データペイロードのデータ並び順を意識するようにしてください。ここでは以下のようなデータ順とします。このデータ順に合わせてデータペイロードを構築します。

データペイロードには90バイト格納できます(実際にはあと数バイト格納できます)。

IEEE802.15.4の無線パケットの1バイトは貴重です。できるだけ節約して使用することを推奨します。1パケットで送信できるデータ量に限りがあります。パケットを分割する場合は分割パケットの送信失敗などを考慮する必要がありコストは大きくつきます。また1バイト余分に送信するのに、およそ16μ秒×送信時の電流に相当するエネルギーが消費され、特に電池駆動のアプリケーションには大きく影響します。

上記のデータペイロードのデータ構造を実際に構築してみます。データペイロードは pkt.get_payload() により simplbuf<uint8_t> 型のコンテナとして参照できます。このコンテナに上記の仕様に基づいてデータを構築します。

上記のように記述できますがMWXライブラリでは、データペイロード構築のための補助関数pack_bytes()を用意しています。

pack_bytesの最初のパラメータはコンテナを指定します。この場合はpkt.get_payload()です。

そのあとのパラメータは可変数引数でpack_bytesで対応する型の値を必要な数だけ指定します。pack_bytesは内部で.push_back()メソッドを呼び出して末尾に指定した値を追記していきます。

3行目のmake_pair()は標準ライブラリの関数でstd::pairを生成します。文字列型の混乱(具体的にはペイロードの格納時にヌル文字を含めるか含めないか)を避けるための指定です。make_pair()の1番目のパラメータに文字列型(char*やuint8_t*型、uint8_t[]など)を指定します。2番目のパラメータはペイロードへの格納バイト数です。

4,5,6行目は、数値型の値 (uint8_t, uint16_t, uint32_t)を格納します。符号付などの数値型、char型など同じ数値型であっても左記の3つの型にキャストして投入します。

analogRead()とanalogRead_mv()は、ADCの結果を取得するものです。前者はADC値(0..1023)、後者は電圧[mv](0..2470)となります。モジュールの電源電圧は内部的に分圧抵抗の値を読んでいるためその変換を行うadalogRead_mv()を利用しています。

これでパケットの準備は終わりです。あとは、送信要求を行います。

パケットを送信するにはpktオブジェクトのpkt.transmit()メソッドを用います。

このアクトでは使用しませんが、戻り値には、要求の成功失敗の情報と要求に対応する番号が格納されています。送信完了まで待つ処理を行う場合は、この戻り値の値を利用します。

設計情報

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

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

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

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

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

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

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

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

  • イベント処理の基本部分mwx::appdefs_crtp

  • ステートマシンpublic mwx::processev_crtp

  • ストリーム mwx::stream

CRTP での仮想化

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

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

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

VBase クラスのメンバ変数 p_inst は、Base <T> 型のオブジェクトへのポインタを格納し、pf_intrfaceは Base<T>::s_intrface へのメンバ関数ポインタです。 Base<T>::s_intrface は、自身のオブジェクトインスタンスを引数として渡され、T型にstatic_castすることでT::intrfaceメソッドを呼び出します。

VBaseへの格納は、ここでは = 演算子のオーバーロードによって実装しています(ソース例は後述)。

上記の例ではb[0].intrface()の呼び出しを行う際には、VBase::pf_intrface関数ポインタを参照しBase<Derived1>::s_intrface()が呼び出されることになります。さらにDerived1::intrface()の呼び出しを行うことになります。この部分はコンパイラによるinline展開が期待できます。

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

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

new, new[] 演算子

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

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

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

システム(MAC層など)が確保するメモリは約2.5KBです。

コンテナクラス

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

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

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

可変数引数

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

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

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

イテレータ

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

C++のSTLでは、begin()メソッドで得られるコンテナの先頭を示すイテレータと、end()メソッドで得られるコンテナの末尾の「次」を示すイテレータの組み合わせが良く用いられます。

コンテナの末尾の「次」をend()としているのは、以下のような記述を想定しているためです。MWXライブラリでもこれに倣ってコンテナの実装を行っています。

イテレータを標準ライブラリの仕様に適合させることで、範囲for文が利用できたり、標準ライブラリのアルゴリズムを利用できるようになります。

(MWXライブラリではC++標準ライブラリに対する適合度や互換性についての検証は行っていません。動作確認の上利用ください)

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

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

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

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

値にアクセスするoperator *この上述のメンバー関数を呼び出しています。(*_pはaxis_xyzt構造体で、(*_p.*get)()は、T::*getに&axis_xyzt::get_xを指定した場合_p->get_x()を呼び出します)

_axis_xyzt_iter_genクラスはbegin(), end()のみを実装し、上記のイテレータを生成します。これで範囲for文やアルゴリズムが利用できるようになります。

このクラス名は非常に長くなりソースコード中に記述するのは困難です。このクラスを生成するためのジェネレータ関数を用意します。下記の例では末尾の行の get_axis_x() です。このジェネレータ関数を用いることで冒頭のようなauto&& vx = get_axis_x(que);といった簡潔な記述になります。

また、この軸だけを抽出するイテレータは、配列型のsmplbufクラスでも同様に利用できます。

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

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

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

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

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

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

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

ユーザが関数を定義したときにこれを有効にし、定義しない場合は別の関数を呼び出す手法として、リンク時の解決方法があります。下記のように__attribute__((weak)) の指定します。ユーザコードで wakeup() 関数が定義された場合は、ユーザーコードを関数をリンクし、未定義の場合は中身が空の関数をリンクします。

上記ハンドラの実装においてはweak指定したメンバー変数を明示的に生成する必要があり、またinline化による最適化が行いにくいため使用しませんが、wakeup()といったいくつかのTWENETからのコールバック関数の受け皿としてweak指定の関数を定義しています。

Streamクラス

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

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

上記は1文字書き出すwrite()メソッドの実装です。親クラスのstream<serial_jen>からはキャストを実行するget_Drived()メソッドを用いて、serial_jen::write()メソッドにアクセスしています。

必要に応じて write(), read(), flush(), available() といったメソッドを定義します。

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

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

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

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

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

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

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

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

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

新しいプロジェクトの作成

新しいプロジェクトの作成は、すでにあるサンプルアクトのディレクトリを別の名前でコピーし、ファイル名の編集を行います。

コピー先のディレクトリは MWSDK 配下のディレクトリでなくても構いません。ただし、ディレクトリ名に空白文字や日本語名が含まれてはいけません。

MWSDK以外のディレクトリにコピーした場合 VS Code のワークスペース定義の一部が機能しなくなります。ワークスペースにmwxライブラリのソースコードディレクトリが追加されている場合は、新たに設定してください。

ライブラリソース設定をしなくてもビルドには影響はありません。より深いコード解釈をVSCode上で行い、編集効率を上げるための設定です。

プロジェクトのファイル構造は以下のようになっています(ここでは PingPong を例に挙げます)。

この PingPong ディレクトリを別の場所(ただしディレクトリ名に日本語や空白が含まない)にコピーします。

編集の必要があるのは、PingPong.cpp のファイル名です。これをディレクトリ名と同じAlphaBravo.cppに変更します。

build\build-BLUE.cmd を実行してBINファイルが生成されれば完了です(Windows10)。

Linux/WSL/macOS ではmake TWELITE=BLUEを実行して、ビルドが成功するか確認します。

  • プロジェクトのディレクトリ名と同じ名前の .cpp ファイルは必須です。 (MWSDK 2020-04 では、任意のファイル名でOKになりました)

の記述サンプルです。詳細はを参照ください。

このアクトの解説の前にをご覧ください。

サンプルアクトの子機設定 (例: +にを書き込んだもの)

MONOSTICK用のボードビヘイビアをインクルードしています。このボードサポートには、LEDの制御、ウォッチドッグ対応が含まれます。

のサンプルを少し改良して、センサーデータ取得中の待ち時間(約50ms)を、スリープで待つようにします。

このアクトの解説の前にのアクトの解説をご覧ください。

Linux/macOSではを用います。本ページでの書き込み・実行の流れを参照いただいた上、tweterm.pyを利用ください。

出来上がったBINファイルをTWELITEに書き込むにはTWE-Programmerを使用します。詳しい使い方はを参照ください。

デバイスマネージャーの画面例

デバイスマネージャでデバイスの存在が確認できない場合は、導入を行ってみてください。FT232R用のドライバを選択します。

BINファイルの選択
BINファイル書き込み中
書き込み後(ターミナルが起動し始動メッセージが表示)
書き込み失敗時の画面例

全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク とボードサポート <BRD_APPTWELITE>をインクルードしておきます。

を用い、センサー値の取得を行います。

このアクトの解説の前にをご覧ください。

受信の確認のための解説をご覧ください。

環境センサーパル のボードビヘイビアをインクルードします。

最初にボードサポート を登録します。ボードサポートの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

ボード上のセンサーの取得を開始します。の解説を参照ください。

アクト は、センサーのデータ取得待ちをスリープで行い、より低消費エネルギーで動作できます。

でUART接続されているなど

全てのアクトで<TWELITE>をインクルードします。ここでは、シンプルネットワーク をインクルードしておきます。

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

書式出力にはMarco Paland氏によるを利用しています。MWXライブラリから利用するための実装が必要になります。下記の例で派生クラスのserial_jenで必要なことは1バイト出力のための vOutput() メソッドを定義することと、vOutput()がstaticメソッドであるため出力のための補助情報を親クラスのpvOutputContextに保存することです。

ビルド対象のファイルを追加する場合は build/Makefile を編集します。編集方法は をご覧ください。

ビヘイビア
こちら
BRD_APPTWELITEの解説
<MONOSTICK>
PAL_AMB
PAL_AMB
tweterm.py
こちら
FTDI社のドライバ
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
#include <BRD_APPTWELITE>
/*** Config part */
// application ID
const uint32_t APP_ID = 0x1234abcd;

// channel
const uint8_t CHANNEL = 13;

/*** function prototype */
MWX_APIRET transmit();
void receive();

/*** application defs */
const char APP_FOURCHAR[] = "BAT1";
uint8_t u8devid = 0;

uint16_t au16AI[5];
uint8_t u8DI_BM;
void setup() {
	// 変数の初期化
	for(auto&& x : au16AI) x = 0xFFFF;
	u8DI_BM = 0xFF;

	/*** SETUP section */
	// App_Twelite仕様のボード定義をシステムに登録します。
	auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();

	// check DIP sw settings
	u8devid = (brd.get_M1()) ? 0x00 : 0xFE;

	// setup analogue
	Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0); // setup analogue read (check every 16ms)

	// setup buttons
	Buttons.setup(5); // init button manager with 5 history table.

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(u8devid); // set Logical ID. (0x00 means parent device)

	/*** BEGIN section */
	// start ADC capture
	Analogue.begin(pack_bits(
						BRD_APPTWELITE::PIN_AI1,
						BRD_APPTWELITE::PIN_AI2,
						BRD_APPTWELITE::PIN_AI3,
						BRD_APPTWELITE::PIN_AI4,
				   	PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	// Timer setup
	Timer0.begin(32, true); // 32hz timer

	// start button check
	Buttons.begin(pack_bits(
						BRD_APPTWELITE::PIN_DI1,
						BRD_APPTWELITE::PIN_DI2,
						BRD_APPTWELITE::PIN_DI3,
						BRD_APPTWELITE::PIN_DI4),
					5, 		// history count
					4);  	// tick delta (change is detected by 5*4=20ms consequtive same values)	


	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- BRD_APPTWELITE(" << int(u8devid) << ") ---" << mwx::crlf;
}
auto&& brd = the_twelite.board.use<BRD_APPTWELITE>();
u8devid = (brd.get_M1()) ? 0x00 : 0xFE;
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
// 以下の記述は MWX ライブラリでは利用できません。
#include <iostream>
std::cout << "hello world" << std::endl;
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(u8devid);
the_twelite.begin(); // start twelite!
Analogue.setup(true, ANALOGUE::KICK_BY_TIMER0);
	Analogue.begin(pack_bits(
						BRD_APPTWELITE::PIN_AI1,
						BRD_APPTWELITE::PIN_AI2,
						BRD_APPTWELITE::PIN_AI3,
						BRD_APPTWELITE::PIN_AI4,
				   	PIN_ANALOGUE::VCC));
Buttons.setup(5);
Buttons.begin(pack_bits(
						BRD_APPTWELITE::PIN_DI1,
						BRD_APPTWELITE::PIN_DI2,
						BRD_APPTWELITE::PIN_DI3,
						BRD_APPTWELITE::PIN_DI4),
					5, 		// history count
					4);  	// tick delta
Timer0.begin(32, true); // 32hz timer
Serial << "--- BRD_APPTWELITE("
       << int(u8devid) 
       << ") ---" << mwx::crlf;
/*** loop procedure (called every event) */
void loop() {
	if (Buttons.available()) {
		uint32_t bp, bc;
		Buttons.read(bp, bc);

		u8DI_BM = uint8_t(collect_bits(bp, 
							BRD_APPTWELITE::PIN_DI4,   // bit3
							BRD_APPTWELITE::PIN_DI3,   // bit2
							BRD_APPTWELITE::PIN_DI2,   // bit1
							BRD_APPTWELITE::PIN_DI1)); // bit0

		transmit();
	}

	if (Analogue.available()) {
		au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
		au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
		au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
		au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
		au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
	}

	if (Timer0.available()) {
		static uint8_t u16ct;
		u16ct++;

		if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
			if ((u16ct % 32) == 0) { // every 32ticks of Timer0
				transmit();
			}
		}
	}

	// receive RF packet.
  if (the_twelite.receiver.available()) {
		receive();
	}
}
	if (Buttons.available()) {
		uint32_t bp, bc;
		Buttons.read(bp, bc);
u8DI_BM = uint8_t(collect_bits(bp, 
		BRD_APPTWELITE::PIN_DI4,   // bit3
		BRD_APPTWELITE::PIN_DI3,   // bit2
		BRD_APPTWELITE::PIN_DI2,   // bit1
		BRD_APPTWELITE::PIN_DI1)); // bit0

/* collect_bits は以下の処理を行います。
u8DI_BM = 0;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI1)) u8DI_BM |= 1;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI2)) u8DI_BM |= 2;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI3)) u8DI_BM |= 4;
if (bp & (1UL << BRD_APPTWELITE::PIN_DI4)) u8DI_BM |= 8;
*/
transmit();
if (Analogue.available()) {
	au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC);
	au16AI[1] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI1);
	au16AI[2] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI2);
	au16AI[3] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI3);
	au16AI[4] = Analogue.read_raw(BRD_APPTWELITE::PIN_AI4);
}
if (Timer0.available()) {
	static uint8_t u16ct;
	u16ct++;

	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
			transmit();
		}
	}
}
if (the_twelite.receiver.available()) {
	receive();
}
MWX_APIRET transmit() {
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		Serial << "..DI=" << format("%04b ", u8DI_BM);
		Serial << format("ADC=%04d/%04d/%04d/%04d ", au16AI[1], au16AI[2], au16AI[3], au16AI[4]);
		Serial << "Vcc=" << format("%04d ", au16AI[0]);
		Serial << " --> transmit" << mwx::crlf;

		// set tx packet behavior
		pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x1) // set retry (0x1 send two times in total)
			<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
			, uint8_t(u8DI_BM)
		);

		for (auto&& x : au16AI) {
			pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
		}
		
		// do transmit 
		return pkt.transmit();
	}
	return MWX_APIRET(false, 0);
}
MWX_APIRET transmit()
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
pkt << tx_addr(u8devid == 0 ? 0xFE : 0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
		<< tx_retry(0x1) // set retry (0x3 send four times in total)
		<< tx_packet_delay(0,50,10); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
# 先頭バイトのインデックス: データ型 : バイト数 : 内容

00: uint8_t[4] : 4 : 4文字識別子
04: uint8_t    : 1 : DI1..4のビットマップ
06: uint16_t   : 2 : Vccの電圧値
08: uint16_t   : 2 : AI1のADC値 (0..1023)
10: uint16_t   : 2 : AI2のADC値 (0..1023)
12: uint16_t   : 2 : AI3のADC値 (0..1023)
14: uint16_t   : 2 : AI4のADC値 (0..1023)
auto&& payl = pkt.get_payload();
payl.reserve(16); // 16バイトにリサイズ
payl[00] = APP_FOURCHAR[0];
payl[01] = APP_FOURCHAR[1];
...
payl[08] = (au16AI[0] & 0xFF00) >> 8; //Vcc
payl[09] = (au16AI[0] & 0xFF);
...
payl[14] = (au16AI[4] & 0xFF00) >> 8; // AI4
payl[15] = (au16AI[4] & 0xFF);
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(APP_FOURCHAR, 4) // string should be paired with length explicitly.
	, uint8_t(u8DI_BM)
);

for (auto&& x : au16AI) {
	pack_bytes(pkt.get_payload(), uint16_t(x)); // adc values
}
for(int i = 0; i < sizeof(au16AI)/sizeof(uint16_t)); i++) {
  pack_bytes(pkt.get_payload(), au16AI[i]);
}
return pkt.transmit();
void receive() {	
	auto&& rx = the_twelite.receiver.read();

  // expand the packet payload
	char fourchars[5]{};
	auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
    );

	// check header
	if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }

	// 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]
	);

	Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
	for (auto&& x : au16AI_remote) {
		Serial << format("/%04d", x);
	}
	Serial << mwx::crlf;

	// set local DO
	digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
	digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);

	// set local PWM : duty is set 0..1024, so 1023 is set 1024.
	Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
	Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
	Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
	Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
}
auto&& rx = the_twelite.receiver.read();
Serial << format("..receive(%08x/%d) : ",
   rx.get_addr_src_long(), rx.get_addr_src_lid());
char fourchars[5]{};
auto&& np = expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
	, make_pair((uint8_t*)fourchars, 4)  // 4bytes of msg
  );
if (strncmp(APP_FOURCHAR, fourchars, 4)) { return; }
	// 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]
	);
Serial << format("DI:%04b", u8DI_BM_remote & 0x0F);
for (auto&& x : au16AI_remote) {
	Serial << format("/%04d", x);
}
Serial << mwx::crlf;
// set local DO
digitalWrite(BRD_APPTWELITE::PIN_DO1, (u8DI_BM_remote & 1) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO2, (u8DI_BM_remote & 2) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO3, (u8DI_BM_remote & 4) ? HIGH : LOW);
digitalWrite(BRD_APPTWELITE::PIN_DO4, (u8DI_BM_remote & 8) ? HIGH : LOW);

// set local PWM : duty is set 0..1024, so 1023 is set 1024.
Timer1.change_duty(au16AI_remote[1] == 1023 ? 1024 : au16AI_remote[1]);
Timer2.change_duty(au16AI_remote[2] == 1023 ? 1024 : au16AI_remote[2]);
Timer3.change_duty(au16AI_remote[3] == 1023 ? 1024 : au16AI_remote[3]);
Timer4.change_duty(au16AI_remote[4] == 1023 ? 1024 : au16AI_remote[4]);
#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_AMB> // include the board support of PAL_AMB
void setup() {
	/*** SETUP section */
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_AMB>();
	// now it can read DIP sw status.
	u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
	if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

	// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

	// the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	Wire.begin(); // start two wire serial bus.
	Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	the_twelite.begin(); // start twelite!

	startSensorCapture(); // start sensor capture!

	/*** INIT message */
	Serial << "--- PAL_AMB:" << FOURCHARS << " ---" << mwx::crlf;
}
auto&& brd = the_twelite.board.use<PAL_AMB>();

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)
Wire.begin(); // start two wire serial bus.
startSensorCapture();
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()) {
			// this will take around 50ms.
			// note: to save battery life, perform sleeping to wait finish of sensor capture.
			brd.sns_LTR308ALS.process_ev(E_EVENT_TICK_TIMER);
		}

		if (!brd.sns_SHTC3.available()) {
			brd.sns_SHTC3.process_ev(E_EVENT_TICK_TIMER);
		}

		// now sensor data is ready.
		if (brd.sns_LTR308ALS.available() && brd.sns_SHTC3.available() && !b_transmit) {
			Serial << "..finish sensor capture." << mwx::crlf
				<< "  LTR308ALS: lumi=" << int(brd.sns_LTR308ALS.get_luminance()) << mwx::crlf
				<< "  SHTC3    : temp=" << brd.sns_SHTC3.get_temp() << 'C' << mwx::crlf
				<< "             humd=" << brd.sns_SHTC3.get_humid() << '%' << mwx::crlf
				<< mwx::flush;

			 // get new packet instance.
			if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
				// set tx packet behavior
				pkt << tx_addr(0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
					<< tx_retry(0x1) // set retry (0x1 send two times in total)
					<< tx_packet_delay(0, 0, 2); // send packet w/ delay

				// prepare packet payload
				pack_bytes(pkt.get_payload() // set payload data objects.
					, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
					, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
					, uint16_t(brd.sns_SHTC3.get_temp())
					, uint16_t(brd.sns_SHTC3.get_humid())
				);

				// do transmit
				MWX_APIRET ret = pkt.transmit();
				Serial << "..transmit request by id = " << int(ret.get_value()) << '.' << mwx::crlf << mwx::flush;

				if (ret) {
					u8txid = ret.get_value() & 0xFF;
					b_transmit = true;
				}
				else {
					// fail to request
					sleepNow();
				}
			}
		}
	}

	// wait to complete transmission.
	if (b_transmit) {
		if (the_twelite.tx_status.is_complete(u8txid)) {		
			Serial << "..transmit complete." << mwx::crlf << mwx::flush;

			// now sleeping
			sleepNow();
		}
	}
}
	if (TickTimer.available()) {
		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);
		}
		if (brd.sns_LTR308ALS.available() 
						&& brd.sns_SHTC3.available() && !b_transmit) {
	pkt << tx_addr(0x00)  // 親機0x00宛
		<< tx_retry(0x1)    // リトライ1回
		<< tx_packet_delay(0, 0, 2); // 遅延は最小限
	// prepare packet payload
	pack_bytes(pkt.get_payload() 
		, make_pair(FOURCHARS, 4)  
		, uint32_t(brd.sns_LTR308ALS.get_luminance()) // luminance
		, uint16_t(brd.sns_SHTC3.get_temp_cent())
		, uint16_t(brd.sns_SHTC3.get_humid_per_dmil())
	);
	// do transmit
	MWX_APIRET ret = pkt.transmit();

	if (ret) {
		u8txid = ret.get_value() & 0xFF;
		b_transmit = true;
	}
	else {
		// fail to request
		sleepNow();
	}
// wait to complete transmission.
if (b_transmit) {
    if (the_twelite.tx_status.is_complete(u8txid)) {        
        Serial << "..transmit complete." << mwx::crlf << mwx::flush;

        // now sleeping
        sleepNow();
    }
}
void startSensorCapture() {
	auto&& brd = the_twelite.board.use<PAL_AMB>();

	// start sensor capture
	brd.sns_SHTC3.begin();
	brd.sns_LTR308ALS.begin();
	b_transmit = false;
}
void sleepNow() {
	uint32_t u32ct = 1750 + random(0,500);
	Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;

	the_twelite.sleep(u32ct);
}
void wakeup() {
	Serial	<< mwx::crlf
			<< "--- PAL_AMB:" << FOURCHARS << " wake up ---"
			<< mwx::crlf
			<< "..start sensor capture again."
			<< mwx::crlf;
	startSensorCapture();
}
uint32_t t_start;

  // 時間待ちの処理を開始した時点でタイムスタンプを保存
  t_start = millis();

...

  // loop() でタイムアウトのチェック
  if (millis() - t_start > 100) {
    sleepNow();
  }
// use twelite mwx c++ template library
#include <TWELITE>
#include <NWK_SIMPLE>
// application ID
const uint32_t APP_ID = 0x1234abcd;

// channel
const uint8_t CHANNEL = 13;

// DIO pins
const uint8_t PIN_BTN = 12;

/*** function prototype */
void vTransmit(const char* msg, uint32_t addr);

/*** application defs */
// packet message
const int MSG_LEN = 4;
const char MSG_PING[] = "PING";
const char MSG_PONG[] = "PONG";
void setup() {
	/*** SETUP section */
	Buttons.setup(5); // init button manager with 5 history table.
	Analogue.setup(true, 50); // setup analogue read (check every 50ms)

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
	nwksmpl << NWK_SIMPLE::logical_id(0xFE) // set Logical ID. (0xFE means a child device with no ID)
	        << NWK_SIMPLE::repeat_max(3);   // can repeat a packet up to three times. (being kind of a router)

	/*** BEGIN section */
	Buttons.begin(pack_bits(PIN_BTN), 5, 10); // check every 10ms, a change is reported by 5 consequent values.
	Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC)); // _start continuous adc capture.

	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
}
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)
// 以下の記述は MWX ライブラリでは利用できません。
#include <iostream>
std::cout << "hello world" << std::endl;
auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
nwksmpl << NWK_SIMPLE::logical_id(0xFE);
        << NWK_SIMPLE::repeat_max(3);
the_twelite.begin(); // start twelite!
Analogue.setup(true);
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC), 50); 
Buttons.setup(5);
Buttons.begin(pack_bits(PIN_BTN),
					5, 		// history count
					10);  	// tick delta
Serial << "--- PingPong sample (press 't' to transmit) ---" << mwx::crlf;
void loop() {
	  // read from serial
		while(Serial.available())  {
				int c = Serial.read();
				Serial << mwx::crlf << char(c) << ':';
				switch(c) {
				    case 't':
				    	  vTransmit(MSG_PING, 0xFF);
				        break;
				    default:
							  break;
				}
		}


	// Button press
	if (Buttons.available()) {
		uint32_t btn_state, change_mask;
		Buttons.read(btn_state, change_mask);

		// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
		if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
			// PIN_BTN pressed
			vTransmit(MSG_PING, 0xFF);
		}
	}

	// receive RF packet.
    while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();

		// rx >> Serial; // debugging (display longer packet information)

		uint8_t msg[MSG_LEN];
		uint16_t adcval, volt;
		uint32_t timestamp;

		// expand packet payload (shall match with sent packet data structure, see pack_bytes())
		expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
					, msg       // 4bytes of msg
								//   also can be -> std::make_pair(&msg[0], MSG_LEN)
					, adcval    // 2bytes, A1 value [0..1023]
				    , volt      // 2bytes, Module VCC[mV]
					, timestamp // 4bytes of timestamp
        );
		
		// if PING packet, respond pong!
        if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
			// transmit a PONG packet with specifying the address.
            vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
        }

		// display the packet
		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                    , rx.get_psRxDataApp()->u32SrcAddr
                    , rx.get_lqi()
                    , rx.get_length()
					, rx.get_psRxDataApp()->u8Seq
                    )
				<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
					, msg
					, adcval
					, volt
					, timestamp
					)
               << mwx::crlf
			   << mwx::flush;
	}
}
		while(Serial.available())  {
				int c = Serial.read();
				Serial << mwx::crlf << char(c) << ':';
				switch(c) {
				    case 't':
				    	  vTransmit(MSG_PING, 0xFF);
				        break;
				    default:
							  break;
				}
		}
	if (Buttons.available()) {
		uint32_t btn_state, change_mask;
		Buttons.read(btn_state, change_mask);
// Serial << fmt("<BTN %b:%b>", btn_state, change_mask);
if (!(change_mask & 0x80000000) && (btn_state && (1UL << PIN_BTN))) {
	// PIN_BTN pressed
	vTransmit(MSG_PING, 0xFF);
if (Timer0.available()) {
	static uint8_t u16ct;
	u16ct++;

	if (u8DI_BM != 0xFF && au16AI[0] != 0xFFFF) { // finished the first capture
		if ((u16ct % 32) == 0) { // every 32ticks of Timer0
			transmit();
		}
	}
}
while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();

		uint8_t msg[MSG_LEN];
		uint16_t adcval, volt;
		uint32_t timestamp;

		// expand packet payload (shall match with sent packet data structure, see pack_bytes())
		expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
					, msg       // 4bytes of msg
											//   also can be -> std::make_pair(&msg[0], MSG_LEN)
					, adcval    // 2bytes, A1 value [0..1023]
				  , volt      // 2bytes, Module VCC[mV]
					, timestamp // 4bytes of timestamp
        );
		
		// if PING packet, respond pong!
    if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
				// transmit a PONG packet with specifying the address.
        vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
    }

		// display the packet
		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                    , rx.get_psRxDataApp()->u32SrcAddr
                    , rx.get_lqi()
                    , rx.get_length()
					, rx.get_psRxDataApp()->u8Seq
                    )
				<< format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
					, msg
					, adcval
					, volt
					, timestamp
					)
               << mwx::crlf
			   << mwx::flush;
	}
while (the_twelite.receiver.available()) {
		auto&& rx = the_twelite.receiver.read();
Serial << format("..receive(%08x/%d) : ",
   rx.get_addr_src_long(), rx.get_addr_src_lid());
uint8_t msg[MSG_LEN];
uint16_t adcval, volt;
uint32_t timestamp;

// expand packet payload (shall match with sent packet data structure, see pack_bytes())
expand_bytes(rx.get_payload().begin(), rx.get_payload().end()
		, msg       // 4bytes of msg
								//   also can be -> std::make_pair(&msg[0], MSG_LEN)
		, adcval    // 2bytes, A1 value [0..1023]
	  , volt      // 2bytes, Module VCC[mV]
		, timestamp // 4bytes of timestamp
    );
if (!strncmp((const char*)msg, "PING", MSG_LEN)) {
    vTransmit(MSG_PONG, rx.get_psRxDataApp()->u32SrcAddr);
}
		Serial << format("<RX ad=%x/lq=%d/ln=%d/sq=%d:" // note: up to 4 args!
                    , rx.get_psRxDataApp()->u32SrcAddr
                    , rx.get_lqi()
                    , rx.get_length()
										, rx.get_psRxDataApp()->u8Seq
                    )
           << format(" %s AD=%d V=%d TS=%dms>" // note: up to 4 args!
                    , msg
                    , adcval
                    , volt
                    , timestamp
                    )
         << mwx::crlf
			   << mwx::flush;
void vTransmit(const char* msg, uint32_t addr) {
	Serial << "vTransmit()" << mwx::crlf;

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		// set tx packet behavior
		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x3) // set retry (0x3 send four times in total)
			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
			, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
			, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
			, uint32_t(millis()) // put timestamp here.
		);
	
		// do transmit 
		pkt.transmit();
	}
}
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		pkt << tx_addr(addr)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x3) // set retry (0x3 send four times in total)
			<< tx_packet_delay(100,200,20); // send packet w/ delay (send first packet with randomized delay from 100 to 200ms, repeat every 20ms)
# 先頭バイトのインデックス: データ型 : バイト数 : 内容

00: uint8_t[4] : 4 : 4文字識別子
08: uint16_t   : 2 : AI1のADC値 (0..1023)
06: uint16_t   : 2 : Vccの電圧値 (2000..3600)
10: uint32_t   : 4 : millis()システム時間
// prepare packet payload
pack_bytes(pkt.get_payload() // set payload data objects.
	, make_pair(msg, MSG_LEN) // string should be paired with length explicitly.
	, uint16_t(analogRead(PIN_ANALOGUE::A1)) // possible numerical values types are uint8_t, uint16_t, uint32_t. (do not put other types)
	, uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)) // A1 and VCC values (note: alalog read is valid after the first (Analogue.available() == true).)
	, uint32_t(millis()) // put timestamp here.
);
pkt.transmit();
// 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:: が必要。
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");
  }
}
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(); }
}
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();
	}
}
#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!
}
|====APP====:==HEAP==..   :==STACK==|
0                                  32KB
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 {}
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 インタフェースを実装しているため、<< 演算子を用いてデータを書き込むことができます。

smplque

FIFOキューを実装しています。キューのサイズはテンプレートのパラメータで決定します。割り込み禁止を用いキューを操作するためのテンプレート引数もあります。

クラス名

内容

alloc_attach

すでに確保済みのバッファメモリを指定する。

Cライブラリ向けに確保したメモリ領域を管理したいとき、同じバッファ領域の分断領域として処理したい時などに使用します。

alloc_static

クラス内に静的配列として確保する。

事前にサイズが決まっていたり、一時利用の領域として使用します。

alloc_heap

ヒープ領域に確保する。 システムのヒープに確保後は破棄できませんが、初期化時にアプリケーションの設定などに従い領域を確保するといった使い方に向いています。

// 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);
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
);
smplque<uint8_t, alloc_local<uint8_t, 5> > que;
que.push('a'); que.push('b'); que.pop(); que.push('c'); ...

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

while(p != e) { // pがeまで進んだ=全要素処理した
  Serial << *p;
  ++p; // イテレータのインクリメントは前置演算子を使います。
     // この場合、p++ と記述すると、コンパイラによる最適化が行われる可能性は
       // 高いものの、コード上はイテレータのコピーが発生します。
}
#include <algorithm>
#include <cctype>

// ラムダ式による文字変換
std::for_each(que.begin(), que.end(), 
  [](uint8_t& x) { x = std::toupper(x); });

// 範囲for文
for (uint8_t x : que) {
  Serial << 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());
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;
	}
};
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);
}
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種類ある
  // ...
}
// 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;
  }
}
  	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;
    
// mwx_appcore.cpp
void wakeup() __attribute__((weak));
void wakeup() { }
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);
	}
};
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);
	}
};
if (auto&& wrt = Wire.get_writer(SHTC3_ADDRESS)) {
	Serial << "{I2C SHTC3 connected.";
	wrt << SHTC3_TRIG_H;
	wrt << SHTC3_TRIG_L;
	Serial << " end}";
}
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}";
}
			writer& operator << (int v) {
				_wire.write(uint8_t(v & 0xFF));
				return *this;
			}
MWSDKのコンパイラ対応
別の手法
Act_samples
  +-PingPong
    +-PingPong.cpp   : アクトファイル
    +-build          : ビルドディレクトリ
    +-.vscode        : VS Code 用の設定ファイル    
SomeDir
  +-AlphaBravo
    +-PingPong.cpp -> AplhaBravo.cpp ※ファイル名を変更
    +-build          : ビルドディレクトリ
    +-.vscode        : VS Code 用の設定ファイル

API

Act/behavior Programming Interface

MWXライブラリのAPIは、今後、改善を目的として仕様の変更を行う場合があります。

MONOSTICK BLUEまたはRED
BLUE PAL または RED PAL
AMBIENT SENSE PAL
PAL_AMBアクト
<NWK_SIMPLE>
環境センサーパル AMBIENT SENSE PAL
BRD_APPTWELITEの解説
Parent_MONOSTICK
<PAL_AMB>
<PAL_AMB>
PAL_AMB-UseNap
MONOSTICK BLUE または RED
TWELITE R
TWELITE DIP
<NWK_SIMPLE>
https://gcc.gnu.org/gcc-4.7/cxx0x_status.html
https://cpprefjp.github.io/implementation-status.html
CRTP (Curiously recurring template pattern : 奇妙に再帰したテンプレートパターン)
printfライブラリ
Makefile の解説
startSensorCapture()

クラスオブジェクト

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

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

役割

例

親機

最低限 M1=GND, DI1:ボタン, DO1:LEDの配線をしておく。

子機

最低限 M1=オープン, DI1:ボタン, DO1:LEDの配線をしておく。

役割

例

親機

子機

PAL_MOT

アクトの機能

  • 動作センサーパル MOTION SENSE PAL を用い、加速度センサーの加速度を連続的に計測し、無線送信します。

  • コイン電池で動作させるための、スリープ機能を利用します。

アクトの使い方

必要なTWELITE

役割

例

親機

子機

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>

setup()

void setup() {
	/*** SETUP section */
	// board
	auto&& brd = the_twelite.board.use<PAL_MOT>();
	brd.set_led(LED_TIMER::BLINK, 100);

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)     
		<< TWENET::channel(CHANNEL);

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(0xFE); 
	
	/*** BEGIN section */
	the_twelite.begin(); // start twelite!
	brd.sns_MC3630.begin(SnsMC3630::Settings(
		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));

	/*** INIT message */
	Serial << "--- PAL_MOT(Cont):" << FOURCHARS 
				 << " ---" << mwx::crlf;
}

最初にボードビヘイビア<PAL_MOT>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

auto&& brd = the_twelite.board.use<PAL_MOT>();

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

加速度センサーの初期化

	brd.sns_MC3630.begin(SnsMC3630::Settings(
		SnsMC3630::MODE_LP_14HZ, SnsMC3630::RANGE_PLUS_MINUS_4G));

加速度センサーの計測を開始します。加速度センサーの設定(SnsMC3630::Settings)には計測周波数と測定レンジを指定します。ここでは14HZの計測(SnsMC3630::MODE_LP_14HZ)で、±4Gのレンジ(SnsMC3630::RANGE_PLUS_MINUS_4G)で計測します。

開始後は加速度センサーの計測が秒14回行われ、その値はセンサー内部のFIFOキューに保存されます。センサーに28回分の計測が終わった時点で通知されます。

begin()

begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

void begin() {
	sleepNow(); // the first time is just sleeping.
}

setup()終了後にsleepNow()を呼び出し初回スリープを実行します。

sleepNow()

void sleepNow() {
	pinMode(PAL_MOT::PIN_SNS_INT, WAKE_FALLING);
	the_twelite.sleep(60000, false);
}

スリープに入るまえに加速度センサーのDIOピンの割り込み設定をします。FIFOキューが一定数まで到達したときに発生する割り込みです。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。

3行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。

wakeup()

加速度センサーのFIFO割り込みにより、スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。

void wakeup() {
	Serial << "--- PAL_MOT(Cont):" << FOURCHARS
	       << " wake up ---" << mwx::crlf;

	b_transmit = false;
	txid[0] = 0xFFFF;
	txid[1] = 0xFFFF;
}

ここではloop()で使用する変数の初期化を行っています。

loop()

ここでは、加速度センサー内のFIFOキューに格納された加速度情報を取り出し、これをもとにパケット送信を行います。パケット送信完了後に再びスリープを実行します。

void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	if (!b_transmit) {
		if (!brd.sns_MC3630.available()) {
			Serial << "..sensor is not available." 
					<< mwx::crlf << mwx::flush;
			sleepNow();
		}

		// send a packet
		Serial << "..finish sensor capture." << mwx::crlf
			<< "  seq=" << int(brd.sns_MC3630.get_que().back().t) 
			<< "/ct=" << int(brd.sns_MC3630.get_que().size());

		// calc average in the queue.
		{
			int32_t x = 0, y = 0, z = 0;
			for (auto&& v: brd.sns_MC3630.get_que()) {
				x += v.x;
				y += v.y;
				z += v.z;
			}
			x /= brd.sns_MC3630.get_que().size();
			y /= brd.sns_MC3630.get_que().size();
			z /= brd.sns_MC3630.get_que().size();

			Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
		}

		for (int ip = 0; ip < 2; ip++) {
			if(auto&& pkt = 
				the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())
				
				// set tx packet behavior
				pkt << tx_addr(0x00)
					<< tx_retry(0x1)
					<< tx_packet_delay(0, 0, 2);
			
				// prepare packet (first)
				uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
									? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
				uint16_t seq = brd.sns_MC3630.get_que().front().t;
			
				pack_bytes(pkt.get_payload()
					, make_pair(FOURCHARS, 4)
					, seq 
					, siz
				);

				// store sensor data (36bits into 5byts, alas 4bits are not used...)
				for (int i = 0; i < siz; i++) {
					auto&& v = brd.sns_MC3630.get_que().front();
					uint32_t v1;

					v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
						| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
						| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
					uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
					pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
					brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
				}

				// perform transmit
				MWX_APIRET ret = pkt.transmit();

				if (ret) {
					Serial << "..txreq(" << int(ret.get_value()) << ')';
					txid[ip] = ret.get_value() & 0xFF;
				} else {
					sleepNow();
				}
			}
		}

		// finished tx request
		b_transmit = true;
	} else {
		if(		the_twelite.tx_status.is_complete(txid[0])
			 && the_twelite.tx_status.is_complete(txid[1]) ) {

			sleepNow();
		}
	}
}

b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。

	if (!b_transmit) {

最初にセンサーがavailableかどうかを確認します。割り込み起床後であるため、availableでないのは通常ではなく、そのままスリープします。

if (!brd.sns_MC3630.available()) {
	Serial << "..sensor is not available." 
			<< mwx::crlf << mwx::flush;
	sleepNow();
}

無線送信パケットでは使用しないのですが、取り出した加速度の情報を確認してみます。

Serial << "..finish sensor capture." << mwx::crlf
	<< "  seq=" << int(brd.sns_MC3630.get_que().front().t) 
	<< "/ct=" << int(brd.sns_MC3630.get_que().size());

// calc average in the queue.
{
	int32_t x = 0, y = 0, z = 0;
	for (auto&& v: brd.sns_MC3630.get_que()) {
		x += v.x;
		y += v.y;
		z += v.z;
	}
	x /= brd.sns_MC3630.get_que().size();
	y /= brd.sns_MC3630.get_que().size();
	z /= brd.sns_MC3630.get_que().size();

	Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;
}

加速度センサーの計測結果はbrd.sns_MC3630.get_que()で得られるFIFOキューに格納されます。

加速度センサーの計測結果を格納している構造体 axis_xyzt は x, y, z の三軸の情報に加え、続き番号 t が格納されています。

格納されているサンプル数はキューのサイズ(brd.sns_MC3630.get_que().size())を読み出すことで確認できます。通常は28サンプルですが処理の遅延等によりもう少し進む場合もあります。最初のサンプルはfront()で取得することができます。その続き番号はfront().tになります。

ここでは、サンプルをキューから取り出す前にサンプルの平均をとってみます。キューの各要素にはfor文(for (auto&& v: brd.sns_MC3630.get_que()) { ... }) でアクセスできます。for文内の v.x, v.y, v.z が各要素になります。ここでは各要素の合計を計算しています。for文終了後は要素数で割ることで平均を計算しています。

次にパケットを生成して送信要求を行いますが、データ量が大きいため2回に分けて送信します。そのため送信処理がfor文で2回行われます。

		for (int ip = 0; ip < 2; ip++) {

送信するパケットに含めるサンプル数とサンプル最初の続き番号をパケットのペイロードの先頭部分に格納します。

// prepare packet (first)
uint8_t siz = (brd.sns_MC3630.get_que().size() >= MAX_SAMP_IN_PKT)
? MAX_SAMP_IN_PKT : brd.sns_MC3630.get_que().size();
uint16_t seq = brd.sns_MC3630.get_que().front().t;

pack_bytes(pkt.get_payload()
	, make_pair(FOURCHARS, 4)
	, seq 
	, siz
);

最後に加速度データを格納します。先程は平均値の計算のためにキューの各要素を参照のみしましたが、ここではキューから1サンプルずつ読み出してパケットのペイロードに格納します。

for (int i = 0; i < siz; i++) {
	auto&& v = brd.sns_MC3630.get_que().front();
	uint32_t v1;

	v1  = ((uint16_t(v.x/2) & 4095) << 20)  // X:12bits
		| ((uint16_t(v.y/2) & 4095) <<  8)  // Y:12bits
		| ((uint16_t(v.z/2) & 4095) >>  4); // Z:8bits from MSB
	uint8_t v2 = (uint16_t(v.z/2) & 255);   // Z:4bits from LSB
	pack_bytes(pkt.get_payload(), v1, v2); // add into pacekt entry.
	brd.sns_MC3630.get_que().pop(); // pop an entry from queue.
}

加速度センサーからのデータキューの先頭を読み出すのは.front()を用います。読みだした後.pop()を用いて先頭キューを開放します。

加速度センサーから取得されるデータは1Gを1000としたミリGの単位です。レンジを±4Gとしているため、12bitの範囲に入るように2で割って格納します。データ数を節約するため最初の4バイトにX,Y軸とZ軸の上位8bitを格納し、次の1バイトにZ軸の下位4bitの合計5バイトを生成します。

2回分の送信待ちを行うため送信IDはtxid[]配列に格納します。

MWX_APIRET ret = pkt.transmit();

if (ret) {
	Serial << "..txreq(" << int(ret.get_value()) << ')';
	txid[ip] = ret.get_value() & 0xFF;
} else {
	sleepNow();
}

その後、loop() 中 b_transmit が trueになっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。

} else {
	if(		the_twelite.tx_status.is_complete(txid[0])
		 && the_twelite.tx_status.is_complete(txid[1]) ) {

		sleepNow();
	}
}

送信完了に確認は the_twelite.tx_status.is_complete() で行っています。txid[]は送信時に戻り値として戻されたID値です。

WirelessUART

WirelessUARTはシリアル通信を行います。

アクトの機能

  • 2台のUART接続のTWELITE同士をアスキー書式で通信する。

アクトの使い方

必要なTWELITE

いずれかを2台。

アクトの解説

setup()

void setup() {
	/*** SETUP section */
	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle();  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	
	uid = random(1, 5); // set uid by random() (1..4)
	nwk	<< NWK_SIMPLE::logical_id(uid); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	SerialParser.begin(PARSER::ASCII, 128); // Initialize the serial parser
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- WirelessUart (id=" << int(uid) << ") ---" << mwx::crlf;
}

論理IDはrandom(1,5)により1,2,3,4のいずれかの値に割り当てています。通常は、論理IDを設定保存したデータ、またはDIP SWといったハードウェアの情報から生成します。

SerialParser.begin(PARSER::ASCII, 128); 

loop()

while(Serial.available())  {
	if (SerialParser.parse(Serial.read())) {
		Serial << ".." << SerialParser;
		const uint8_t* b = SerialParser.get_buf().begin();
		uint8_t addr = *b; ++b; // the first byte is destination address.
		transmit(addr, b, SerialParser.get_buf().end());
	}
}

シリアルからのデータ入力があった時点で、シリアルパーサーに1バイト入力します。アスキー形式が最後まで受け付けられた時点でSerialParser.parse()はtrueを戻します。

SerialParserは内部バッファに対してsmplbufでアクセスできます。上の例ではバッファの1バイト目を送信先のアドレスとして取り出し、2バイト目から末尾までをtransmit()関数に渡します。

パケットを受信したときには、送信元を先頭バイトにし続くペイロードを格納したバッファsmplbuf_u8<128> bufを生成し、出力用のシリアルパーサーserparser_attach poutからシリアルに出力しています。

if (the_twelite.receiver.available()) {
	auto&& rx = the_twelite.receiver.read();
	
	// check the packet header.
	const uint8_t* p = rx.get_payload().begin();
	if (rx.get_length() > 4 && !strncmp((const char*)p, (const char*)FOURCHARS, 4)) {
		Serial << format("..rx from %08x/%d", rx.get_addr_src_long(), rx.get_addr_src_lid()) << mwx::crlf;

		smplbuf_u8<128> buf;
		mwx::pack_bytes(buf			
				, uint8_t(rx.get_addr_src_lid())            // src addr (LID)
				, make_pair(p+4, rx.get_payload().end()) );	// data body

		serparser_attach pout;
		pout.begin(PARSER::ASCII, buf.begin(), buf.size(), buf.size());
		Serial << pout;
	}
}

テスト用のコマンド

テストデータは必ずペースト機能を用いてターミナルに入力してください。入力にはタイムアウトがあるためです。

参考: TWE ProgrammerやTeraTermでのペーストはAlt+Vを用います。

入力の末尾にCR LFが必要です。

最初はCR LFが省略できるXで終わる系列を試してください。終端文字列が入力されない場合は、その系列は無視されます。

例

:FE00112233X

:FE001122339C

任意の子機宛に00112233を送付します。

例

:03AABBCC00112233X

:03AABBCC0011223366

子機3番に対してAABBCC00112233を送付します。

TWE Programmerのターミナル機能を用いて送付する場合は、Alt+Vキーを用いてペーストします。

PulseCounter

パルスカウンターは、マイコンを介在せず信号の立ち上がりまたは立ち下りの回数を計数するものです。不定期のパルスを計数し一定回数までカウントが進んだ時点で無線パケットで回数を送信するといった使用方法が考えられます。

アクトの機能

  • 子機側のDIO8に接続したパルスを計数し、一定時間経過後または一定数のカウントを検出した時点で無線送信する。

  • 子機側はスリープしながら動作する。

アクトの使い方

必要なTWELITE

役割

例

親機

子機

アクトの解説

setup()

// Pulse Counter setup
PulseCounter.setup();

パルスカウンターの初期化を行います。

begin()

void begin() {
	// start the pulse counter capturing
	PulseCounter.begin(
		  100 // 100 count to wakeup
		, PIN_INT_MODE::FALLING // falling edge
		);

	sleepNow();
}

パルスカウンターの動作を開始し、初回スリープを実行します。PulseCounter.begin()の最初のパラメータは、起床割り込みを発生させるためのカウント数100で、2番目は立ち下がり検出PIN_INT_MODE::FALLINGを指定しています。

wakeup()

void wakeup() {
	Serial	<< mwx::crlf
			<< "--- Pulse Counter:" << FOURCHARS << " wake up ---"
			<< mwx::crlf;

	if (!PulseCounter.available()) {
		Serial << "..pulse counter does not reach the reference value." << mwx::crlf;
		sleepNow();
	}
}

起床時にPulseCounter.available()を確認しています。availableつまりtrueになっていると、指定したカウント数以上のカウントになっていることを示します。ここではfalseの場合再スリープしています。

カウント数が指定以上の場合はloop()で送信処理と送信完了待ちを行います。

loop()

uint16_t u16ct = PulseCounter.read();

パルスカウント値の読み出しを行います。読み出した後カウンタはリセットされます。

PAL_MOT-oneshot

本サンプルは、収録バージョンによって差が大きいため本ページでは2つの解説を記載します。

アクトの解説 (v2)

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。

状態変数

enum class E_STATE {
	INIT = 0,
	START_CAPTURE,
	WAIT_CAPTURE,
	REQUEST_TX,
	WAIT_TX,
	EXIT_NORMAL,
	EXIT_FATAL
} eState;

列挙体として eState 変数を宣言しています。

begin()

void begin() { 
	// sleep immediately, waiting for the first capture.
	sleepNow();
}

setup()を終了した後に呼ばれます。ここでは初回スリープを実行しています。

wakeup()

void wakeup() {
	Serial << crlf << "--- PAL_MOT(OneShot):" 
	       << FOURCHARS << " wake up ---" << crlf;
	eState = E_STATE::INIT;
}

起床後は状態変数eStateを初期状態INITにセットしています。この後loop()が実行されます。

loop()

void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();
	bool loop_more;
	do {
	  loop_more = false;
	  switch(eState) {
 	    ...
	  }
	} while(loop_more);

loop() の基本構造は状態変数eStateによるswitch ... case節です。eStateの初期状態はINITです。loop_moreは状態変数を書き換えた直後、loop()を抜ける前にもう一度実行したいときにtrueにセットします。

以下では各case節を解説します。eStateの初期状態はINITです。

			case E_STATE::INIT:
				brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
				loop_more = true;
				eState = E_STATE::START_CAPTURE;
			break;

状態INITでは、初期化(結果格納用のキューのクリア)を行います。

			case E_STATE::START_CAPTURE:
				u32tick_capture = millis();
				brd.sns_MC3630.begin(
					// 400Hz, +/-4G range, get four samples (can be one sample)
					SnsMC3630::Settings(
						SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4)); 
				eState = E_STATE::WAIT_CAPTURE;
			break;

状態START_CAPTUREでは、MC3630センサーのFIFO取得を開始します。ここでは400Hzで4サンプル取得できた時点でFIFO割り込みが発生する設定にしています。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_captureに格納します。

			case E_STATE::WAIT_CAPTURE:
				if (brd.sns_MC3630.available()) {
					brd.sns_MC3630.end(); // stop now!
					eState = E_STATE::REQUEST_TX; loop_more = true;
				} else if ((millis() - u32tick_capture) > 100) {
					Serial << crlf << "!!!FATAL: SENSOR CAPTURE TIMEOUT.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;

状態WAIT_CAPTUREでは、FIFO割り込みを待ちます。割り込みが発生し結果格納用のキューにデータが格納されるとsns_MC3630.available()がtrueになります。

タイムアウトした場合は状態EXIT_FATALに遷移します。

			case E_STATE::REQUEST_TX:
				u32tick_tx = millis();
				txid = TxReq();
				if (txid) {
					eState = E_STATE::WAIT_TX;
				} else {
					Serial << crlf << "!!!FATAL: TX REQUEST FAILS.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;

状態REQUEST_TXではローカル定義関数TxReq()を呼び出し、得られたセンサーデータの処理と送信パケットの生成、そうし尿級を行います。タイムアウトのチェックのため、開始時点のシステム時刻をu32tick_txに格納します。

			case E_STATE::WAIT_TX:
				if(the_twelite.tx_status.is_complete(txid.get_value())) {
					eState = E_STATE::EXIT_NORMAL; loop_more = true;
				} else if (millis() - u32tick_tx > 100) {
					Serial << crlf << "!!!FATAL: TX TIMEOUT.";
					eState = E_STATE::EXIT_FATAL;
				}
			break;

状態WAIT_TXでは、無線パケットの送信完了を待ちます。

タイムアウト時には状態EXIT_FATALに遷移します。

			case E_STATE::EXIT_NORMAL:
				sleepNow();
			break;

			case E_STATE::EXIT_FATAL:
				Serial << flush;
				the_twelite.reset_system();
			break;

一連の動作が完了したときは状態EXIT_NORMALに遷移しローカル定義の関数sleepNow()を呼び出しスリープを実行します。またエラーを検出した場合は状態EXIT_FATALに遷移し、システムリセットを行います。

MWX_APIRET TxReq()

この関数では、センサーより得られたサンプル値の取得と、複数サンプルの平均値の計算、

int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
	x += v.x;
	y += v.y;
	z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();

取得サンプルの平均値を計算します。

ここでは除算を行っていますが、TWELITEマイコンには除算回路がないため、計算時間を要する演算となります。例えば以下のような改良が考えられます。

  • サンプル数を2のべき乗として、その数を変数に入れず直接指定した除算を行う(ビットシフトによる演算に最適化されます)。

  • 平均値を求めず、合計値とサンプル数を送り、受信先で計算する。

auto&& x_minmax = std::minmax_element(
	get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
	get_axis_x_iter(brd.sns_MC3630.get_que().end()));

brd.sns_MC3630.get_que().clear(); // clean up the queue

X軸の最大値と最小値を計算します。

ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。

C++ Standard Template Library のアルゴリズムを使用する例としてstd::mimmax_element紹介していますが、上述のforループ内で最大、最小を求めても構いません。

ここでキューをクリア.sns_MC3630.get_que().clear()しています。

	// prepare tx packet
	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {		
		// set tx packet behavior
		pkt << tx_addr(0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x1) // set retry (0x1 send two times in total)
			<< tx_packet_delay(0, 0, 2); // send packet w/ delay
		
		// prepare packet (first)
		pack_bytes(pkt.get_payload() // set payload data objects.
				, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
				, uint16_t(x)
				, uint16_t(y)
				, uint16_t(z)
				, uint16_t(*x_minmax.first)  // minimum of captured x
				, uint16_t(*x_minmax.second) // maximum of captured x
			);

		// perform transmit
		ret = pkt.transmit();

		if (ret) {
			Serial << "..txreq(" << int(ret.get_value()) << ')';
		}

最期にパケットの生成と送信を要求を行います。パケットには X, Y, Z 軸の加速度、X軸の最小値,Y軸の最小値を含めています。

アクトの解説 (初版)

MWSDK2020_05 版の SDK 添付のサンプルコードです。

起床→加速度センサーの取得開始→加速度センサーのFIFO割り込み待ち→加速度センサーのデータの取り出し→無線送信→スリープという流れになります。

加速度センサーは、FIFOキューが一杯になるとFIFOキューへのデータ追加を停止します。

wakeup()

起床後加速度センサーを稼働させます。

void wakeup() {
	Serial << mwx::crlf << "--- PAL_MOT(OneShot):" << FOURCHARS << " wake up ---" << mwx::crlf;
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	brd.sns_MC3630.get_que().clear(); // clear queue in advance (just in case).
	brd.sns_MC3630.begin(SnsMC3630::Settings(
			SnsMC3630::MODE_LP_400HZ, SnsMC3630::RANGE_PLUS_MINUS_4G, 4)); 
				// 400Hz, +/-4G range, get four samples (can be one sample)

	b_transmit = false;
	txid = 0xFFFF;
}

加速度センサーの結果を保存するキューの内容を抹消(.sns_MC3630.get_que().clear())しておきます。加速度センサーのサンプルの取得忘れがあったり、また停止させるまでに次のサンプルが取得したような場合を想定します。

ここで加速度センサーを都度開始します。設定は400Hz,±4G,FIFO割り込みは4サンプルとしています。4サンプルも必要ない場合は1サンプルの設定でも構いません。

loop()

void loop() {
	auto&& brd = the_twelite.board.use<PAL_MOT>();

	if (!b_transmit) {
		if (brd.sns_MC3630.available()) {
			brd.sns_MC3630.end(); // stop now!

			Serial << "..finish sensor capture." << mwx::crlf
				<< "  ct=" << int(brd.sns_MC3630.get_que().size());

			// get all samples and average them.
			int32_t x = 0, y = 0, z = 0;
			for (auto&& v: brd.sns_MC3630.get_que()) {
				x += v.x;
				y += v.y;
				z += v.z;
			}
			x /= brd.sns_MC3630.get_que().size();
			y /= brd.sns_MC3630.get_que().size();
			z /= brd.sns_MC3630.get_que().size();

			Serial << format("/ave=%d,%d,%d", x, y, z) << mwx::crlf;

			// just see X axis, min and max
			//auto&& x_axis = get_axis_x(brd.sns_MC3630.get_que());
			//auto&& x_minmax = std::minmax_element(x_axis.begin(), x_axis.end());
			auto&& x_minmax = std::minmax_element(
				get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
				get_axis_x_iter(brd.sns_MC3630.get_que().end()));

			brd.sns_MC3630.get_que().clear(); // clean up the queue

			// prepare tx packet
			if (auto&& pkt = nwk.the_twelite.network.use<NWK_SIMPLE>()) {
				auto&& pkt = nwk.prepare_tx_packet(); // get new packet instance.

				// set tx packet behavior
				pkt << tx_addr(0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
					<< tx_retry(0x1) // set retry (0x1 send two times in total)
					<< tx_packet_delay(0, 0, 2); // send packet w/ delay
				
				// prepare packet (first)
				pack_bytes(pkt.get_payload() // set payload data objects.
						, make_pair(FOURCHARS, 4)  // just to see packet identification, you can design in any.
						, uint16_t(x)
						, uint16_t(y)
						, uint16_t(z)
						, uint16_t(*x_minmax.first)  // minimum of captured x
						, uint16_t(*x_minmax.second) // maximum of captured x
					);

				// perform transmit
				MWX_APIRET ret = pkt.transmit();
				
				if (ret) {
					Serial << "..txreq(" << int(ret.get_value()) << ')';
					txid = ret.get_value() & 0xFF;
				} else {
					sleepNow();
				}
				
				// finished tx request
				b_transmit = true;
			}
		}
	} else {
		// wait until transmit completion.
		if(the_twelite.tx_status.is_complete(txid)) {
			sleepNow();
		}
	}
}

このアクトでは、サンプル取得後、すぐに加速度センサーの動作を停止します。

if (brd.sns_MC3630.available()) {
	brd.sns_MC3630.end(); // stop now!

取得サンプルの平均値を計算します。

int32_t x = 0, y = 0, z = 0;
for (auto&& v: brd.sns_MC3630.get_que()) {
	x += v.x;
	y += v.y;
	z += v.z;
}
x /= brd.sns_MC3630.get_que().size();
y /= brd.sns_MC3630.get_que().size();
z /= brd.sns_MC3630.get_que().size();

X軸の最大値と最小値を計算します。

auto&& x_minmax = std::minmax_element(
	get_axis_x_iter(brd.sns_MC3630.get_que().begin()),
	get_axis_x_iter(brd.sns_MC3630.get_que().end()));

brd.sns_MC3630.get_que().clear(); // clean up the queue

ここではイテレータとstd::minmax_element()アルゴリズムを用いて計算します。get_axis_x_iterはキューのイテレータをパラメータとして、axis_xyzt構造体の.xにアクセスするものです。

最後にキューをクリア.sns_MC3630.get_que().clear()しています。

改版履歴

軽微な修正にあたるものは、本改版履歴には記載を行わずGitHub上の改版のみとなります。必要に応じて修正を引用ください。

更新方法

TWELITE STAGE の配布パッケージリリース後の修正・追加分などはGitHubレポジトリに格納しております。必要に応じて配布パッケージの位置を差し替えて利用いただくようお願いいたします。

MWXライブラリコードの更新方法

  1. 各リリースのリンクよりGitのクローンを行うか zip 形式でソースコードをダウンロードします。

  2. 以下のディレクトリの内容を差し替えます。

.../MWSTAGE/              --- TWELITE STAGE 配布ディレクトリ
        .../MWSDK         --- MWSDKディレクトリ
              .../TWENET/current/src/mwx <-- このディレクトリを差し替える

リリース前の更新

リリース前の更新については上記に掲載する場合があります。

0.1.5 - 2020-08-05

ライブラリ名

依存バージョン

mwx

twesettings

TWENET C

1.3.4

一括ダウンロード

主な改定内容

0.1.4 - 2020-07-29 (MWSDK2020_07_UNOFFICIAL)

ライブラリ名

依存バージョン

mwx

twesettings

TWENET C

1.3.3

一括ダウンロード

主な改定内容

  • delayMilliseconds() の追加

  • digitalReadBitmap() の追加

  • delay() の精度向上

  • Serial1 インスタンスが定義されていない問題を修正

  • Analogueの割り込みハンドラが呼び出されない問題を修正

0.1.3 - 2020-05-29

MWSDK2020_05 に対応

  • 重複チェッカ duplicate_checker の初期化等に不備があり期待通りの除去を行っていなかった

  • format() の実装を機種依存の少ないものとした。また、引数を最大8までとした。64bit引数が含まれる場合は引数の数は制限される。

修正は MWSDK2020_05 を前提としています。

本修正については、更新を推奨します。

0.1.2 - 2020-04-24

MWSDK2020_04 に対応

  • Timer0..4の初期化の問題を修正

  • mwx::format() の内部処理を変更

  • インタラクティブモード対応のための実験的なコードの追加

本修正は MWSDK2020_04 を前提としています。

本修正については、更新を推奨します。

0.1.1 - 2020-02-28

パケット内の中継フラグの扱いについての問題を修正

本修正については、更新を推奨します。

0.1.0 - 2019-12-23

初版リリース (SDL 2019/12月号収録)

PAL_MAG

アクトの機能

  • 開閉センサーパル OPEN-CLOSE SENSE PAL を用い、磁気センサーの検出時に割り込み起床し、無線送信します。

  • コイン電池で動作させるための、スリープ機能を利用します。

アクトの使い方

必要なTWELITE

役割

例

親機

子機

アクトの解説

インクルード

#include <TWELITE>
#include <NWK_SIMPLE>
#include <PAL_>

setup()

void setup() {
	/*** SETUP section */
	// use PAL_AMB board support.
	auto&& brd = the_twelite.board.use<PAL_MAG>();
	// now it can read DIP sw status.
	u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
	if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

	// LED setup (use periph_led_timer, which will re-start on wakeup() automatically)
	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

	// the twelite main object.
	the_twelite
		<< TWENET::appid(APP_ID)     // set application ID (identify network group)
		<< TWENET::channel(CHANNEL); // set channel (pysical channel)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk << NWK_SIMPLE::logical_id(u8ID); // set Logical ID. (0xFE means a child device with no ID)

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- PAL_MAG:" << FOURCHARS << " ---" << mwx::crlf;
}

最初にボードビヘイビア<PAL_MAG>を登録します。ボードビヘイビアの初期化時にセンサーやDIOの初期化が行われます。最初に行うのは、ボードのDIP SWなどの状態を確認してから、ネットワークの設定などを行うといった処理が一般的だからです。

auto&& brd = the_twelite.board.use<PAL_MAG>();

u8ID = (brd.get_DIPSW_BM() & 0x07) + 1;
if (u8ID == 0) u8ID = 0xFE; // 0 is to 0xFE

ここでは、ボード上の4ビットDIP SWのうち3ビットを読み出して子機のIDとして設定しています。0の場合は、ID無しの子機(0xFE)とします。

LEDの設定を行います。ここでは 10ms おきに ON/OFF の点滅の設定をします(スリープを行い起床時間が短いアプリケーションでは、起床中は点灯するという設定とほぼ同じ意味合いになります)。

	brd.set_led(LED_TIMER::BLINK, 10); // blink (on 10ms/ off 10ms)

begin()

begin()関数はsetup()関数を終了し(そのあとTWENETの初期化が行われる)一番最初のloop()の直前で呼ばれます。

void begin() {
	sleepNow(); // the first time is just sleeping.
}

setup()終了後にsleepNow()を呼び出し初回スリープを実行します。

sleepNow()

void sleepNow() {
	uint32_t u32ct = 60000;
	
	pinMode(PAL_MAG::PIN_SNS_OUT1, PIN_MODE::WAKE_FALLING);
	pinMode(PAL_MAG::PIN_SNS_OUT2, PIN_MODE::WAKE_FALLING);

	the_twelite.sleep(u32ct);
}

スリープに入るまえに磁気センサーのDIOピンの割り込み設定をします。pinMode()を用います。2番めのパラメータはPIN_MODE::WAKE_FALLINGを指定しています。これはHIGHからLOWへピンの状態が変化したときに起床する設定です。

7行目でthe_twelite.sleep()でスリープを実行します。パラメータの60000は、TWELITE PAL ボードのウォッチドッグをリセットするために必要な起床設定です。リセットしないと60秒経過後にハードリセットがかかります。

wakeup()

スリープから復帰し起床すると wakeup() が呼び出されます。そのあとloop() が都度呼び出されます。wakeup()の前に、UARTなどの各ペリフェラルやボード上のデバイスのウェイクアップ処理(ウォッチドッグタイマーのリセットなど)が行われます。例えばLEDの点灯制御を再始動します。

void wakeup() {
	if (the_twelite.is_wokeup_by_wktimer()) {
		sleepNow();
	}
}

ここではウェイクアップタイマーからの起床の場合(the_twelite.is_wokeup_by_wktimer())は再びスリープを実行します。これは上述のウォッチドッグタイマーのリセットを行う目的のみの起床です。

磁気センサーの検出時の起床の場合は、このままloop()処理に移行します。

loop()

ここでは、検出された磁気センサーのDIOの確認を行い、パケットの送信を行い、パケット送信完了後に再びスリープを実行します。

void loop() {
	if (!b_transmit) {
		if (auto&& pkt = 
      the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet())

			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);
	
			Serial << "..sensor north=" << int(b_north) 
			       << " south=" << int(b_south) << mwx::crlf;
	
			// set tx packet behavior
			pkt << tx_addr(0x00)
				<< tx_retry(0x1)
				<< tx_packet_delay(0, 0, 2);
	
			// prepare packet payload
			pack_bytes(pkt.get_payload()
				, make_pair(FOURCHARS, 4) 
				, b_north
				, b_south
			);
	
			// do transmit
			MWX_APIRET ret = pkt.transmit();
	
			if (ret) {
				u8txid = ret.get_value() & 0xFF;
				b_transmit = true;
			}
			else {
				// fail to request
				sleepNow();
			}
		} else {
		  sleepNow();
		}
	} else { 
		if (the_twelite.tx_status.is_complete(u8txid)) {		
			b_transmit = 0;
			sleepNow();
		}
	}
}

b_transmit変数によってloop()内の振る舞いを制御しています。送信要求が成功した後、この値を1にセットしパケット送信完了待ちを行います。

	if (!b_transmit) {

磁気センサーの検出DIOピンの確認を行います。検出ピンは二種類あります。N極検知とS極検知です。単に磁石が近づいたことだけを知りたいならいずれかのピンの検出されたことが条件となります。

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);

起床要因のピンを確認するにはthe_twelite.is_wokeup_by_dio()を用います。パラメータはピン番号です。戻り値をuint8_tに格納しているのはパケットのペイロードに格納するためです。

通信条件の設定やペイロードにデータを格納後、送信を行います。

// do transmit
MWX_APIRET ret = pkt.transmit();

その後、loop() 中 b_transmit が true になっている場合は、完了チェックを行い、完了すれば sleepNow() によりスリープします。

if (the_twelite.tx_status.is_complete(u8txid)) {		
	b_transmit = 0;
	sleepNow();
}

送信完了に確認は the_twelite.tx_status.is_complete(u8txid) で行っています。u8txidは送信時に戻り値として戻されたID値です。

PulseCounter

パルスカウンタ (mwx::periph_pulse_counter)

パルスカウンタは、マイコンのCPUが稼働していない時もパルスを読み取り計数する回路です。パルスカウンターは2系統あります。PC0はPulseCounter0, PC1はPulseCounter1に割り当てられます。

またPulseCounterはPulseCounter1の別名です。

メソッド

begin()

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

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

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

end()

検出を中止します。

available()

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

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

read()

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

Analogue

ADC (mwx::periph_analogue.hpp)

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

定数

ピンの定義

メソッド

setup()

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

begin()

begin()の呼び出し後、速やかに最初のADC処理が開始され、その終了割り込から次のピンの処理を開始します。すべての処理が終われば(指定されている場合)コールバック関数が呼び出されます。次のタイマー割り込みが発生まで待ってから新たなADC処理を開始します。

2番目のパラメータは、ACを開始するまでのタイマー割り込みの回数を指定します。例えばTickTimerは1msごとに呼び出されますが、パラメータに16を指定すれば 16msごとの処理になります。

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

end()

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

available()

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

read(), read_raw()

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

Vccはread()で読み出すことを推奨します。read_raw()の値からmVに変換するには、特別な変換式を適用する必要があるためです。

ADC完了(available)後、次のADC処理が実行するタイミング付近まで遅れて値を読み出すと、次のADC値が戻される場合があります。ADCの処理は割り込みハンドラで実施されているためloop()の処理中であっても値が更新されるためです。

ADC割り込みハンドラ

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

スリープ時の振る舞い

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

Slp_Wk_and_Tx

Slp_Wk_and_Tx は、定期起床後、何か実行(センサーデータの取得など)を行って、その結果を無線パケットとして送信するようなアプリケーションを想定した、テンプレートソースコードです。

setup(), loop() の形式では、どうしても loop() 中が判読しづらい条件分岐が発生しがちです。本Actでは、loop()中を switch構文による単純な状態遷移を用いることで、コードの見通しを良くしています。

TWELITE STAGE 2020_05 には収録されていません。以下のリンク(GitHub)より入手ください。

アクトの機能

  • 起動後、速やかにスリープする

    1. setup() : 初期化する

    2. begin() : スリープに遷移する

  • スリープ起床後、状態変数を初期化し、以下の順に動作を行う

    1. wakeup(): スリープからの起床、各初期化を行う

    2. loop()状態INIT->WORK_JOBに遷移: 何らかの処理を行う(このActでは 1ms ごとの TickCount ごとにカウンタを更新し 100 カウント後にTX状態に進む)

    3. loop() 状態TX: 送信要求を行う

    4. loop() 状態WAIT_TX: 送信完了待ちを行う

    5. loop() 状態EXIT_NORMAL: スリープする (1. に戻る)

  • loop() 状態EXIT_FATAL: エラーが発生した場合は、モジュールリセットする

アクトの解説

インクルード

パケット送信を行うため <NWK_SIMPLE> をインクルードしています。また、アプリケーションIDなど基本的な定義は "Common.h" に記述しています。

Common.h には基本的な定義に加え、以下の列挙体が定義されています。こちらを状態変数として利用します。

setup()

the_tweliteクラスオブジェクトの初期化とネットワーク <NWK_SIMPLE> の登録を行います。

begin()

setup()の直後に一度だけ呼び出されます。SleepNow()関数夜を呼び出して初回のスリープ手続きを行います。

wakeup()

起床直後に呼び出されます。ここでは状態変数eStateを初期状態E_STATE::INITにセットします。この後loop()が呼び出されます。

loop()

上記のコードは、実際のコードを簡略化したものです。

このコードではloop_moreを条件としてdo..while() 構文のループになっています。これはloop()を脱出せずに次の状態のコード(case節)を実行する目的です。

wakeup()でINIT状態にセットされていますので初回のloop()ではINITが評価されます。ここは変数の初期化しているだけです。続く状態の処理を行うのにloop()を脱出する必要がないためloop_moreをtrueにしています。

WORK_JOB状態ではTichTimer.available()になるたびにカウンタを減算し0になったら次の状態TXに遷移します。

TX状態ではvTransmit()関数を呼び出しパケット送信要求を行います。この時点ではまだパケットが送信されていないため、この時点でスリープをしてはいけません。パケット送信要求が成功したらWAIT_TX状態に遷移します。タイムアウトの管理のため、この時点でのシステム時間u32millis_txを保存します。失敗したらEXIT_FATALに遷移します(モジュールのリセット実行)。

WAIT_TX状態では送信IDに対応する送信が完了したかを判定し、完了したらEXIT_NORMAL、100ms以上経過したらEXIT_FATAL状態に遷移します。

EXIT_NORMAL状態はSleepNow()を呼び出し、スリープします。

EXIT_FATAL状態ではシステムリセットを行います。

void SleepNow()

周期スリープを行います。スリープ時間はrandom()関数を用いて、一定の時間ブレを作っています。これは複数のデバイスの送信周期が同期した場合、著しく失敗率が上がる場合があるためです。

MWX_APIRET vTransmit()

ID=0x00の親機宛に無線パケットの送信要求を行います。格納されるデータはActサンプルで共通に使われている4文字識別子(FOURCC)に加え、システム時間[ms]が含まれます。

MWX_APIRETはuint32_t型をラップしたクラスで、MSBを失敗成功のフラグとし、以下31ビットをデータとして用いています。pkt.transmit()の戻り型になっており、送信要求の成功と失敗ならびに送信IDをデータ部に格納します。

the_twelite

TWENET 利用の中核クラス (mwx::twenet)

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

概要

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

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

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

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

メソッド

MWXライブラリには、ここで紹介したメソッド以外にも定義されています。

アクト記述には直接関係ないもの、設定しても有効に機能しないもの、内部的に使用されているものが含まれます。

<<演算子 (設定)

オブジェクト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)

パラメータbEnableが1であれば常に受信回路を動作させ、他からの無線パケットを受信できるようになります。デフォルトは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を指定すると、その指定は無効になります。

MWXライブラリコード中には他にも設定項目がありますが、現時点ではライブラリの機能に無関係な設定であったり、設定を行うと矛盾を起こす可能性があるものです。

begin()

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

  • the_twelite 設定完了

  • ビヘイビアの初期化

TWENETの初期化は setup() 関数が終了した後にも実行されます。多くの処理はTWENETが終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。

例

change_channel()

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

get_channel_phys()

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

get_hw_serial()

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

sleep()

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

スリープ前に組み込みオブジェクトやビヘイビアの on_sleep() メソッドが呼び出され、スリープ前の手続きを行います。スリープ復帰後は反対に on_wakeup() メソッドにより復帰処理が行われます。

is_wokeup_by_dio()

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

is_wokeup_by_wktimer()

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

reset_system()

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

ビヘイビア

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

  • board: ボード対応のビヘイビアです。ボード上の各デバイス利用手続きが付加されます。

  • app: ユーザアプリケーションを記述したビヘイビアです。割り込みやイベント記述、ステートマシンによる状態遷移によるふるまいの記述が可能です。また複数のアプリケーション記述を定義しておいて、起動時に全く振る舞いの違うアプリケーションを選択する記述が容易に行えます。

  • settings: 設定(インタラクティブモード)を実行するためのビヘイビアです。<SET_STD>を登録します。

use<B>()

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

誤ったビヘイビアを指定した場合は、パニック動作(無限ループ)となりプログラムの動作が停止します。

グローバル変数としてビヘイビアのオブジェクトを宣言することを想定していません。利用都度use<B>()を用いてください。

ただし、グローバル変数にオブジェクトのポインタを定義して以下のように記述することは可能です。(MWXライブラリでは原則としてポインタ型の利用を最小限にとどめ参照型を利用する方針ですので、下記のような記述は推奨しません)

クラスオブジェクト

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

tx_status

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

is_complete()

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

is_success()

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

receiver

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

read()メソッドで得られる受信パケットデータは、続くパケットが受信処理時に上書きされる設計となっています。available直後に読み出してなにか短い処理をする場合は問題になることはありませんが、原則として読み出し→アプリケーションが使うため必要なデータのコピー→loop()の終了を速やかに行います。例えばloop()中で長いdelay()を行うと受信パケットの取りこぼしなどが発生します。

available()

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

read()

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

配線例 (AI1-AI4は省略可)

アクトを動作させる。

+

を用い、センサー値の取得を行います。

このアクトの解説の前にをご覧ください。

受信の確認のための解説をご覧ください。

アクトを動作させる。

+

 動作センサーパルのボードビヘイビアをインクルードします。

このアクトの解説の前にをご覧ください。

でUART接続されているなど

を初期化します。

を用いたアクト例です。

このアクトの解説の前にをご覧ください。

受信の確認のための解説をご覧ください。

アクトを動作させる。

1.

2. +

アクトでは連続的に加速度データを取得して都度無線送信していました。このアクトではスリープ復帰後に数サンプル加速度データを取得しそのデータを送ります。

このアクトの解説の前にのアクトの解説をご覧ください。

※ 最新のコードは「を参照ください。

※ 最新のコードは「を参照ください。

MWSDKの他の更新が必要になる場合があります。更新時のリリース記述を参照してください。MWSDKの更新についてはを参照ください。

ライブラリのソースコードは GitHub ()にて公開しています。ライブラリのソースコードの差し替えは、以下の手順で行ってください。

()

を追加

チャネルマネージャ の実装

()

を用い、センサー値の取得を行います。

このアクトの解説の前にをご覧ください。

受信の確認のための解説をご覧ください。

アクトを動作させる。

+

開閉センサーパルのボード ビヘイビアをインクルードします。

1番目のパラメータにはADCを行いたいポートを指定します。ポートの指定はピンの定義で述べたポート番号に対応するビットをセットしたビットマップになります。例えば PIN_ANALOGUE::A2とPIN_ANALOGUE::VCCの2つのピンの値を得たい場合は (1 <<PIN_ANALOGUE::A1 | 1<<PIN_ANALOGUE::VCC )を指定します。を用いpack_bits(PIN_ANALOGUE::A1,PIN_ANALOGUE::VCC)のように記述することもできます。

このアクトの解説の前にをご覧いただくことを推奨します。

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

network : ネットワークを実装するビヘイビアです。通常はを登録します。

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

イベントドリブンのビヘイビアの記述ではで管理します。

イベントドリブンのビヘイビアの記述ではで取得します。

TWELITE DIP
TWELITE DIP
MONOSTICK BLUEまたはRED
Parent_MONOSTICK
BLUE PAL または RED PAL
環境センサーパル AMBIENT SENSE PAL
動作センサーパル MOTION SENSE PAL
BRD_APPTWELITEの解説
Parent_MONOSTICK
<PAL_MOT>
BRD_APPTWELITEの解説
MONOSTICK BLUE または RED
TWELITE R
TWELITE DIP
シリアルパーサー
パルスカウンター
BRD_APPTWELITEの解説
Parent_MONOSTICK
PAL_MOT
PAL_MOT
こちら
https://github.com/monowireless/mwx
https://github.com/monowireless/mwx/wiki
MWSDK2020_08_UNOFFICIAL
README.md
設定ビヘイビア(インタラクティブモード機能)
MWSDK2020_07_UNOFFICIAL
README.md
https://github.com/monowireless/mwx/releases/tag/0.1.3
https://github.com/monowireless/mwx/releases/tag/0.1.2
https://github.com/monowireless/mwx/releases/tag/0.1.1
https://github.com/monowireless/mwx/releases/tag/0.1.0
開閉センサーパル OPEN-CLOSE SENSE PAL
BRD_APPTWELITEの解説
Parent_MONOSTICK
<PAL_MAG>
v2 ... 状態変数を用いた loop() 実装に書き換え
初版 ... MWSDK2020_05 版の SDK 添付
サンプルアクト>最新版の入手」
サンプルアクト>最新版の入手」
void begin(uint16_t refct = 0, 
           E_PIN_INT_MODE edge = PIN_INT_MODE::FALLING,
           uint8_t debounce = 0)

設定

連続サンプル数

最大検出周波数

0

-

100Khz

1

2

3.7Khz

2

4

2.2Khz

3

8

1.2Khz

void end()
inline bool available()
uint16_t read()

定数

種別

uint8_t PIN_ANALOGUE::A1 = 0

ADC1ピン

uint8_t PIN_ANALOGUE::A2 = 1

ADC2ピン

uint8_t PIN_ANALOGUE::A3 = 2

uint8_t PIN_ANALOGUE::D0 = 2

ADC3ピン (DIO0)

uint8_t PIN_ANALOGUE::A4 = 3

uint8_t PIN_ANALOGUE::D1 = 3

ADC4ピン (DIO1)

uint8_t PIN_ANALOGUE::VCC = 4

Vcc 電源電圧

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

パラメータ

解説

bWaitInit

trueを指定すると、半導体内部のレギュレータの初期化を待つ。

kick_ev

周期実行に指定するタイマーデバイスを指定する。指定可能なデバイスは、以下の5種類で、初回以外は割り込みハンドラ内でADが開始される。

E_AHI_DEVICE_TICK_TIMER (TickTimer)

E_AHI_DEVICE_TIMER0 .. 4 (Timer0 .. 4)

fp_on_finish

指定されたポートすべてのADCが終了後に、割り込みハンドラ内から呼び出されるコールバック関数。ADC計測値をFIFOキューなどに別途格納したい場合に利用する。

void begin(uint8_t bmPorts, uint8_t capt_tick = 1)
void begin()
void end()
inline bool available()
inline int16_t read(uint8_t s)
inline int16_t read_raw(uint8_t s)
#include <TWELITE>
#include <NWK_SIMPLE>

#include "Common.h"
enum class E_STATE {
    INIT = 0,    // INIT STATE
    WORK_JOB,    // do some job (e.g sensor capture)
    TX,          // reuest transmit
    WAIT_TX,     // wait its completion
    EXIT_NORMAL, // normal exiting.
    EXIT_FATAL   // has a fatal error (will do system reset)
};
void setup() {
	/*** SETUP section */
	txreq_stat = MWX_APIRET(false, 0);

	// the twelite main class
	the_twelite
		<< TWENET::appid(APP_ID)    // set application ID (identify network group)
		<< TWENET::channel(CHANNEL) // set channel (pysical channel)
		<< TWENET::rx_when_idle(false);  // open receive circuit (if not set, it can't listen packts from others)

	// Register Network
	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	nwk	<< NWK_SIMPLE::logical_id(DEVICE_ID); // set Logical ID. 

	/*** BEGIN section */
	the_twelite.begin(); // start twelite!

	/*** INIT message */
	Serial << "--- Sleep an Tx Act ---" << crlf;
}
void begin() {
	Serial << "..begin (run once at boot)" << crlf;
	SleepNow();
}
void wakeup() {
	Serial << crlf << int(millis()) << ":wake up!" << crlf;
	eState = E_STATE::INIT;
}
void loop() {
	bool loop_more; // if set, one more loop on state machine.
	do {
		loop_more = false; // set no-loop at the initial.
		switch(eState) {
			case E_STATE::INIT: 
				eState = E_STATE::WORK_JOB; loop_more = true;
				break;
			case E_STATE::WORK_JOB:
			  if (TickTimer.available())
			    if(--dummy_work_count == 0)
						eState = E_STATE::TX: loop_more = true;
				break;
			case E_STATE::TX:
			  txreq_stat = vTransmit();
				if(txreq_stat) {
				  u32millis_tx = millis();
				  eState = E_STATE::WAIT_TX;
				} else {
				  eState = E_STATE::EXIT_FATAL; loop_more = true;
				}
			  break;
			case E_STATE::WAIT_TX:
			  if (the_twelite.tx_status.is_complete(txreq_stat.get_value())) {
			    eState = E_STATE::EXIT_NORMAL; loop_more = true;
			  } else if (millis() - u32millis_tx > 100) {
			    eState = E_STATE::EXIT_FATAL; loop_more = true;
			  }
			  break;
			case E_STATE::EXIT_NORMAL:
				SleepNow();
				break;
			case E_STATE::EXIT_FATAL:
				the_twelite.reset_system();
				break;
		}
	} while (loop_more);
void SleepNow() {
	uint16_t u16dur = SLEEP_DUR;
	u16dur = random(SLEEP_DUR - SLEEP_DUR_TERMOR, SLEEP_DUR + SLEEP_DUR_TERMOR);

	Serial << int(millis()) << ":sleeping for " << int(u16dur) << "ms" << crlf << mwx::flush;
	the_twelite.sleep(u16dur, false);
}
MWX_APIRET vTransmit() {
	Serial << int(millis()) << ":vTransmit()" << crlf;

	if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
		// set tx packet behavior
		pkt << tx_addr(0x00)  // 0..0xFF (LID 0:parent, FE:child w/ no id, FF:LID broad cast), 0x8XXXXXXX (long address)
			<< tx_retry(0x1) // set retry (0x3 send four times in total)
			<< tx_packet_delay(0,0,2); // send packet w/ delay (send first packet with randomized delay from 0 to 0ms, repeat every 2ms)

		// prepare packet payload
		pack_bytes(pkt.get_payload() // set payload data objects.
			, make_pair(FOURCC, 4) // string should be paired with length explicitly.
			, uint32_t(millis()) // put timestamp here.
		);
		
		// do transmit 
		//return nwksmpl.transmit(pkt);
		return pkt.transmit(); 
	}

	return MWX_APIRET(false, 0);
}
void setup() {
  ...
 	the_twelite
		<< TWENET::appid(APP_ID)    
		<< TWENET::channel(CHANNEL) 
		<< TWENET::rx_when_idle();  
  ...
  the_twelite.begin();
}
// シリアル番号を得る
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);
void begin()
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();
}
inline bool change_channel(uint8_t u8Channel)
uint8_t get_channel_phys()
inline uint32_t get_hw_serial()
inline void sleep(
        uint32_t u32Periodms, 
        bool_t bPeriodic = true, 
        bool_t bRamOff = false, 
        uint8_t u8Device = TWENET::SLEEP_WAKETIMER_PRIMARY)

パラメータ

解説

u32Periodms

スリープ時間[ms]

bPeriodic

前回の起床時間をもとに次の起床時間を再計算する。 ※次の起床タイミングが迫っているなどの理由で、現在のタイミングからになる場合もあります。

bRamoff

trueに設定すると、RAMを保持しないスリープになる(起床後はwakeup()ではなくsetup()から再初期化する必要がある)

u8Device

スリープに用いるウェイクアップタイマーの指定。

TWENET::SLEEP_WAKETIMER_PRIMARYまたは TWENET::SLEEP_WAKETIMER_SECONDARYを指定する。

bool is_wokeup_by_dio(uint8_t port)
bool is_wokeup_by_wktimer()
inline void reset_system()
// 例
auto&& brd = the_twelite.board.use<PAL_AMB>();
void loop() {
  auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
}
NWK_SIMPLE *pNwk = nullptr;

setup() {
  auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
	pNwk = &nwk;
}

void transmit() {
  if (auto&& pkt = pNwk->prepare_tx_packet()) {
    ...
  }
}
bool is_complete(uint8_t cbid)
bool is_success(uint8_t cbid)
bool available()
packet_rx& read()
chmgr

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が稼働状態であれば、復帰後に再開します。再開後、初回確定を行います。

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の転送を行います。

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で指定したデータバイト分だけ読み飛ばします。

MONOSTICK BLUEまたはRED
Parent_MONOSTICK
BLUE PAL または RED PAL
動作センサーパル MOTION SENSE PAL
MONOSTICK BLUEまたはRED
Parent_MONOSTICK
TWELITE DIP
BLUE PAL または RED PAL
環境センサーパル AMBIENT SENSE PAL
0.1.5
0.2.5
0.1.4
0.2.4
MONOSTICK BLUEまたはRED
Parent_MONOSTICK
BLUE PAL または RED PAL
開閉センサーパル OPEN-CLOSE SENSE PAL
pack_bits
BRD_APPTWELITEの解説
https://github.com/monowireless/Act_samples
ビヘイビア
<NWK_SIMPLE>
setup()

SPI

SPIバス (MASTER) の読み書きを行います。

注意事項

定数

定数

意味

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

使用するSPIスレーブのセレクトピンを指定する。

0 : DIO19

1 : DIO0 (DIO 19 は予約されます)

2 : DIO1 (DIO 0,19 は予約されます)

settings

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

bitOrderはSPI_CONF::MSBFIRSTかSPI_CONF::LSBFIRSTを指定します。

dataModeはSPI_CONF::SPIMODE0..3を指定します。

例

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種類あります。いずれかを選択して利用します。

SerialParser

シリアルポート向き書式入力 (mwx::serial_parser)

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

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

Serial

TWELITE の UART0 ポート (mwx::serial_jen)

  • Serialオブジェクトはシステム起動時に UART0, 115200 bps で初期化され、ライブラリ内で初期化処理が行われます。ユーザコード上は、setup()から利用できます。

  • Serial1オブジェクトは、ライブラリ内で用意されていますが、初期化処理は行っていません。UART1を有効化するためには、必要な初期化手続き Serial1.setup(), Serial1.begin() を行ってください。

起動直後の setup(), wakeup() やスリープ直前の flush 処理で、出力が不安定になる場合があります。

setup()

void setup(uint16_t buf_tx, uint16_t buf_rx)

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

  • TX/RX用のFIFOバッファのメモリ確保

  • TWE_tsFILE 構造体のメモリ確保

Serial(UART0) は ライブラリ内で setup() の呼び出しが自動で行われます。ユーザによる呼び出しを行う必要はありません。

また、Serial (UART0) のバッファサイズは、コンパイル時に決定されます。マクロ MWX_SER_TX_BUFF (未指定時は 768), MWX_SER_RX_BUFF(未指定時 256) により変更できます。

パラメータ

解説

buf_tx

TX用のFIFOバッファサイズ

buf_rx

RX用のFIFOバッファサイズ

begin()

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

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

Serial (UART0) は ライブラリ内で begin() の呼び出しが自動で行われます。ユーザによる呼び出しを行う必要はありません。

パラメータ

解説

speed

UART のボーレートを指定する。

config

未使用 (8N1固定)

指定したボーレートの下2桁の数値は0に丸めて処理します。またハードウェアの制限により指定したボーレートより誤差が生じます。

end()

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

get_tsFile()

TWE_tsFILE* get_tsFile();

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

Serial (UART) では、_sSerial 構造体が定義されています。

beginTransaction(), endTransaction(), transfer(), transfer16(), transfer32()

transceiver

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

を実装し TWELITE の UART0 で入出力する。

メンバ関数版 (以下のメンバ関数を用いた入出力)
ヘルパークラス版(stream機能が使用可能)
serparser
mwx::stream

TickTimer

システムタイマー (mwx::periph_ticktimer)

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

必ず1ms刻みでavailableになる訳ではない点に注意してください。

ユーザプログラムの記述内容や、システム内部の割り込み処理などが要因で、大きな遅延が発生することもあり、イベントが飛ばされるような場合もあります。

void loop() {
  if (TickTimer.available()) {
    if ((millis() & 0x3FF) == 0) { // これは処理されない場合がある
      Serial << '*';
    }
  }
}

メソッド

available()

inline bool available()

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

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の値部を取得する。

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=true

true の時、読み込み終了時に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 = true

STOPビットを発行します。

戻り値 uint8_t

0: 成功 4: 失敗

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_stopをtrueにすると、その読み出しにおいて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;

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出力を有効にします。

change_hz()で周波数を変更することが出来ます。change_hz()ではbegin()の指定より細かい指定が可能です。

change_duty()で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番目のパラメータはデューティ比の最大値を指定します。

duty_maxは1024,4096,16384のいずれかの指定を推奨します。

内部でのカウント値の計算に除算が発生しますが、これら3つに限りビットシフトによる演算を行っていますが、これ以外の値では計算量の大きい除算処理が実行されます。

change_hz()

void change_hz(uint16_t hz, uint16_t mil = 0) 

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

Wire

二線シリアル(I2C) master の読み書き (mwx::periph_wire)

二線シリアル(I2C) master の読み書きを行います。

型定義

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

typedef uint8_t size_type;
typedef uint8_t value_type;

注意事項

API 中に STOP ビットの扱いが厳格でない呼び出しを行うものもあります。

write(), writer::operator() () には、本解説以外にもいくつか引数が定義されている。

  • 固定配列型 uint8_t cmds[]={11,12}; ... Wire.write(cmds);

  • initializer_list<> 型 Wire.write({11,12})

初期化と終了

Wire インスタンスの生成

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

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

例: -DMWX_TWOWIRE_BUFF=128

begin()

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

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

初期化せずにWireの操作を行うとTWELITE無線モジュールがハングアップします。

スリープからの起床時は、スリープ直前で動作していた場合、直前の状態に復帰します。

パラメータ

解説

u8mode

バススピードを指定する。

WIRE_100KHZ または WIRE_400KHZ を指定する。

b_portalt

ハードウェアのピン割り当てを変更する。

例

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

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

読み書き

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

その他

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

bool probe(uint8_t address)

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

割り込みハンドラの処理を記述するには、の定義が必要です。

requestFrom(), beginTransmission(), endTransmission(), write()

reader, writer

アプリケーションビヘイビア
メンバ関数版 (以下のメンバ関数を用いた入出力)
ヘルパークラス版(stream機能が使用可能)

mwx::stream

入出力ストリーム

入出力ストリームを処理する上位クラスです。

  • 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 を返します。

パラメータ

解説

戻り値 int

0: データなし 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バイト出力します。

print(), println()

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.

引数型

解説

char

1バイト出力 (数値としてフォーマットはしない)

int

整数出力 (printf の "%d")

double

数値出力 (printf の "%.2f")

uint8_t

1バイト出力する(char型と同様)

uint16_t

2バイト出力する(ビッグエンディアン順)

uint32_t

4バイト出力する(ビッグエンディアン順)

format()

printf 形式での出力

mwx::crlf

改行 CRLF の出力

mwx::flush

出力のフラッシュ

bigendian()

数値型をビッグエンディアン順で出力する。(右辺値)

std::pair<T*, T*>

バイト型の begin(), end() ポインタを格納したペア。make_pair により生成できる。Tは uint8_t 型を想定する。(右辺値)

bytelist()

std::initializer_list を用いるバイト列の出力

smplbuf<T>::to_stream()

smplbuf<T> のデータを出力する Tは uint8_t 型を想定する

バイト列として出力する際は、uint8_t, uint16_t, uint32_t 型にキャストします。また文字列として数値出力する場合は明示的にint形にキャストするようにしてください。

operator >>

// examples
Serial << "this value is" // const char*
       << int(123)
       << '.';
       << mwx::crlf;
// result -> this value is 123.

// example: SPI transmission
uint8_t val;
if (auto&& x = SPI.get_rwer()) {
  (x << (0x61)) >> null_stream(1); // discard read byte
  (x << (0x00)) >> val; // read a byte to val
}

入力処理を行います。

setup() 内では実行できません。

引数型

解説

uint8_t

1バイト入力

uint16_t

2バイト入力(ビッグエンディアン順)

uint32_t

4バイト入力(ビッグエンディアン順)

null_stream(int n)

nバイト読み捨てる

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); // set timeout as 1000ms.
uint32_t u32val;
Serial >> u32val;

入力タイムアウトとエラーを管理します。set_timeout() によりタイムアウト時間を指定し、>>演算子により入力処理を行います。所定時間内までに入力が得られない場合は get_error_status() によりエラー値を読み出せます。clear_error_status()によりエラー状況をクリアします。

引数型

解説

centisec

1/100秒単位でタイムアウト時間を設定します

エラー値

値

意味

0

エラーなし

1

エラー状況

mwx::bigendian

twe::stream にビッグエンディアン順で数値型のデータを出力する

mwx::stream の << 演算子に対して数値型をビッグエンディアンのバイト列で出力するヘルパークラスです。

Serial << mwx::bigendian(0x1234abcdUL);

// output binary -> 0x12 0x34 0xab 0xcd

コンストラクタ

template <typename T>
bigendian::bigendian(T v)

パラメータ

解説

v

uint16_t または uint32_t の型の値

packet_tx

このクラスはTWENET CライブラリのtsTxDataApp構造体のラッパクラスで、このクラスをベースとした派生クラスのオブジェクトをネットワークビヘイビアより取得して利用します。

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

送信設定

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

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

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

各設定の利用可否や意味合いは、ネットワーク ビヘイビアの仕様によります。

tx_addr

tx_addr(uint32_t addr)

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

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>では、同じ内容のパケットをu8count+1回送信します。

force_retryの設定は無視されます。

tx_packet_delay

tx_packet_delay(uint16_t u16DelayMin,
                uint16_t u16DelayMax,
                uint16_t u16RetryDur)

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

内部処理の都合で指定通りのタイミングで送信処理が始まらない場合もあります。また、IEEE802.15.4の処理でもパケット創出までの時間ブレが発生します。これらのタイミングのブレは、多くのシステムではパケットの衝突回避を行う上で有効な手立てとなります。

厳格なタイミングでのパケット送信は、IEEE802.15.4の規格の性質上、例外的な使用方法とお考え下さい。

この指定は有効です。

最初の送信から1秒を超えて再送され到達した同一パケットについては、新たなパケットが到達したとして重複除外が為されません。再送間隔を長く設定したり、中継でのパケット到達遅延により1秒を超えて同じパケットを受信する場合があります。

重複パケットの処理の設定は<NWK_SIMPLE>ビヘイビアの初期化で設定できます。

tx_process_immediate

tx_process_immediate()

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

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

この指定は有効です。

tx_ack_required

tx_ack_required()

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

<NWK_SIMPLE>では、この指定は無効です。コンパイルエラーになります。

<NWK_SIMPLE>は、シンプルに動作する中継ネットワークの実装を目的としており、ACK付きの通信は行いません。

tx_addr_broadcast

tx_addr_broadcast()

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

<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>ではタイプIDを内部的に使用しています。ユーザは使用できません。

mwx::crlf

twe::stream に改行コードを出力する

mwx::stream の << 演算子に対して改行コード (CR LF) を出力するためのヘルパークラスのインスタンスです。

Serial << "hello world!" << mwx::crlf;

begin()

loop()関数の初回コールの手前で一度だけ呼び出されます。TWENET の初期化は終了しているのでsetup()のような制約を考慮する必要はありません。

主な使い方は、

  • 始動メッセージの表示

  • 始動直後のスリープ遷移

  • setup()で始動することに不都合がある処理

このコールバック関数定義は省略可能です。

smplbuf

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

template <typename T, int N> smplbuf_local
template <typename T> smplbuf_attach
template <typename T> smplbuf_heap

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

// 内部に固定配列
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); 

上記の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>>

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

smplbuf_u8<32> b1;
b1.reserve(5);

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

for(uint8_t x : b1) {
  Serial << int(x) << ",";
}

smplbuf_u8<32> b1;
b1 << uint8_t(0x01)
   << uint32_t(0x1234abd);

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

宣言・初期化

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

smplbuf_attach<T>
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_attachでは、使用するバッファの先頭ポインタT* bufと配列の初期サイズsizeと最大サイズNを指定します。

smplbuf_localのみ、ローカルオブジェクトとして宣言する場合は、初期化メソッド.init_local()を省略できます。

alloc_localでグローバルオブジェクトを生成する場合は、smplbufコンテナの使用前に.init_local()メソッドを呼び出します。

初期化子リスト

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)

末尾にメンバー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_hear(), 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は末尾から一つ手前となります。

to_stream()

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

//例
smplbuf_local<uint8_t, 10> b1;
b1.init_local();
b1 = { 0x30, 0x31, 0x32, 0x33 };

Serial << b1.to_stream();
// Output->0123

ストリームへの出力目的で利用します。

ストリーム (stream)

inline size_t write(int n)
inline static void vOutput(char out, void* vp)
inline void flush(void)

上記の実装を行っています。

  • << 演算子を用いて配列にデータを書き込む

  • printfのアルゴリズムを用いた関数を持ちて配列にデータを書き込む

  • flush()によりバッファの末尾にヌル文字を書きこむ(配列のサイズは変更しません)

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)
#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;
}

smplque

FIFOキューを構造のコンテナクラスです。

template <typename T, int N, class Intr> smplbuf_local
template <typename T, class Intr> smplbuf_attach
template <typename T, class Intr> smplbuf_heap

要素型は原則として数値や数値などを格納する構造体を想定しています。デストラクタによる破棄手続きが必要なオブジェクトを格納することを想定していません(キューから要素を抹消する際にオブジェクトを抹消する処理をしていないため)。

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

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

// 内部に固定配列
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);

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

smplque_local<int, 32> q1;
q1.init_local();

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,

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

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) << ',';
}

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のみ、ローカルオブジェクトとして宣言する場合は、初期化メソッド.init_local()を省略できます。

smplque_localのグローバルオブジェクトを生成する場合は、smplbufコンテナの使用前に.init_local()メソッドを呼び出します。

メソッド

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文やアルゴリズムが利用できます。

serparser

シリアル書式入出力 (mwx::serial_parser)

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

// 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) コロンを指定します。

データ部

N

2N

元データの各バイトをアスキー文字列2文字(A-F は大文字)で表現します。 例えば 0x1F は 1 (0x31) F (0x46) と表現します。

チェックサム

2

データ部の各バイトの和を8ビット幅で計算し2の補数をとります。つまりデータ部の各バイトの総和+チェックサムバイトを8ビット幅で計算すると0になります。 チェックサムバイトをアスキー文字列2文字で表現します。 例えば 00A01301FF123456 では 0x00 + 0xA0 + ... + 0x56 = 0x4F となり、この二の補数は0xB1 です。(つまり 0x4F + 0xB1 = 0x00)

フッタ

2

[CR] (0x0D) [LF] (0x0A) を指定する。

バイナリ形式

通常はアスキー形式を利用してください。

マイコン間通信での実装を考えるとバイナリ形式のほうが効率的ですが、実験などでの送受信の確認にはバイナリ通信に対応した特別なターミナルなどを準備する必要があり、チェックサムの計算も必須です。アスキー形式より利用の難易度は高くなります。

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

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

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

定義

======

元データのバイト数

形式におけるバイト数

解説

ヘッダ

2

0xA5 0x5A を指定します。

データ長

2

データ長はビッグエンディアン形式の2バイトで、MSB (0x8000) を設定した上、データ部の長さを指定します。 例えばデータ部の長さが 8 バイトなら0x80 0x08 を指定します。

データ部

N

N

元データを指定します。

チェックサム

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_heap

begin()メソッドのパラメータで指定したサイズをヒープに確保する

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

serparser_attach

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

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

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

void begin(uint8_t ty)

serparser_heap - ヒープに確保

void begin(uint8_t ty, uint16_t 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を戻します。

parse()で読み出し完了になったとき、次のparse()を実行すると読み出し中のステータスに戻ります。

例

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] 

packet_rx

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

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

現時点では、シンプル中継ネットワーク<NWK_SIMPLE>で必要とされるものを中心にメソッド等のインタフェースを実装しています。

メソッド

get_payload()

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

<NWK_SIMPLE>を用いた場合は、先頭に<NWK_SIMPLE>用のヘッダデータがあります。戻りとして参照されるコンテナは、このヘッダ部分を除いた部分配列になります。ヘッダ部分まで参照したいときはget_psRxDataApp()によりtsRxDataApp構造体を参照してください。

get_psRxDataApp()

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

get_length()

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

get_lqi()

LQI値 (Link Quality Indicator)を得る。

LQIとは電波通信品質を示す値です。0から255までの数値で表されます。

ちなみに、いくつかの段階で評価する場合は、50未満(悪い -80dbm 未満)、50~100(やや悪い)、100~150(良好)、150以上(アンテナの近傍)といった区分けも可能です。これらは目安である点にご留意ください。

get_addr_src_long(), get_addr_src_lid()

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

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

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

get_addr_dst()

宛先アドレスを得ます。

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

alloc

コンテナクラス(smplbuf, smplque)のテンプレート引数として指定し、内部で利用するメモリの確保または領域指定します。

このクラスはユーザコードから直接呼び出すものではありませんが、内部的にコンテナの宣言に用いられています。

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

初期化

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

メソッド

alloc_size()

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

_is_attach(), _is_local(), _is_heap()

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

setup()

コード実行の初期に呼び出され、初期化コードを記述します。

TWENETの初期化は setup() 関数が終了した後にも実行されます。多くの処理はTWENETが終了した後に実行するようになっているため、ここでは初期化以外の処理を行わないようにしてください。

注意すべき事項を以下に列挙します。

  • スリープthe_twenet.sleep()の実行はできません。初期化後速やかにスリープしたいときはbegin()関数内に最初のスリープ処理を記述してください。

  • delay()関数は後述の処理*に置き換えられます。この場合、パラメータのmsはミリ秒を指定するものではありません。

* delay()の代替処理

format (mwx::mwx_format)

mwx::stream に printf の書式を入力

mwx::stream の << 演算子に対してフォーマット書式を書き出すヘルパークラスです。ライブラリ内では Using format=mwx::mwx_format; として別名定義しています。

可変数引数リストに登録できる引数は最大4つ。

  • コンストラクタで受け取った引数リストを、パラメータパックの展開機能を用いてクラス内部変数に格納する

  • operator << が呼び出された時点で、fctprintf() を呼び出し、ストリームにデータを書き出す

コンストラクタ

コンストラクタでは、書式のポインタとパラメータを保存します。続く <<演算子による呼び出しでフォーマットを解釈して出力処理を行います。

fmtは本オブジェクトが破棄されるまで、アクセス可能であることが必要です。

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

smplbufはインタフェースも有しているため、いくつかのストリーム用のメソッドを使用することができます。

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

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

応用としてがあります。

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

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

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

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

このクラスオブジェクトは、のまたはthe_twelite.receiver.read()により取得できます。

メモリの確保方法alloc
ストリーム(stream)
メモリの確保方法alloc
axis_xyzt構造体の特定のメンバーに注目したイテレータによるアクセス
alloc
get_axis_{x,y,z}_iter()
形式
形式
形式
smplbuf_u8_attach& get_payload()
const tsRxDataApp* get_psRxDataApp() 
uint8_t get_length()
uint8_t get_lqi()
uint32_t get_addr_src_long()
uint8_t get_addr_src_lid()
uint32_t get_addr_dst()

値

解説

MSB(bit31)がセットされている

宛先としてシリアル番号を指定しています。

0x00-0xFF

宛先として論理ID(8bit)が指定されています。

内容

alloc_attach<T>

すでにあるバッファを指定する

alloc_local<T, int N>

Nバイトのバッファを内部に静的確保する

alloc_heap<T>

指定したサイズをヒープに確保する

void attach(T* p, int n) // alloc_attach
void init_local()        // alloc_local
void init_heap(int n)    // alloc_heap
uint16_t alloc_size()
static inline void delay(uint32_t ms) {
		volatile uint32_t ct = ms * 4096;
		while (ct > 0) {
			--ct;
		}
}
Serial << format("formatted print: %.2f", (double)3123 / 100.) << mwx::crlf;

// formatted print: 31.23[改行]
format(const char *fmt, ...)

パラメータ

解説

fmt

フォーマット書式。

TWESDK/TWENET/current/src/printf/README.md 参照

...

フォーマット書式に応じたパラメータ。 ※ 最大数は4で、5つ以上のパラメータではコンパイルエラーとなる。

※ 書式との整合性はチェックしないため、不整合な入力に対しては安全ではない。

mwx::flush

twe::stream へのバッファ出力をフラッシュする。

mwx::stream の出力バッファをフラッシュする。flush() メソッドを呼び出すヘルパークラスへのインスタンス。

for (int i = 0; i < 127; ++i) {
    Serial << "hello world! (" << i << ")" << twe::endl << twe::flush;
}
  • シリアルポートの場合は出力完了までポーリング待ちを行う

  • mwx::simpbuf バッファの場合は 0x00 を末尾に出力する(サイズは変更しない)

loop()

アプリケーションのメインループです。ループ終了後はCPUがDOZEモードに遷移し低消費電流で次の割り込みを待ちます。

アクトの記述では、ほとんどの処理がこのループ内に記述されます。

バック関数定義は省略可能です。

ビヘイビア

wakeup()

スリープから起床したときにloop()に移行する前に呼ばれ、スリープ復帰後の初期化処理や復帰状態によって処理を分岐するための手続きを含めます。

センサーの読み出しなどの処理のみでloop()での処理がないときは、この関数内で再びスリープを実行できます。

このコールバック関数定義は省略可能です。

コールバック関数

アプリケーションの記述を行うコールバック関数です。コールバックはシステム(ライブラリ)から呼び出されるという意味です。ユーザがいくつかのコールバック関数を定義することでシステムの振る舞いを記述します。

以下のコールバック関数は必須定義です。

  • setup()

  • loop()

それ以外の関数は定義しない場合は、何も実行しない空の関数が替わりにリンクされます。

通常のコールバック呼び出し順序

init_coldboot()
  ↓ (TWENET内部処理:初期化1)
setup()
  ↓(TWENET内部処理:初期化2)
begin() --- 初回のみ
  ↓
loop() <--+
  ↓       |イベント処理、ビヘイビア処理
CPU DOZE -+

正確なふるまいを参照したい方はソースコードmwx_appcore.cppを参照してください。

スリープ復帰時のコールバック呼び出し順序

the_twelite.sleep()
  ↓ sleeping...
 
 
init_warmboot()
  ↓ (TWENET内部処理:初期化3)
wakeup()
  ↓(TWENET内部処理:初期化4)
loop() <--+
  ↓       |イベント処理、ビヘイビア処理
CPU DOZE -+

正確なふるまいを参照したい方はソースコードmwx_appcore.cppを参照してください。

init_warmboot()

通常は使用しません。

スリープ復帰後、ペリフェラルAPIが初期化されない再初期に呼び出されます。

この関数では割り込み要因の検出を行うことができます。

このコールバック関数定義は省略可能です。

init_coldboot()

通常は使用しません。

ペリフェラルAPIも初期化もされていない、コード実行の再初期に呼び出されます。

このコールバック関数定義は省略可能です。

transmit_complete()コールバック
receive()コールバック
コールバック関数

ビヘイビア

クラス定義 (.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ファイルを作成します。

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にセットすることで、続くイベント呼び出しを行いません。

handledがfalseのまま割り込みハンドラを終了した場合、アプリケーションループ(通常コード)の中でイベントハンドラが呼び出されます。イベントハンドラにはhandledのパラメータはありません。イベントハンドラは通常コードですので、比較的大きな処理を行うことが出来ます。イベントハンドラのオーバーヘッドも発生するため、頻繁な割り込み都度呼び出されるような処理の場合、処理しきれなくなる可能性があります。また、イベントの発生はシステム内部のFIFOキューにより処理されるため、一定時間内に処理できない場合はイベントが消失する場合もあります。

以下にハンドラ関数定義用のマクロの解説を行います。

DIO

MWX_DIO_INT(N, uint32_t arg, uint8_t& handled)
MWX_DIO_EVENT(N, arg)

DIO(ディジタルIO)割り込み・イベントです。Nは対象DIOの番号を指定します。argは将来の拡張のための定義です。

TICKTIMER

MWX_TICKTIMER_INT(uint32_t arg, uint8_t& handled)
MWX_TICKTIMER_EVENT(uint32_t arg)

TickTimer割り込み・イベントです。argは将来の拡張のための定義です。

TickTimerのhandledフラグをtrueにセットしてはいけません。TWENETが動作しなくなります。

TIMER

MWX_TIMER_INT(N, uint32_t arg, uint8_t& handled)
MWX_TIMER_EVENT(N, uint32_t arg)

タイマー割り込み・イベントです。Nは対象タイマーの番号を指定します。argは将来の拡張のための定義です。

その他

MWXライブラリで標準的に定義しない、その他の割り込み・イベントの定義です。AHIペリフェラルマニュアルの理解が必要です。

その他の割り込み・イベントは以下のハンドラ関数で受けることが出来ます。これらは将来的に専用のハンドラが定義された場合、利用できなくなります。

MWX_MISC_INT(uint32_t arg, uint32_t arg2, handled)
MWX_MISC_EVENT(auint32_t rg, uint32_t arg2)

ペリフェラル (AHI) の割り込みハンドラのu32DeviceIdがarg、u32ItemBitmapがarg2に対応します。

状態マシン

状態マシン(ステートマシン)は、メッセージを受け取り、そのメッセージに応じて状態を遷移させながら動作させるアプリケーションの記述方法です。

受け取るイベントは以下となります。

イベント名

内容

E_EVENT_START_UP

システム始動時に呼び出される。電源投入直後はパラメータが0で呼び出されます。実行初期であるため、通常処理を行う状態に遷移する場合は一旦begin()メソッドからPEV_Process()を呼び出し動作を開始させます。 スリープ復帰後も呼び出されるがパラメータは0以外です。この状態からは通常処理を行えます。

E_EVENT_NEW_STATE

状態遷移直後に新しい状態で呼び出されます。ある状態に遷移したときに最初に実行される処理を記述します。

E_EVENT_TICK_TIMER

1msごとのTickTimerで呼び出されます。

E_EVENT_TICK_SECOND

1秒毎に呼び出されます。

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
}

送信完了イベントを状態マシンに伝えます。つまり状態ハンドラの呼び出しを行います。

直接状態ハンドラを呼び出すことは行わないでください。E_EVENT_NEW_STATEが実行されないなどの問題が発生します。

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かどうか判定します。

millis()

システム時刻[ms]を得ます。

システム時刻はTickTimerの割り込みで更新されます。

delay()

ポーリングによる時間待ちを行います。

msにて与えられた期間待ち処理を行います。

時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。

delay()を呼び出してから約5ms経過するごとにTWELITEマイコン内部のウォッチドッグ処理を行います。

※例えばwhile(1) delay(1); を実行した場合は、delay()内部で5ms経過しないためウォッチドッグ処理が行われず、一定時間後リセットが実行されます。

setup(), wakeup()関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。

パラメータに1,2といった短い時間を指定した場合は、誤差が大きくなる場合があります。

random()

乱数を生成します。

1行目は0..(maxval-1)の値を戻します。maxvalの値が最大値ではないことに注意してください。

2行目はminval..maxval-1の値を戻します。

ビヘイビアは、指定の方法でクラスを定義することで、クラスオブジェクトに登録できるようになります。登録したビヘイビアはTWENETに組み込まれて動作し、ユーザコードではアプリケーションの振る舞いを記述できるようになります。ループでの記述ではできなかったTWENETからの割り込みやイベントのコールバック関数を定義することが出来ます。ループでの記述に比べ、定義が多くなりますが、より複雑なアプリケーションを記述するのに向いています。

ビヘイビアのサンプルを参照してください。

割り込みを発生させるためにはによる適切な入力設定, による割り込み開始の設定が必要です。

割り込みを発生させるためには、をソフトウェア割り込みを有効にして開始します。

サンプルでは、センサーの動作開始からセンサーの値取得、無線パケット送信から送信完了まで、スリープ遷移といったアプリケーションの動作の流れを記述しています。実例として参考にしてください。

the_twelite
PAL_AMB-behavior
pinMode()
attachDioInt()
Timerオブジェクト
PAL_AMB-behavior
uint32_t millis()
void delay(uint32_t ms)
uint32_t random(uint32_t maxval)
uint32_t random(uint32_t minval, uint32_t maxval)

delayMicroseconds()

MWSDK2020_05 には含まれません。対応パッケージはMWSDK_2020_07_UNOFFICIAL以降となります。

ポーリングによる時間待ちを行います(μ秒指定)。

void delayMicroseconds(uint32_t microsec)

microsecにて与えられた期間待ち処理を行います。

時間の計測はTickTimerのカウントによって行っています。また長い時間待ちを行う場合はCPUのクロックを低下してポーリング処理を行います。

setup(), wakeup()関数内では、TickTimerがまだ動作していないため、whileループによる時間待ち処理になります。この場合、指定値との誤差は大きくなります。このループカウンタは32Mhzに合わせて調整しています。これら関数内でCPUクロックを変化させた場合は、そのクロックに比例した誤差が発生します。

パラメータに10以下といった短い時間を指定した場合は、誤差が大きくなる場合があります。

DIO 汎用ディジタルIO

汎用ディジタルIO(DIO)の操作には以下の関数を利用します。

  • pinMode()

  • digitalWrite()

  • digitalRead()

  • attachIntDio()

  • detachIntDio()

定数

ピン名と番号

ピンのモード(DIO0..19)

以下の列挙値は型名 E_PIN_MODEで取り扱われます。

ピンのモード(DO0,1)

以下の列挙値は型名 E_PIN_MODEで取り扱われます。

ピンの状態

以下の列挙値は型名 E_PIN_STATEで取り扱われます。

ピンの立ち上がり、立下り

以下の列挙値は型名 E_PIN_INT_MODEで取り扱われます。

定義

名称

const uint8_t PIN_DIGITAL::DIO0 .. 19

DIOピン0~19

const uint8_t PIN_DIGITAL::DO0 .. 1

DOピン0,1

定義

プルアップ

名称

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

有

入力状態に戻す

定義

名称

PIN_MODE::OUTPUT

出力

PIN_MODE::OUTPUT_INIT_HIGH

出力(初期状態HIGH)

PIN_MODE::OUTPUT_INIT_LOW

出力(初期状態LOW)

PIN_MODE::DISABLE_OUTPUT

出力設定をやめる

定義

値

名称

PIN_STATE::HIGH

1

HIGHレベル(=Vccレベル)

PIN_STATE::LOW

0

LOWレベル(=GNDレベル)

定義

名称

PIN_INT_MODE::FALLING

立ち下り

PIN_INT_MODE::RISING

立ち上がり

digitalWrite()

ディジタル出力ピンの設定を変更します。

static inline void digitalWrite(uint8_t u8pin, E_PIN_STATE ulVal)

事前にpinMode()にて設定対象のピンを出力用に設定しておきます。1番目のパラメータは、設定対象のピン番号を指定します。2番目のパラメータはHIGHかLOWのいずれかを指定します。

入力が E_PIN_STATE 型となっています。E_PIN_STATEからint型への変換演算子は定義していませんので、数値による直接の入力はできないようになっています。

PAL_AMB-behavior

アクトの機能

  • 環境センサーパル AMBIENT SENSE PAL を用い、センサー値の取得を行います。

  • コイン電池で動作させるための、スリープ機能を利用します。

アクトの使い方

TWELITEの準備

役割

例

親機

子機

親機にPALを使用する場合は、コイン電池での動作はできません。目安として50mA以上の電流を安定して得られるような電源環境を用意してください。

ファイル構成

  • PAL_AMB-behavior.hpp : setup()のみの定義です。DIP-SWを読み出し、D1..D3が上位置の場合は親機として動作し、それ以外は子機としてDIP SWに対応するIDをセットします。

  • Parent/myAppBhvParent.hpp : 親機用のビヘイビアクラス定義

  • Parent/myAppBhvParent.cpp : 実装

  • Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装

  • Parent/myAppBhvParent.hpp : 子機用のビヘイビアクラス定義

  • Parent/myAppBhvParent.cpp : 実装

  • Parent/myAppBhvParent-handlers.cpp : ハンドラーの実装

親機のビヘイビア名は<MY_APP_PARENT>、子機は<MY_APP_CHILD>です。

初期化 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>を登録します。

親機がMONOSTICKの場合は、PAL用のDIP SWの読み値は0となり、親機としてふるまいます。ただしこの動作はMONOSTICKの仕様として定義されているものではありません。

親機のビヘイビア

親機はスリープをしない受信機としてふるまい、子機からのパケットを受信したときにシリアルポートにパケットの情報を出力します。

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の状態ハンドラが続けて呼び出されます。この時のイベントevはE_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()を実行してください。

pinMode()

DIO(汎用ディジタルIO)ピンの設定を行います。

DO0,1は特殊なピンで、原則として他の目的で利用されるものですが、出力としても設定可能です。ただしハード的な制約があるピンですので、利用には注意が必要です。

両方のピンは、電源投入時にHIGHレベルが担保される必要があります。不安定な電圧をとったりするような回路構成の場合、モジュールが起動しないなどの問題が出ます。

を用い、センサー値の取得を行います。

を用いた親機子機の記述を行っています。

センサーを値を得るのにの機能を使わずを用いて直接記述しています。

子機はステートマシンによるにより記述しています。

このアクトの解説の前に、、を参照してください。またについても参照ください。

このサンプルはの記述方法のサンプルです。ビヘイビアはより複雑なアプリケーションを記述する際に用います。

+

ビルドファイルの追加はを参照してください。

この関数では DIO0..19 と、DO0,1のピンの状態を変更できます。設定内容は E_PIN_MODE の列挙値のとを参照してください。

環境センサーパル AMBIENT SENSE PAL
ビヘイビア
ボードビヘイビア
Wire
BRD_APPTWELITEの解説
PAL_AMBの解説
PAL_AMB-usenapの解説
ビヘイビアの解説
ビヘイビア
Makefileの解説
状態遷移
void pinMode(uint8_t u8pin, E_PIN_MODE mode)

detachIntDio()

割り込みハンドラの登録を解除します。

void detachIntDio(uint8_t u8pin)
MONOSTICK BLUEまたはRED
BLUE PAL または RED PAL
環境センサーパル AMBIENT SENSE PAL
DIOの解説
DOの解説

attachIntDio()

DIO割り込みを有効にします。

void attachIntDio(uint8_t u8pin, E_PIN_INT_MODE mode)

例

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に*を表示する。

事前に入力設定したピンに対して、1番目のパラメータは割り込みを有効にしたいピン番号で、2番目は割り込み方向()を指定します。

割り込みハンドラ、イベントハンドラの記述はで行います。

アプリケーションビヘイビア
立ち上がり、立ち下がり

digitalReadBitmap()

mwxライブラリ 0.1.4 以降に収録

入力設定のポートの値を一括読み出しします。

uint32_t digitalReadBitmap()

LSB側から順にDIO0 ... DIO19 の順に値が格納されます。

HIGH側のピンには 1 が、LOW側のピンには 0 が設定されます。

collect_bits()

整数から指定したビット位置の値を取得し、指定した順番のビットマップを作成します。

  パラメータbmに指定する値から、その後の可変数パラメータで指定する0..31のビット位置に対応する値を取り出します。取り出した値はパラメータ順に並べビットマップとして戻り値になります。

ビットマップの並び順は、最初のパラメータを上位ビットとし末尾のパラメータがbit0になります。

例ではb1のビット4,2,1,0を取り出すと (1,0,1,0) になります。これをb1010として0x10のように計算されます。

constexpr uint32_t collect_bits(uint32_t bm, ...)
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)

digitalRead()

入力設定のポートの値を読み出す。

static inline E_PIN_STATE digitalRead(uint8_t u8pin)

事前に入力に設定したピンの入力値をLOWまたはHIGHで得ます。

E_PIN_STATE型からint型への変換演算子は定義していないため、数値型への直接的な代入はできません。

expand_bytes()

バイト列を分解し変数に格納します。

const uint8_t* expand_bytes(
        const uint8_t* b, const uint8_t* e, ...)

expand_bytes()は、パラメータにuint8_t*型のイテレータの組み合わせを指定します。これは解析対象の先頭と末尾の次のイテレータの指定となります。eの位置まで解析が進んだ場合はエラーとなりnullptrを返します。

展開にエラーがない場合は、次の読み出しを行うイテレータを戻します。

可変数パラメータには以下を指定します。

バイト数

データ長

解説

uint8_t

1

uint16_t

2

ビッグエンディアン並びとして展開する

uint32_t

4

ビッグエンディアン並びとして展開する

uint8_t[N]

N

uint8_t 型の固定長配列

std::pair<char*,N>

N

char*,uint8_t*型の配列と配列長のペア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つ続いています。

pack_bits()

指定したビット位置に1をセットします。

constexpr uint32_t pack_bits(...)

パラメータは可変数引数で指定でき、各パラメータはビット位置を指定する0..31の整数を指定する。例えばpack_bits(1,3,6)と指定すると((1UL<<1)|(1UL<<3)|(1UL<<6))を返します。

constexprは定数による計算が可能な場合はコンパイル時に定数展開します。

pack_bytes()

要素データを並べてバイト列を生成します。

uint8_t* pack_bytes(uint8_t* b, uint8_t* e, ...)

pack_bytesはコンテナクラスのbegin(),end()イテレータをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。

可変引数パラメータに与えるデータは以下に示すとおりです。

データ型

バイト数

解説

uint8_t

1

uint16_t

2

ビッグエンディアン並びで格納される

uint32_t

4

ビッグエンディアン並びで格納される

uint8_t[N]

N

uint8_t 型の固定長配列

std::pair<char*,N>

N

char*,uint8_t*型の配列と配列長のペア。make_pair()で生成できる。

smplbuf_u8& pack_bytes(smplbuf_u8& c, ...)

pack_bytesはコンテナオブジェクトをパラメータとし、続くパラメータで指定されるデータをバイト列としてコンテナに書き込みます。コンテナの.push_back()メソッドで末尾に追加します。

可変引数パラメータに与えるデータは以下に示すとおりです。

データ型

バイト数

解説

uint8_t

1

uint16_t

2

ビッグエンディアン並びで格納される

uint32_t

4

ビッグエンディアン並びで格納される

uint8_t[N]

N

uint8_t 型の固定長配列

std::pair<char*,N>

N

char*,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に再格納しています。

ボード (BRD)

Board behaviors

ボードビヘイビアは、TWELITE無線マイコンに接続したハードウェアを取り扱うための手続きが含まれます。

  • 定数の定義(ピン番号など)

  • ハードウェアの初期化

  • センサー等の取り扱い

<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);

上記のコードのように各ピンが初期化されます。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

ウォッチドッグタイマーのタイムアウトは1秒です。

MONOSTICKでは通常スリープするアプリケーションを実行しませんが、その場合はMONOSTICK::PIN_WDT_ENをHIGHにしてからスリープします。

メソッド

set_led()

void set_led_red(uint8_t mode, uint16_t tick)
void set_led_yellow(uint8_t mode, uint16_t tick)

LED(赤、黄)の制御を行います。

ボードビヘイビアでの制御を行わない場合は、このメソッドを呼び出さないでください。

黄色のLED(MONOSTICK::PIN_LED_YELLOW)はSPIMISOピン(半導体のピン名DO1)です。本ボードビヘイビアではPWM制御による点灯用のメソッドや手続きは含まれません。必要に応じて以下の記述を行います。

  • set_led_yellow()は呼び出さないようにして下さい。

  • 始動後にPWM出力の初期化を別途行います。SPIMISOピンはApp_Twelite標準アプリケーションではPWM3に対応し、Timer3クラスオブジェクトにより制御できます。

  • スリープ復帰後にPWM出力の初期化を別途行います。その際、DO1の出力設定を解除します。

  • PWM設定前にDO1の出力設定を解除してください。 pinMode(PIN_LED_YELLOW, DISABLE_OUTPUT);

黄色のLED(MONOSTICK::PIN_LED_YELLOW)は、スリープ中に点灯させることはできません。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定

意味

LED_TIMER::BLINK

LEDを点滅させます。tickに与える時間[ms]ごとにON/OFFが切り替わります。スリープ復帰後はカウントをリセットし点灯状態から始まります。

LED_TIMER::ON_RX

パケットの受信時にtickに与える時間[ms]だけ点灯します。

LED_TIMER::ON_TX_COMP

送信完了時にtickに与える時間[ms]だけ点灯します。

スリープ復帰後も復帰前の設定が維持されます。

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_u8CalcU16(), CRC8_u8CalcU32()はu16c, u32cをビッグエンディアン並びとして、CRC8を計算します。

CRC8は、計算式や初期値などによって種類がありますが、本ライブラリでは多項式をX^8+X^5+X^4+1(Polynomial Valueを0x31)をとしたものを使用しています。これはCRC8-CCITT や CRC8-Maximと呼ばれることがあります。

XORは各要素の排他的論理和 XOR をとったものです。

LRCは各要素の値の合計を計算し、下位8ビットの2の補数を取ります。結果、チェックサムを含め全要素を足し算すると0になります。

CRC8, XOR, LRC(で使用)の計算を行います。

アスキー形式

<BRD_APPTWELITE>

標準アプリケーションApp_Tweliteと同じ配線を想定したボードビヘイビアです。定数定義と、M1-M3,BPSピンの読み出し機能があります。

定数

以下の定数を定義しています。BRD_APPTWELITE::PIN_DI1のようにアクセスできます。

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;

メソッド

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ピンの値を返します。

この値はシステム起動時に確認されて以降は、スイッチを操作しても更新されません。

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);

上記のコードのように各ピンが初期化されます。

ウォッチドッグタイマー

起動時、スリープ起床時、起動後一定時間経過後に外部のウォッチドッグタイマーを再セットします。

ウォッチドッグタイマーをタイムアウトしないためにTWELITEを60秒以内の設定(キャリブレーション済み内部CRタイマー使用時)で起床してください。

メソッド

set_led()

void set_led(uint8_t mode, uint16_t tick)

LED(D1)の制御を行います。

ボードビヘイビアでの制御を行わない場合は、このメソッドを呼び出さないでください。

modeは以下のパラメータを取ります。tickは点灯時間[ms]を指定しますが、詳細はmodeの解説を参照してください。

指定

意味

LED_TIMER::BLINK

LEDを点滅させます。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==LOW を1, SW2 == LOWを2, SW3 == LOWを4, SW4 == LOWを8とした和を返します。

D1..D4のHIGH(1),LOW(0)の値とは反対になります。

DIP SWのLOW(0)側がセットされた、つまり、1の値を持つと意味付けしているためです。

この値はシステム起動時に確認されて以降は、スイッチを操作しても更新されません。

<PAL_MAG>

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);

のボードビヘイビアです。

開閉センサーパル OPEN-CLOSE SENSE PAL

<PAL_MOT>

  • 加速度センサー MC3630

void setup() {
  auto&& brd = the_twelite.board.use<PAL_MOT>();
}

メンバーオブジェクト

sns_MC3630

のボードビヘイビアです。

に加えボード上のセンサーを取り扱えるようになっています。

のオブジェクトです。

動作センサーパル MOTION SENSE PAL
共通定義
MC3630センサー

センサー (SNS)

Sensor behaviors

センサーの手続きを定型化したクラスを用意しています。

センサー取り扱いのための手続き

センサーの取り扱い前に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)

待ち時間処理のセンサーの場合はarg1にE_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

(対応しているセンサーのみ)センサーが接続されているときにtrueを返します。

probe()直後の初回の通信が失敗することがある。

LTR-308ALS - 照度センサー

I2Cバスを利用した照度センサーです。

ボードビヘイビア <PAL_AMB> を読み込んだ時のみ使用可能です。begin()以外の共通メソッドの手続きはボードビヘイビア中で実行されています。

処理の流れ

  1. Wire.begin(): バスの初期化

  2. .begin(): センサーの動作開始

  3. 時間待ち50ms

  4. .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)

待ち時間処理のセンサーの場合はarg1にE_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

SHTC3 - 温湿度センサー

I2Cバスを利用した温湿度センサーです。

ボードビヘイビア <PAL_AMB> を読み込んだ時のみ使用可能です。begin()以外の共通メソッドの手続きはボードビヘイビア中で実行されています。

処理の流れ

  1. Wire.begin(): バスの初期化

  2. .begin(): センサーの動作開始

  3. 時間待ち5ms

  4. .get_luminance(): 値の読み出し

動作に必要な手続き

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)

待ち時間処理のセンサーの場合はarg1にE_EVENT_TICK_TIMERまたはE_EVENT_START_UPを与え時間の経過を知らせます。このメソッド呼出し後に、必要な時間が経過していればavailableになり、センサー値の読み出しが可能になります。

available()

bool available()

センサーが読み出し条件を満足したときにtrueを返します。

probe()

bool probe()

センサーが接続されているときにtrueを返します。

<PAL_AMB>

  • 温湿度センサー SHTC3

  • 照度センサー LTR308ALS

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

メンバーオブジェクト

sns_SHTC3

sns_LTR308ALS

のボードビヘイビアです。

に加えボード上のセンサーを取り扱えるようになっています。

のオブジェクトです。

のオブジェクトです。

環境センサーパル AMBIENT SENSE PAL
共通定義
SHTC3センサー
LTR308ALSセンサー

MC3630 - 加速度センサー

SPIバスを用いた加速度センサーです。

ボードビヘイビア <PAL_MOT> を読み込んだ時のみ使用可能です。begin(), available()以外の共通メソッドの手続きはボードビヘイビア中で実行されています。

動作の流れ

  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>ボードビヘイビア中で実行されています。

半導体内部のFIFOキューが一杯になっても読み出さなかった場合は、データ取得は終了し、新たな値は格納されません。

データ構造

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()で参照するキューのサイズに格納されるデータ数を読み出すようにしてください。

スリープ復帰後に<PAL_MOT>ではread()が行われます。

get_que()

smplque<axis_xyzt>& get_que()

共通メソッド

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_UNOFFICIAL

1Hz Low Power (非公式設定)

MODE_LP_2HZ_UNOFFICIAL

2Hz Low Power (非公式設定)

MODE_LP_7HZ_UNOFFICIAL

7Hz Low Power (非公式設定)

MODE_LP_14HZ

14Hz Low Power (デフォルト)

MODE_LP_28HZ

28Hz Low Power

MODE_LP_54HZ

54Hz Low Power

MODE_LP_105HZ

105Hz Low Power

MODE_LP_210HZ

210Hz Low Power

MODE_LP_400HZ

400Hz Low Power

MODE_ULP_25HZ

25Hz Ultra Low Power

MODE_ULP_50HZ

50Hz Ultra Low Power

MODE_ULP_100HZ

100Hz Ultra Low Power

MODE_ULP_190HZ

190Hz Ultra Low Power

MODE_ULP_380HZ

380Hz Ultra Low Power

非公式設定はMC3630のデータシートに記述がないもので設定時の動作は未定義となります。お客様のほうでの動作確認の上利用下さい。非公式設定にかかわる問題やご質問について弊社サポートでは対応いたしかねます。

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バスの再初期化を行い、加速度データを読み出します。

各サンプルは構造体を要素とするキューに格納されます。メンバーx,y,zはそれぞれX,Y,Z軸に対応します。

加速度のサンプルを取得します。キューはを要素としたです。availableになってから速やかにキューを空にする必要があります。

axis_xyzt
smplque
axis_xyzt
smplque

ネットワーク (NWK)

Network behaviors

ネットワーク ビヘイビアは、IEEE802.15.4のMAC層のパケットの送受信に対して、アドレス定義、配送制御などを行います。

<NWK_SIMPLE>

設定 (STG) - インタラクティブモード

設定ビヘイビア

設定ビヘイビアは、インタラクティブモードによる設定CUIを利用するためのビヘイビアです。設定のためのインタフェースはシリアルポートの入出力により行います。TWELITE STAGE/TeraTerm/screenなどターミナルソフトウェアを用い、対話型の設定が可能になります。

このビヘイビアは、内部でUART0(Serial)を利用します。設定ビヘイビアで利用しなかった入力文字列は、設定ビヘイビア内に確保されたFIFOキューに投入され、Serialはこのキューを参照するようにふるまいます。設定ビヘイビアを登録しない場合と内部的なふるまいが違う点に留意ください。

<SET_STD>

シンプル中継ネット <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

子局

0xFE

IDを割り振らない子局

論理IDを宛先として指定できますが、0xFE,0xFFは特別な意味を持ちます。下表に宛先指定についてまとめます。

宛先ID

意味

0x00

子局から親局を指定する。親局からの指定は無効。

0x01..0xEF

特定の子局を指定する。

0xFE

すべての子局を指定する同報通信(ブロードキャスト)。

0xFF

すべての無線局を指定する同報通信(ブロードキャスト)。

また、無線局を特定するのに32bitで指定するシリアル番号も利用できます。

パケットの配送は、IEEE802.15.4のブロードキャストを用います。ACKを用いないため、配送の成功が送信元では判別できませんが、替わりに要求に合った成功率が得られる適当な再送回数を設定したうえ、到達確認が必要な場合は通常パケットの通信を用います。

大規模で頻繁な通信を行う場合は非効率に見えるかもしれませんが、もっぱらデータ収集のみを行い、比較的中継段数が少ないネットワークの場合などより効率的になる場合もあります。

またネットワークを構築するための通信を必要としないため、障害等例外的な状況においても全く通信が止まってしまうといったことが原理的に少なくなります。親機が受信状態かつ子機からの無線到達範囲にあって、子機がパケットを送信しさえすれば、多くの場合親機は受信できます。ネットワークの構築のために通信が必要なネットワークでは、一旦、設定情報などが失われた後は、再度親機と子機間の通信確立のための通信が完了しなければデータが送れません。ネットワークビヘイビア<NWK_SIMPLE>がその命名にシンプルとしているのは、こういった理由があります。

このシンプルネットワークを動作させるためには、多くの場合複数回届く再送パケット(同一パケット)を無視する必要があります。<NWK_SIMPLE>での同一パケットの識別は送信元のシリアル番号と送信時のパケットの続き番号によって行います(重複チェッカと呼びます)。続き番号は0..63として、順番に割り当てられ、近い時間に届くパケットの続き番号は近い番号であるという仮定を置いています。一定時間以上経過した番号的に遠い(今10番を受信したとしたら40番台のパケットはしばらく前に送信されたと考えられる)続き番号はタイムアウトとして重複対象から除外します。

重複チェッカでの考慮すべきことは以下になります。

一般的な使用条件(例:中継段数が3以下で1秒あたりのパケットも10局未満から到達)では、特別な考慮は不要です。

  • チェック可能な要素数(数を増やせばメモリ消費とチェックのための処理時間が増える)

  • タイムアウト時間の設定

デフォルトではタイムアウトは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::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)です。

メソッド

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();
}

このオブジェクトには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_APIRETがtrueの場合に送信要求が成功ですが、この要求時点では送信処理が始まりません。

実際の利用例はをご覧ください。

をした場合、TWELITE無線マイコンが無線パケットを受信できないため、必然的に、中継動作は行われなくなります。

送信オブジェクトを取得します。オブジェクトはの派生クラスになります。このオブジェクトに送信アドレスやペイロードを格納し.transmit()メソッドで送信を行います。

パケットの送信IDはの.get_value()で得られる値部に格納されます。またはより送信完了を確認できます。

サンプルアクトの解説
packet_tx
受信回路を動作させない設定
MWX_APIRET
the_twelite.x_status.is_complete()
transmit_complete()
https://code.visualstudio.comcode.visualstudio.com