Seeeduino XIAOでES920LRの設定と無線通信

組み込みエンジニア

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

Seeeduino XIAOの標準ライブラリであるSerial1を使用すると外部機器とシリアル通信ができます。Serial1とLoRaモジュールのES920LRをシリアル通信で接続しES920LRの初期化と電文を構成して無線通信を行う方法をまとめました。

ES920LRB(EASEL製:東京デバイセズで購入)を2台使用して親機と子機の設定を行います。ES920LRには後継機種としてES920LR2及びES920LR3があります。温湿度と気圧のデータのデータを無線通信するためにAE-BME280(秋月電子)を使用しています。

本記事ではES920LRの初期化をプロフェッサーモードで設定を行い出力データ部に自作の電文を構成して無線通信を行います。Seeeduino XIAOのシリアル通信の方法とES920LRの設定を行って無線通信する方法は以下の記事にまとめています。

Seeeduino XIAOのSerialライブラリの使い方

LoRaモジュールES920LRの設定と無線通信の確認

これらで紹介した記事を応用して無線通信を実現します。Seeeduino XIAOで動作確認したことについてリンクをまとめています。

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

Serial1を使用する

Seeeduino XIAOでSerial1を使用すると6ピンがTX、7ピンがRXになります。Arduino UNOなどで使用するSerialはシリアルモニタ専用で使用することができます。Serial1はSerialと同じように使用することができるためシリアル通信を手軽に実装することができます。Serial1クラスのメンバー関数を使ってシリアル通信を実装します。

ライブラリの初期化

void setup() {

  Serial1.begin(115200); //外部機器との通信用
  //while(!Serial ){ } //ポートを開くまでの待ち
}

begin()関数でシリアル通信を開始します。引数にシリアル通信のボーレートを入力します。ES920LRのボーレートのデフォルトに合わせて115200bpsを指定しています。

begin()を発行した後シリアル通信ポートが開始する(初期設定待ち)までループさせて確実に設定が完了するまで待機させることもありますが、初期化時に意図して遅延させている場合やbegin()関数のコールからすぐにシリアル通信をしない場合は実装する必要はありません。

ボーレート以外の設定は特に指定しない場合はデータ長は8ビット・パリティなしの条件で動作します。

Arduino IDEやVSCodeで選択できるボーレートから選択すると多くの場合問題になりませんが、ボーレートを早くしすぎると配線の長さや周辺回路の部品などの影響で通信できなくなることがあるので外部機器のクロックを含めて検討する必要があります。

データを受信する

while(Serial1.available()){
  SeeedRcv.buf[SeeedRcv.wp] = Serial1.read(); //データをリード

  if( ++SeeedRcv.wp >= sizeof(SeeedRcv.buf)){ //次に保管する位置を更新
    SeeedRcv.wp = 0;
  }
}

受信データの有無を確認するためavailable()関数を使用します。受信データが存在する場合はavailabale()関数の戻り値が1以上になります。データが存在する場合はread()関数でデータを読み込みます。

例では受信データをSeeedRcv[]の配列に保存先を更新しながら一時保管しています。一時保管したデータはデータチェックに使用します。

While()で受信データを確認しているのはメイン処理に来るたびに受信データ分だけ確実に取得するためです。受信タイミングにもよりますがif()で確認するよりもデータの一時保管が早くなることがあります。

データを送信する

//COM_TXモードで送信                
Serial1.write(&Txdata.buf[0], Txdata.sz);

データの送信はwrite()関数を使用します。write()関数は4通りの指定方法がありますが、例では第1引数に送信するデータのポインタ、第2引数に送信するデータ数を指定する方法を使用しています。Txdata.buf[0]からTxdata.szで指定したサイズのデータを送信します。

print()関数やprintln()関数を使用するとテキストデータの文字列を送信することができますがバイナリデータを送信するためにwrite()を使用します。

すき間時間で資格をゲット【STUDYing(スタディング)】

シリアル通信の管理する

void Serialmain(bool init){

  switch(ComMode){
    case COM_RX_WAIT:
      //受信待ちで一時保管したデータのチェックを行う
      //ヘッダーとSUMで電文の整合性チェックをチェック
      break;
    case COM_RCVDATA: 
      //受け入れた電文に対して処理を行う
      break;
    case COM_TX:
      //返信電文を送信する
      break;        
    }
}

