トワイライト(TWELITE)とSeeeduino XIAOで土壌の状態の履歴を保存

組み込みエンジニア

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

ZigBeeモジュールであるトワイライト(TWELITE)と土壌センサーを組み合わせた子機とトワイライトとSeeeduino XIAOを組み合わせた親機で無線通信を行います。親機は受信したデータを表示しSDカードに動作履歴として保存します。

下記記事のMONOSTICKをSeeeduino XIAOとトワイライトで構成して履歴をSDカードに保存して動作確認を行います。

トワイライト(TWELITE)で土壌の状態を取得する

土壌センサーはSEN0114(DFROBOT製:秋月電子で購入)を使用しています。Seeeduino XIAO用拡張ボード(秋月電子)を使用しています。

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

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

土壌センサーの情報を取得する

SEN0114は土壌の抵抗分で発生する電圧を出力するセンサーです。水分量が多いほど土壌の抵抗分が少なくなり電気を通しやすくなることを利用したものです。下記リンクにセンサーの情報とスケッチ例が説明されています。

SEN0114 Moisture Sensor-DFROBOT

Arduino環境で使用できるように出力調整されており土壌の状態の目安が記載されています。下記記事で参考値として使用したin water時の値の値750を使用します。

トワイライト(TWELITE)で土壌の状態を取得する

以下ではDC5VやDC3.3VをDCなしで記載します。

トワイライト親機の構成

親機の構成
親機の構成

トワイライトとSeeeduino XIAOを組み合わせたモジュールを親機とします。親機は子機から受信したSEN0114の値と子機からの電界強度を表示してSDカードに保存します。

トワイライトのDIO13(内蔵プルアップのDI)の信号レベルで親機と子機の切り替えます。親機で使用する場合は未接続で使用できるようにしています。

トワイライトの電界強度はバイナリデータを換算して表示します。メーカのHPに換算に関する説明があります。

モノワイヤレス社HP-LQI値から電界強度[dBm]の推定

//電界強度の換算例
double d_lqi;
d_lqi = ((double)7*lqi-1970)/20; //LQI

電界強度を確認しながら配置することで設置場所の目安にすることができます。

親機は子機から受信できない場合もあるため一定の間隔以上で時間経過のカウントを更新してカウント値のみをSDカードに保存するようにして未受信であることが分かるようにしています。

トワイライト子機の構成

子機の構成
子機の構成

トワイライトとSEN0114を組み合わせたモジュールを子機とします。電源は太陽光パネルを利用したTWE-EH-Sを使用します。スリープによる間欠動作させた場合条件によっては電池無しでも動作させることができます。

トワイライトのDIO13をGNDに接続することで子機として動作します。子機は10分毎にSEN0114から取得した土壌の様子を親機に無線通信します。

電源はTWE-EH-Sを使用しているため消費電流を減少させる必要があるため子機はスリープを使用して間欠動作させます。トワイライトがスリープしている間はSEN0114の電源をOFFして消費電流を減少させます。

SEN0114の消費電流は35mA(@5V)となっているためトワイライトのDOに直接シンク電流として取り込むのは望ましくありません。トランジスタをスイッチングで使用してトワイライトに電流が流れ込まないようにします。

TWE-EH-SのBOOTはトワイライトの5ピンのDOによって制御します。BOOTをLOWにするとリセットがかからなくなるためトワイライトへの電源供給が続きます。BYPは8ピンのDOをLOWにすると余剰電力のコンデンサC2をバイパスします。(動作の説明の詳細はトワイライト(TWELITE)の自作アプリとソーラーモジュールで無線通信を参照)

C1は無線タグアプリの場合は220uFで十分ですが自作アプリで処理を追加している分消費電流が増えてしまい電圧降下が大きくなり起動が安定しないことがあるため1000uFを追加し初期起動時の電圧降下の対策を行っています。

C2は余剰電力をチャージできるため容量の大きな電気二重層コンデンサなどを実装すると効果的です。2.2Fの電気二重層コンデンサでも曇り空でわずかに日が差す程度の環境下において1時間程度でフル充電できます。

