トワイライト(TWELITE)にAD変換と無線通信を実装する

組み込みエンジニア

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

ZigBeeモジュールであるトワイライト(TWELITE)のAD変換機能を使って温度センサーの出力データを取得し無線通信するアプリを作ります。温度センサーにMCP7800Aを使用して室温を計測してMONOSTICKでデータを表示します。

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

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

AD変換と無線通信を実装する

IoTモジュールをエナジーハーベストによる電源や電池で運用する場合、スリープをうまく使用して電池を長持ちさせる必要があります。下記記事ではMWSTAGEの開発環境の作り方とスリープの仕方についてまとめています。興味があればご覧ください。

トワイライト(TWELITE)の開発環境MWSTAGEを試してみる

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

AD変換と無線通信をACT内で実装するためには以下をインクルードする必要があります。

#include <TWELITE>
#include <NWK_SIMPLE>

AD変換を実装する

ACTのsetup()関数内にAD変換に関する初期化を実装することで使用することができます。APIの詳細はモノワイヤレス社のHPを参考にしてください。

モノワイヤレス社ーAnalogue関係のAPIについて

Analogue.setup(true, ANALOGUE::KICK_BY_TICKTIMER);

Analogue.setup()の第1引数は、AD変換のレギュレータが安定するまで待つための設定です。マイコンを初期化をする場合は設定が安定するまで待つほうがよいのでtrueにしています。

第2引数のキックバック(AD変換を開始するトリガー)をTickTimerにしています。TickTimerは1ms毎に動作するタイマーです。

第3引数にコールバック関数をセットするとAD変換後にコールされます。使用例は以下の通りです。

//AD変換完了後にコールバックさせる場合の例
void testcall(); //プロトタイプ宣言

//setup設定の第3引数に関数名を指定
Analogue.setup(
          true
        , ANALOGUE::KICK_BY_TICKTIMER // TICKTIMER (1ms) による開始
        , testcall // コールバック関数の指定(指定ポート全部が終了したとき)
        );

// called when finished all ADC channels
void testcall(){
    //コールバックされた時の処理を入れる
    Serial << "CallBack-OK" << mwx::crlf;
}

温度センサーの値を取得するため直流電圧の入力になります。度センサーなど1秒当たりの変化が少ない素子なので使用する場合はAD変換の頻度を抑えて消費電流を減らすことも有効です。

Analogue.begin(pack_bits(PIN_AD1,PIN_ANALOGUE::VCC),100);

Analogue.begin()の第1引数にAD変換に使用したいポートをセットできます。電源電圧と温度センサーの2つをAD変換するためpack_bits()を使用して2つのポートを指定しています。

第3引数の100はAD変換の間隔を100回に一回にする設定です。TickTimerをキックとして100に一回になるので100ms間隔でAD変換する設定になります。

]

AD変換値の移動平均をとる

AD変換値は周辺の回路や配線によってノイズなどの影響を受けて安定しないことがあります。そのため周辺回路にフィルタを設置したりソフトでフィルタを実装することがあります。AD変換に関するサンプリングと移動平均によるフィルターの作り方の例を下記記事にまとめています。

PICマイコン(PIC12F675)のAD変換とタイマーの組み合わせ

loop()関数内でAnalogue.available()でAD変換の完了を確認して変換値をadTemp.buf[]に格納しています。実装例は以下の通りです。

if(Analogue.available()){
    au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC); //VCCの値
    au16AI[1] = Analogue.read(PIN_AD1);	//温度センサーの値
    adTemp.buf[adTemp.wp] = Analogue.read(PIN_AD1);/ /温度センサーの値を格納

    uint16_t sum;
    ad_finish = true;
    for( uint8_t i=0; i < ADC_FLT_MAX; i++ ){
	if( adTemp.buf[i] == 0xFFFF){
	    ad_finish = false;
	    break;
	}else{
	    sum += adTemp.buf[i];	//合計を算出
	}
    }

    if( ad_finish ){
	adTemp.dat = sum >> 2;/ /4で割る(2回右にシフトする)
    }
    if( ++adTemp.wp >= ADC_FLT_MAX){ //値の格納場所を更新する
	adTemp.wp = 0;
    }
}

adTemp.buf[]内にAD変換値がすべて格納された時からbufのサイズ分のAD変換値の合計値を計算してbufのサイズで割ることで平均値を算出しています。計算する際はシフト演算を使用することで計算処理による遅延が少なくなります。

AD変換値を格納するバッファのサイズは2の乗数になるように宣言しておくと良いです。

無線通信を実装する

無線通信に関するAPIの詳細はモノワイヤレス社のHPにありますの参考にしてください。

モノワイヤレス社ーthe_twelite(TWENET利用の中核クラス)

無線通信について「Slp_Wk_andTx」のサンプルアクトが追加されています。無線通信に関してモード遷移を利用して送信処理を待つように工夫されたものになっています。