自作の関数のSerialmain()関数でシリアル通信の管理を行います。引数はES920LRの初期化の場合と通常の電文で処理を切り替えるために使用します。初期化する場合は引数にtrueを指定します。

受信と送信をモードによる切り替え処理で管理します。以下のように3つのモードを実装します。

モード説明
COM_RX_WAIT一時保管したデータをヘッダーやデータ長から電文を判断する。
データのチェックサムによって問題なければ電文受け入れる。
COM_RCVDATA受け付けた電文を解析して処理を行う。
COM_TXデータを送信する。
送受信を管理するモードの説明

COM_RX_WAITで受信したデータの確認を行いチェックサムの条件を満足すると電文を受け入れてモードをCOM_RCVDATAに遷移します。

COM_RCVDATAで電文を解析して処理を行います。データを返信する場合は送信用のデータをセットしてモードをCOM_TXに遷移します。返信しない場合はモードをCOM_RCVDATAに戻して受信待ちにします。

モードを切り替えて処理するため受信データのチェックと送信の処理が同時に発生することはありません。

スポンサーリンク

ES920LRで無線通信する

ES920LRの初期設定をプロフェッサーモードで行います。Seeeduino XIAOのシリアル通信を使ってES920LRの初期設定を行い、自作の電文を構成して無線通信を行います。

ES920LRの初期設定

プロフェッサーモードはショートコマンドを使用してES920LRの設定する方法です。設定方法の詳細はEASEL社のHPに公開されているデータシート(ES920LRコマンド仕様ソフトウェア説明書_v121)で確認することができます。

EASEL社ー920MHz帯LoRaモジュール ES920LR

ES920LRの設定は以下の手順で行います。半角のスペースを␣改行コードをCRLF(0x0D 0x0A)と表記します。

ES920LRの初期化の例
  • 手順1
    プロフェッサーモードに遷移

    電源ONから3秒後に[Select Mode]が表示されるのを待って
    2CRLFでプロフェッサーモードに遷移します。

  • 手順2
    ノードの選択

    親機/子機で動作するかを設定します。
    親機:a␣1 CRLF、子機:a␣2 CRLF

  • 手順3
    ノードのPAMIDの設定

    親機/子機で共通のPANIDを設定します。
    e␣12AB CRLF(12ABは16進数)

  • 手順4
    自局のアドレスの設定

    親機/子機の自局のアドレスを設定します。
    親機:f␣0000 CRLF、子機:f␣0001 CRLF

  • 手順5
    送信先(宛先)のアドレスの設定

    送信相手のアドレスを設定します。
    親機:g␣0001 CRLF、子機:g␣0000 CRLF

  • 手順6
    ペイロードのフォーマットの設定

    ※バイナリデータで送信に変更
    A␣2 CRLF

  • 手順7
    RSSIの追加

    電文にRSSIのデータを付加します。
    p␣1 CRLF

  • 手順8
    起動時のモードの設定(本記事では未実装)

    電源ONした後のモードを設定する。オペレーションモードで起動
    q␣2 CRLF

  • 手順9
    設定の保存(本記事では未実装)

    手順6までに設定した項目を保存する。

  • 手順10
    オペレーションモードスタート

    オペレーションモードに遷移します。
    z CRLF

ES920LRは電源をONして3秒(bootモードに遷移するまでの時限)ほどコマンドを受け付けることができません。そのためプロフェッサーモードに遷移させるコマンドを3秒経過してから送信するようにします。

上位互換のES920LR2やES920LR3は起動時の仕組みが改善されているため3秒のウェイトを置く必要はないようです。

本記事では未実装ですが起動時に親機と子機の役割が明確な場合は手順8による起動時のモードの設定と手順9の設定の保存を行うことで次回起動時に初期化処理をせずに無線通信ができます。