BYPを初期起動時にLOWにするとC2が十分にチャージできていない場合C1にチャージした電荷がC2側に電荷が移ってしまうため電圧低下によりTWELITEのBOOTの制御が維持できなくなりTWE-EH-Sがリセット状態となります。

初期化時にはBYPを使用せず一定電圧以上になったことを確認してバイパスと効果的です。

スリープとSEN0114の測定のタイミング

SEN0114の測定はスリープと測定の手順はスリープとショートスリープを使い分けながら手順1~手順3の手順に沿って行います。

スリープと測定の手順
  • 手順1
    スリープからのウェークアップ

    スリープからウェークアップしたタイミングでSEN0114の電源をONしてnapする。

  • 手順2
    SEN0114の測定結果を取得する

    napからウェークアップしてSEN0114の出力電圧をAD変換値を取得する。

  • 手順3
    無線通信とスリープ

    AD変換値を無線通信する。SEN0114の電源をOFFしてからスリープする。

手順1はトワイライトの9ピンをHIGHにしてSEN0114の電源をONしてnapします。napはSEN0114の電源をONしてから測定値が安定するまでの時間として100msを確保する目的とトワイライトの消費電流を減少させる目的で使用しています。

napが短すぎると消費電流の減少の効果が低くなります。またnapが長いとSEN0114が消費する電流が増えてしまいます。

手順2はSEN0114の出力電圧をAD変換します。

if(Analogue.available()){
 vc2 = Analogue.read(PIN_AD1);
  vc2 = vc2 * 2; //TWE-EH-SのVC2は1/2なので2倍
  sen0114Data = Analogue.read(PIN_AD2); //電圧変換した値
  sen0114Data_raw = Analogue.read_raw(PIN_AD2); //デジット値
}

トワイライトのAnalogueライブラリでread()関数を使用すると電圧変換された値が取得でき、read_raw()関数を使用すると電圧変換前のデジット値が取得できます。

手順3は手順2で取得したTWE-EH-Sの電圧値とSEN0114の計測値を無線通信します。トワイライトの9ピンをLOWにしてSEN0114の電源をOFFしたあとスリープします。

スポンサーリンク

動作確認

動作確認の様子

子機はトワイライト(TWELITE)で土壌の状態を取得するで確認したものと同様に屋外に配置して太陽光パネルに日光が当たるように子機を配置します。挿し木している植物に水を与えてSEN0114を挿入します。屋外に設置するためSEN0114を除いたモジュール一式をビニール袋に包んで設置します。

直射日光が当たる環境であったため10分もしないうちに動作開始しました。C2についても30分程度でフル充電になりました。

動作確認の方法

昼の12:00に子機を屋外に設置して放置します。TWE-EH-Sによって電源が確立すると子機が動作開始するので親機のSeeeduino XIAO拡張ボードのOLEDに表示した土壌の様子を確認します。動作確認は以下の条件で行いました。

  1. 自作アプリを利用し間欠動作の時限を600秒
  2. 動作確認前に植物に水を与えて土壌を湿らせる。
  3. 太陽光パネルを空に向けて設置する
  4. 翌日の8:00以降に動作確認を終了

動作確認を行った日の天気は翌日まで晴天であり太陽光が十分に得られる環境でした。昼間に充電した電源で翌日まで計測できるかを確認しました。

動作確認の結果

動作確認の結果
動作確認の結果 左:OLEDに表示した子機のデータ 右:SDカードに保存した履歴データ

子機から無線通信を受けると土壌の様子と電界強度が表示されました。子機から10分毎に無線データを受信すると表示が切り替わることを確認しました。

動作確認を終了してSDカードに保存したデータ確認しました。子機から受信したデータの履歴が確認できました。このデータをCSVファイルに変換してグラフ表示した結果は以下の通りとなります。

SDカードに保存した履歴データ
SDカードに保存した履歴データ

昼の12:00に屋外の日当たりが良い場所に設置したため10分もしないうちに余剰電力分のコンデンサを含めてフル充電になりました。

