トワイライト(TWELITE)でBME280のデータを取得する

組み込みエンジニア

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

トワイライトのWire(I2C通信)を使用することでBME280から温湿度・気圧のデータを取得することができます。MWSTAGEではセンサーデバイスライブラリであるBMx280クラスのメソッドをコールするだけでBME280のデータを取得できます。

BMx280クラスを使用して温湿度・気圧のデータを取得しMONOSTICKに無線通信して動作確認をしています。BME280はAE-BME280(秋月電子)を使用しています。

BME280のデータを取得する

BME280は温湿度に加えて気圧の3つの環境情報を同時に測定できるのが特徴です。また、マイコンとの通信方式がI2CまたはSPIを選択できるためマイコンに組み込みやすい構成となっています。製作メーカであるBOSCH社が提供しているAPIを組み込んで動作確認したことについてまとめています。

トワイライト(TWELITE)のI2C通信を実装し無線通信する

トワイライト(TWELITE)のSPI通信を実装し無線通信する

上記記事ではAPIを組み込むために処理を追加する必要がありますが、BMx280クラスを使用する場合はメソッドをコールするだけでよいので手早く動作確認ができます。

センサー・デバイス(SNS)のBMx280クラスを使用する

MWX Libraryのセンサーデバイス(SNS)においてBMx280x-環境センサー用のクラスが実装されています。メソッドの使用方法などの詳細はモノワイヤレス社のHPに公開されています。

モノワイヤレス社ーセンサー・デバイス(SNS)BMx280-環境センサー

これらのメソッドを使用することで簡単にBME280からデータを取得することができます。

BME280からデータを取得する

BMx280クラスを使用するために専用のヘッダーとオブジェクトを宣言する必要があります。

#include <SNS_BME280>

SNS_BME280 sns_bme280;

void setup() {
    Wire.begin(); //I2Cを有効にする
    sns_bme280.setup(); 
}

BMx280の通信方式はI2Cであるためsetup()メソッドを使用する前にWireを有効にする必要があります。setup()メソッドに引数を指定しない場合は初期値の0x76がスレーブアドレスとして指定されます。BME280のスレーブアドレスが0x77の場合は0x77を引数にします。

//測定を開始する場合
sns_bme280.begin();

//経過時間を通知(測定完了を通知しないセンサーの場合)
sns_bme280.process_ev(20); //20ms経過を通知

測定を開始はbegin()メソッドを使用します。測定完了をavailable()によって確認しますが、BMx280クラスでは経過時間を通知しなければavaiable()がtrueにならないためprocess_ev()で時間経過を通知しています。

BME280の測定時間はデータのサンプリングによって変化しますが数十msなのでメソッド発行から20ms後に測定完了を通知します。

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(); //気圧
}

測定結果の取得は温度データの場合get_temp()メソッド、湿度データの場合get_humid()メソッド、気圧の場合get_press()メソッドを使用します。100倍値を取得したい場合はget_temp_cent()及びget_humid_per_dmil()のメソッドを使用します。気圧は100倍値のメソッドはありません。

ZigBee通信する際は100倍値を送信した方がデータ量が少なくできるため有効です。

BME280で取得したデータを無線通信する例

BME280で取得したデータを無線通信する例
BME280で取得したデータを無線通信する例

トワイライトの低消費電流を活かすためにスリープしながらBME280の測定を行います。センサー情報を取得しながら無線通信を行うサンプルアクトの公開されています。

モノワイヤレス社ーMWX Library Slp_Wk_and_Tx

Slp_Wk_and_txアクトの機能を引用しながらBME280からデータを取得し無線通信を行います。手順は以下の通りです。

  1. スリープする
  2. ウェイクアップしたときBME280測定を開始する(begin()メソッド)
  3. ナップ(100ms)する(ナップ中にBME280の測定完了している)
  4. ウェイクアップする
  5. BME280からデータを取得
  6. 測定結果を無線通信する
  7. 1に戻って繰り返す

2.のウェイクアップした瞬間にBME280の測定コマンドを送出してナップ(ショートスリーブ)することでBME280の測定時にトワイライトの消費電流を押さえることができます。BME280の測定は数十msで完了するためナップからウェイクアップしたときにはデータを取得することができます。

if (!sns_bme280.available()) {
    sns_bme280.process_ev(1); 
    //napにより100ms以上経過しているため1をセット0ではavailableとならない
}

経過時間の通知のためprocess_ev()メソッドを使用しますが、0を指定してもavailable()がtrueにならないため最低でも1を指定する必要があります。