switch(initmode){
  case INIT_WAIT: //3秒以上のウェイト
    if(timInitWait == TIME_UP ){
      timInitWait = TIME_OFF;
      initmode = INIT_SEL;
    }
    break;
  case INIT_SEL: //プロフェッサーモードを選択
    if( IntTxDataSet((uint8_t*)&select,1)){ //2CRLFを送信
      initmode = INIT_SEL_WAIT; //OKが返信されるのを待つモード
    }            
    break;
  case INIT_NODE: //ノードの設定
    if(selParent){ //親機の場合
      if( IntTxDataSet((uint8_t*)&node1,3)){ //a␣1 CRLFを送信
        initmode = INIT_NODE_WAIT; //OKが返信されるのを待つモード
      }                   
    }
    else{ //子機の場合
      if( IntTxDataSet((uint8_t*)&node2,3)){ //a␣2 CRLFを送信
        initmode = INIT_NODE_WAIT; //OKが返信されるのを待つモード
      }                     
    }
    break;
}

初期化処理はモード切替で行います。ES920LRの初期化の例の手順に従うようにモードを遷移させていきます。INIT_SELモードでは「2CRLF」を送信し、INIT_SEL_WAITモードで返信を待ちます。

プロフェッサーモードではコマンドを受け付けると「OK」を返信します。「OK」が確認できれば次のモードであるINIT_NODEに遷移します。このように送信と返信待ちを繰り返しながら各種設定を行っていきます。

使用環境によってはシリアル通信がうまくいかずNGが返信されたりOKが掴めなかったりして処理が停滞してしまうことがあります。対策として失敗したときのリトライ処理(本記事では未実装)を実装すると安定性が良くなります。

送受信に使用する電文の構成

引用:EASEL社-ES920LRコマンド仕様ソフトウェア説明書_v121.pdf
引用:EASEL社-ES920LRコマンド仕様ソフトウェア説明書_v121.pdf

ES920LRの初期設定の時に通信方式をPayloadモードに変更しています。Payloadモードでは先頭に全体の入力長を1バイトで指定し2バイト目からRSSIなどの付加情報が続き出力データ(最大50バイト)の順に構成されます。

アスキーコードは受信時にモニター表示するとデータを確認しやすいメリットがありますが、バイナリデータに置き換えると2バイト必要になるため送信バイト数が増えてしまうデメリットがあります。

例)バイナリデータの「0x31」を送信する場合はアスキー文字では0x33 0x31と2バイト分のデータとなります。

LoRa通信などLPWAでは低消費電力を実現するために可能な限り送信するバイト数を減らすことが必要です。対策としてバイナリデータで送信バイト数を減らす方法が有効です。

Payloadモードのフォーマットに従って電文は出力データ部に構成します。

電文の構成
電文の構成

1バイト目には全体の入力長を指定します。2バイト目は電文を判別するためのヘッダーとして「L」を指定します。コマンドに番号を指定しデータ1からデータ4はコマンドの番号に対する処理の内容を指定します。SUMはコマンドからデータ4までを加算した値の下位1バイトを付加します。

1バイト目の入力長は送信時に使用します。受信時はRSSIの前に全体の入力長が付加されるため出力データ部分はヘッダーからになります。コマンドは子機がボタンを押したときにBME280モジュールの温度、湿度、気圧のデータを切り替えて送信するために使用します。

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

送信データの生成

bool TxDataSet(bool boo, uint8_t cmd){
  bool ret = false;
  uint8_t i=0;
  uint32_t data;
    
  if( ComMode == COM_RX_WAIT ){ //データが送信中でない
    Txdata.buf[i++] = 7;//sz
    Txdata.buf[i++] = HEADER;//5; //cmd+4byte
    Txdata.buf[i++] = cmd;
    data = (uint32_t)(bme280data[cmd] * 100);
    Txdata.buf[i++] = ( data >> 24 );
    Txdata.buf[i++] = ( data >> 16 );
    Txdata.buf[i++] = ( data >> 8 );
    Txdata.buf[i++] = ( data & 0xFF );
    Txdata.buf[i++] = CalcSum(&Txdata.buf[2], 5);
    Txdata.sz = i;
    ComMode = COM_TX;
    timTxWait = TIM_TX_WAIT;
    ret = true;
  }
  return ret;
}

TxData.buf[]配列に送信データを格納します。データの受信と送信をモードで管理しているためデータが受信待ち(送信中でない)でなければ送信データをセットします。

最初に全体のデータ長をセットし、次にヘッダをセットします。

