トワイライト(TWELITE)のパルスカウンタの使い方

組み込みエンジニア

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

TWELITEのスリープを使うことで低消費電流での動作ができます。スリープからのウェイクアップ方法はDIの変化を検出する方法やパルスカウンタによる方法があります。DIの変化を検出しながらパルスカウンタの値を確認する方法をまとめました。

ウェイクアップの要因を決定する

ウェイクアップする要因は以下の通りのパターンとなります。

  • スリープタイマの時間経過
  • パルスカウンタが規定値以上になる
  • DIの状態変化のパルスを検出

今回はDIの状態変化でウェイクアップしてBME280の情報を親機に送信するアクトを作ります。パルスカウンタはマイコンを介さずにパルスの状態変化の回数をカウントすることができるためスリープでもパルスによる変化を検出できる機能です。

パルスカウンタの実装

パルスカウンタについての詳細はモノワイヤレスのHPに記載されているためポイントだけ説明します。

モノワイヤレス社のHP-MWX Library PulseCounter

例ではパルスカウンタの0を使用していますがパルスカウンタの1を使用する場合は0を1に置き換えてください。0や1を入れない場合は1を選択されます。

//パルスカウンタ1を使う場合PulseCounter1にする
void setup() { 
  PulseCounter0.setup();
 PulseCounter0.begin(0,PIN_INT_MODE::FALLING,3);
}

setup()メソッドでパルスカウンタの初期化を行います。begin()メソッドでパルスカウンタの動作に関する設定を行います。

begin()の第1引数はパルスカウンタの値が規定値よりも大きい場合にウェイクアップ割り込みを発生させる回数です。0を指定するとウェイクアップ要因になりませんが、パルスのカウント数を獲得することができます。

第2引数はパルスのエッジを指定します。立ち上がりエッジの場合PIN_INT_MODE::RISINGを指定し、立下りエッジの場合PIN_INT_MODE::FALLINGを指定します。

第3引数はノイズ対策で連続サンプル数の一致を確認してエッジの検出を行います。指定できる設定は0~3であり2の乗数に対応したサンプル数の一致の検出となります。

パルスカウンタの設定をした後直ちにスリープさせたい場合はbegin()関数内でパルスカウンタの設定を行うと効率がよさそうです。

void begin() {
  PulseCounter0.begin(0,PIN_INT_MODE::FALLING,3);
  sleepNow();
}

パルスカウンタが使用できるピン

TWELITE-DIPのデータシートを確認すると機能割り当て(※3)にPC1がDIO8に割り当てられておりPC0はDIO0に割り当てられています。詳細は下記リンクのデータシートを参照してください。

MW-PDS-TWELITEDIP-JP.pdf (mono-wireless.com)

TWELITE-DIPのシルク印刷の1に入力するとパルスカウンタ0が動作します。シルク印刷の8に入力するとパルスカウンタ1が動作します。

DIの状態変化(パルスエッジで検出)

void setup() {
    pinMode(PIN_DI1,PIN_MODE::WAKE_FALLING_PULLUP);
}

pinMode()メソッドでピンの設定ができます。第1にDI/DOのピン番号を指定します。第2引数にピンの機能を選択します。INPUT/OUTPUTなどがありますがPIN_MODEの型で選択できる機能が定義されています。

WAKE_FALLING_PULLUPを選択するとINPUT機能となり立下りエッジでスリープ状態からウェイクアップするようになります。またプルアップが有効になるためDI情報がHIGHレベルになります。

パルスカウンタとDIの状態変化の検出の組み合わせ

void wakeup(){
    uint16_t pulcnt;

    pulcnt = PulseCounter0.read();

    if (b_senser_started) { //napからwakeup
        if( pulcnt == 0){
            if( b_found_bme280 ){
                eState = E_STATE::WORK_JOB;
            }
            else{
                eState = E_STATE::INIT;
            }
            b_transmit = false;
        }
        else{
            napNow();
        }
    }
    else{ //sleepからwakeup
        if(pulcnt >= 1){
            b_senser_started = true;
            sns_bme280.begin();
            napNow();
        }
        else{
            sleepNow();
        }
    }
}

ウェイクアップしwakeup()が呼び出される条件は以下の通りとなります。

  • DIが立下りエッジを検出する
  • スリープで設定した時限を経過する
  • パルスカウンタで指定した回数以上になる

今回はパルスカウンタの規定値に0を指定するためウェイクアップの要因になりませんが0以上を指定している場合はウェイクアップの条件となります。

ウェイクアップしたときパルスカウンタの値を読み込むと1以上の値になっているとDIの変化によるウェイクアップであることが確認できます。

パルスカウンタの値の値が0であればスリープの時間が経過したことによるウェイクアップであるためBME280の計測を開始せずに再びスリープを開始します。