動作確認

トワイライトのBMx280クラスを使用して測定結果を無線通信で送信します。無線通信データの確認はMONOSTICKで行います。MONOSTICKを親機とし、子機でBME280のデータを取得し無線通信します。

動作確認用の回路

動作確認用の回路図(子機)
動作確認用の回路図(子機)

BME280モジュールにはプルアップ抵抗が実装されているためプルアップ抵抗は必要ありません。スリープとナップを使用し消費電流を押さえながらBME280のデータを取得して無線通信を行います。

SW1はEEPROMの動作確認用でlogical_idをランダム値を利用して書き換えするために使用します。SW1を長押しするとEEPROMにデータを書き込みます。

EEPROMにランダム値を書き込むとトワイライトをリスタートさせます。無線通信が開始するとMONOSTICK上に子機の番号を表示するためlogical_idが変更されていることが確認できます。

動作結果

動作結果
動作結果

子機はBME280から取得した結果をシリアルモニタで表示しています。親機は子機の受信したデータを表示しています。5秒間スリープして測定する動作を繰り返しているため5秒おきに親機の表示が追加されています。

親機の179の表示は子機のlogical_idでありSW1を押してEEPROMの値が変更されると表示が変更されていることも確認できています。

子機の番号をEEPROMに保管して通信などで変更できればと思い動作確認用にSW1を入れていますが子機はスリープを使用していることやボタンが押せない遠隔地に設置した場合に意味をなさないため基本的にソフト書き込み時にIDを固定する方法がベストだと感じました。

ソースコード全体

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

トワイライト子機のソースコード:

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

#define EEP_ADRS_START 0x300
#define EEP_ADRS_START_CHK 0x380
#define EEP_WRITE_WAIT 2000
#define PIN_DI1 5
#define FILTER_MAX 5
#define TIME_UP 0
#define TIME_OFF -1
#define HELPER_USE
#define SLEEP_DUR 5000
#define TIME_OUT_MAX 100

typedef union{
    uint8_t byte[6];
    struct{
        uint32_t app_di;
        uint8_t channel;
        uint8_t logical_id;       
    }dat;
}SYSTEM_TYP;

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
};

const SYSTEM_TYP inittbl = {0x12, 0x34, 0xab, 0xcd, 0x0d, 0xFE};

/* 変数宣言 */
SNS_BME280 sns_bme280;
SYSTEM_TYP twesystem;
int16_t timWriteStart = TIME_OFF;
int16_t timTxTimeout = TIME_OFF;
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;

/* プロトタイプ宣言 */
void InitEepSetting(void);
void EepApp(void);
void mainTimer(void);
MWX_APIRET transmit();
void napNow(void);
void sleepNow(void);

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

    pinMode(PIN_DI1,INPUT_PULLUP);
    Buttons.setup(FILTER_MAX);
    Buttons.begin( 1UL << PIN_DI1, FILTER_MAX ,1); 
    //ウェイクアップしている間でボタンの判断が必要なため1msとする。
    Serial.begin();

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

    InitEepSetting(); //EEPROMのデータを展開
    // the twelite main class
    txreq_stat = MWX_APIRET(false, 0);
    the_twelite
 << TWENET::appid(twesystem.dat.app_di)
		<< TWENET::channel(twesystem.dat.channel)
                << TWENET::tx_power(0x03);

    auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
    nwksmpl << NWK_SIMPLE::logical_id(twesystem.dat.logical_id);
    the_twelite.begin();
}
/*** 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{
                    if( timWriteStart == TIME_OFF ){
                        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()){
                    timTxTimeout = TIME_OFF;  
                    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.", millis() & 8191)
                        << crlf << "  BME280: T=" << sns_bme280.get_temp() << "C"
                        << " H=" << sns_bme280.get_humid() << "%" 
                        << " P=" << sns_bme280.get_press() << "hPa" << crlf;
                    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:
                if( timWriteStart == TIME_OFF ){ //EEPROM書き込みがスタートしていない
                    sleepNow();
                }
                break;
            case E_STATE::EXIT_FATAL:
		Serial << crlf << "!FATAL: RESET THE SYSTEM.";
		delay(100);
		the_twelite.reset_system(); //異常とみなしリセット
                break;
        }
    }while(loop_more);

    mainTimer();

    if( Buttons.available() ){
        uint32_t  bm,cm;
      
        if( Buttons.read(bm,cm) ){
            if( ( bm & (1UL << PIN_DI1)) == 0 ){
                timWriteStart = EEP_WRITE_WAIT;
            }else{
                timWriteStart = TIME_OFF;
            }
        }
    }
}
/* callback begin */
void begin(){
    sleepNow();
}
/* callback wakeup */
void wakeup(){

    if (b_senser_started) { //napからwakeup
        if( b_found_bme280 ){
            eState = E_STATE::WORK_JOB;
            timTxTimeout = TIME_OUT_MAX;
        }
        else{
            eState = E_STATE::INIT;
        }
        b_transmit = false;
    }
    else{ //sleepからwakeup
        b_senser_started = true;
        sns_bme280.begin();
        napNow();
    }
}
/* ナップ(ショートスリープ)処理 */
void napNow(){
    uint32_t u32ct = 100;
    the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}