青線はTWE-EH-Sが供給しているトワイライトの電源電圧です。橙線はSEN0114から取得したデジット値です。太陽光パネルから十分に電力が得られる環境であったためトワイライトの電源が3.3Vを超えて安定しています。

TWE_VCCとHumid値はAD変換の基準電圧がTWE_VCCを基準にしているため連動した波形になっています。電源が3.3Vを超えているため、SEN0114の電圧が高くなったため調整値である750を超えて830近くの値になっています。

陽が沈み太陽光パネルが発電できない状態でも日中で充電した電気二重層コンデンサがバックアップ電源になっているため夜間でも子機は動作を継続しています。トワイライトの動作電圧の下限である2.0Vを下回る前に翌日の日光によっ電源が供給できたため子機が停止せずに動作継続することができています。

土壌の様子は頻繁に変わらないため子機の間欠動作を1時間程度にして消費電流を落とすことで夜間においても余裕を持った動作ができそうです。

スポンサーリンク

ソースコード全体

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

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

#include <TWELITE>
#include <NWK_SIMPLE>
#include <STG_STD>

#define TIME_UP 0
#define TIME_OFF -1
#define TIME_OUT_MAX 100
#define SLEEP_DUR 600000
#define AD_INIT_VALUE 0xFFFF
#define AD_VCC_CHK 2400 //2.4V
#define BUF_SZ 3
#define HEADER 0xA5

const uint8_t PIN_DI1 = mwx::PIN_DIGITAL::DIO12; //インタラクティブモード
const uint8_t PIN_DI2 = mwx::PIN_DIGITAL::DIO13; //parent
const uint8_t PIN_DO1 = mwx::PIN_DIGITAL::DIO18; //Boot
const uint8_t PIN_DO2 = mwx::PIN_DIGITAL::DIO19; //Byp
const uint8_t PIN_DO3 = mwx::PIN_DIGITAL::DIO9; //sen0114
const uint8_t PIN_AD1 = mwx::PIN_ANALOGUE::A1; //TWE-EH-SのVCC
const uint8_t PIN_AD2 = mwx::PIN_ANALOGUE::A2; //SEN0114

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

struct TXCOM_TYP{
  uint8_t header;
  uint8_t sz;
  uint16_t buf[BUF_SZ];
  uint8_t sum;
};

enum  E_STATE {
  WORK_JOB = 0,
  TX,
  WAIT_TX,
  EXIT_NORMAL,
  EXIT_FATAL
};

enum  APP_MODE{
  INTERACTIVE = 0,
  NORMAL
};

/* 変数宣言 */
APP_MODE appmode = APP_MODE::NORMAL;
SYSTEM_TYP twesystem;
MWX_APIRET txreq_stat;
E_STATE eState;
TXCOM_TYP txdata;
int32_t timTxTimeout = TIME_OFF;
int16_t sen0114Data;
int16_t sen0114Data_raw;
uint16_t twevcc = AD_INIT_VALUE;
uint16_t vc2 = AD_INIT_VALUE;
uint16_t vcc = AD_INIT_VALUE;
uint16_t addata_raw;
uint16_t addata;
uint8_t lqi;
bool vc2Dosetflg = false;
bool b_senser_started = false;
bool parentflg = true;