コマンド(cmd)は0:気温、1:気圧、2:湿度を切り替えに使用します。続けてcmdに対応したBME280モジュールのデータをセットします。BME280モジュールのデータは4バイトデータなのでビットシフトを使用して1バイトデータに分割してセットしています。受信側(親機)でデータを再構成した際に小数点以下の精度を保つために100倍値を分割して送信します。

コマンドからBME280モジュールのデータまでのSUM値を計算して電文の最後尾に付加します。送信データをセットした後は、送信モードに遷移させてデータを送信します。

受信データのチェック

  if( rxsz >= 6 ){ //データ長分だけ獲得しているか
    for( i = 0; i < 6; i++){//データサイズ算出のため仮おき
      dat[i] = SeeedRcv.buf[ rp ];
      if(++rp >= sizeof(SeeedRcv.buf) ) rp = 0;
    }
                          
    if( dat[5] == HEADER){
       allsz = dat[0] + 1; //全データ数
       datsz = dat[0] - OFFSET_RSSI - 2; //headerとsumを除外
                    
       if( rxsz >= allsz){ //確定したサイズ以上か
         timRxWait = TIME_OFF;

         for(i=0; i < sizeof(Rcvdata); i++){
           Rcvdata[i] = 0; //バッファのクリア
         }

         for( i=0; i < allsz; i++ ){
           Rcvdata[i] = SeeedRcv.buf[SeeedRcv.rp]; //一時保管したデータを移す
           ReadPointerAdd(); //読み込み位置の更新
         }

         sumchk = CalcSum(&Rcvdata[6], datsz); //チェックサムの計算
         if( sumchk == Rcvdata[allsz-1]){ //チェックサムの確認
           ComMode = COM_RCVDATA; //モードを遷移
         }
       }    
     }
    else{
      ReadPointerAdd(); 
    }
  }

一時保存した受信データが電文の構成と一致するかをCOM_RX_WAITモードでチェックします。受信データにはRSSIが付加されているので受信データの先頭から6バイト目がヘッダになります。

ヘッダが一致すると受信データを電文保管用のバッファ(Rcvdata[])に移し替えてRcvdata[]の7バイト目から5バイト分のチェックサム(SUM)を計算します。

電文のチェックサム値と計算したチェックサム値が一致すると正常な電文と判断してCOM_RCVDATAモードに遷移します。COM_RCVDATAモードでは電文から温度、気圧、湿度のデータを解析します。

 rssi = (strtol((char*)&Rcvdata[1],NULL,16));
 Serial.print("rssi:");  
 Serial.println(rssi);

RSSIのデータは仕様上アスキーコードで受信するため符号付のバイナリデータに変換する必要があります。strtol()でアスキーコードを16進数のバイナリデータに変換しています。strtol()関数は文字列の区切り(0や変換不能な文字)までをバイナリデータに変換します。RSSIデータの後ろにはヘッダー(L)があるため変換できずにRSSIの4バイト分がバイナリデータに変換されます。

float data;

data = ( Rcvdata[7] << 24 )
     + ( Rcvdata[8] << 16 )
     + ( Rcvdata[9] << 8 )
     + ( Rcvdata[10] );
data /= 100;
Serial.print("Temperature = ");
Serial.print(data );
Serial.println(" °C");

子機で4バイトデータを1バイトに分割したので4バイトデータに戻します。ビットシフトを使ってデータを構成しますが、子機で100倍値の値をセットしたので100で割ってデータを戻しています。100で割ることで小数点以下2桁を表示しています。

スポンサーリンク

動作確認

動作確認用の回路図(赤線:子機実装 青線:親機実装)
動作確認用の回路図(赤線:子機実装 青線:親機実装)

Seeeduino XIAOとES920LRをシリアル通信して無線通信を行います。親機と子機で回路を切り替えます。親機で使用する場合は0ピンをGNDに接続して電源をONします。子機の場合は未接続とします。

回路図の赤線は子機で使用し青線は親機で使用します。子機のSW1を押した回数で温度・気圧・湿度のデータを切り替えて無線通信します。

子機と親機ともに電源をONして3秒以上が経過し初期化処理が完了した後、子機のSW1を押して親機のシリアルモニタに無線受信したBME280モジュールのデータが表示されるかを確認します。

