TWELITE

試したアクトプログラム

======================================
twelite子機0x01 -> 0x02への送信をテストして通信距離を調べました。
通信の送信、受信のサンプルにもなっています。
======================================
TWELITのメリットは無線モジュールが付いている事です。

通信距離のテストをする為にLEDSend、LEDReceiveの2つのプログラムを作ってみます。

LEDSendは3秒毎に電波を送信します。
LEDReceiveは電波を受け取るとLEDが0.5秒間だけ点灯します。

LEDSendを実行するDIPを置いておき、LEDReceiveを実行するDIPを持って移動すれば、LEDが点滅している間は電波が届いている事が分かります。
私のテストでは見通しが良い所では200mは確実に届き、250mで不安定になりました。手に持って移動したので1.2m程の高さにアンテナがあります。
DIPの到達距離1kmのテストは約3mの高さで実験したと書かれています。

actサンプルから「act0」のフォルダをコピーしてフォルダ名を「LEDSend」に変えます。
LEDSendフォルダを開いてact0.cppファイルをLEDSend.cppに名前を変え、LEDSend.cppをエディタで開きます。
以下のコードをコピーして貼り付け、上書き保存します。


LEDSendのソース
-----
#include <TWELITE>
#include <NWK_SIMPLE>

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t u8devid = 0x01;     // このDIPのIDは0x01
uint16_t count16;        // カウント用16bit
bool b_trans;            // 送信時のフラグ
uint8_t datax;            // 送信するデータ用

void setup()    {
    the_twelite
    << TWENET::appid(APP_ID)
    << TWENET::channel(CHANNEL);

    auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
        nwk
            << NWK_SIMPLE::logical_id(u8devid);    // 0x01

    the_twelite.begin();

// 変数の初期化
    count16 = 0;
    b_trans = false;
    datax = 0x00;
}

void loop() {
// 3秒経過したらb_transフラグをtrueにする
    if (TickTimer.available()) {    // 1m秒毎に実行される
        count16 += 1;        // カウントアップ
        if (count16 >= 3000) {    // カウントが3000になったら
            b_trans = true;    // フラグを送信にセット
            count16 = 0;    // カウントをリセット    
        }
    }

// 送信実行
    if (b_trans) {        // フラグが送信なら
        if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
                pkt
                    << tx_addr(0x02)    // 送信先は0x02
                    << tx_retry(0x1)    // リトライ回数 送信回数は合計で2回
                    << tx_packet_delay(0,0,2);

        // 送信するデータをセット
                pack_bytes(
                    pkt.get_payload(),    // 格納先
                    uint8_t(datax)        // 送信するデータ
                    );

        // 送信実行
            pkt.transmit();
            b_trans = false;    // 送信フラグをfalseにする
        }
    }
}
-----

再び、actサンプルから「act0」のフォルダをコピーしてフォルダ名を「LEDReceive」に変えます。
LEDReceiveフォルダを開いてact0.cppファイルをLEDReceive.cppに名前を変え、LEDReceive.cppをエディタで開きます。
以下のコードをコピーして貼り付け、上書き保存します。


LEDReceiveのソース
-----
#include <TWELITE>
#include <NWK_SIMPLE>

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t PIN_LED = 18;        // LED接続用のピンを定義
const uint8_t u8devid = 0x02;          // このDIPのIDは0x02
uint8_t datax;                // 受信するデータ用

void setup()    {
    pinMode(PIN_LED,OUTPUT);    // LED点灯用のピン設定
    digitalWrite(PIN_LED,HIGH);    // LOWでLED点灯
    datax = 0;

    the_twelite
        << TWENET::appid(APP_ID)
        << TWENET::channel(CHANNEL)
        << TWENET::rx_when_idle();    // 受信準備

    auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

    nwk 
        << NWK_SIMPLE::logical_id(u8devid);    // このDIPのID 0x02 をセット

    the_twelite.begin();
}

void loop() {
    if (the_twelite.receiver.available()) {
        auto&& rx = the_twelite.receiver.read();

        expand_bytes(
            rx.get_payload().begin(),    // コンテナの先頭のポインタ
            rx.get_payload().end(),    // コンテナの末尾のポインタ
            datax            // 受信データ(今回は使っていない)
        );

    // 受信の度にLED点灯
        digitalWrite(PIN_LED,LOW);    // LED点灯
        delay(500);
        digitalWrite(PIN_LED,HIGH);    // 0.5秒後消す

        }
}
-----

TWELITE DIPのピン配置のカードの裏の接続を参照
DO1ピン18 <-- (カソード)LED(アノード) <-- 680Ω <-- VCCに接続すると3秒毎に点滅します。
LEDの長い足がアノードです。

======================================
押しボタンを感知するサンプル
======================================
DI1 シルク12ピン -> 押しボタン -> GNDに接続しています。
押しボタンの状態で分岐するサンプルですが、
結果がSerialなのでTWELITE R2(ライター)に接続した状態で12ピンの接続をする必要があります。


ButtonTest.cpp
-----
#include <TWELITE>

const uint8_t PIN_BTN = 12;    // DI1 シルク12ピン

/*** the setup procedure (called on boot) */
void setup() {
    Buttons.setup(5);    // ボタンのチャタリング処理用メモリ確保
    Buttons.begin(pack_bits(PIN_BTN),5,10);    // チャタリング処理設定
}

/*** the loop procedure (called every event) */
void loop() {

// ボタンの状態を調べる
    if (Buttons.available()) {    // ボタンが変化した(押した・離した)
        uint32_t btn_state, change_mask;    // 変数定義
        Buttons.read(btn_state, change_mask);    // ボタンの状態を読み込む
        // btn_state    // 現在のボタンの状態 
        // change_mask    // どのボタンが変化したか

        if (!(change_mask & 0x80000000) && (btn_state & (1UL << PIN_BTN))) {
            Serial << "PIN_BTN 離した" << crlf;    // L -> H
        }
        else
        {
            Serial << "PIN_BTN 押した" << crlf;    // H -> L    
        }
    }
}
-----
// PIN_BTNのピンはプルアップされている
// 押しボタンスイッチは押すと通電、離すと切断する

======================================
制御用DIP 0x09から2つのDIP(DIP 0x01,DIP 0x02)に無線で個別に信号を送るテスト
3台のDIPが必要です。(区別できるように数字を書くと良いです)
======================================
制御用DIPに押しボタンスイッチをつけます。
押しボタンスイッチは押している間は端子間が接続され、離すと端子間が切れます。
ボタンを押している間はDIP 0x01に3秒毎に信号を送信します。
ボタンを離している間はDIP 0x02に3秒毎に信号を送信します。

DIP 0x02は「通信距離を調べるテスト」で使用したDIPがそのまま使えます。
DIP 0x01は「LEDReceive.cpp」の1行を修正してDIPに書き込んでください。

変更箇所は以下の部分です。


LEDReceive.cpp
-----
7行目
const uint8_t u8devid = 0x02; <-- 0x02 を 0x01 に変更
-----

送信する制御用DIPのプログラムはLEDSelectDIP.cppとします。


LEDSelectDIP.cpp
-----
#include <TWELITE>
#include <NWK_SIMPLE>

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t u8devid = 0x09;     // このDIPのIDは0x09
const uint8_t PIN_BTN = 12;    // ボタンのピン
uint8_t sendid;          // 送り先のDIPのID
uint16_t count16;        // カウント用16bit
bool b_trans;            // 送信時のフラグ
uint8_t datax;            // 送信するデータ用

