Arduinoで実験 (シリアル通信)

2013/04/20


◆ シリアル通信

広義のシリアル通信には I2C や SPI も含まれるが、本稿では USART での通信に絞ってまとめてみます。

USART は同期回路を備えたマイコン本体で実現するハートウェア・シリアル通信と、ソフトウェアで同期を取るソフトウェア・シリアルとに大別されます。

マイコンのシリアル通信用チャンネルが不足する場合や、マイコンのハードウェアがシリアル通信をサポートしていない場合に、ソフトウェア・シリアル通信を利用します。

通信なので送信側と受信側とがあり、お互いの通信速度やデータ形式の設定を行う必要があります。 Arduino では通信速度の設定のみとなります。

パソコン側がシリアル通信に用いる方式は、古くは DIN13 ピンの RS-232C 規格が主流でしたが、現在ではほぼ USB のみとなりました。

USB との通信については、現時点では Arduino 側のハードウェアにまかせます。 Arduino Leonard や due 等、USB 機能をサポートしているマイコンを搭載した機種についてはいつか学習する予定です。

早速、実験開始です。

//
// 04-01 シリアル通信の実験スケッチ
//

int i = 0;

void setup() {
  Serial.begin(9600);  // シリアル通信の速度(ボーレート)を設定
}

void loop() {
  Serial.print(i);         // 改行なしで変数 i の値を送信
  Serial.println(" Sec");  // 改行付きで文字列の送信。
  i++;                     // 1 加算
  delay(1000);
}

スケッチ転送後シリアルモニタを実行すると1秒毎にメッセージが送られてきます。

変な文字が表示される場合、シリアルモニタの右下にボーレートを設定するところがあるので 9600 に設定してください。

では、上記スケッチの Serial.print 文を少し変えてみます。

//
// 04-02 シリアル通信の実験スケッチ
//

int i = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print(i, DEC);  // ← ここを変更
  Serial.println(" Sec");
  i++;
  delay(1000);
}

実行しても何も変わらないはずです。 print 内の DEC は、10進数で送信する命令で、省略しても10進数で送信されます。

では、 DEC を HEX に書き換えて実行します。 出力が 16進数になるはずです。 同じく BIN とすると 2進数となります。

続いて実験してみます。

//
// 04-03 小数点付きの数値の送信
//

double d = 0.01;

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print(d);       // そのまま送信
  Serial.print(" , ");   // 区切り文字
  Serial.print(d, 1);    // オプションに 1 で送信
  Serial.print(" , ");
  Serial.print(d, 2);    // オプションに 2 で送信
  Serial.print(" , ");
  Serial.print(d, 3);    // オプションに 3 で送信
  Serial.print("\n\a");  // 改行文字を送信;

  d += 0.01;
  delay(1000);
}

小数点を持つ変数を送信する場合、小数点以下の桁数をオプションで指定することができ、四捨五入されて表示されます。 オプションを省略すると 2 となります。

文字列内に制御文字( \n, \t 等)を入れることができます。

Serial.print では自動で変数の値を文字に変換してくれます。


◆ 文字の送信

前項のスケッチでも print() 命令で文字・文字列を送信しましたが、1バイトのデータを送信する場合には write() 命令を用います。

//
// 04-04 文字の送信
//

void setup() {
  Serial.begin(9600);

  Serial.write('H');
  Serial.write('e');
  Serial.write('l');
  Serial.write('l');
  Serial.write('0xd');
  Serial.write('ハ');
  Serial.write('ロ');
  Serial.write('ー');
}

void loop() {
}

上記スケッチを実行すると、 Hello は正しく表示されるがその後のカタカナがちゃんと表示されません。

Arduino IDE のエディタは utf-8 の文字コードを使用していて、カタカナは 1バイトでは無いためです。


◆ 受信

データを受信する際、シリアル通信の速度がマイコンの処理速度よりもかなり遅いため少し手間をかける必要があります。

始めにシリアル通信の受信バッファに何かデータが届いていないかどうかを確かめて、データが届いていたらデータを読み込み。届いていなかったら待機、又はスルーするようにします。

次のスケッチはトークバックという、受信したデータをそのまま送り返すという動作確認用のスケッチです。

//
// 04-05 データ受信
//

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {  // 受信バッファに何か入っていたら
    char c = Serial.read();  // 1バイトのデータを読み込み
    Serial.write(c);         // 1バイトのデータを送信
  }
}

Serial.read() で受信バッファ内のデータを1バイト読み取ると 受信バッファ内のデータ数が 1減るので Serial.available() で返ってくる値も 1 減る。


◆ 文字列の受信

文字列を受信する場合には受信するデータの形によっていくつかのパターンがあり、次にいくつかの例をあげておきます。

一定の文字数が受信バッファにたまったら文字数分読み込み、文字列へ格納する場合。

//
// 04-06 文字数により文字列へ格納
//

char dat[5];

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() >= 3) {  // 受信バッファに 3文字以上たまったら
    for(int i=0; i<3; i++) {      // for文で 3回ループし
      dat[i] = Serial.read();     // 1バイトずつデータを格納
    }
    dat[3] = '\0';                // データの最後に文字の終端コードを入れる

    Serial.print("DATA = ");
    Serial.println(dat);
  }
}

上記のスケッチでは途中で文字数の違うデータが来た場合にデータの頭がずれてきます。

次のスケッチは決まった終了文字が入力されるまでデータを格納する場合です。

//
// 04-07 終了文字を判別し文字列へ格納
//

char dat[32];   // 格納用文字列
int count = 0;  // 文字数のカウンタ

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    dat[count] = Serial.read();
    if (count > 30 || dat[count] == '=') {  // 文字数が既定の個数を超えた場合、又は終了文字を受信した場合
      dat[count] = '\0';                    // 末尾に終端文字を入れる
      count = 0;                            // 文字カウンタをリセット
      Serial.print ("Data = ");
      Serial.println(dat);
    } else {
      count++;                              // 文字カウンタに 1 加算
    }
  } 
}
 

上記の方法でデータを格納する場合、データ個数による制限を設けていないと変数域以外のメモリ領域を書き換えるため、他の変数がおかしくなったり、最悪暴走が起きるので必ず文字数の制限を入れること。


◆ 他の命令

Arduino 日本語リファレンスにあるように他には peek と flash 命令があります。

peek は受信バッファのデータを残したまま先頭文字を読み取ります。 peek 命令で読み取ったデータと、peek 後に read で読み込むデータは同じものです。

flash は受信バッファの内容をクリアします。 データ送信中の場合は送信が終わってからクリアします。


◆ ソフトウェア・シリアル通信

Arduino Mega 等複数チャンネルを持つハード以外で複数チャンネルを使用したい場合に使います。

スケッチの先頭で #include <SoftwareSerial.h> と、ライブラリをインクルードして使います。 これも、Arduino 日本語リファレンスに詳しく説明があり、ほぼシリアル通信と同じ命令系統なので、すんなり理解できると思います。


◆ 良くあるスケッチ例

コマンド文字列の送信

// 数バイトのコマンドを送信します。

  unsigned char cmd[] = {0x22, 0x00, 0x00, 0x22};

  for (int i=0; i<sizeof(cmd); i++) {
    Serial.write(cmd[i]);
  }

コマンドの受信待機

// データ受信まで待機(3秒間隔)

  unsigned long time = millis();

  while(time + 3000 > millis() ) {
    while(Serial.available() > 0) {
      Serial.println("Get Data");
    }
  }
  Serial.println("Loop Out");

受信コマンドの分岐

// 受信コマンドで出力を変える

  while(Serial.available() > 0) {

    Serial.println("Get Data");
  }