こちらの続編です.Arduinoを使ったリアルタイム制御,とくに母艦PCとのシリアルデータ通信をしながら制御するときの注意点についてまとめています.
シリアル通信のAPI
シリアル通信のAPIをArduino側,PC側(ここではProcessing)のそれぞれについて整理しておきます.この記事ではArduino→PCの方向の通信についてのみ示します.
Arduino側
Arduino IDEのSerialライブラリを使います.- Serial.print(val, format)
val をASCII文字列としてシリアルポートに出力します. valは任意のデータ型でOKです.すなわちchar, byte, int, float, char[] (文字列)などが扱えますが,すべてASCII文字列に変換して出力されます.valが数値の場合はformatによって変換書式を指定します(省略可).Serial.print(123)のようにすると"123"という3バイトの文字列が送信されるということです. - Serial.write(val)
valをそのままバイトデータとしてシリアルポートに出力します.val は byte型です.char, int などbyte型に暗黙キャストできるデータ型もOKですが,正負の扱いなどでハマるので明示的にbyteにキャストしておいたほうがいいです.複数バイトをまとめて送るには Serial.write(byte[] buf, int buf_len) のようにバイト配列を与えます.
Processing側
ProcessingのSerialライブラリを使います.データ読み込みのための関数が複数あってややこしいですが,ここでは以下の二つに絞ります.ポイントは「データをどんな型として読み込むのか」「データをどこまで読み込むのか(終端をどう判断するか)」です.- String Serial.readString()
シリアルポートから文字列データを読み込み,結果を文字列として返します.データの終端は文字列のデリミタ(\0)がデフォルトですが,readStringUntil()を使うと終端文字を指定できます. - int Serial.readBytes(byte[] buffer)
シリアルポートからバイトデータを読み込み,読み込んだバイト数(int)を返します.データはbufferのサイズいっぱいまで読み込まれますが,readByteUntil()を使うと終端バイトを指定できます.
文字列通信は非効率
入門記事やサンプルコードなどを参考にすると,一番楽な Serial.print/Arduino とSerial.readString/Processing の組を使ってしまいがちです.任意のデータ型をそのまま送れて,また結果をシリアルモニタなどで視認できるので使いやすいことは確かですが,データをいったん文字列の形にして送るので効率はよくありません.たとえば 123 という値を送るにはArduino側:などとしますが,このとき {'1','2','3'} = {0x31, 0x32, 0x38}という3バイトが通信されます.一方,Serial.write/ArduinoとSerial.readBytes/Processingの組ならば
Serial.print(123);Processing側:
String inBuffer = myPort.readString(); int data = int(inBuffer);
Arduino側:のようにしますが,これだと 123 = 0x7B という1バイトの通信ですみます.
Serial.write(byte(123));
Processing側:
byte[] inBuffer = myPort.readBytes();
byte data = inBuffer[0];
long intのように桁の多い整数(時刻など)やfloatなどをやりとりするとこの差はさらに大きくなります.例えば long int は4バイトですが,10進整数に直すと-2147483648〜2147483647ですから文字列では最大で11バイトも食います.
通信速度を57600bpsとすると,57.6bit/msec ≒7byte/msecですから,オーバーヘッドを無視しても1msecでたかだか数バイトしか送れないことになります.前の記事で書いたようにArduinoは1〜2msec くらいの周期でリアルタイム制御ができるのですが,シリアル通信で何msecも消費してしまっては台無しです.リアルタイム性を維持するためには
- 文字列通信ではなくバイト通信を使う
- 送信周期を間引く(数ループに1回だけデータを送信する)
(続きの記事はこちらです)