Arduinoでシリアルメッセージを解釈する

超簡単!TWELITE標準アプリのシリアルメッセージ(書式形式)を解釈し、パソコンにデータの値を出力するサンプルスケッチです。

ダウンロード

MW_FormatParser.zip

導入手順

  1. 下記のように配線する

    親機子機
    配線図
    使用する電子部品 TWELITE DIP x 1
    Arduino x 1
    抵抗 2.2kΩ(赤・赤・赤) x 1
    抵抗 3.3kΩ(橙・橙・赤) x 1
    抵抗 4.7kΩ(黄・紫・赤) x 1
    抵抗 10kΩ(茶・黒・橙) x 1
    トランジスタ 2SC1815 x1
    TWELITE DIP x 1
    タクトスイッチ x 1
    電池ボックス x 1

    ※ 親機のTWELITE DIPは必ずBPSピンをGNDに接続して使用してください。

  2. 下記URLからArduino IDEをダウンロードし、インストールする。
    https://www.arduino.cc/en/Main/Software

  3. Arduino IDEのインストールフォルダ(多くの場合はC:\Program Files (x86)\Arduino にインストールされます)内のlibraries内にMW_FormatParser.zipを保存して解凍する。

  4. Arduino IDEを起動させ、メニューバーの ファイル -> スケッチ例 -> MONOWIRELESS Serial Format Parser -> App_Twelite を選択する。

  5. メニューバーの スケッチ -> マイコンボードに書き込む を選択する。

  6. シリアルモニタをクリックする

出力例

TWELITEの子機からのパケットを親機が受信したときArudino IDEのシリアルモニタに下記のように出力されます。

Logical ID = 0x78
Command ID = 0x81
LQI = 99
Serial ID = 0x810039E7
Receive ID = 0x0
Timer = 17.67
Number of Relay = 0
Power Voltage = 2809
DI = 0000
DI Mask = 1111
ADC1 = 164
ADC2 = 364
ADC3 = 576
ADC4 = 1088

スケッチの解説

sercmdは書式形式を解釈し、バイト列を得るためのクラスです。
このクラスは、書式形式の解釈とは逆にバイト列から書式形式を出力することができます。本スケッチではTWELITEからの入力用とTWELITEへの出力用の二つ定義します。
TWELITEへの出力用のsercmdを宣言する際に、MWPutChar()のような1バイトシリアル出力するための関数を定義し引数として渡します。

#include <sercmd.h>         // 書式形式のパーサー

…

// Global Object
sercmd SerialIn(false, 0, NULL );            // シリアル読み込み用のオブジェクト
sercmd SerialOut(false, 0, vPutChar );      // シリアル書き込み用のオブジェクト

…

// 書式形式出力用に1バイト出力関数
void vPutChar( unsigned char chr )
{
  ser.write(chr); // 1バイトシリアル出力する関数
}

下記関数がメインループとなります。
TWELITEからのシリアル入力を1バイトずつ読み込み、TWELITEからのシリアル電文が終わったと判断したらそのデータを解釈してPCにUART出力します。パーサからの戻り値(u8stat)がE_SERCMD_COMPLETE(0x80)であれば解釈完了を示しており、sercmdクラスのau8dataに読み込んだバイト列、u16lenに読み込んだバイト列のバイト数が代入されています。このとき、sercmdクラスのau8dataのデータはヘッダやフッダ、チェックサムは取り除かれた状態で保持されており、アスキー形式の場合は2文字で1バイトの数値に変換されたバイト列で保持されています。また、u8statがE_SERCMD_ERRORやE_SERCMD_CHECKSUM_ERRORの場合、読み込みが失敗を示しているため次のデータを待ちます。
PCから0~3を入力し、入力された文字に応じてTWELITEに0x80コマンド(DIOの制御コマンド)を送信します。