/* プロトタイプ宣言 */
void mainTimer(void);
void napNow(void);
void sleepNow(void);
MWX_APIRET transmit(void);
void childMain(void);
void parentMain(void);
void receive(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz);
void SerialTxSet(void);

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

  Serial.begin();
  auto&& set = the_twelite.settings.use<STG_STD>();
  set << SETTINGS::appname("MY_APP"); //アプリ名
  set << SETTINGS::appid_default(0x1234cdef); //デフォルトID

  //親と子を切り替える
  pinMode(PIN_DI2,INPUT_PULLUP);
  if( digitalRead(PIN_DI2) == LOW){
      parentflg = false;
  }

  pinMode(PIN_DI1, INPUT_PULLUP);
  if (digitalRead(PIN_DI1) == LOW) {
    set << SETTINGS::open_at_start(); //インタラクティブモード起動
    appmode = APP_MODE::INTERACTIVE; //loop()で動作しないようにするため                               
	}
  else{
    appmode = APP_MODE::NORMAL;
    set.reload();
    twesystem.dat.app_di = set.u32appid();
    twesystem.dat.channel = set.u8ch();
    twesystem.dat.logical_id = set.u8devid();
    // the twelite main class
    txreq_stat = MWX_APIRET(false, 0);

    if( parentflg){
        the_twelite
          << TWENET::appid(twesystem.dat.app_di)
          << TWENET::channel(twesystem.dat.channel)
          << TWENET::rx_when_idle();
      Serial << "parent-start" << crlf << mwx::flush;
      auto&& nwksmpl = the_twelite.network.use<NWK_SIMPLE>();
      nwksmpl << NWK_SIMPLE::logical_id(0x00); // set Logical ID.
    }
    else{
      pinMode(PIN_DO1, OUTPUT_INIT_LOW); //BOOTをLOW
      pinMode(PIN_DO2, OUTPUT_INIT_HIGH); //BYPをHIGH
      pinMode(PIN_DO3, OUTPUT_INIT_LOW );
      Analogue.setup(true, ANALOGUE::KICK_BY_TICKTIMER);
      Analogue.begin(pack_bits(PIN_AD1,PIN_AD2,PIN_ANALOGUE::VCC),5);

      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);
      Serial << "child-start" << crlf << mwx::flush;
    }

    the_twelite.begin();
  }
}

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

  switch (appmode){
    case APP_MODE::INTERACTIVE:
      //インタラクティブモード
      break;
    case APP_MODE::NORMAL:
      if(parentflg){
        parentMain();
      }
      else{
        childMain();
      }

      mainTimer();
      break;
  }
}
/* 子機の場合の処理*/
void childMain(void){
  bool loop_more;

  do{
    loop_more = false;

    switch(eState){
      case E_STATE::WORK_JOB:
        if(Analogue.available()){
          twevcc = Analogue.read(mwx::PIN_ANALOGUE::VCC);
          vc2 = Analogue.read(PIN_AD1);
          vc2 = vc2 * 2; //TWE-EH-SのVC2は1/2なので2倍
          sen0114Data = Analogue.read(PIN_AD2);
          sen0114Data_raw = Analogue.read_raw(PIN_AD2);

          if( vc2Dosetflg == false){
            if( vc2 >= AD_VCC_CHK && vc2 != AD_INIT_VALUE){
              vc2Dosetflg = true;
              pinMode(PIN_DO2, OUTPUT_INIT_LOW); //BYPをLOW
            }
          }

          //Serial 
          //    << crlf << format("..%04d/finish sensor capture.", millis() & 8191)
          //    << crlf << "  vcc: " << int(vc2)
          //    << crlf << "  SEN0114: " << double(sen0114Data)/1000 << " raw: " << sen0114Data_raw << crlf;
          eState = E_STATE::TX;
          loop_more = true;
        }
        break;
      case E_STATE::TX:
        txreq_stat = transmit(); //データの取得準備

        if (txreq_stat) {
          timTxTimeout = TIME_OUT_MAX;
          eState = E_STATE::WAIT_TX;
          loop_more = true;
        }
        else {
          timTxTimeout = TIME_OFF; 
          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 (timTxTimeout == TIME_UP ) {                 
          //Serial << int(millis()) << "!FATAL: tx timeout." << crlf;
          eState = E_STATE::EXIT_FATAL;
          loop_more = true;
        }
        break;
      case E_STATE::EXIT_NORMAL:
        timTxTimeout = TIME_OFF; 
        sleepNow();
        break;
      case E_STATE::EXIT_FATAL:
        delay(100);
        the_twelite.reset_system(); //異常とみなしリセット
        break;    
      }
  }while (loop_more);
}
/* 親機の処理 */
void parentMain(void){

  while (the_twelite.receiver.available()) {
    receive();
  }
}
/* 無線受信処理*/
void receive(void) {
  vcc = 0xFFFF;
  vc2 = 0xFFFF;
  addata_raw = 0xFFFF;
  addata = 0xFFFF;
  lqi = 0xFF;

  auto&& rx = the_twelite.receiver.read();
  lqi = rx.get_lqi();

  auto&& np = expand_bytes( rx.get_payload().begin(),rx.get_payload().end()
          ,vcc
          ,vc2
          ,addata_raw
          ,addata
  );

  //Serial << "LQI: " << ((double)7*lqi-1970)/20 << "dBm" 
  //        << " Vcc: " << (double)vcc/1000 << "V" 
  //        << " Vc2: " << (double)vc2/1000 << "V" 
  //        << " Humid: " << int(addata_raw) << " " << double(addata)/1000 << "V" 
  //        << mwx::crlf << mwx::flush;

  SerialTxSet();
}
/* タイマ管理処理 */
void mainTimer(void){

  if( TickTimer.available()){
    if( timTxTimeout > TIME_UP ){
      --timTxTimeout;
    }
  }
}
/* callback begin */
void begin(){

  if(parentflg == false){
    if(appmode != APP_MODE::INTERACTIVE){
      napNow();
    }
  }
}
/* ナップ(ショートスリープ)処理 */
void napNow(){
  uint32_t u32ct = 100;

  the_twelite.sleep(u32ct, false, false, TWENET::SLEEP_WAKETIMER_SECONDARY);
}
/* スリープ処理 */
void sleepNow(){
  uint32_t u32ct;

  digitalWrite(PIN_DO3,LOW);
  u32ct = SLEEP_DUR + random(0,200);
  b_senser_started = false;
  the_twelite.sleep(u32ct);
}
/* 送信パケット準備 */
MWX_APIRET transmit(void) {

  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(),
                twevcc,
                vc2,
                (uint16)sen0114Data_raw,
                (uint16)sen0114Data
    );
                    
    return pkt.transmit();
  }
  return MWX_APIRET(false, 0);
}

