トワイライト(TWELITE)のスリープとウェイクアップを実装する

組み込みエンジニア

こんにちは、ENGかぴです。

ZigBeeモジュールであるトワイライト(TWELITE)はスリープを使用することで低消費電流で動作させることができるためエナジーハーベストによって生成した電源を使って動作させることができます。スリープを使って無線タグアプリを模擬しました。

トワイライトを太陽光パネルで動作させたことやMWSTAGEの環境でソフト開発して無線通信したことなどについてまとめています。

トワイライト(TWELITE)のソフト開発と無線通信でできること

スリープとウェイクアップを実装する

IoTモジュールをエナジーハーベストによる電源や電池で運用する場合、間欠動作させて電池の消耗を抑えることが前提になります。そのためIoTモジュールをスリープ状態にさせることは運用上必須の条件になります。スリープ状態にすることで消費電流は数uA程度になります。

TWELITEもスリープ機能を搭載しており無線タグアプリを使用することでかなり低消費電力での運用が可能になります。太陽光パネルによるエナジーハーベストで無線タグアプリを動作させた結果については下記にまとめています。

トワイライト(TWELITE)と太陽光パネルによる通信実験

トワイライト(TWELITE)で夜間での通信にチャレンジ

スリープ機能を使う方法

void sleepNow() {
    uint32_t u32ct = 9000 + random(0,2000);
    Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
    the_twelite.sleep(u32ct);
}

スリープ機能はthe_twelite.sleep(引数)に数値を入力するとスリープされることができます。1を入力すると1msのスリープになります。この処理をまとめてsleepNow()という関数で管理しています。

スリープ時間の設定をu32ctに設定していますrandom(0,2000)は0から2000までの乱数をとなるのでスリープ時間は9000~11000msの間になります。通信する機器が増えると同じタイミングでスリープからウェイクアップ機能して通信を行うと干渉を起こすリスクがあるのでタイミングが一定にならないようにしています。

モノワイヤレス社のHPにもこの対策の意味について説明されています。

スリープの時間が経過するとwakeup()関数に遷移します。wakeup()関数は他のファイルで定義されていますが、内部の処理はwakeup()をユーザーが実装することで処理を追加することができます。

