マイコンでシリアル通信する①

はじめに

マイコンとPC、マイコンマイコンの間で情報をやり取りする際、文字形式で送受信すると情報量がべらぼうに増える。

デジタルな情報伝達は時間を原資に通信する。送受信したい情報量が増えれば定期処理に間に合わなくなってくる。

情報量を可能な限り削減することは短い周期で制御をおこなう開発において非常に重要らしい。

ArduinoマイコンtoPC間の相互通信プログラムの雛形を書いた。

これをロボットとか作るときに使うこととする。

まずはプログラム

Arduino言語で記述。

外部ライブラリとしてMsTimer2を使っている。

#include <MsTimer2.h>

#define Si1buf_size 256
#define Si1readbyte_len (9 + 4)

#define sendbyte_len (9 + 4)

byte Serial1_buffer[Si1buf_size];
int Serial1_buffer_len = 0;


unsigned long cnt0 = 0;//時間カウンタ
int cnt1 = 0;//LEDカウンタ
int cnt2 = 0;//データ送信カウンタ
int cnt3 = 0;//制御カウンタ
int cnt4 = 0;//計測カウンタ

float t = 0.0;

float dt = 0.001;//s
float interupt_time = dt;
boolean cnt1_flag = HIGH;
boolean cnt2_flag = LOW;
boolean cnt3_flag = LOW;
boolean cnt4_flag = LOW;

float cnt1_time = 1.0;//割り込み周期[sec]//LEDカウンタ
float cnt2_time = 0.1;//割り込み周期[sec]//データ送信カウンタ
float cnt3_time = 0.1;//割り込み周期[sec]//制御カウンタ
float cnt4_time = 0.1;//割り込み周期[sec]//計測カウンタ

//受信データ
uint8_t uint8data = 0;
uint16_t uint16data = 0;
uint32_t uint32data = 0;
float floatdata = 0.0;

//送信データ
uint8_t s_uint8data = 0;
uint16_t s_uint16data = 0;
uint32_t s_uint32data = 0;
float s_floatdata = 0.0;

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("Start");

  for (int i = 0; i < Si1buf_size; i++)Serial1_buffer[i] = 0x00;
  Serial1_buffer_len = 0;

  MsTimer2::stop();
  MsTimer2::set(int(interupt_time * 1000.0), timer_1);
  MsTimer2::start();
}

void loop() {
  D_L();
  C_L();
  //W_L();
  R_L();
}

void timer_1() {//
  t = t + dt;
  cnt0 = cnt0 + 1;
  cnt1 = cnt1 + 1;
  cnt2 = cnt2 + 1;
  cnt3 = cnt3 + 1;
  cnt4 = cnt4 + 1;
  // Serial.println(t);
  if (cnt1 > (int)(cnt1_time / dt) - 1) { //led function
    cnt1_flag = !cnt1_flag;
    cnt1 = 0;
  }
  if (cnt2 > (int)(cnt2_time / dt) - 1) { //send data flag
    cnt2_flag = HIGH;
    cnt2 = 0;
  }
  if (cnt3 > (int)(cnt3_time / dt) - 1) { //control flag
    cnt3_flag = HIGH;
    cnt3 = 0;
  }
  if (cnt4 > (int)(cnt4_time / dt) - 1) { //check power flag
    cnt4_flag = HIGH;
    cnt4 = 0;
  }
}