/* callback wakeup */
void wakeup(){

  if (b_senser_started) { //napからwakeup
    eState = E_STATE::WORK_JOB;
    //Serial << "nap-wakeup" << crlf << mwx::flush;
  }
  else{ //sleepからwakeup
    b_senser_started = true;
    digitalWrite(PIN_DO3,HIGH);
    //Serial << "sleep-wakeup" << crlf << mwx::flush;
    napNow();
  }
}

/* TX function add */
void SerialTxSet(void){
  uint8_t allsz = 0;
  uint8_t *adrs;

  txdata.header = HEADER;
  txdata.sz = sizeof(txdata.buf);
  txdata.buf[0] = lqi;
  txdata.buf[1] = vcc;
  txdata.buf[2] = addata_raw;
  txdata.sum = CalcSum((uint8_t*)&txdata.buf[0],txdata.sz);
  allsz = sizeof(txdata.header)+ sizeof(txdata.sz) 
        + txdata.sz + sizeof(txdata.sum);

  adrs =&txdata.header;
  for(uint8_t i = 0; i < allsz; i++ ){
      Serial << (*adrs);
      adrs++;
  }
  //Serial.write(txdata.sum);
}

/* ChkSum function add */
uint8_t CalcSum(uint8_t *buf, uint8_t sz=0){
  uint8_t ret = 0;

  for(uint8_t i=0; i < sz; i++ ){
    ret = ret + (*buf);
    buf++; 
  }
  return ret;
}

トワイライトのソースコードはMWSTAGEの環境でアクト内のソースファイルを置き換えることで使用することができます。

Seeeduino XIAOのソースコード:

#include <TimerTC3.h>
#include <SPI.h>
#include <SD.h>
#include <U8x8lib.h>
#include <Wire.h>

#define HEADER 0xA5
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define RING_SZ 256
#define BUF_SZ 32
#define RXCHK_SZ 2
#define TIM_RX_WAIT 20 //200ms
#define SD_CS 2
#define DRY_SOIL 300
#define HUMID_SOIL 650
#define TIME_SET 60000
#define TIME_ADD  5000

