用語

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

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

一般的な用語

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といったより新しいバージョンがあります。

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

クラス

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

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

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

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

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

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

ラッパークラス

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

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!"); }
};

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

スコープ

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

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

void my_main() {
  myhello helo1(1);
  helo1.say_hello();
  
  {
    myhello helo2(2);
    helo2.say_hello();
  }
}

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

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

struct myhello {
  int _i;
  void say_hello() { printf("hello! %d\n", _i); }
  operator bool() { return true; } // if()での判定用の演算子
  
  myhello(int i = 0) : _i(i) {} // コンストラクタ
  ~myhello() { printf("good bye! %d\n", _i); } // コンストラクタ
};

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

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

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

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

名前空間 (namespace)

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

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

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

テンプレート (template)

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

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

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

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

nullptr

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

参照型

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ライブラリでの一部を利用しています。

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

アルゴリズム

例えば最大や最小値を求めるといった処理を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です。このアルゴリズムは、イテレータの示す要素同士で<,>,==といった演算子での比較が出来れば最大と最小を計算してくれます。戻り型もイテレータの型から導かれます。

最終更新