void W_L() { //データ送信する関数
  if (cnt2_flag) {
    int i = 0;
    byte s_uchMsg[sendbyte_len];//送信するデータ数分配列を確保
    uint16_t ushTmp;
    uint32_t ushTmp32;

    //start byte
    s_uchMsg[0] = 0xa5;
    s_uchMsg[1] = 0x5a;
    s_uchMsg[2] = 0x80;

    //data length
    s_uchMsg[3] = (byte)(sendbyte_len - 5);

    //uint8data
    ushTmp = (byte)(s_uint8data);
    s_uchMsg[4] = ushTmp;

    //uint16_t
    ushTmp = (uint16_t)(s_uint16data);
    s_uchMsg[5] = (byte)(ushTmp >> 8 & 0x00ff);
    s_uchMsg[6] = (byte)(ushTmp & 0x00ff);

    //uint32_t
    ushTmp32 = (uint32_t)(s_uint32data);
    s_uchMsg[7] = (byte)(ushTmp32 >> 24 & 0x000000ff);
    s_uchMsg[8] = (byte)(ushTmp32 >> 16 & 0x000000ff);
    s_uchMsg[9] = (byte)(ushTmp32 >> 8 & 0x000000ff);
    s_uchMsg[10] = (byte)(ushTmp32 & 0x000000ff);

    //floatdata
    ushTmp = (uint16_t)((s_floatdata + 1.0) /  2.0 * 65535.0);
    s_uchMsg[11] = (byte)(ushTmp >> 8 & 0x00ff);
    s_uchMsg[12] = (byte)(ushTmp & 0x00ff);

    for (i = 4; i < ((uint8_t)s_uchMsg[3] + 4); i++) {
      s_uchMsg[13] = s_uchMsg[13] ^ s_uchMsg[i];
    }

    Serial.write(s_uchMsg, sizeof(s_uchMsg));
    cnt2_flag = LOW;
  }
}


void C_L() { //制御関数
  if (cnt3_flag) {
    cnt3_flag = LOW;
  }
}

void D_L() { //計測する関数
  if (cnt4_flag) {

    cnt4_flag = LOW;
  }
}


void R_L() {
  int i = 0;
  int tmp_len = 0;

  //受信バッファ
  uint8_t b_uint8data = 0;
  uint16_t b_uint16data = 0;
  uint32_t b_uint32data = 0;
  float b_floatdata = 0.0;

  byte Serial1_buffer_tmp[Si1buf_size];
  for (i = 0; i < Si1buf_size; i++) {
    Serial1_buffer_tmp[i] = 0x00;
  }

  //シリアル情報取得
  while ( Serial.available()) {
    if (tmp_len >= Si1buf_size)break;
    Serial1_buffer_tmp[tmp_len] = Serial.read();
    tmp_len = tmp_len + 1;
  }
    //バッファに貯める
    if (tmp_len > (Si1buf_size - Serial1_buffer_len)) {
      //バッファサイズからはみ出るとき、はみ出た分削除の後、取得分追加
      int over_len = 0;
      over_len = tmp_len - (Si1buf_size - Serial1_buffer_len);
      byte swp_sil1_buf[Si1buf_size];
      for (i = 0; i < Si1buf_size; i++) {
        swp_sil1_buf[i] = Serial1_buffer[i];
      }
      for (i = 0; i < Si1buf_size - over_len; i++) {
        Serial1_buffer[i] = swp_sil1_buf[i + over_len];
      }
      Serial1_buffer_len = Serial1_buffer_len - over_len;
      for (i = 0; i < tmp_len; i++) {
        Serial1_buffer[Serial1_buffer_len + i] = Serial1_buffer_tmp[i];
      }
      Serial1_buffer_len = Serial1_buffer_len + tmp_len;
    } else {
      //バッファサイズからはみ出ないとき、取得分追加
      for (i = 0; i < tmp_len; i++) {
        Serial1_buffer[Serial1_buffer_len + i] = Serial1_buffer_tmp[i];
      }
      Serial1_buffer_len = Serial1_buffer_len + tmp_len;
    }
    //データ読み出し

    for (int i = 0; i < Serial1_buffer_len - Si1readbyte_len; i++) {
      int buf_len = 0;

      if ( Serial1_buffer[i + buf_len] == 0xa5 ) {
        buf_len = buf_len + 1;
        if ( Serial1_buffer[i + buf_len] == 0x5a ) {
          buf_len = buf_len + 1;

          uint16_t ushTmp;
          uint32_t ushTmp32;
          byte low;
          byte high;
          byte low2;
          byte high2;
          byte datalen;

          buf_len = buf_len + 1;//0x80を飛ばす。twelite対応のため
          datalen = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;

          //uint8data
          b_uint8data = (uint8_t)Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;

          //uint16data
          low = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          high = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          ushTmp = (uint16_t)(((low << 8) & 0xff00) | (high & 0x00ff));
          b_uint16data = (uint16_t)ushTmp;

          //uint32data
          low = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          low2 = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          high = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          high2 = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          ushTmp32 = (uint32_t)(((low << 24) & 0xff000000) | ((low2 << 16) & 0x00ff0000) | ((high << 8) & 0x0000ff00) | (high2 & 0x000000ff));
          b_uint32data = (uint32_t)ushTmp32;

          //floatdata
          low = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          high = Serial1_buffer[i + buf_len];
          buf_len = buf_len + 1;
          ushTmp = (uint16_t)(((low << 8) & 0xff00) | (high & 0x00ff));
          b_floatdata = ((float)ushTmp / 65535.0 * 2.0 - 1.0);

          //チェックサム
          byte checksum = 0x00;
          for (int j = 4; j < (uint8_t)datalen + 4; j++) {
            checksum = checksum ^ Serial1_buffer[i + j];
          }
 
          if ( Serial1_buffer[i + buf_len] == checksum ) {
            buf_len = buf_len + 1;
            //パケット取得成功のため、取得分より前データ削除
            int over_len = i + buf_len;
            byte swp_sil1_buf[Si1buf_size];
            for (int i = 0; i < Si1buf_size; i++)swp_sil1_buf[i] = Serial1_buffer[i];
            for (int i = 0; i < Si1buf_size - over_len; i++)Serial1_buffer[i] = swp_sil1_buf[i + over_len];
            Serial1_buffer_len = Serial1_buffer_len - over_len;

            uint8data = b_uint8data;
            uint16data = b_uint16data;
            uint32data = b_uint32data;
            floatdata = b_floatdata;


            Serial.print(uint8data);
            Serial.print(',');
            Serial.print(uint16data);
            Serial.print(',');
            Serial.print(uint32data);
            Serial.print(',');
            Serial.println(floatdata);

          }
        }
      }
    }
}

