PICマイコン(PIC16F1827)でES920LRの設定と無線通信

組み込みエンジニア

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

PIC16F1827はシリアル通信機能(EUSART)を持っているため外部機器と通信を行うことができます。MCCを使用してEUSARTの設定を行いES920LRの初期設定する方法と電文を使って無線通信する方法をまとめました。

ES920LRB(EASEL製:東京デバイセズで購入)を2台使用して親機と子機の設定を行います。ES920LRには後継機種としてES920LR2及びES920LR3があります。

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

PICマイコン(PIC16F1827)のシリアル通信の応用

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

これらで紹介した記事を応用して無線通信を実現します。

EUSARTを実装する

PIC16F1827のEUSARTの設定はMCCを使って行っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はMCCを使用してEUSART及びTMR0の設定を行っています。

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。ES920LRの初期のボーレートが115200bpsなのでEUSART設定時の誤差を減らすために16MHzを選択しています。

EUSARTを設定する

PICマイコンのシリアル通信はEUSARTと表現されています。EUSARTはDevice Resources内のPeripherals欄のEUSARTを選択して追加します。

EUSARTの設定(MCC)
EUSARTの設定(MCC)

EUSARTを非同期で使用するためにModeでasynchronousを選択しEnable Transmit及びEnable Receiveにチェックを入れます。Baud RateをES920LRのデフォルトである115200bpsにします。

クロックが低い状態で115200bpsを選択すると誤差が大きくなり送受信が失敗したりなど通信が不安定になるため注意が必要です。

送信や受信時に割り込みを使用してデータの格納を行う方が確実なデータ取得がしやすいためEnable EUSART Interruptsをチェックして割り込みを使用します。

パリティを使用しないためTransmission Bits及びReception Bitsを8ビット構成にしています。

Software Settings欄にはTransmit Buffer SizeやReceive Buffer Sizeで割り込み時にデータを一時保存しておくバッファのサイズを選択できますがサイズを大きくするとデータ容量が増えてしまいます。

TMR0の設定

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0Aを指定しているため10ms毎にコールバック関数が呼び出されます。

void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定

上記例ではmainTimer()が10ms毎にコールされます。

電文を受信する

while(EUSART_is_rx_ready()) //受信データがあるか
{
    picRcv.buf[picRcv.wp] = EUSART_Read(); //データをリード
    if( ++picRcv.wp >= sizeof(picRcv.buf)){ //次に保管する位置を更新
        picRcv.wp = 0;
    }
}

EUSARTで受信したデータを一時保管します。EUSART_is_rx_ready()は受信したデータがあるとtrueを戻り値で返します。trueであればEUSART_Read()で受信したデータを読み込み一時保管します。

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

受信データの受付とデータの送信