パルスカウンタの値が1以上であるとBME280の計測を開始しショートスリーブ(ナップ)します。

チャタリングなどでナップ中にウェイクアップしてしまうことを防ぐためにパルスカウンタの値が0であればもう一度ナップするようにしています。

動作確認

TWELITE-DIPを含むモジュールを子機、MONOSTICKを親機とします。

パルスカウンタの動作確認用の回路図
パルスカウンタの動作確認用の回路図(子機)

BME280モジュールはプルアップ抵抗が実装されているためプルアップ抵抗は必要ありません。 TWELITEはSW1が押されるまでスリープで待機します。SW1が押されるとBME280の計測を行いMONOSTICKに温湿度・気圧のデータを送信してスリープします。

SW1を接続しているDIはパルスカウンタ0を兼ねているピンでありマイコン内蔵のプルアップを使用して立下りエッジを検出して動作するようにします。

子機の動作確認
子機の動作確認

スリープの時間を10秒にしているため10秒経過するとsleeping 10000msが表示されます。SW1を押すとウェイクアップしてBME280の計測を行い温湿度・気圧データを表示しています。表示した後はMONOSTICKに対して無線通信を行っています。

親機の動作確認
親機の動作確認

MONOSTICK(親機)に無線通信で受信したBME280の情報が表示されていることが確認できました。子機と親機の情報が一致していることが分かります。

SW1を押したとき押し方によってはチャタリングが大きくなり2回ボタンが押されたような判定になることがありました。

チャタリング防止についてはパルスカウンタの判定を厳しくするなどで調整することで解決できそうです。子機側は低消費電流のリモコンを作ることができますが、親機は無線情報を受信する必要があるためスリープすることができないため低消費電流での動作はできません。

ソースコード全体

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

子機のソースコード:

#include <TWELITE>
#include <NWK_SIMPLE>
#include <SNS_BME280>

#define PIN_DI1 1
#define SLEEP_MAX 10000
/*** Config part */
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
uint8_t u8ID = 0xFE;
uint16_t cnt;

typedef struct{
    int16_t temp;
    int16_t humi;
    int16_t press;
}SENSOR_TYP;

enum  E_STATE {
    INIT = 0,
    WORK_JOB,
    WORK_JOB2,
    TX,
    WAIT_TX,
    EXIT_NORMAL,
    EXIT_FATAL
};
/* 変数宣言 */
SNS_BME280 sns_bme280;
MWX_APIRET txreq_stat;
E_STATE eState;
bool b_senser_started = false;
bool b_transmit = false;
uint32_t u32millis_tx; // millis() at Tx
SENSOR_TYP getsensor;
bool b_found_bme280 = false;
/*** Local function prototypes */
MWX_APIRET transmit();
void napNow(void);
void sleepNow(void);
/*** the setup procedure (called on boot) */
void setup() {

    pinMode(PIN_DI1,PIN_MODE::WAKE_FALLING_PULLUP);
    Wire.begin();
    sns_bme280.setup();
    if(sns_bme280.probe()){
        b_found_bme280 = true;
        Serial << "bme280-ok" << crlf << mwx::flush;
    }

    PulseCounter0.setup(); // Pulse Counter setup
    the_twelite
        << TWENET::appid(APP_ID)
	<< TWENET::channel(CHANNEL);
    auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
    nwk << NWK_SIMPLE::logical_id(u8ID);
    the_twelite.begin(); // start twelite!
    Serial << "--- Pulse Counter:" << " ---" << mwx::crlf;
}
/*** the loop procedure (called every event) */
void loop() {
    bool loop_more;

    do{
        loop_more = false;
        switch(eState){
            case E_STATE::INIT: //デバイスの確認
                sns_bme280.setup();
                if(sns_bme280.probe()){
                    b_found_bme280 = true;
                    loop_more = true;
                    eState = E_STATE::EXIT_NORMAL;
                    Serial << "bme280-ok" << crlf << mwx::flush;
                }
                else{
                    sleepNow();
                }
                break;
            case E_STATE::WORK_JOB: //経過時間の通知
              
                if (!sns_bme280.available()) {
                    sns_bme280.process_ev(1);
                    //napにより100ms以上経過しているため1をセット
                    //0ではavailableとならない
                }
                loop_more = true;
                eState =  E_STATE::WORK_JOB2;
                break;
            case E_STATE::WORK_JOB2: //測定データの取得
                if(sns_bme280.available()){
                    getsensor.temp = sns_bme280.get_temp_cent();
                    getsensor.humi = sns_bme280.get_humid_per_dmil();
                    getsensor.press = sns_bme280.get_press();

                    Serial 
                        << crlf << format("..%04d/finish sensor capture.", cnt)
                        << crlf << "  BME280: T=" << sns_bme280.get_temp() << "C"
                        << " H=" << sns_bme280.get_humid() << "%" 
                        << " P=" << sns_bme280.get_press() << "hPa" << crlf;
                        ++cnt;
                    eState =  E_STATE::TX;
                    loop_more = true;
                }
                break;
            case E_STATE::TX:
                txreq_stat = transmit(); //データの取得準備
                if (txreq_stat) {
                    u32millis_tx = millis();
                    eState = E_STATE::WAIT_TX;
                    loop_more = true;
                }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;
                }
                else if (millis() - u32millis_tx > 100) {
                    Serial << int(millis()) << "!FATAL: tx timeout." << crlf;
                    eState = E_STATE::EXIT_FATAL;
                    loop_more = true;
                }
                break;
            case E_STATE::EXIT_NORMAL:
                sleepNow();
                break;
            case E_STATE::EXIT_FATAL:
		Serial << crlf << "!FATAL: RESET THE SYSTEM.";
		delay(100);
		the_twelite.reset_system(); //異常とみなしリセット
                break;
        }
    }while(loop_more);
}
/*** begin procedure the first call */
void begin() {
    PulseCounter0.begin(0,PIN_INT_MODE::FALLING,3);
    sleepNow();
}
// perform sleeping
void sleepNow() {
    uint32_t u32ct = SLEEP_MAX; // 10sec
    Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
    b_senser_started = false;
    b_transmit = false;
    the_twelite.sleep(u32ct);
}
/* callback wakeup */
void wakeup(){
    uint16_t pulcnt;

    pulcnt = PulseCounter0.read();

    if (b_senser_started) { //napからwakeup
        if( pulcnt == 0){
            if( b_found_bme280 ){
                eState = E_STATE::WORK_JOB;
            }
            else{
                eState = E_STATE::INIT;
            }
            b_transmit = false;
        }
        else{
            napNow();
        }
    }
    else{ //sleepからwakeup
        if(pulcnt >= 1){
            b_senser_started = true;
            sns_bme280.begin();
            Serial << "wakeup" << int(pulcnt) << crlf << mwx::flush;
            napNow();
        }
        else{
            sleepNow();
        }
    }
}
/* ナップ(ショートスリープ)処理 */
void napNow(){
    uint32_t u32ct = 100;
    Serial << "sleep-nap" << crlf << mwx::flush;
    the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}