void setup()    {
    Buttons.setup(5);    // ボタン

    the_twelite
    << TWENET::appid(APP_ID)
    << TWENET::channel(CHANNEL);

    auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
        nwk
            << NWK_SIMPLE::logical_id(u8devid);    // 0x09

    Buttons.begin(pack_bits(PIN_BTN),5,10);    // ボタン
    the_twelite.begin();

// 変数の初期化
    count16 = 0;
    b_trans = false;
    datax = 0x00;
    sendid = 0x01;  // 初期の送信先は0x01とする
}

void loop() {
// 3秒経過したらb_transフラグをtrueにする
    if (TickTimer.available()) {    // 1m秒毎に実行される
        count16 += 1;        // カウントアップ
        if (count16 >= 3000) {    // カウントが3000になったら
            b_trans = true;    // フラグを送信にセット
            count16 = 0;    // カウントをリセット    
        }
    }

// 押しボタンの状態を調べる
    if (Buttons.available()) {    // ボタンが変化した(押した・離した)
        uint32_t btn_state, change_mask;
        Buttons.read(btn_state, change_mask);
        if (!(change_mask & 0x80000000) && (btn_state & (1UL << PIN_BTN))) {
            // PIN_BTN 離した
            sendid = 0x01;    // 送り先を 0x01 にセット
        }
        else    // PIN_BTN 押した
        {
            sendid = 0x02;    // 送り先を 0x02 にセット
        }
    }


// 送信実行
    if (b_trans) {        // フラグが送信なら
        if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
                pkt
                    << tx_addr(sendid)    // 送信先を設定
                    << tx_retry(0x1)      // リトライ回数
                    << tx_packet_delay(0,0,2);

        // 送信するデータをセット
                pack_bytes(
                    pkt.get_payload(),    // 格納先
                    uint8_t(datax)        // 送信するデータ
                    );

        // 送信実行
            pkt.transmit();
            b_trans = false;    // 送信が終わったので送信フラグをfalseにする
        }
    }
}
-----

======================================
アナログ入力のサンプル
======================================
測定する電圧は抵抗10kΩ×2で分圧した。


AnalogueTEST.cpp
-----
#include <TWELITE>

uint16_t au16AI[2];    // アナログ値保存用配列

/*** the setup procedure (called on boot) */
void setup() {
    Analogue.setup(true);
   // 引数の内容(ADCの初期化、Tick_timer、終了時のコールバック関数なし)

    Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC),100);
    // ADCするピンはA1とVCC、他にA2も選べる。
    // ADCの開始のタイマーの割込み回数 -> 100m秒間隔
    // ピンA1,A2はアナログ専用ピンなのでそのまま使えるが、アナログ/デジタル共有のAI2,AI4ピンは
    // スリープ時に利用制限があるので今回は使わない。
    // VCCは電池の電圧を測定する。電池の消耗具合が分かる。
}

/*** the loop procedure (called every event) */
void loop() {
//  A1 22ピンに電圧をかけておく

    if (Analogue.available()) {    // アナログ・デジタル変換が完了した

        // ADCから値を読み込む
        au16AI[0] = uint16_t(analogRead_mv(PIN_ANALOGUE::A1));    // 22ピンの電圧
        au16AI[1] = uint16_t(analogRead_mv(PIN_ANALOGUE::VCC));   // 電源電圧
    
        Analogue.end();  // ADCを一旦止める

        Serial << format("A1=%d, VCC=%d",au16AI[0] ,au16AI[1]) << crlf;

//    Serial << "A1= " << int(au16AI[0] << " VCC= " << int(au16AI[1] << crlf;  // これでもOK

        delay(1000);

       // ADC再開
        Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC),100);  
    }

    delay(20);    // 負荷軽減?

}
-----

MWXライブラリのADCサンプルコードにはAnalogue.available()とAnalogue.end()が使用されていません。
通常、アナログ・デジタル変換には変換時間が必要です。
しかし、十分に長い時間経った後ではAnalogue.available()で確認する必要が無いのかもしれません。
このサンプルコードではAnalogue.end()でADC回路を停止していますが、
常時AD変換する場合は、Analogue.end()とdelay(1000)後の//ADC再開Analogue.begin()は不要かもしれません。
ADC回路の開始・停止を繰り返す事が良いのか悪いのかは分かりません。自己責任でお願いします。