struct COM_TYP{
  uint8_t header;
  uint8_t sz;
  uint8_t buf[BUF_SZ];
  uint8_t sum;
};

struct RING_MNG{
  uint8_t wp;
  uint8_t rp;
  uint8_t buf[RING_SZ];
};

/* 変数宣言 */
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);
String filepath = "sen0114.txt";
File myfile;
RING_MNG rxarduino;
COM_TYP rxdata;  //チェック後の受信データ
int16_t cnt10ms;
int16_t timRxWait = TIME_OFF;
int16_t timDataWait = TIME_OFF;
uint32_t timcnt;
uint8_t ziglesscnt;

/* プロトタイプ宣言 */
void RxDataChk(void);
void TimerCnt(void);
void mainTimer(void);
void TimerCnt(void);
void ReadPointerAdd(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz);
void Control(void);
void saveSd(void);

void setup(){

  Serial.begin(115200);
  Serial1.begin(115200);

  u8x8.begin();
  u8x8.setFlipMode(1);   // set number from 1 to 3
  u8x8.setFont(u8x8_font_5x8_r);
  u8x8.setCursor(0, 0);
  u8x8.print("Seeeduino soil");
  u8x8.setCursor(0, 1);
  u8x8.print("TweLite LQI:");
  u8x8.setCursor(0, 2);
  u8x8.print("--> No Data");  
  u8x8.setCursor(0, 3);
  u8x8.print("SOIL HUMID:");
  u8x8.setCursor(0, 4);
  u8x8.print("--> No Data");
  u8x8.setCursor(0, 5);
  u8x8.print("STATUS:");
  u8x8.setCursor(0, 6);
  u8x8.print("--> No Data");

  if(!SD.begin(SD_CS)){
    Serial.println("initialization failed!");
    u8x8.setCursor(0, 6);
    u8x8.clearLine(6);
    u8x8.print("-->SD-ERR");
    while (1);
  }

  TimerTc3.initialize(1000);
  TimerTc3.attachInterrupt(TimerCnt);
}

void loop(){

  while( Serial1.available()){
    rxarduino.buf[rxarduino.wp] = Serial1.read();
    //Serial.println(rxarduino.buf[rxarduino.wp]);
    if( ++rxarduino.wp >= RING_SZ ){ //次にデータを入れる場所を更新
      rxarduino.wp = 0;
    }
  }

  RxDataChk();
  mainTimer();
}

/* callback function add */
void TimerCnt(void){
  ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT; //10msごとにここに遷移する

    if( timRxWait  > TIME_UP ){
      timRxWait--;  
    }
    if( timDataWait > TIME_UP ){
      timDataWait--;
    }
  }

  if( timDataWait == TIME_UP ){
    timDataWait = TIME_SET;
    ++timcnt;

    if(++ziglesscnt >= 3 ){
      u8x8.setCursor(0, 2);
      u8x8.clearLine(2);
      u8x8.print("--> No Data");
      u8x8.setCursor(0, 4);
      u8x8.clearLine(4);
      u8x8.print("--> No Data");
      u8x8.setCursor(0, 6);
      u8x8.clearLine(6);
      u8x8.print("--> No Data");
    }
  }
}