/* 送信パケット準備 */
MWX_APIRET transmit() {

    if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
        pkt << tx_addr(0x00)  
	    << tx_retry(0x2) 
	    << tx_process_immediate();
             
        pack_bytes(pkt.get_payload(),
                   (uint16)getsensor.temp,
                   (uint16)getsensor.humi,
                   (uint16)getsensor.press);
                   
	return pkt.transmit();
    }
    return MWX_APIRET(false, 0);
}

親機のソースコード:

#include <TWELITE>
#include <NWK_SIMPLE>
#include <MONOSTICK>

#define CNT_MAX 1000

/*** Config part */
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;

int16_t cnt1ms;
uint32_t ts;
bool rcvflg;
/*** function prototype */
void receive();
/*** setup procedure (run once at cold boot) */
void setup() {

    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
        << TWENET::appid(APP_ID) 
	<< TWENET::channel(CHANNEL)
	<< TWENET::rx_when_idle();

    auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    nwksmpl << NWK_SIMPLE::logical_id(0x00);
    the_twelite.begin();
    Serial << "--- MONOSTICK_bme280 act ---" << mwx::crlf;
}
/*** loop procedure (called every event) */
void loop() {

    if(TickTimer.available()){
	++cnt1ms;
    }
    while (the_twelite.receiver.available()) {
	receive();
	rcvflg = true;
    }

    if( cnt1ms >= CNT_MAX){
	cnt1ms -= CNT_MAX;
	ts = (ts + 1 ) % 100000;

	if(rcvflg){
	    rcvflg = false;
	}
        else{
	    Serial << "ts:" <<  int(ts) << mwx::crlf << mwx::flush;;
	}
    }
}

/* 無線通信受信 */
void receive() {
    uint8_t u8DI_BM_remote = 0xff;
    uint16_t temp;
    uint16_t humi;
    uint16_t press;

    auto&& rx = the_twelite.receiver.read();
    Serial << "ts:" << int(ts) << ":" << int(rx.get_addr_src_lid());
    auto&& np = expand_bytes(rx.get_payload().begin(),rx.get_payload().end()
            ,temp,hum,press);
    Serial << " temp: " << (double)temp/100 <<"C" 
	   << " humid: " << (double)humi/100 << "%" 
           << " press: " << (int)press << "hPa"
	   << mwx::crlf << mwx::flush;
}

関連リンク

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

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

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

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

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