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

組み込みエンジニア

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

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

SHT3xクラスを使用して温湿度データを取得しMONOSTICKに無線通信して動作確認をしています。温湿度センサーはSHT35-DISモジュール(秋月電子)を使用しています。

SHT35-DISのデータを取得する

SHT35-DISはSENSIRION社の高精度温湿度センサーです。マイコンとの通信方式はI2Cであり測定レンジが-40℃から+125℃と広いのが特徴です。SHT35のコマンドの指定の方法やCRC8の計算方法については下記にまとめています。

PICマイコン(PIC16F1827)でSHT35のデータを取得

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

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

モノワイヤレス社ーセンサー・デバイス(SNS) SHT3x-温湿度センサー

これらのメソッドを組み込むことで簡単にSHT35からデータを取得することができます。

SHT35からデータを取得する

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

#include <SNS_SHT3X>

SNS_SHT3X sns_sht3x;

void setup() {
    Wire.begin(); //I2Cを有効にする
    sns_sht3x.setup(0x45); //スレーブアドレスを指定する
}

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

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

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

測定を開始はbegin()メソッドを使用します。測定条件としてシングルショットモード(繰り返し精度レベル:高)・クロックストレッチ設定を無効とするコマンドコードである0x2400が送出されます。

測定完了をavailable()によって確認しますが、SHT3xクラスでは経過時間を通知しなければavaiable()がtrueにならないためprocess_ev()で時間経過を通知しています。SHT35の測定時間は数十msなのでメソッド発行から20ms後に測定完了を通知します。

available()内の処理で測定完了が確認できなかった点は少し残念ですが、未定義であるsns_stat()が実装されればこれらの問題は解決しそうです。

if(sns_sht3x.available()){ //測定完了したか  
    getsensor.temp = sns_sht3x.get_temp_cent();
    getsensor.humi = sns_sht3x.get_humid_per_dmil();
}

測定結果の取得は温度データの場合get_temp()メソッド、湿度データの場合get_humid()メソッドを使用します。100倍値を取得したい場合はget_temp_cent()及びget_humid_per_dmil()のメソッドを使用します。ZigBee通信する際は100倍値を送信した方がデータ量が少なくできるため有効です。

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

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

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

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

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

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

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

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

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

動作確認

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

SHT35のデータ取得とは関係性はありませんが動作確認用にEEPROMを使用して子機の番号を変更できるようにしています。

動作確認用の回路

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

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

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

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

動作結果

動作結果
動作結果

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

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

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

ソースコード全体

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

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

#include <TWELITE>
#include <NWK_SIMPLE>
#include <SNS_SHT3X>

#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{
    uint16_t temp;
    uint16_t humi;
}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_SHT3X sns_sht3x;
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_sht3x = 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_sht3x.setup(0x45);
    if(sns_sht3x.probe()){
        b_found_sht3x = true;
        Serial << "sht3x-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_sht3x.setup(0x45);
                if(sns_sht3x.probe()){
                    b_found_sht3x = true;
                    loop_more = true;
                    eState = E_STATE::EXIT_NORMAL;
                    Serial << "sht3x-ok" << crlf << mwx::flush;
                }
                else{
                    if( timWriteStart == TIME_OFF ){
                        sleepNow();
                    }
                }
                break;
            case E_STATE::WORK_JOB: //経過時間の通知
        
                if (!sns_sht3x.available()) {
                    sns_sht3x.process_ev(1); 
                    //napにより100ms以上経過しているため1をセット0ではavailableとならない
                }

                loop_more = true;
                eState =  E_STATE::WORK_JOB2;
                break;
            case E_STATE::WORK_JOB2: //測定データの取得
                if(sns_sht3x.available()){
                    timTxTimeout = TIME_OFF;  
                    getsensor.temp = sns_sht3x.get_temp_cent();
                    getsensor.humi = sns_sht3x.get_humid_per_dmil();

                    Serial 
                        << crlf << format("..%04d/finish sensor capture.", millis() & 8191)
                        << crlf << "  SHT3X: T=" << sns_sht3x.get_temp() << 'C'
                        << " H=" << sns_sht3x.get_humid() << '%' << 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())) { //送信完了ステータス待ち
                    //Serial << int(millis()) << ":tx completed! (" << int(txreq_stat.get_value()) << ')' << crlf;
                    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;
                //Serial << "TIME_SET" << crlf << mwx::flush;
            }else{
                timWriteStart = TIME_OFF;
                //Serial << "TIME_OFF" << crlf << mwx::flush;
            }
        }
    }
}
/* callback begin */
void begin(){
    sleepNow();
}
/* callback wakeup */
void wakeup(){

    if (b_senser_started) { //napからwakeup
        if( b_found_sht3x ){
            eState = E_STATE::WORK_JOB;
            timTxTimeout = TIME_OUT_MAX;
        }
        else{
            eState = E_STATE::INIT;
        }

        b_transmit = false;
        //Serial << "nap-wakeup" << crlf << mwx::flush;
    }
    else{ //sleepからwakeup
        b_senser_started = true;
        sns_sht3x.begin();
        //Serial << "sleep-wakeup" << crlf << mwx::flush;
        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) // set retry (0x1 send two times in total)
	    << tx_process_immediate();
             
        pack_bytes(pkt.get_payload(),getsensor.temp, getsensor.humi);
	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

/*** 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(); // start twelite!


    Serial << "--- MONOSTICK_sht35 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;

    auto&& rx = the_twelite.receiver.read();
    //Serial << format("..receive(%08x/%d) : ", rx.get_addr_src_long(), rx.get_addr_src_lid());
    Serial << "ts:" << int(ts) << ":" << int(rx.get_addr_src_lid());
    auto&& np = expand_bytes(rx.get_payload().begin(),rx.get_payload().end()
            ,temp
            ,humi
    );

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

関連リンク

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

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

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

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

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