ArduinoとProcessingで複数のデータを双方向でシリアル通信する方法(スケッチ(ソース))の一例を紹介したいと思います。
ArduinoIDEにあるシリアルモニターでもいいのですが、物足りないときにProcessing使ってます。いろいろとビジュアル的にモニターできて便利なのでよく使ってます。
▼モニター例▼
動画では、Arduinoで6軸センサー(MPU6050)の値を取得・加工して、加速度値やジャイロ値などの複数データをシリアル送信してます。その情報をProcessing(パソコン)側で受信して、リアルタイムで可視化してます。
今回はこの内容をそのまま説明すると結構なボリュームなので、6軸センサー値をProcessingでテキスト表示するという内容で説明したいと思います。
Arduino側スケッチ(ソース)
スケッチを抜粋して説明します。スケッチ全体は記事後半に記載してます。
まず、通信の概要としては、
➀Processing側から「定期的」に送信要求(ダミーデータを送信)する。 ➁Arduino側で送信要求を受け取ったらセンサー値を送信。 ➂Processinng側でデータ受信し処理
このような感じで行います。
通信部分のスケッチ(ソース)を抜粋して説明していきます。ここでは6軸センサーの値取得する部分は割愛します。
・ArduinoからMPU6050の使い方はこちらで紹介してます。
setup内
Serial.begin(57600);
通信速度を指定してシリアル通信を開始します。ここで設定する通信ボーレート(bps)はProcessing側と合わせておきます。シリアル通信に関するセットアップ内容はこの設定のみです。
受信データの確認
if (Serial.available() == 1) { byte inBuf[1]; Serial.readBytes(inBuf, 1); if (inBuf[0] == 's') { //ここに送信処理を記述 } else { while (Serial.available() > 0)Serial.read(); } } if (Serial.available() > 1) { while (Serial.available() > 0)Serial.read(); }
ここからはloop内に記載していきます。ループ処理の中でProcessinngからの送信要求を確認しています。
後述しますが、Processinng側では定期的に「’s’」をArduinoへ送信しています。(文字はなんでもいいです。ただの確認用です。)
Arduino側で「’s’」の受け取りを確認してます。処理内容としては
- 1byteのデータを受信していて、その値が「’s’」なら送信処理する。
⇒「’s’」でないなら受信バッファをクリアにする。 - 1byte以上のデータを受信していたら受信バッファを空にする。
- 何も受信していないときはスルー。
このような処理をしてます。今回、Processing側からは送信要求用の「’s’」のみを送付してますがここで、ほかの情報を加えれば双方で通信も可能かと思います。
送信処理部分
byte outBuf[14]; outBuf[0] = 's'; // 1 outBuf[1] = (int16_t)(gx * 100) >> 8; // 2 outBuf[2] = (int16_t)(gx * 100) & 0xFF; // 3 outBuf[3] = (int16_t)(gy * 100) >> 8; // 4 outBuf[4] = (int16_t)(gy * 100) & 0xFF; // 5 outBuf[5] = (int16_t)(gz * 100) >> 8; // 6 outBuf[6] = (int16_t)(gz * 100) & 0xFF; // 7 outBuf[7] = (int16_t)(ax * 100) >> 8; // 8 outBuf[8] = (int16_t)(ax * 100) & 0xFF; // 9 outBuf[9] = (int16_t)(ay * 100) >> 8; // 10 outBuf[10] = (int16_t)(ay * 100) & 0xFF; // 11 outBuf[11] = (int16_t)(az * 100) >> 8; // 12 outBuf[12] = (int16_t)(az * 100) & 0xFF; // 13 outBuf[13] = 'e'; // 14 Serial.write(outBuf, 14);
送信処理を行っている部分です。今回は6軸センサーの値(加速度:3軸分、ジャイロ:3軸分)の6値のデータ送信する例です。
Arduinoのシリアル通信ではSerial.printで文字列として送信する場合が多い??かと思いますが、今回は、「数値」でデータ授受したい、Serial.writeを使ってbyte単位で情報を送信します。
処理としては、センサー値を100倍してから16bitの整数情報にして、上位(8bit)、下位(8bit)をbyte型の変数(配列)に順番に格納してます▼
outBuf[1] = (int16_t)(gx * 100) >> 8; outBuf[2] = (int16_t)(gx * 100) & 0xFF;
2byte分でひとつの値を格納してます。これを加速度3軸分、ジャイロ3軸分行ってます。また送信データの最初と最後にチェック用として「’s’」と「’e’」も一緒に送信してます。100倍しているのは小数点以下の情報も送りたいためです。
ですので送信情報は6軸センサー値で12byte分、チェック用で2byte分、合計で14byteとなります。
最後に14byte分の情報をSerial.write()で纏めて送信処理してます。
Arduinoでの送信処理部分はこれだけです。
Processing側ソース
Processing側の送受信処理の抜粋です。ソース全体は記事後半に記載してます。
セットアップ
import processing.serial.*; Serial myPort; void setup() { myPort = new Serial(this, "COM8", 57600); }
最初にシリアル通信の定義をします。Aruduinoが接続されているポートと通信ボーレートを定義します。通信ボーレートはArduino側と合わせます。
送受信処理
if (millis()-serialTimer > 50) { serialTimer = millis(); //ここに受信処理と送信処理を記述 }
Processing側ではおおよそ50msごとに送受信処理をしています。それほど正確ではないですが、millis()でカウントしてだいたい50msごとの処理になるようにしてます。
送信(要求)処理
byte[] outBuf = new byte[1]; outBuf[0] = 's'; myPort.write(outBuf);
Arduinoで「’s’」(送信要求)を定期的に送付してます。ここでは1byteの情報だけですが、他の情報も送信したければここで加えておきます。
受信データの確認
if (myPort.available() == 14) { myPort.readBytes(inBuf); if (inBuf[0]=='s'&&inBuf[13]=='e') { //ここに受信処理を記述 } else { while (myPort.available()>0)myPort.read(); println("missMatch"); } } else if (myPort.available() > 14) { while (myPort.available()>0)myPort.read(); println("overflowe"); }
処理としては
- 14byte分のデータが届いているか確認。受信データの最初と最後が「’s’」「’e’」であれば受信処理。
⇒「’s’」「’e’」でなければ受信バッファをクリア。 - 14byte以上のデータが届いていたら受信バッファをクリア。
- 14byte分のデータが届いてなければスルー
といった流れです。
受信処理
gx = (inBuf[1]<<8)+(inBuf[2]&0xff); gy = (inBuf[3]<<8)+(inBuf[4]&0xff); gz = (inBuf[5]<<8)+(inBuf[6]&0xff); ax = (inBuf[7]<<8)+(inBuf[8]&0xff); ay = (inBuf[9]<<8)+(inBuf[10]&0xff); az = (inBuf[11]<<8)+(inBuf[12]&0xff); gx/=100.0; gy/=100.0; gz/=100.0; ax/=100.0; ay/=100.0; az/=100.0;
Arduino側の送信処理とは逆で、受信した情報を、上位bit、下位bitで順に結合していきます。それで結合した値を100で除します。Processing側の送受信部はこれだけです。
結構まわりくどいやり方で紹介しましたが、有線(USBシリアル)であればここまで記述しなくても十分通信できるかと・・。以前、無線(シリアル通信の無線モジュール)で同様のことをやろとしたときに、結構データの取りこぼしがあったので、送受信のbyteカウントして、今回のようにきっちり処理してみました。ここまですれば無線でもしっかり通信できました。
ソース全体
Arduino側
#include <Wire.h> int16_t axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw, temperature; float ax, ay, az, gx, gy, gz; void setup() { Serial.begin(57600); Wire.begin(); TWBR = 12; Wire.beginTransmission(0x68); Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(0x68); Wire.write(0x1C); Wire.write(0x10); Wire.endTransmission(); Wire.beginTransmission(0x68); Wire.write(0x1B); Wire.write(0x08); Wire.endTransmission(); Wire.beginTransmission(0x68); Wire.write(0x1A); Wire.write(0x05); Wire.endTransmission(); } void loop() { Wire.beginTransmission(0x68); Wire.write(0x3B); Wire.endTransmission(); Wire.requestFrom(0x68, 14); while (Wire.available() < 14); axRaw = Wire.read() << 8 | Wire.read(); ayRaw = Wire.read() << 8 | Wire.read(); azRaw = Wire.read() << 8 | Wire.read(); temperature = Wire.read() << 8 | Wire.read(); gxRaw = Wire.read() << 8 | Wire.read(); gyRaw = Wire.read() << 8 | Wire.read(); gzRaw = Wire.read() << 8 | Wire.read(); ax = axRaw / 4096.0; ay = ayRaw / 4096.0; az = azRaw / 4096.0; gx = gxRaw / 65.5; gy = gyRaw / 65.5; gz = gzRaw / 65.5; if (Serial.available() == 1) { byte inBuf[1]; Serial.readBytes(inBuf, 1); if (inBuf[0] == 's') { byte outBuf[14]; outBuf[0] = 's'; // 1 outBuf[1] = (int16_t)(gx * 100) >> 8; // 2 outBuf[2] = (int16_t)(gx * 100) & 0xFF; // 3 outBuf[3] = (int16_t)(gy * 100) >> 8; // 4 outBuf[4] = (int16_t)(gy * 100) & 0xFF; // 5 outBuf[5] = (int16_t)(gz * 100) >> 8; // 6 outBuf[6] = (int16_t)(gz * 100) & 0xFF; // 7 outBuf[7] = (int16_t)(ax * 100) >> 8; // 8 outBuf[8] = (int16_t)(ax * 100) & 0xFF; // 9 outBuf[9] = (int16_t)(ay * 100) >> 8; // 10 outBuf[10] = (int16_t)(ay * 100) & 0xFF; // 11 outBuf[11] = (int16_t)(az * 100) >> 8; // 12 outBuf[12] = (int16_t)(az * 100) & 0xFF; // 13 outBuf[13] = 'e'; // 14 Serial.write(outBuf, 14); } else { while (Serial.available() > 0)Serial.read(); } } if (Serial.available() > 1) { while (Serial.available() > 0)Serial.read(); } }
Processing側
import processing.serial.*; float gx, gy, gz, ax, ay, az; int serialTimer; Serial myPort; void setup() { size(500, 500); myPort = new Serial(this, "COM8", 57600); } void draw() { if (millis()-serialTimer > 50) { serialTimer = millis(); byte[] inBuf = new byte[14]; println(myPort.available()); if (myPort.available() == 14) { myPort.readBytes(inBuf); if (inBuf[0]=='s'&&inBuf[13]=='e') { gx = (inBuf[1]<<8)+(inBuf[2]&0xff); gy = (inBuf[3]<<8)+(inBuf[4]&0xff); gz = (inBuf[5]<<8)+(inBuf[6]&0xff); ax = (inBuf[7]<<8)+(inBuf[8]&0xff); ay = (inBuf[9]<<8)+(inBuf[10]&0xff); az = (inBuf[11]<<8)+(inBuf[12]&0xff); gx/=100.0; gy/=100.0; gz/=100.0; ax/=100.0; ay/=100.0; az/=100.0; } else { while (myPort.available()>0)myPort.read(); println("missMatch"); } } else if (myPort.available() > 14) { while (myPort.available()>0)myPort.read(); println("overflowe"); } byte[] outBuf = new byte[1]; outBuf[0] = 's'; myPort.write(outBuf); } background(0); fill(240); textSize(24); text(nf(gx, 0, 2), 50, 50); text(nf(gy, 0, 2), 50, 80); text(nf(gz, 0, 2), 50, 110); text(nf(ax, 0, 2), 50, 140); text(nf(ay, 0, 2), 50, 170); text(nf(az, 0, 2), 50, 200); }
コメント
はじめまして
いつも当サイトの記事を非常に参考にさせて頂いてます.
現在MPU6050に関して勉強している者です.
角度算出に伴い,Madgwickフィルターを用いています.
算出した角度に関してもprocessing側に送信して最終的には,角度,角速度,加速度をテキストファイルに記録したいと考えています.
ArduinoのソースコードにおいてSerial.print()を用いて相対的な角度変化を確認できたのですが…
processing側に送信すると90度センサを傾けるまでに,相対的な角度変化過程で大きな値(1回転してしまう)となってしまい困っています.
シリアルモニターと同様な値を送信することは不可能なのでしょうか?
RHさん、サイト拝見下さり有難うございます。
さてご質問の件、Serial.print()でも情報送信は可能です。ただ、発生している現象の具体的解決方法はわかりませんが、Serial.print()の場合、文字列情報としてデータを送信するため今回の角度情報のようなものはその時々で送信データ量が変化してしまいます。そのため、受信側(Processing側)で受信情報の処理に少し工夫がいるかと思います。私の場合は、その工夫が面倒だったので本ページで紹介しているようなbyte情報でデータのやり取りしてます。
返信して頂きありがとうございます.
やはり解決方法はないのですかね…もう少し自分なりに工夫してみようかと思います.
シリアルモニターでは角度変化は正常でしたので,最悪そちらのデータをテキストファイルに写して記録したいと思います.