void loop() {
  /*
   * TWELITEからのシリアルデータを読み込み、PCへシリアル出力する
   */
  int c = ser.read();
  if(c >= 0){
    // 1バイトずつ解釈をしていく
    byte u8stat = SerialIn.u8Parse(c);
    // 解釈に成功したら終了
    if (u8stat == E_SERCMD_COMPLETE) {
      // 0x81コマンドだったら表示
      tsAppTwelite sAppTwelite;
      if(bParseAppTwelite(SerialIn, &sAppTwelite)){
        vShowData(sAppTwelite);
      }
    } else if (u8stat == E_SERCMD_CHECKSUM_ERROR) {
      // 電文中のチェックサムと計算したチェックサムが異なっているので、おそらく正しく読み込めなかった
      Serial.print("Command checksum error\n");
    }
  }

  /*
   * PCからのシリアルデータを読み込み、入力された文字によってTWELITEにコマンドを送信する
   */
  int d = Serial.read();  // PCからの入力を読み込む
  // 入力があった場合、処理する
  if(d >= 0){
    unsigned char cmdtmp[] = {0x78,0x80,0x01,0xFF,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; // シリアルメッセージのテンプレート
    // 入力値によってメッセージを変更する
    switch(d){
      case '0':
        // DIを消灯する
        cmdtmp[3] = 0x00;   // DIをすべてHiにする
        break;
      case '1':
        // DIを全灯する
        cmdtmp[3] = 0x0F;   // DIをすべてLoにする
        break;
      case '2':
        // PWMを消灯する
        cmdtmp[3] = 0x00;   // DIをすべてHiにする
        cmdtmp[4] = 0x00;   // DIの状態は変更しない
        cmdtmp[5] = 0x00; cmdtmp[6] = 0x00;   // PWM1をLoにする
        cmdtmp[7] = 0x00; cmdtmp[8] = 0x00;   // PWM2をLoにする
        cmdtmp[9] = 0x00; cmdtmp[10] = 0x00;  // PWM3をLoにする
        cmdtmp[11] = 0x00; cmdtmp[12] = 0x00; // PWM4をLoにする
        break;
      case '3':
        // PWMを全灯する
        cmdtmp[3] = 0x00;   // DIをすべてHiにする
        cmdtmp[4] = 0x00;   // DIの状態は変更しない
        cmdtmp[5] = 0x04; cmdtmp[6] = 0x00;   // PWM1をHiにする
        cmdtmp[7] = 0x04; cmdtmp[8] = 0x00;   // PWM2をHiにする
        cmdtmp[9] = 0x04; cmdtmp[10] = 0x00;  // PWM3をHiにする
        cmdtmp[11] = 0x04; cmdtmp[12] = 0x00; // PWM4をHiにする
        break;
      default:
        // 何もしない
        break;
    }
    // DIの指定部分が変わっている場合シリアルメッセージをTWELITEに送信する
    if( cmdtmp[3] != 0xFF ){
      // バイト列のコポー
      memcpy( SerialOut.au8data, cmdtmp, sizeof(cmdtmp) );
      // バイト列の長さを指定する
      SerialOut.u16len = sizeof(cmdtmp);
      // TWELITEにコマンドを送信する
      SerialOut.vOutput();
    }
  }
}

下記コードは、TWELITEから読み込んだバイト列を解釈する関数です。受信したシリアル電文とtsAppTwelite構造体のアドレスを引数として渡します。
受信したステータスが相手端末の状態通知であれば構造体に解釈た値を代入してtrueを返し、それ以外であればfalseを返します。

// 受信したデータを保持する構造体
typedef struct
{
  byte LID;               // 送信側の論理デバイスID
  byte CMD;               // コマンド
  unsigned long SID;      // 送信側のシリアルナンバー
  byte IDE;               // パケット識別子
  byte PVE;               // プロトコルバージョン
  byte LQI;               // LQI
  byte RID;               // 受信側の論理デバイスID
  byte RNM;               // 中継回数
  float TMS;              // タイムスタンプ
  unsigned short PWR;     // 電源電圧(mV)
  byte DI;                // DIの状態 (1だったらLo)
  byte DIM;               // DIのマスク(1度でも変化したら1)
  unsigned short AD[4];   // アナログ電圧
} tsAppTwelite;

…

// シリアルデータを解釈する 解釈出来たらTrue,出来なかったらFalseを返す
bool bParseAppTwelite( sercmd cmd, tsAppTwelite* sAppTwelite )
{
  sAppTwelite->LID = cmd.au8data[0];
  sAppTwelite->CMD = cmd.au8data[1];
  // コマンドIDが0x81かつバイト列が23バイトの時はIO変化のパケットなので解釈する
  if(sAppTwelite->CMD == 0x81 && cmd.u16len == 23){
    sAppTwelite->IDE = cmd.au8data[2];
    sAppTwelite->PVE = cmd.au8data[3];
    sAppTwelite->LQI = cmd.au8data[4];
    sAppTwelite->SID = ((unsigned long)cmd.au8data[5]<<24)|((unsigned long)cmd.au8data[6]<<16)|(cmd.au8data[7]<<8)|(cmd.au8data[8]);
    sAppTwelite->RID = cmd.au8data[9];
    sAppTwelite->TMS = (cmd.au8data[10]<<8|cmd.au8data[11])/64.0;
    sAppTwelite->RNM = cmd.au8data[12];
    sAppTwelite->PWR = cmd.au8data[13]<<8|cmd.au8data[14];
    sAppTwelite->DI = cmd.au8data[16];
    sAppTwelite->DIM = cmd.au8data[17];
    // アナログ値の計算を行う
    for(int i=0; i<4; i++){
      sAppTwelite->AD[i] = cmd.au8data[18+i]<<2;
      sAppTwelite->AD[i] += ( cmd.au8data[22]>>(2*i) )&0x03;
      sAppTwelite->AD[i] = sAppTwelite->AD[i]<<2;
    }
    return true;
  }
  return false;
}

下記コードは、保持していたデータを成形しPCにシリアル出力を行う関数です。引数としてアナログ入力デジタル入力値が保持された構造体を渡します。

// 表示する
void vShowData(tsAppTwelite sAppTwelite)
{
  if(sAppTwelite.CMD == 0x81){
    Serial.print("Logical ID = 0x");Serial.println(sAppTwelite.LID, HEX);
    Serial.print("Command ID = 0x");Serial.println(sAppTwelite.CMD, HEX);
    Serial.print("LQI = ");Serial.println(sAppTwelite.LQI);
    Serial.print("Serial ID = 0x");Serial.println(sAppTwelite.SID, HEX);
    Serial.print("Receive ID = 0x");Serial.println(sAppTwelite.RID, HEX);
    Serial.print("Timer = ");Serial.println(sAppTwelite.TMS);
    Serial.print("Number of Relay = ");Serial.println(sAppTwelite.RNM);
    Serial.print("Power Voltage = ");Serial.println(sAppTwelite.PWR);
    Serial.print("DI = ");
    // DIの状態ビット (普通に出力すると先頭の0が消えるため、1ビットずつ出力する)
    for(int i=3; i>=0; i-- ){
      Serial.print((sAppTwelite.DI&(1<<i))>>i, BIN);
    }
    Serial.println();
    // DIの変更状態ビット (普通に出力すると先頭の0が消えるため、1ビットずつ出力する)
    Serial.print("DI Mask = ");
    for(int i=3; i>=0; i-- ){
      Serial.print((sAppTwelite.DIM&(1<<i))>>i, BIN);
    }
    Serial.println();
    Serial.print("ADC1 = ");Serial.println(sAppTwelite.AD[0]);
    Serial.print("ADC2 = ");Serial.println(sAppTwelite.AD[1]);
    Serial.print("ADC3 = ");Serial.println(sAppTwelite.AD[2]);
    Serial.print("ADC4 = ");Serial.println(sAppTwelite.AD[3]);
    Serial.println();
  }  
}