/* スリープ処理 */
void sleepNow(){
    uint32_t u32ct = SLEEP_DUR + random(0,200);
    b_senser_started = false;
    b_transmit = false;
    the_twelite.sleep(u32ct);
}
/* 送信パケット準備 */
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);
}
/* EEPROMのデータ展開処理 */
void InitEepSetting(void){
    uint8_t i;
    SYSTEM_TYP mst;
    SYSTEM_TYP chk;
    bool boo = true;
    
    #ifdef HELPER_USE
        auto&& strm = EEPROM.get_stream_helper();
        strm.seek(EEP_ADRS_START);
        strm >> mst.dat.app_di >> mst.dat.channel >> mst.dat.logical_id;
        strm.seek(EEP_ADRS_START_CHK);
        strm >> chk.dat.app_di >> chk.dat.channel >> chk.dat.logical_id;
    #else
        for( i =0; i < sizeof(inittbl);i++){
            mst.byte[i] = EEPROM.read(EEP_ADRS_START + i);
        }

        for( i =0; i < sizeof(inittbl);i++){
            chk.byte[i] = EEPROM.read(EEP_ADRS_START_CHK + i);
        }    
    #endif

    for( i =0; i < sizeof(inittbl.byte);i++){
        if( mst.byte[i] != chk.byte[i] ){
            twesystem.byte[i] = inittbl.byte[i];
            boo = false;
        }
        else{
            twesystem.byte[i] = mst.byte[i];
        }
    }

    if( boo){
        Serial << "EEP_CHK_OK" << crlf << mwx::flush;
    }else{
        Serial << "EEP_CHK_NG" << crlf << mwx::flush;
    }
}
/* EEPROMのデータ書き込み処理 */
void EepApp(void){

    twesystem.dat.app_di = 0x1234abcd;
    twesystem.dat.channel = 13;
    twesystem.dat.logical_id = random(1,0xFD);

    #ifdef HELPER_USE
        auto&&  strm = EEPROM.get_stream_helper();
        strm.seek(EEP_ADRS_START);
        strm << twesystem.dat.app_di << twesystem.dat.channel << twesystem.dat.logical_id;
        strm.seek(EEP_ADRS_START_CHK);
        strm << twesystem.dat.app_di << twesystem.dat.channel << twesystem.dat.logical_id;
    #else
        for( uint8_t i; i < sizeof(chgtwesystem); i++){
            EEPROM.update(EEP_ADRS_START + i , chgtwesystem.byte[i]);
            EEPROM.update(EEP_ADRS_START_CHK + i, chgtwesystem.byte[i]);
        }
    #endif

    Serial << "EEP_WRITE_OK" << crlf << mwx::flush;
    delay(100);
    the_twelite.reset_system();
}
/* タイマ管理処理 */
void mainTimer(void){

    if( TickTimer.available()){
        if(timWriteStart > TIME_UP ){
            --timWriteStart;
        }

        if( timTxTimeout > TIME_UP ){
            --timTxTimeout;
        }
    }

    if( timTxTimeout == TIME_UP ){
        timTxTimeout = TIME_OFF;
        sleepNow();
    }

    if(timWriteStart == TIME_UP){
        timWriteStart = TIME_OFF;
        EepApp();
    }
}

MONOSTICKのソースコード:

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

#define CNT_MAX 1000

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

/* 変数宣言 */
int16_t cnt1ms;
uint32_t ts;
bool rcvflg;

/* プロトタイプ宣言 */
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 main class
    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,humi,press);

    Serial << " temp: " << (double)temp/100 << "C" 
	   << " humid: " << (double)humi/100 << "%" 
           << " press: " << (int)press << "hPa"
           << mwx::crlf << mwx::flush;
}

関連リンク

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

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

テックジム|ゼロからはじめるPython入門講座の申込

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

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