少しだけC++の文法

変数の定義の例
uint8_t 変数名 = 0; // unsigned-integer-type 8ビットの符号なし整数型 8,16,32,64ビットがある
int8_t 変数名 = 0; // signed-integer-type 8ビットの符号あり整数型 8,16,32,64ビットがある
int 変数名 = 0; // サイズは4バイトで、最大値は2147483647、最小値は-2147483648
bool 変数名 = false; // trueかfalseの値を持つ。
char 変数名 = 'P'; // アルファベット1文字を記憶
char 変数名[] = 'PING'; // 文字の配列
// 定義と同時に初期化(= 0;)をするのが望ましい。必須ではない。

// 変更できない変数の例
const uint8_t 変数名 = 18; // const + 変数の定義 + 値 <- 値は必須
// 後から代入できないから。

// 変数の種類と記載位置
// グローバル変数(プログラム全体で参照できる)
// #include <・・・>のすぐ下の行に定義

// 局所変数(ローカル変数)
if (Buttons.available()) { // {
uint32_t btn_state, change_mask; // 定義した btn_state, change_mask の変数は
Buttons.read(btn_state, change_mask); // このブロック中だけで使用できる。
} // }

スコープ
TWENET::appid()とかTWENET::channel()とかは
TWENETクラスのメソッドを明示する書き方。
::はスコープ解決演算子(Scope Resolution Operator)

スリープについて説明します。

温室の室温を計測して送信するシステムで考えてみましょう。
室温は1分毎とか10分毎にデータを蓄積できれば十分です。
計測処理に0.1秒かかるとして、残りは待機時間になります。
delay()でも処理を待つことができますが、電力の消費は変わりません。
スリープは省エネの待機状態になります。
delay()の代わりにsleep()を使うと省エネになります。
乾電池などでの駆動を考える時には必ず使う機能です。

======================================
スリープのサンプル
======================================


SleepTEST.cpp
-----
#include <TWELITE>

uint16_t count16 = 0;

void setup() {
	Serial << "setup " << int(count16) << crlf;
}

void wakeup() {
	Serial << "wakeup " << int(count16) << crlf;
}

void loop() {
	count16 += 1;

	Serial << "loop " << int(count16) << crlf;
	Serial.flush();
	the_twelite.sleep(600000);	// 10分設定でもOK
}
-----

sleep処理の流れ

setup()
loop()
sleep() <- sleep()の開始。loop()中で呼び出します。
sleep中…
wakeup() <- 時間がくると起床します。
loop() <- その後loop()に処理が移ります。
sleep() <- sleep()の開始
sleep中…
wakeup()
・・・
を繰り返します。

DIPの普段の消費電流は約21mA(CPUクロック:16MHz:規定値)だが、
sleepの時は消費電流は0.0015mAになります。

sleep関数の引数[デフォルト値]
sleep(
ミリ秒, <- 必須
前回の起床時間をもとに次の起床時間を再計算する。[true],
RAMを保持しないでスリープになる。[false],
スリープ用に用いるウェイクアップタイマー [SLEEP_WAKETIMER_PRIMARY]
)

「前回の起床時間をもとに次の起床時間を再計算する」の意味
ChatGPTの説明によると、sleepの時間を1000msとし、処理時間が50msかかるとすると
trueだと起床時刻が0,1000,2000,3000…となる。(先に起床時刻を決めてから処理をする)
falseだと起床時刻が0,1050,2100,3150…となる。(処理が済んでから1000ms間スリープする)

「RAMを保持しないでスリープになる」の意味
通常は変数の値を保持するべきなので、デフォルト値のfalseを使います。
もし、trueにすると、setup(),loop(),sleep中,setup(),loop(),sleep中…という流れになるそうです。多分。