メインループは下記のようになっているが、

void loop() {
  D_L();
  C_L();
  //W_L();
  R_L();
}
  • D_L()、C_L()関数が、計測、制御のための関数となっている。今回は空の関数。
  • W_L()関数が、データ送信のための関数となっている。0.1sec周期で実行。
  • R_L()関数が、データ受信のための関数となっている。全ループ実行。

上記プログラムではW_L()をコメントアウトしているが、データを送信するときはこの関数を実行する。

実行すると、後述のプロトコルに沿ったデータが送信される。

使い方

マイコンとの通信ソフトとして「SerialDebugTool2」を使用している。

マイコンと接続して、プロトコルに則ったデータを送信してやるとちゃんと受信する。(はず)

マイコンからは、テキスト形式の送信データが返ってくる。

f:id:j03964608:20211013004205p:plain

プロトコル

  1. スタートバイト A5 5A 80

  2. データ長さ 09*1

  3. 送信したいデータ 01 00 00 00 00 00 00 00 00
  4. チェックサム 01(送信したいデータすべてのXOR論理和)

上記をつなげると、

「A5 5A 80 09 01 00 00 00 00 00 00 00 00 01」

となり、これを送信すると、「01 00 00 00 00 00 00 00 00」の部分がマイコンにしっかり伝わる。

「01 00 00 00 00 00 00 00 00」をあらかじめ決めておいた型に分配してやると、欲しいデータがそのまま得られるって寸法よ。

まとめ

通信は面倒。なので雛形作っておいた方が後々楽かと思って整理しておく。

ROSとか使えばもっと楽なんだろうね。

次は型変換について整理しておく。

*1:送信したいデータの長さが10byteだったら16進数で0x0A