動作確認(シリアルモニタ)
動作確認(親機のシリアルモニタ)

子機のSW1を押すとcmdに対応したデータを親機に送信します。SW1を押すと温度データを受信しシリアルモニターに表示できていることが確認できました。続けてSW1を押すと気圧データ、さらに押すと湿度データが受信できていることが確認できました。

ソースコード全体

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

#include <TimerTC3.h>

#include <Adafruit_BME280.h>

#define PIN_DI_1 0
#define PIN_DI_2 1

#define TIME_OFF -1     //タイマーを使用しない場合
#define TIME_UP 0       //タイムアップ
#define BASE_CNT 10 //10msがベースタイマとなる
#define TIM_RX_WAIT 20   //200ms
#define TIM_TX_WAIT 300  //300ms
#define TIM_BTN_WAIT 5
#define TIM_INIT_WAIT 300
#define TIM_BME280_WAIT 500
#define DI_FILT_MAX 4
#define BTN_CNT_MAX 3
#define FILT_MIN 1
#define RING_SZ 64
#define TX_SZ 10
#define HEADER 'L'
#define OFFSET_SZ 5
#define OFFSET_RSSI 4
#define CR 0x0D
#define LF 0x0A

const char sel[] = "2"; //モード選択:processor
const char node1[] = "a 1";	//ノード種別:Coordinaor(親機)
const char node2[] = "a 2";	//ノード種別:EndDevice(子機)
const char panid[] = "e 12AB";	//自ノードのPAMアドレス:0x4553
const char ownid1[] = "f 0000";	//自ノードのアドレス:0x0000(親機)
const char ownid2[] = "f 0001";	//自ノードのアドレス:0x0001(子機)
const char dstid1[] = "g 0001";	//送信先のアドレス:0x0001(親機)
const char dstid2[] = "g 0000";	//送信先のアドレス:0x0000(子機)
const char form[] = "A 2";	//フォーマットをバイナリ
const char rssi[] = "p 1";	//RSSIを付加
const char savepara[] = "w";	//内臓FlashROMに保存
const char start[] = "z";	//コンフィグレーションを終了、オペレーションに遷移

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

typedef struct{
  uint8_t sz;
  uint8_t buf[TX_SZ];
}TX_MNG;

typedef enum{	//モード
  COM_RX_WAIT = 0,
  COM_RCVDATA,
  COM_TX,
  COM_MAX
}COM_MODE_NO;

typedef enum{	//モード
  INIT_WAIT = 0,
  INIT_SEL,
  INIT_SEL_WAIT,        
  INIT_NODE,
  INIT_NODE_WAIT,
  INIT_PANID,
  INIT_PANID_WAIT,
  INIT_OWNID,
  INIT_OWNID_WAIT,
  INIT_DSTID,
  INIT_DSTID_WAIT,
  INIT_FORM,
  INIT_FORM_WAIT,
  INIT_RSSI,
  INIT_RSSI_WAIT,
  //INIT_SAVE,
  //INIT_SAVE_WAIT,
  INIT_START,
  INIT_START_WAIT,
  INIT_END,
  INIT_ERR,
  INIT_MAX,
}INIT_MODE;

struct DIFILT_TYP{
  uint8_t wp;
  uint8_t buf[DI_FILT_MAX];
  uint8_t di1;
};

/* 変数定義 */
int16_t  timRxWait = TIME_OFF;
int16_t  timTxWait = TIME_OFF;
int16_t timInitWait = TIME_OFF;
int16_t timbtnWait = TIME_OFF;
int16_t timdifilt;
int16_t timBme280Set;
int16_t cnt10ms;
RING_MNG SeeedRcv;
uint8_t Rcvdata[RING_SZ];
uint8_t btncnt;
TX_MNG Txdata;
COM_MODE_NO ComMode;
uint8_t	initmode; //LORAモード
bool  selParent = false; //親機ならtrue
bool  loraInit= true;
bool btnflg1;
DIFILT_TYP difilt;
Adafruit_BME280 bme280;
float bme280data[3];
float data;

void TimerCnt(void);
void mainTimer(void);
bool IntTxDataSet( uint8_t *dat, uint8_t sz );
bool TxDataSet(bool boo, uint8_t cmd);
uint8_t CalcSum(uint8_t *buf, uint8_t sz );
bool RcvChk(void);
void Serialmain(bool init);
void ReadPointerAdd(void);
void DiFilter(void);
void Bme280Get(void);