モノワイヤレス社ーサンプルアクト「Slp_Wk_and_Tx」

無線通信の初期化

無線通信を行うためにはアプリケーションIDと使用するチャンネルを指定する必要があります。サンプルに倣って以下のように設定します。

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;

setup()関数内でこれらの値を使ってthe_tweliteクラスへの登録を行います。

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

the_tweliteにアプリケーションIDと使用するチャンネルを指定しています。rx_when_idleは受信回路を有効にする指定です。今回は温度センサーの値を子機として送信するのみなので指定する必要はありませんが実装しています。

auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
       nwksmpl << NWK_SIMPLE::logical_id(0xFE)
       the_twelite.begin(); // start twelite!

始めにthe_twelite.network.useにインクルードしたMWK_SIMPLEを指定します。次に親機か子機であるかを設定します。0x00を指定すると親機になり0xFE(ID未設定の子機)を指定すると子機になります。子機として温度センサーの値を送信したいので0xFEを指定しています。

the_twelite.begin()で通信の設定が完了します。データを送信するためには別途準備が必要です。

データの送信

無線でデータを送信するためにはデータをペイロードに格納しネットワークオブジェクトのthe_twelite.network.use<MWK_SIMPLE>()のtransmit()メソッドを発行します。サンプルに倣ってペイロードに温度センサーの値とVCCの値をセットしています。

MWX_APIRET transmit() {

    if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	Serial << "analogue"<< int(au16AI[0]) << ".."<< int(adTemp.dat)   << mwx::crlf << mwx::flush;

	pkt << tx_addr(u8devid)  
	    << tx_retry(0x3) 
	    << tx_packet_delay(0,0,2); //再送指定しない場合はtx_process_immediate()でもよい
	
	pack_bytes(pkt.get_payload() , au16AI[0], adTemp.dat);
	return pkt.transmit();
    }
	return MWX_APIRET(false, 0);
}

tx_addr()に送信先を指定しますが親機に送信するため0x00を指定します。tx_retryは送信失敗した時のリトライ回数です。回数を多くしすぎると消費電流が大きくなるので少なめがよさそうです。

tx_packet_delay()の第1引数は送信開始までの最低待ち時間、第2引数が最長待ち時間です。2つとも0を指定しているので条件とタイミング次第で直ちに送信します。第3引数は再送間隔で最初のパケットを送って失敗した場合2ms間隔で再送を行うことになります。送信後スリープさせるため再送においても短い間隔に設定しています。

直ちに送信する場合で再送指定しない場合はtx_process_immediate()による指定も可能です。

送信設定後はペイロードに自分が送りたいデータを格納します。ペイロードの準備が完了したら。ネットワークオブジェクトにtransmit()を指示することで通信がスタートします。

動作確認

温度センサーから温度情報をアナログ変換で取得し無線通信によってMONOSTICKに通知します。MONOSTICKはParent-MONOSTICKをそのまま使用しています。

動作確認用の回路図

ADCと無線通信の確認に使用する回路
ADCと無線通信の確認に使用する回路

電源はTWE-EH-Sを使用します。TWELITEが動作開始したらTWE_GNDとGNDが切り離されないようにBOOTをLOWにします。基本的に5秒間隔でスリープしながらMCP9700A(温度センサー)の値を取得しながらAD変換を行い、ウェイクアップした時にMONOSTICKにデータを送信するようにします。

スリープ時の電流を測定すると温度センサー分の消費電流(約6uA)が増えているためTWELITE単体でのスリープ時電流1.2uAを上回る6.2uAとなっていました。

5秒間隔で消費電流が増加していることからウェイクアップしてAD変換やデータの送信などをしていることが分かりました。テスターのサンプリング時間などもあるため正確ではありませんが約200uAでした。

5秒に200uA消費するので1秒当たり40uA消費していることになり電池を長持ちさせるさせるためには送信タイミングについてもシビアに考えていく必要がありそうです。

ACTで無線タグアプリを模擬したソフトを作ることでエナジーハーベストによるIoTモジュールの動作を検討において幅が広がる感じで面白いと感じています。

動作確認(TeraTermでMONOSTICKのシリアルデータを確認)

通信データの確認
通信データの確認

MONOSTICKのシリアルデータをTeraTermで表示して子機からの通信データを確認します。FMT PACKETで子機からのデータを確認します。(RAW PACKETでも子機からデータは同じです)子機から送信されたデータは青の部分と赤の部分です。

この電圧を実測すると電源が3.16Vで温度センサーが0.79Vとなっていたので測定できていることが分かりました。電源電圧が分かるので電圧低下を判断して送信頻度を落とすなど対策ができそうです。