void eUsartmain(bool init){

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

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

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

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

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

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

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

データの送信

if(EUSART_is_tx_ready()){ //送信ビジーでないか
    if ( Txdata.cnt < sizeof(Txdata.data)){
        EUSART_Write(Txdata.data[Txdata.cnt]); //データを送信
        ++Txdata.cnt; //送信データカウントを更新
    }
    else{ //全データの送信が完了
        timTxWait = TIME_OFF;
        ComMode = COM_RX_WAIT; //受信待ちに遷移
    }
}  
                
if( timTxWait == TIME_UP ){ //送信タイムアウトか
    timTxWait = TIME_OFF;
    ComMode = COM_RX_WAIT; //受信待ちに遷移
}

COM_TXモードでは送信がビジー(待機状態)でなければデータを送信します。送信データを配列として準備し送信データカウントを更新しながら配列の先頭から順に送信します。

送信タイムアウトを設けていますが何らかの要因で送信がうまくいかなかった場合にタイムアウトで受信モードに切り替えることができます。

送信タイムアウトに引っかかってしまう場合は異常状態とみなしてシリアル通信機能をリスタートするなど対策に使用できます。

スポンサーリンク

ES920LRで無線通信する

ES920LRの初期設定をプロフェッサーモードで行います。PIC16F1827のシリアル通信で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
    起動時のモードの設定(本記事では未実装)

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

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

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

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

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

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

上位互換のES920LR2やES920LR3は起動時の仕組みが改善されているため3秒のウェイトを置く必要はありません。

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

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バイト目から入力データ(最大50バイト)を指定します。

アスキー文字の方が受信時に文字として見やすいため分かりやすいですが、バイナリデータを文字に置き換えると2バイト必要となり送信バイト数が増えてしまいます。

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

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

Payloadモードのフォーマットに従って電文を構成します。

電文の構成
電文の構成

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

受信データのチェック

  if( rxsz >= 2 ){ //データ長分だけ獲得しているか
      for( i = 0; i < 2; i++){//データサイズ算出のため仮おき
          dat[i] = picRcv.buf[ rp ];
          if(++rp >= sizeof(picRcv.buf) ) rp = 0;
      }
      if( dat[1] == HEADER){ //ヘッダーの確認
          if( dat[0] > sizeof(picRcv.buf) ){
              allsz = sizeof(picRcv.buf);
          }
          else{
              datsz = dat[0] - 2; //headerとsumを除外
              allsz = dat[0] + 1; //データ数分を含む
          }
                  
          if( rxsz >= allsz){ //確定したサイズ以上か
              timRxWait = TIME_OFF;
              for(i=0; i < sizeof(Rcvdata); i++){
                  Rcvdata[i] = 0; //バッファのクリア
              }
              for( i=0; i < allsz; i++ ){ //一時保管したデータを移す
                  Rcvdata[i] = picRcv.buf[picRcv.rp]; 
                  ReadPointerAdd(); //読み込み位置の更新
              }
              sumchk = CalcSum(&Rcvdata[2], datsz); //チェックサムの計算
              if( sumchk == Rcvdata[allsz-1]){ //チェックサムの確認
                  ComMode = COM_RCVDATA; //モードを遷移
              }
          }    
      }
  }

受信したデータが電文の構成であるかを確認するためにCOM_RX_WAITモードで受信データのチェックを行います。受信データはデータ数が先頭で2バイト目にヘッダとなるように電文を構成しているため最初の2バイトで全体のサイズとヘッダの一致を確認します。

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

電文の最終バイトに付加したチェックサムの値と計算したチェックサムが一致した時正常な電文として受け入れてCOM_RCVDATAモードに遷移します。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

PIC16F1827とES920LRをシリアル通信して無線通信を行います。ES920LRの動作電圧はDC3.3VなのでPIC16F1827をDC5Vで動作させる場合は注意が必要です。

親機と子機で回路を切り替えます。親機で使用する場合は6ピン(RB0)をGNDに接続して電源をONします。子機の場合は未接続とします。回路図の赤線は子機で使用し青線は親機で使用します。子機のSW1を押すと無線通信を行い親機のLED1を5秒間点灯させます。

PIC16F1827はDC3.3Vでも動作できるためDC3.3Vの電源を使用して直接ES920LRと直接接続しても問題ありません。回路例ではDC5VのPIC16F1827のシリアル通信信号をDC3.3Vに電圧変換してES920LRと接続しています。

三端子レギュレータBA33BC0FP(ローム)を使用していますが、負荷側にコンデンサ(22uF~1000uF)が必要になります。消費電流はほとんどないためLP2950L-3.3V(UTC)やNJM2845DL1(西日本無線)でも代用可能です。

UART_TXはES920LRからPIC16F1827に向けた通信になるためDC3.3VからDC5Vに電圧変換を行います。UART_RXはPIC16F1827らES920LRに向けた通信になるためDC5VからDC3.3Vに電圧変換しています。

TR1とTR2のようにpnpトランジスタを使用することで通信の極性が反転しないようにしています。PIC16F1827側のコレクタは内蔵プルアップで接続しています。

電源をONして5秒くらい経過した後、子機のSW1を押すと親機のLED1が5秒間点灯した後消灯するが確認できました。距離が離れるとLED1の点灯の確認が難しくなるため親機から子機に対して返信電文を送信して子機に表示するなど工夫が必要だと感じました。

MCCのピン設定は以下の通りです。

PIC16F1827のピン設定(MCC)
PIC16F1827のピン設定(MCC)

PB0はINPUTでウィークプルアップ(WPU)設定しています。WPUを有効にするためにはRegistersのnWPUENをenabledにする必要があります。

Easy SetupでWPUにチェックするのみでは有効にならないので注意が必要です。Notificationsにワーニングで通知されます。

スポンサーリンク

ソースコード全体

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

PIC16F1827のソースコード:

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1     //タイマーを使用しない場合
#define TIME_UP 0       //タイムアップ
#define TIM_RX_WAIT 20   //200ms
#define TIM_TX_WAIT 300  //300ms
#define TIM_BTN_WAIT 5
#define TIM_INIT_WAIT 300
#define RING_SZ 64
#define TX_SZ 10
#define HEADER 'L'
#define OFFSET_SZ 5
#define CR 0x0D
#define LF 0x0A

const char select[] = "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 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 cnt;
    uint8_t max;
    uint8_t data[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_SAVE,
    //INIT_SAVE_WAIT,
    INIT_START,
    INIT_START_WAIT,
    INIT_END,
    INIT_ERR,
    INIT_MAX,
}INIT_MODE;

/* 変数定義 */
int16_t  timRxWait = TIME_OFF;
int16_t  timTxWait = TIME_OFF;
int16_t  timbtn = TIME_OFF;
int16_t timRecive = TIME_OFF;
int16_t timInitWait = TIME_OFF;
RING_MNG    picRcv;
TX_MNG  Txdata;
COM_MODE_NO ComMode;
uint8_t Rcvdata[RING_SZ];
bool    loraInit= true;
bool    selParent = false; //親機ならtrue
bool    pushbtn = false;
INIT_MODE	initmode;		//LORAモード

/* 関数プロトタイプ宣言 */
void eUsartmain(bool init);
void mainApp(void);
void mainTimer(void); //タイマー0割り込みでの処理
void ReadPointerAdd(void);
uint8_t CalcSum(uint8_t *buf, uint8_t sz );
bool RcvChk(void);
bool TxDataSet(bool boo, uint8_t cmd);
bool IntTxDataSet( uint8_t *dat, uint8_t sz );

/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    if( IO_RB0_GetValue() == 0 ){ //親機はLOWとする
        selParent = true;
    }
            
    timInitWait = TIM_INIT_WAIT;
    
    while (1)
    {
        eUsartmain(loraInit);
        mainApp();
    }
}
/* タイマ管理関数 */
void mainTimer(void){
 
    if( timRxWait > TIME_UP ){
        --timRxWait; //タイマを更新
    }
    
    if( timTxWait > TIME_UP ){
        --timTxWait; //タイマを更新
    }
    
    if( timRecive > TIME_UP ){
        --timRecive;
    }
    
    if( timbtn > TIME_UP ){
        --timbtn;
    }
    
    if( timInitWait > TIME_UP ){
        --timInitWait;
    }
}
/* メイン関数 */
void mainApp(void){
    
    while(EUSART_is_rx_ready())
    {
        picRcv.buf[picRcv.wp] = EUSART_Read(); //データをリード
        if( ++picRcv.wp >= sizeof(picRcv.buf)){ //次に保管する位置を更新
            picRcv.wp = 0;
        }
    }
    
    if( timRecive == TIME_UP ){
        timRecive = TIME_OFF;
        IO_RB6_SetLow();
    }
    
    if( selParent == false ){
        if( timbtn == TIME_UP && pushbtn ){
            if( TxDataSet(selParent, 0) ){
                pushbtn = false;
            }
        }

        if( IO_RB4_GetValue() == 0 ){
            if( timbtn == TIME_OFF ){
                timbtn = TIM_BTN_WAIT;
                pushbtn = true;
            }
        }
        else{
            timbtn = TIME_OFF;
            pushbtn = false;
        }
    }
    
    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*)&select,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_START:
                if( IntTxDataSet((uint8_t*)&start,3)){
                    initmode = INIT_START_WAIT;
                }                   
                break;                
            case INIT_END:
                loraInit = false;
                break; 
        }
    }
}
/* シリアル通信メイン処理 */
void eUsartmain(bool init){
    int16_t rxsz;
    uint8_t datsz;
    uint8_t allsz;
    uint8_t rp = picRcv.rp;  
    uint8_t i;
    uint8_t sumchk;
    uint8_t dat[2];
    
    if(init == false ){
        if( timRxWait == TIME_UP){ //受信タイムアウトか
            timRxWait = TIME_OFF;
            ReadPointerAdd(); 
            ComMode = COM_RX_WAIT;
        }
    }
    
    switch( ComMode ){
        case COM_RX_WAIT:
            rxsz = picRcv.wp - picRcv.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] = picRcv.buf[picRcv.rp]; //一時保管したデータを移す
                            ReadPointerAdd(); //読み込み位置の更新
                        }
                        ComMode = COM_RCVDATA; //モードを遷移
                    }
                }
                else{
                    if( rxsz >= 2 ){ //データ長分だけ獲得しているか
                        for( i = 0; i < 2; i++){//データサイズ算出のため仮おき
                            dat[i] = picRcv.buf[ rp ];
                            if(++rp >= sizeof(picRcv.buf) ) rp = 0;
                        }
                            
                        if( dat[1] == HEADER){
                            if( dat[0] > sizeof(picRcv.buf) ){
                                allsz = sizeof(picRcv.buf);
                            }
                            else{
                                datsz = dat[0] - 2; //headerとsumを除外
                                allsz = dat[0] + 1; //データ数分を含む
                            }
                            
                            if( rxsz >= allsz){ //確定したサイズ以上か
                                timRxWait = TIME_OFF;

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

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

                                sumchk = CalcSum(&Rcvdata[2], 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:
            if(EUSART_is_tx_ready()){ //送信ビジーでないか
                if ( Txdata.cnt < Txdata.max ){
                    EUSART_Write(Txdata.data[Txdata.cnt]); //データを送信
                    ++Txdata.cnt; //送信データカウントを更新
                }
                else{ //全データの送信が完了
                    timTxWait = TIME_OFF;
                    ComMode = COM_RX_WAIT; //受信待ちに遷移
                }
            }  
                
            if( timTxWait == TIME_UP ){ //送信タイムアウトか
                timTxWait = TIME_OFF;
                ComMode = COM_RX_WAIT; //受信待ちに遷移
            }
            break;        
    }
}
/* 読み込み位置の更新 */
void ReadPointerAdd(void){
    
    if(++picRcv.rp >= sizeof(picRcv.buf) ){
        picRcv.rp = 0;
    }
}
/* チェックサムの計算 */
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;
}
/* 電文の処理 */
bool RcvChk(void){
    bool ret = false;
    uint32_t cmd;
    int16_t tim;
    
    if(loraInit){
        if( Rcvdata[0] == 'O' && Rcvdata[1] == 'K'){
            ++initmode;
        }
    }
    else{
        if(selParent){ //子→親の処理と返信
            cmd = Rcvdata[2];
                   
            switch(cmd){
                case 0:
                    tim = ( Rcvdata[4] << 8 ) + Rcvdata[5];
                    timRecive = tim;
                    IO_RB6_SetHigh();
                    //TxDataSet(true,0);
                    //ret = true;
                    break;
                //case 1:
                    //動作パターンを追加していく
                //    break;
            } 
        }
        else{ //親→子
            //親からの返信で処理する場合は追加
        }
    }
     
    return ret;
}
/* 送信データ生成 */
bool TxDataSet(bool boo, uint8_t cmd){
    bool ret = false;
    uint8_t i=0;
    
    if( ComMode == COM_RX_WAIT ){
        Txdata.cnt = 0;
        
        Txdata.data[i++] = 6;//HEADER;
        Txdata.data[i++] = HEADER;//5; //cmd+4byte
        
        if( boo ){ //親→子
            Txdata.data[i++] = cmd | 0x80;
            Txdata.data[i++] = 0x30;
            Txdata.data[i++] = 0x30;
            Txdata.data[i++] = 0x30;      
        }
        else{ //子→親
            Txdata.data[i++] = cmd;
            Txdata.data[i++] = 0;
            Txdata.data[i++] = 1;
            Txdata.data[i++] = 0xF4;       
        }

        Txdata.data[i++] = CalcSum(&Txdata.data[2], 4);
        Txdata.max = i;
        ComMode = COM_TX;
        timTxWait = TIM_TX_WAIT;
        ret = true;
    }
    return ret;
}
/* 送信データ生成 */
bool IntTxDataSet( uint8_t *dat, uint8_t sz ){
    bool ret = false;
    uint8_t i;
     
    if( ComMode == COM_RX_WAIT ){
        Txdata.cnt = 0;
        for( i = 0; i < sz; i++){
            Txdata.data[i] = *dat;
            ++dat;
        }

        Txdata.data[i++] = CR;
        Txdata.data[i++] = LF;
        Txdata.max = i;
        ComMode = COM_TX;
        timTxWait = TIM_TX_WAIT;
        ret = true;
    }
    return ret;
}
/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていないと使用できません。

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

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

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

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

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

LPWA通信のメリットやデメリットの特徴とIoTとの関連性

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

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