/* RX function add */
void  RxDataChk(){
  int rxsz;
  uint8_t sz;
  uint8_t allsz;
  uint8_t *adrs;
  uint8_t rp = rxarduino.rp;
  uint8_t chkdat[RXCHK_SZ];

  if( timRxWait == TIME_UP){ //受信タイムアウトか
    timRxWait = TIME_OFF;
    ReadPointerAdd();
  }

  rxsz = rxarduino.wp - rxarduino.rp; //受信データ数の算出
  if( rxsz < 0 ){
    rxsz = rxsz + RING_SZ;
  }

  if( rxsz == 0 ){
    timRxWait = TIME_OFF;
  }
  else{
    if( timRxWait == TIME_OFF ){
      timRxWait = TIM_RX_WAIT; //受信タイムアウトをセット
    }

    if(rxsz >= RXCHK_SZ){
      for(uint8_t i=0; i< RXCHK_SZ; i++ ){  //ヘッダーのチェックのため一時保存
        chkdat[i] = rxarduino.buf[rp];
        if(++rp > RING_SZ ){
          rp = 0;
        }
      }

      if( chkdat[0]== HEADER ){
        sz = chkdat[1] & 0x3F;//szが63を超えないようにする(データ長がおかしい場合の対策)
        allsz = RXCHK_SZ + sz + 1; //+1はSUMで受信データすべての長さを算出

        if( rxsz >= allsz ){
          timRxWait = TIME_OFF;

          memset(&rxdata.header, 0, sizeof(rxdata));
          adrs = &rxdata.header;

          for(uint8_t i=0; i< allsz; i++ ){
            *adrs = rxarduino.buf[rxarduino.rp];
            ++adrs;
            ReadPointerAdd();
          }

          rxdata.sum = CalcSum(&rxdata.buf[0],sz);

          if( rxdata.sum == rxdata.buf[sz]){//SUMのチェック
            Control();    
          }
          else{
            ReadPointerAdd();
          }
        }
      }
      else{
        ReadPointerAdd();
      }
    }
  }
}
/* 読み込み位置の更新 */
void ReadPointerAdd(void){
    
  if(++rxarduino.rp >= RING_SZ ){
    rxarduino.rp = 0;
  }
}

/* ChkSum function add */
uint8_t CalcSum(uint8_t *buf, uint8_t sz=0){
  uint8_t ret = 0;

  for(uint8_t i=0; i < sz; i++ ){
    ret = ret + (*buf);
    buf++; 
  }
  return ret;
}
/* データ表示 */
void Control(void){
  uint16_t lqi;
  uint16_t vcc;
  double d_lqi;
  double d_vcc;
  uint16_t data_raw;
  double hosei;

  lqi = ( rxdata.buf[0] << 8 ) + rxdata.buf[1];
  vcc = ( rxdata.buf[2] << 8 ) + rxdata.buf[3];
  data_raw = ( rxdata.buf[4] << 8 ) + rxdata.buf[5];

  if( lqi != 0xFFFF && vcc != 0xFFFF &&
      data_raw != 0xFFFF ){

    timDataWait = TIME_SET + TIME_ADD;
    ++timcnt;
    ziglesscnt = 0;

    d_lqi = ((double)7*lqi-1970)/20; //LQI
    d_vcc = (double)vcc/1000;

    if( d_vcc < 3.3){
      hosei = (double)3.3/d_vcc;
      data_raw = (double)data_raw * hosei;
      Serial.println(data_raw);
    }    

    u8x8.setCursor(0, 2);
    u8x8.clearLine(2);
    u8x8.print("--> ");
    u8x8.print(d_lqi);
    u8x8.print("dBm");
    u8x8.setCursor(0, 4);
    u8x8.clearLine(4);
    u8x8.print("--> ");
    u8x8.print(data_raw);
    u8x8.setCursor(0, 6);
    u8x8.clearLine(6);

    if( data_raw < DRY_SOIL){
      u8x8.print("-->dry soil");
    }
    else if( data_raw >= DRY_SOIL && data_raw < HUMID_SOIL){
      u8x8.print("-->humid soil");
    }
    else{
      u8x8.print("-->in water");
    }
    //SDカードに保存
    myfile = SD.open(filepath,FILE_WRITE);

    if( myfile ){ //ファイルが開けたら書き込む
      myfile.print("time:");
      myfile.print(timcnt);
      myfile.print(" LQI:");
      myfile.print(d_lqi);
      myfile.print("dBm");
      myfile.print(" vcc:");
      myfile.print(d_vcc);
      myfile.print("V");
      myfile.print(" humid:");
      myfile.println(data_raw);
      myfile.close(); //ファイルを閉じる

      Serial.print("time:");
      Serial.print(timcnt);
      Serial.print(" LQI:");
      Serial.print(d_lqi);
      Serial.print("dBm");
      Serial.print(" vcc:");
      Serial.print(d_vcc);
      Serial.print("V");
      Serial.print(" humid:");
      Serial.println(data_raw);
    }
  }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

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

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