void setup() {

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

  pinMode(PIN_DI_1,INPUT_PULLUP);
  pinMode(PIN_DI_2,INPUT_PULLUP);
  delay(10);
  if( digitalRead(PIN_DI_1) == 0 ){
    selParent = true;
  }

  bme280.begin();
  timInitWait = TIM_INIT_WAIT;
  TimerTc3.initialize(1000);
  TimerTc3.attachInterrupt(TimerCnt);

  for( uint8_t i=0; i < 10; i++ ){
    mainTimer();
    DiFilter();
    delay(10);
  }  
}

void loop() {

  Serialmain(loraInit);
  mainApp();
  DiFilter();
  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( timTxWait  > TIME_UP ){
      --timTxWait;  
    }

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

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

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

    if( timBme280Set > TIME_UP ){
      --timBme280Set;
    }
  }
}
/* メイン関数 */
void mainApp(void){
    
  while(Serial1.available()){
    SeeedRcv.buf[SeeedRcv.wp] = Serial1.read(); //データをリード
    //Serial.println(SeeedRcv.buf[SeeedRcv.wp]);

    if( ++SeeedRcv.wp >= sizeof(SeeedRcv.buf)){ //次に保管する位置を更新
      SeeedRcv.wp = 0;
    }
  }
    
  if(loraInit){
    switch(initmode){
      case INIT_WAIT:
        if(timInitWait == TIME_UP ){
          timInitWait = TIME_OFF;
          initmode = INIT_SEL;
        }
        break;
      case INIT_SEL:
        if( IntTxDataSet((uint8_t*)&sel,1)){
          initmode = INIT_SEL_WAIT;
        }            
        break;
      case INIT_NODE:
        if(selParent){
          if( IntTxDataSet((uint8_t*)&node1,3)){
            initmode = INIT_NODE_WAIT;
          }                   
        }
        else{
          if( IntTxDataSet((uint8_t*)&node2,3)){
            initmode = INIT_NODE_WAIT;
          }                     
        }
        break;
      case INIT_PANID:
        if( IntTxDataSet((uint8_t*)&panid,6)){
          initmode = INIT_PANID_WAIT;
        }                
        break;
      case INIT_OWNID:
        if(selParent){
          if( IntTxDataSet((uint8_t*)&ownid1,6)){
            initmode = INIT_OWNID_WAIT;
          }                   
        }
        else{
          if( IntTxDataSet((uint8_t*)&ownid2,6)){
            initmode = INIT_OWNID_WAIT;
          }                     
        }
        break;
      case INIT_DSTID:
        if(selParent){
          if( IntTxDataSet((uint8_t*)&dstid1,6)){
            initmode = INIT_DSTID_WAIT;
          }                   
        }
        else{
          if( IntTxDataSet((uint8_t*)&dstid2,6)){
            initmode = INIT_DSTID_WAIT;
          }                     
        }
        break;
      case INIT_FORM:    
        if( IntTxDataSet((uint8_t*)&form,3)){
          initmode = INIT_FORM_WAIT;
        }
        break;
      case INIT_RSSI:
        if( IntTxDataSet((uint8_t*)&rssi,3)){
          initmode = INIT_RSSI_WAIT;
        }
        break;
      case INIT_START:
        if( IntTxDataSet((uint8_t*)&start,3)){
          initmode = INIT_START_WAIT;
        }                   
        break;                
      case INIT_END:
        loraInit = false;
        Serial.println("init-OK");
        break; 
    }
  }

  if( selParent == false ){
    if(difilt.di1 == 0){
      if(btnflg1){
        btnflg1 = false;
        TxDataSet(selParent,btncnt);

        if(++btncnt >= BTN_CNT_MAX){
          btncnt = 0;
        }
      }
    }
    else{
      btnflg1 = true;
    }

    if( timBme280Set == TIME_UP ){
      timBme280Set = TIM_BME280_WAIT;
      Bme280Get();
    }
  }
}
/* シリアル通信メイン処理 */
void Serialmain(bool init){
  int16_t rxsz;
  uint8_t datsz;
  uint8_t allsz;
  uint8_t rp = SeeedRcv.rp;  
  uint8_t i;
  uint8_t sumchk;
  uint8_t dat[6];
    
  if(init == false ){
    if( timRxWait == TIME_UP){ //受信タイムアウトか
      timRxWait = TIME_OFF;
      ReadPointerAdd(); 
      ComMode = COM_RX_WAIT;
    }
  }
    
  switch( ComMode ){
    case COM_RX_WAIT:
      rxsz = SeeedRcv.wp - SeeedRcv.rp; //受信データ数の算出
    
      if( rxsz < 0 ){
        rxsz = rxsz + RING_SZ;
      }
            
      if( rxsz == 0 ){
        timRxWait = TIME_OFF;
      }
      else{
        if( timRxWait == TIME_OFF ){
          timRxWait = TIM_RX_WAIT; //受信タイムアウトをセット
        }
                
        if(init){
          if( timRxWait == TIME_UP ){ //データ長分だけ獲得しているか
            timRxWait = TIME_OFF;
                        
            for(i=0; i < sizeof(Rcvdata); i++){
              Rcvdata[i] = 0; //バッファのクリア
            }

            for( i=0; i < rxsz; i++ ){
              Rcvdata[i] = SeeedRcv.buf[SeeedRcv.rp]; //一時保管したデータを移す
              ReadPointerAdd(); //読み込み位置の更新
            }
            ComMode = COM_RCVDATA; //モードを遷移
          }
        }
        else{
          if( rxsz >= 6 ){ //データ長分だけ獲得しているか
            for( i = 0; i < 6; i++){//データサイズ算出のため仮おき
              dat[i] = SeeedRcv.buf[ rp ];
              if(++rp >= sizeof(SeeedRcv.buf) ) rp = 0;
            }
                          
            if( dat[5] == HEADER){
              allsz = dat[0] + 1; //全データ数
              datsz = dat[0] - OFFSET_RSSI - 2; //headerとsumを除外
                    
              if( rxsz >= allsz){ //確定したサイズ以上か
                timRxWait = TIME_OFF;

                for(i=0; i < sizeof(Rcvdata); i++){
                  Rcvdata[i] = 0; //バッファのクリア
                }

                for( i=0; i < allsz; i++ ){
                  Rcvdata[i] = SeeedRcv.buf[SeeedRcv.rp]; //一時保管したデータを移す
                  ReadPointerAdd(); //読み込み位置の更新
                }

                sumchk = CalcSum(&Rcvdata[6], datsz); //チェックサムの計算
                if( sumchk == Rcvdata[allsz-1]){ //チェックサムの確認
                  ComMode = COM_RCVDATA; //モードを遷移
                }
              }    
            }
            else{
              ReadPointerAdd(); 
            }
          }                 
        }
      }
      break;
    case COM_RCVDATA: 
      if( RcvChk() ){
        ComMode = COM_TX;
        timTxWait = TIM_TX_WAIT;
      }
      else{
        ComMode = COM_RX_WAIT;
      }
      break;
    case COM_TX:
      Serial1.write(&Txdata.buf[0], Txdata.sz);
      ComMode = COM_RX_WAIT; //受信待ちに遷移
      break;        
  }
}
/* 電文の処理 */
bool RcvChk(void){
  bool ret = false;
  uint32_t cmd;
  int16_t rssi;
  float data;

  if(loraInit){
    if( Rcvdata[0] == 'O' && Rcvdata[1] == 'K'){
      ++initmode;
    }
  }
  else{
    if(selParent){ //子→親の処理と返信
      cmd = Rcvdata[6];
      rssi = (strtol((char*)&Rcvdata[1],NULL,16));
      Serial.print("rssi:");  
      Serial.println(rssi);

      data = ( Rcvdata[7] << 24 )
            + ( Rcvdata[8] << 16 )
            + ( Rcvdata[9] << 8 )
            + ( Rcvdata[10] );
      data /= 100;
      switch(cmd){
        case 0:
          Serial.print("Temperature = ");
          Serial.print(data );
          Serial.println(" °C");    
          break;
        case 1:
          Serial.print("Pressure = ");
          Serial.print(data );
          Serial.println(" hPa");          
          break;
        case 2:
          Serial.print("Humidity = ");
          Serial.print(data);
          Serial.println(" %");
          break;
      } 
    }
    else{ //親→子
      //親からの返信で処理する場合は追加
    }
  }
     
  return ret;
}
/* 送信データ生成 */
bool IntTxDataSet( uint8_t *dat, uint8_t sz ){
  bool ret = false;
  uint8_t i;
     
  if( ComMode == COM_RX_WAIT ){
    for( i = 0; i < sz; i++){
      Txdata.buf[i] = *dat;
      ++dat;
    }

    Txdata.buf[i++] = CR;
    Txdata.buf[i++] = LF;
    Txdata.sz = i;
    ComMode = COM_TX;
    timTxWait = TIM_TX_WAIT;
    ret = true;
  }
    return ret;
}
/* 送信データ生成 */
bool TxDataSet(bool boo, uint8_t cmd){
  bool ret = false;
  uint8_t i=0;
  uint32_t data;
    
  if( ComMode == COM_RX_WAIT ){
    Txdata.buf[i++] = 7;//sz
    Txdata.buf[i++] = HEADER;//5; //cmd+4byte
    Txdata.buf[i++] = cmd;

    if( boo ){ //親→子
      Txdata.buf[i++] = cmd | 0x80;
      Txdata.buf[i++] = 0x30;
      Txdata.buf[i++] = 0x30;
      Txdata.buf[i++] = 0x30;       
    }
    else{ //子→親
      data = (uint32_t)(bme280data[cmd] * 100);
      Serial.println(data);
      Txdata.buf[i++] = ( data >> 24 );
      Txdata.buf[i++] = ( data >> 16 );
      Txdata.buf[i++] = ( data >> 8 );
      Txdata.buf[i++] = ( data & 0xFF );
    }

    Txdata.buf[i++] = CalcSum(&Txdata.buf[2], 5);
    Txdata.sz = i;
    ComMode = COM_TX;
    timTxWait = TIM_TX_WAIT;
    ret = true;
  }
  return ret;
}
/* チェックサムの計算 */
uint8_t CalcSum(uint8_t *buf, uint8_t sz ){
  uint8_t ret = 0;

  for(uint8_t i=0; i < sz; i++ ){
    ret += *buf;
    buf++; 
  }
  return ret;
}
/* 読み込み位置の更新 */
void ReadPointerAdd(void){
    
  if(++SeeedRcv.rp >= sizeof(SeeedRcv.buf) ){
    SeeedRcv.rp = 0;
  }
}
/* DiFilter function add */
void DiFilter(void){

  if( timdifilt == TIME_UP ){
    difilt.buf[difilt.wp] = digitalRead(PIN_DI_2);
  
    if( difilt.buf[0] == difilt.buf[1] &&
        difilt.buf[1] == difilt.buf[2] &&
        difilt.buf[2] == difilt.buf[3] ){ //4回一致を確認
          difilt.di1 = difilt.buf[0];
    }
  
    if( ++difilt.wp >= DI_FILT_MAX ){
      difilt.wp = 0;
    }

    timdifilt = FILT_MIN;
  }
}
/* BME280のデータ取得 */
void Bme280Get(void){

  Serial.print("Temperature = ");
  bme280data[0] = bme280.readTemperature();
  Serial.print(bme280data[0]);
  Serial.println(" °C");

  Serial.print("Pressure = ");
  bme280data[1] = bme280.readPressure() / 100.0F;
  Serial.print(bme280data[1]);
  Serial.println(" hPa");

  Serial.print("Humidity = ");
  bme280data[2] = bme280.readHumidity();
  Serial.print(bme280data[2]);
  Serial.println(" %");
  Serial.println();
}

本記事では未実装ですが初期化する際にシリアル通信が失敗した場合のリトライ処理を入れても良いと思います。

初期化時に設定の保存コマンドを使用していないのはデバッグでどちらを使用しても毎回初期化を行うため子機と親機の区別をつける必要がないからです。起動時にオペレーションモードに遷移する設定を保存しておくことで電源ON時の初期化の処理を省略してスムーズに無線通信を開始することができます。

関連リンク

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

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

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

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

LoRaモジュールES920LRの設定と無線通信の確認

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

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