第九項 PCとの連携・シリアル通信の応用
電子工作創作表現(2019/6/21)
スライドPDF
先週の「シリアル通信」前回まで
- PCから数字を1つ送る
- Arduinoから来る数字を受信する
前回は、ArduinoのSerial.write関数を使って数字を送受信するというプログラムを解説しました。今回はその続きで、もう少し込み入った情報をやりとりする方法について解説していきます。
シンプルに数字を送るだけ
- 複数のデータはどう区別する?
- 255より大きい値を送るには?
シリアル通信は1バイトずつ数字を送ることができるので、0~255までの数字を送る事ができます。しかしそうなると、複数の値(アナログ入力の0番と1番や、傾きセンサのXYZ)を送ってもいつどのデータが来ているのか分かりませんし、値も255までしか送れないというのはかなり幅が狭いです。
基本的な文字をやり取りする「アスキーコード」
- 簡単な文字を128種類におさめた「ASCII CODE」
- 英数字記号などの簡単な「文字」が送れる
そこでもう少し複雑なやりとりをしようとすると、数字を文字として置き換えた「アスキーコード」の出番がやってきます。英数字記号とデータ用の特殊な文字で構成されていて、128種類に収められているので全てを1バイトの文字として使うことができます。「アスキーコード 一覧」などで検索すると、一覧が出てきます。
1バイトを1文字として扱う
- 例:111:110:107:97:110 で「onkan」
- 数字も文字として登録されていて「0」は48から始まる
- 文字としてのデータが「アスキー」数字としてのデータが「バイナリ」
1バイトの数字がそのまま文字として扱われるので、例えば上記数字の並びをアスキーコード表に照らし合わせてみると「onkan」という文字になります。そして数字も文字として登録されていて、文字の「0」が48でそこから1ずつ増える形で対応しています。
こうなってくると少しややこしくて、数字のデータを送る時に文字として送るのか数字として送るのか、ルールを決める時に混乱が生じてしまいがちです。そこで文字としてのデータのことを「アスキー」、数字としてのそのままのデータを「バイナリ」と呼ぶのが慣例になっています。
アスキーデータのやり取り
- 送るデータの自由度は上がる
- ただしプログラムは煩雑になる
アスキーデータでやり取りをすると、情報の自由度が上がる一方でプログラムは煩雑になります。
しかしパソコンとの連携をする上でとても重要な方式なので、じっくり説明していこうと思います。
シリアルモニタはアスキーベース
- ArduinoIDEの「シリアルモニタ」はアスキーベース
- 受信したデータを出力して、送信もできる
アスキーベースでのシリアル通信を簡単に試す時には、ArduinoIDEのシリアルモニタが便利です。Arduinoから送られてきたデータをそのままウィンドウに表示(ダンプ)してくれるのと、テキストを入力すればそれをそのままArduinoに送信してくれるので、作りはじめにシリアル通信を試したい時や、何かバグが発生した時の検証などに便利なので活用しましょう。
Arduinoでデータを受信する
- 文字を入れる変数「String」を使う
- 「<on 2>」「<off 2>」というコマンドでLEDをスイッチする例
Arduinoで受信したシリアル信号を文字として扱うためには「String」という変数の型を使って、受け取った文字データをストックしていきます。
どんな文字で指示するかというのは、実現したい事に応じて考える必要があります。ここではデジタルピンのLEDをオンオフするために「on ピン番号」というコマンドを不等号で囲う形にします。
「>」を一つの命令の終わりと認識することで、複数バイトのデータを場合分けすることができるようになります。Stringには文字を抜き出したり、アスキーの数字をバイナリの数字に変換するなど色々な機能が搭載されているので、都度必要な機能を参照しながら使うのが良いでしょう。
https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/
//====================以下Arduinoスケッチ===========================
// Arduinoのシリアルモニタから、デジタルピンを制御する
// でHIGH、 でLOWにする
String recv; //文字列を保持する変数String
void setup()
{
Serial.begin(9600);
//2~13までをすべて出力にする
for (int i = 2;i <= 13;i++)
{
pinMode(i, OUTPUT);
}
}
void loop()
{
while (Serial.available())
{
//charは1文字だけを保持する変数
char data = Serial.read();
//読めるデータは1バイトずつなので、recvに追加していく
recv += data;
//データの最後に「>」を入れるルール
if (data == '>')
{
//先頭3文字が「<on」かチェック
if (recv.substring(0, 3) == "<on")
{
//5文字目以降の文字を抜き出してtrimedに入れる
String trimed = recv.substring(4);
//trimedで抜き出した文字としての数字をintに変換
int pin = trimed.toInt();
//指示されたピン番号をHIGHにする
digitalWrite(pin, HIGH);
}
//先頭4文字が「/off」かチェック
if (recv.substring(0, 4) == "<off")
{
//6文字目以降の文字を抜き出してtrimedに入れる
String trimed = recv.substring(5);
//trimedで抜き出した文字としての数字をintに変換
int pin = trimed.toInt();
//指示されたピン番号をLOWにする
digitalWrite(pin, LOW);
}
//recvの中身を読み終えたので、リセットする
recv = "";
}
}
}
Arduinoでデータを送信する
- Serial.print() もしくは Serial.println()
- 数字は自動的に変換してくれる
今度は逆に、Arduinoから文字データを送信するプログラムを書いてみます。Arduinoから1バイトのデータを送る時はSerial.write()という命令を使っていましたが、文字を送る際にはprint()もしくはprintln()という関数を使います。
3つのアナログ入力した値を出力しますが、この辺りは加速度センサを使った時にも使ったコードに近いですね。シリアル周りを重点的に解説していきます。
//====================以下Arduinoスケッチ===========================
void setup()
{
Serial.begin(9600);
}
void loop()
{
Serial.print(analogRead(0));//アナログで読み取った値をシリアルでプリント
Serial.print("\t");//3つの値をタブ文字で分割する
Serial.print(analogRead(1));
Serial.print("\t");
Serial.print(analogRead(2));
Serial.print("\n");
//最後に改行文字'\n'を入れる
//最後はSerial.println(analogRead(2));としてもOK。
//printlnとすると最後の\nは勝手に追加してくれます
}
表記できない文字の表記
- 「\(バックスラッシュ)」の後にアルファベットで、タブや改行などを表現
- 「エスケープ文字」と言って特殊な文字を表記する時に使う
Serial.print("message")という感じで、文字を送る場合にはダブルクォーテーションで囲みます。ここではシリアルプロッタのフォーマットにならって3つの値をタブ文字で区切りました。0~1023までの値がプリントされています。
タブや改行のようにコード上では書けない記号を表記する場合、バックスラッシュを後ろに付けてエスケープ文字という形で表記する場合があります。
max/mspで受け取る
maxではbangを送ると受信したデータが流し込まれるので、prependオブジェクトでスタックした数字のデータを、改行のタイミング('\n'がバイナリの10)でitoaオブジェクトに流し込むことで文字のデータとして拾い上げています。
touchdesignerで受け取る
touchDesignerでは前回同様SerialDATを使い、Row/Callback Formatをone per lineに設定すると改行ごとにデータを区切ってくれます。
これを更に別々の数値として扱うにはsplitメソッドを使い、配列として分割した後にConstant ChOPなど使いやすいオブジェクトに入れるなどをします。
Processingで受け取る
- Arduinoに近い書き方
Processingで受信する処理は、Arduinoがシリアルを受信する処理とかなり近く、Stringを連結していって改行のタイミングでブロックを処理してあげるという処理を施します。
import processing.serial.*;
int data[] = new int[3];
String recv;
Serial serial;
void setup()
{
size(1023, 300);
serial = new Serial(this, "COM3", 9600);
}
void draw()
{
background(0);
fill(255);
rect(0, 0, data[0], 50);
rect(0, 100, data[1], 50);
rect(0, 200, data[2], 50);
}
void serialEvent(Serial s)
{
char dat = char(serial.read());
if (dat != '\n') recv += dat;
if (dat == '\n')
{
String[] list = split(recv, '\t');
data[0] = int(list[0]);
data[1] = int(list[1]);
data[2] = int(list[2]);
recv = "";
}
}