温度センサーについて電圧値から温度を計算してみます。温度センサー(MCP9700A)のデータシートを確認すると0℃の時500mVで1℃の変化で±10mVとなっています。データを見ると0.78V(780mV)であるから28℃になります。

室温が26℃でソーラー電源確保のため窓際に置いていたことを考慮しても温度が測定できている判断できます。

ソースコード全体

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

#include <TWELITE>
#include <NWK_SIMPLE>

#define TIME_UP 0
#define TIME_OFF -1
#define ZIG_WAIT_MAX 100
#define ADC_FLT_MAX 4

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

struct ADC_MEAN
{
    uint8_t wp;
    uint16_t buf[ADC_FLT_MAX];
    uint16_t dat;
};

const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18; //Boot
const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO12; //SW1--wakeup
const uint8_t PIN_AD1 = mwx::PIN_ANALOGUE::A1; //温度

// application use
MWX_APIRET txreq_stat;
uint16_t au16AI[2];
ADC_MEAN adTemp;
bool b_transmit = false;
bool ad_finish = false;
uint8_t u8txid = 0;
uint8_t u8devid = 0x00;	//parent
int16_t timzigwait = TIME_OFF;

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

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

    for(auto&& x : au16AI) x = 0xFFFF;
    for(auto&& x : adTemp.buf) x = 0xFFFF;

    pinMode(PIN_DO1, OUTPUT_INIT_LOW); //BOOTをLOW

    Analogue.setup(true, ANALOGUE::KICK_BY_TICKTIMER);
    Analogue.begin(pack_bits(PIN_AD1,PIN_ANALOGUE::VCC),100);
    txreq_stat = MWX_APIRET(false,0);
    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(0xFE);  
	   the_twelite.begin(); // start twelite!

    Serial << "--- wakup-com->start ---" << mwx::crlf;
}

/*** the loop procedure (called every event) */
void loop() {
	
    if( TickTimer.available()){
        if(timzigwait > TIME_UP ){
	    --timzigwait;
	}
    }
	
    if ( b_transmit ) { //送信受け付け完了したか
	if (the_twelite.tx_status.is_complete(txreq_stat.get_value())) { //送信が完了したか	
	    //Serial << "..transmit complete." << mwx::crlf << mwx::flush;
	    timzigwait = TIME_OFF;
	    sleepNow();
	}else{
	    if(timzigwait == TIME_UP){
		timzigwait = TIME_OFF;
		sleepNow();
	    }
	}
    }else{
	txreq_stat = transmit();
	if(txreq_stat){
	    //Serial << "..sleep2." << mwx::crlf << mwx::flush;
	    b_transmit = true;
	    timzigwait = ZIG_WAIT_MAX;
	}else{
	    Serial << "..chk2." << mwx::crlf << mwx::flush;
            sleepNow();
	}
    }
	
    if(Analogue.available()){
	au16AI[0] = Analogue.read(PIN_ANALOGUE::VCC); //VCCの値
	au16AI[1] = Analogue.read(PIN_AD1); //温度センサーの値
	adTemp.buf[adTemp.wp] = Analogue.read(PIN_AD1);//温度センサーの値を格納

	uint16_t sum;
	ad_finish = true;

	for( uint8_t i=0; i < ADC_FLT_MAX; i++ ){
	    if( adTemp.buf[i] == 0xFFFF){
		ad_finish = false;
		break;
	    }else{
	        sum += adTemp.buf[i]; //合計を算出
	    }
	}

	if( ad_finish ){
	    adTemp.dat = sum >> 2; //4で割る(2回右にシフトする)
	}

	if( ++adTemp.wp >= ADC_FLT_MAX){ //値の格納場所を更新する
	    adTemp.wp = 0;
	}
    }
}
// perform sleeping
void sleepNow() {
    uint32_t u32ct = 5000 + random(200); //5000ms+ランダム間スリープ
    //Serial << "..sleeping " << int(u32ct) << "ms." << mwx::crlf << mwx::flush;
    the_twelite.sleep(u32ct);
}
// perform wakeup
void wakeup(){
    Serial << "..wakeup" << mwx::crlf;
    b_transmit = false;
}
/*** transmit a packet */
MWX_APIRET transmit() {

    if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
	//Serial << "analogue"<< int(au16AI[0]) << ".."<< int(adTemp.dat)   << mwx::crlf << mwx::flush;
	pkt << tx_addr(u8devid)  
	    << tx_retry(0x3) // set retry
	    << tx_packet_delay(0,0,2); 

	pack_bytes(pkt.get_payload(), au16AI[0], adTemp.dat);
	return pkt.transmit();
    }
	return MWX_APIRET(false, 0);
}

本ソフトを少し見直して消費電流を減らしたものを下記記事で使用しています。

トワイライト(TWELITE)で親機を実装し子機から無線受信する

関連リンク

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

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

テックジム-将来のためにプログラミングを学ぶ

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

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