void wakeup() {

    if( iLedCounter2 == FLG_CLR){
        iLedCounter2 = FLG_SET;
        digitalWrite(PIN_DO3, HIGH);
    }else{
        iLedCounter2 = FLG_CLR;
        digitalWrite(PIN_DO3, LOW);
    }
    Serial << "--- wakeup (using a timer) ---" << crlf;

ウェイクアップした時にLEDの点灯と消灯の状態を切り替える処理を実装しています。今回はスリープさせた後にボタンを押すことでウェイクアップされる機能を実装してスリープとウェイクアップの動きを確認します。

LEDが消灯したタイミングでスリープした場合の消費電流を測定すると1.2uA~1.3uAとなっておりデータシートと一致しているためスリープできていることが分かりました。

動作確認用の回路図

スリープの動作確認用の回路図
スリープの動作確認用の回路図

TWE-EH-Sを使ってTWELITEに電源を供給します。電気二重層コンデンサに電荷をフルチャージしてもLEDを点灯させる電流によっては長持ちしませんが、短時間の動作であれば供給は可能(この条件下の実測では30秒が限界)です。

TWELITEに電源が供給されるとLED1が点灯と消灯を繰り返します。SW1を押すとTWELITEがスリープ状態(今回は9秒から11秒のランダム)になります。スリープ状態の時間が経過するとウェイクアップしてLED2を点灯と消灯を切り替えます。LED2はウェイクアップした時に切り替えるようにしています。

スリープ状態になるとDIDO情報が維持されるためLED1が点灯したままスリープ状態になることがあります。今回のソフトはスリープの状態が分かることを目的にしているためLEDを消灯するような処理を入れていません。

スリープは消費電流を抑えるために使用するので実運用時はスリープする前にLEDを消灯するなど消費電流を抑えることが必要です。

TWELITEのピン番号とTWELITE DIPのピン番号の確認

引用元:モノワイヤレス社のHPーTWELITE DIPのピン配置
引用元:モノワイヤレス社のHPーTWELITE DIPのピン配置

TWELITEのピン番号とTWELITE DIPのピン番号は異なります。TWELITE DIPのシルクを見ると番号が書いてありDIDOピンの番号と一致するようになっているのでピン配置図でDIDOピンの場所が分かります。

回路図で使用しているDOとDIは、赤枠で囲っているものはDOに割り当てた番号、青枠で囲っているのはDIに割り当てた番号です。

DIDO機能の設定

MWSTAGEではDIDOの設定が簡単にできるようにpinMode()関数が実装されています。

pinMode(PIN_DO1, OUTPUT_INIT_LOW); //BOOTをLOW
pinMode(PIN_DO2, OUTPUT);
pinMode(PIN_DO3, OUTPUT);
pinMode(PIN_DI1, INPUT_PULLUP);
pinMode(PIN_DI2, WAKE_FALLING_PULLUP);

DIDOはpinMode()関数に引数を指定すると使用できます。VScode上でpinModeの上にカーソルを置き右クリックして定義へ移動を選択すると関数の詳細が分かります。

pinMode()関数の詳細
pinMode()関数の詳細

pinMode()関数の詳細が分かるのでこの中でPIN_DIGITALとE_PIN_MODEについて確認することで引数の詳細が分かります。

PIN_DIGITALの詳細
PIN_DIGITALの詳細
PIN_MODEの詳細
PIN_MODEの詳細

PIN_DIGITALの定義を見るとDIO0からDIO19の番号を引数に指定する必要があることが分かります。

下から4つのものはDIOの合計数とDOの合計値になっています。DIO0からDIO19を特殊な使い方をしない限り気にする必要ありません。

E_PIN_MODEは指定したDIDOピンに対しての機能を割り当てる際に使用する引数の詳細です。

ピン機能内容
INPUTDIOピンをDIとして使用する
OUTPUTDIOピンをDOとして使用する
INPUT_PULLUPDIとして使用し、マイコンのプルアップを使用する
INPUT_PULLDOWNDIとして使用し、マイコンのプルダウンを使用する
OUTPUT_INIT_HIGHDOとして使用し、HIGHを出力する
OUTPUT_INIT_LOWDOとして使用し、LOWを出力する
WAKE_FALLINGDIとし、立下りエッジでウェイクアップする
WAKE_RISINGDIとし、立ち上がりエッジでウェイクアップする
WAKE_FALLING_PULLUPWAKE_FALLINGで、マイコンのプルアップを使用する
WAKE_RISING_PULLUPWAKE_RISINGで、マイコンのプルアップを使用する
DISABLE_OUTPUTDOを使用しない
E_PIN_MODEの設定詳細

PIN_DIGITALを参照して定数を宣言してpinMode()への引数として使用する例は以下の通りです。

const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18; //BOOT
const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19; //LED1
const uint8_t PIN_DO3 = mwx::PIN_DIGITAL::DIO12; //wakeup LED
const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO9; //sleep
const uint8_t PIN_DI2 = mwx::PIN_DIGITAL::DIO4; //wakeup

pinMode(PIN_DO1, OUTPUT_INIT_LOW); //BOOTをLOW
pinMode(PIN_DO2, OUTPUT);
pinMode(PIN_DO3, OUTPUT);
pinMode(PIN_DI1, INPUT_PULLUP);
pinMode(PIN_DI2, WAKE_FALLING_PULLUP);

ピン設定を変更したいとき定数の箇所のみの変更になるため間違い変更ミスによるリスクを低減できます。

ボタンのDIフィルタ(チャタリング防止)

ボタン通したときONとOFFが安定しない期間(チャタリング)が発生します。この対策としDIフィルタを作ることで対策を行います。下記記事のDIフィルタの考え方と基本的には同じです。

PICマイコン(PIC12F675)のDIピンのフィルタの考え方

MWSTAGEにおいてはButtons()関数でチャタリングの防止ができるように関数(ライブラリ)が準備されています。

Buttons.setup(5);
Buttons.begin(pack_bits(PIN_DI1,PIN_DI2),5,10);

Buttons()はsetupとbeginを同時に使用する必要があります。begin内でsetupでメモリを確保した値を参照している構造となっているため先にsetupを行い、beginで対象のDIを指定するように使用します。今回はボタンが2つあるため以下のようにソースコード書きたくなります。

Buttons.setup(5);
Buttons.begin(1UL << PIN_DI1, 5, 10);
Buttons.begin(1UL << PIN_DI2, 5, 10);

この場合はPIN_DI1のフィルタがPIN_DI2による処理が上書きされPIN_DI1のDIが動作しなくなってしまいます。

ボタンを1つ以上する際は、複数のDIに対応したpack_bitsが実装されているので使用します。対象のDIを指定することでまとめて使用することができるようになります。

Buttons.setup(5);
Buttons.begin(pack_bits(PIN_DI1,PIN_DI2),5,10);

Buttons.begin()の引数である5はDI情報が5回一致すると採用するための回数であり、10は10ms毎のインターバルでDIを確認する意味の引数になります。

動きを確かめるために引数の10を200にすると1s間ボタンの状態が維持されないと反応しない動きになります。

Buttons()関数はボタンのチャタリング防止のために準備されていますが、DIフィルタとしても使えるので用途としてはボタンに限らずスイッチやフォトカプラなどによる信号のフィルタリングにも使用できそうです。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <TWELITE>

#define FLG_SET 1
#define FLG_CLR 0

const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18; //BOOT
const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19; //LED1
const uint8_t PIN_DO3 = mwx::PIN_DIGITAL::DIO12; //wakeup LED
const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO9; //sleep
const uint8_t PIN_DI2 = mwx::PIN_DIGITAL::DIO4; //wakeup

int iLedCounter1 = FLG_CLR;
int iLedCounter2 = FLG_CLR;

/*** Local function prototypes */
void sleepNow();

/*** the setup procedure (called on boot) */
void setup() {

    pinMode(PIN_DO1, OUTPUT_INIT_LOW); //BOOTをLOW
    pinMode(PIN_DO2, OUTPUT);
    pinMode(PIN_DO3, OUTPUT);
    pinMode(PIN_DI1, INPUT_PULLUP);
    pinMode(PIN_DI2, WAKE_FALLING_PULLUP);

    Buttons.setup(5);
    //Buttons.begin(1UL << PIN_DI1, 5, 10);
    //Buttons.begin(1UL << PIN_DI2, 5, 10);
    Buttons.begin(pack_bits(PIN_DI1,PIN_DI2),5,10);


    Timer0.begin(10); // 10Hz Timer 
    Serial << "--- btnsleep(using a timer) ---" << crlf;
}

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

    if (Buttons.available()) {
        uint32_t bm, cm;
        Buttons.read(bm, cm);

        if (bm & (1UL << PIN_DI1)) {
            Serial << "Button Released!" << crlf;
        }else{
            Serial << "Button Pressed!" << crlf;
            sleepNow();
        } 
    }

    if( Timer0.available()){
        if( iLedCounter1 == FLG_CLR){
            iLedCounter1 = FLG_SET;
            digitalWrite(PIN_DO2, HIGH);
        }else{
            iLedCounter1 = FLG_CLR;
            digitalWrite(PIN_DO2, LOW);
        }
    }
}
// perform sleeping
void sleepNow() {
    uint32_t u32ct = 9000 + random(0,2000);
    Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf;
    the_twelite.sleep(u32ct);
}

// wakeup procedure
void wakeup() {

    if( iLedCounter2 == FLG_CLR){
        iLedCounter2 = FLG_SET;
        digitalWrite(PIN_DO3, HIGH);
    }else{
        iLedCounter2 = FLG_CLR;
        digitalWrite(PIN_DO3, LOW);
    }
    Serial << "--- wakeup (using a timer) ---" << crlf;
}

関連リンク

トワイライトを太陽光パネルで動作させたことやMWSTAGEの環境でソフト開発して無線通信したことなどについてまとめています。

トワイライト(TWELITE)のソフト開発と無線通信でできること

Code Camp完全オンラインのプログラミング個人レッスン【無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました