#前置き
Arduinoは、センサーと組み合わせることで簡単になんちゃってIoTができてしまう、非常に素晴らしいデバイスです。MATLABは、ツールボックスが非常に豊富で、インタラクティブなシステムもすぐに書けてしまうので、欲しい機能をすぐに実装したりするのに非常に優しい言語です。
よって、センシングをArduinoに行わせて、Wirelessに送信したデータをMATLABで処理するというのは、比較的ニーズのある方法だと思います。
Wirelessでデータを送る主要な方法は、Bluetoothを使う方法と、Wifiを使う方法がありますが、今回は前者の方法を選択しました。
PCにダイレクトに繋いだArduinoからデータを読み取る方法(例えばこの動画)や、MATLAB側からArduinoに向かってデータを送信する方法(例えばこのサイト)は検索するとたくさん出てくるのですが、Arduino→MATLABをBluetooth経由で行う方法がなかなか分からなかったので、その方法を書いておきます。
一応公式ドキュメントはあるのですが、どのようにデータが行き来しているのかはよく分からなかったので、その中身の仕組みについて書き残しておきます。
#やりたいこと
Arduinoからデータ(具体的には数値)を送信し、MATLABでそれを受信する。
同時に複数のデータを送受信できたら成功とする。
#環境
- Arduino UNO R3
- Bluetooth Module HC-05 ←MATLABがサポートしているのは"HC-05"と"HC-06"だけです。(参照)
- MATLAB R2019a (Instrument Control Toolboxをインストールする必要あり)
ArduinoとBluetooth Moduleはこのサイトにあるように、次の画像のように接続します。図にあるように、HC-05のTXDはArduinoのRXDに、TXDはRXDに、とクロスして繋がなければいけないので注意しましょう。(LEDはあってもなくてもいいですが、LEDを繋ぐときは多分抵抗を挟まないといけないと思います。)
#Arduino側のコード
以下のコードをArduinoに書き込みます。今回は簡単に、"0"と"1"という値を送信するだけにします。
上で、「同時に」と言いましたが、ほぼ時間ディレイはないので、このようなシリアル通信で良いこととします。(おそらくArduino UNOはパラレル通信ができません。)
#include <SoftwareSerial.h>
int TxD;
int RxD;
int data;
SoftwareSerial bluetooth(TxD, RxD);
void setup() {
Serial.begin(9600);
bluetooth.begin(9600);
}
void loop(){
if(bluetooth.available() > 0){
Serial.println(0);
Serial.println(1);
//bluetooth.println(0); //補足1. 参照
//Serial.print(0); //補足2. 参照
}
}
#MATLAB側のコード
まず初めに、以下のコマンドラインを打って、bluetooth objectを開きましょう。(objectの名前'DSD TECH HC-05'はデバイスによって微妙に異なっていたりするので、正しく打ち込みましょう。一行目はそれを確認するためのコマンドです。)
>> instrhwinfo('Bluetooth','DSD TECH HC-05');
>> bt = Bluetooth('DSD TECH HC-05', 1);
>> fopen(bt);
この状態でbtの中身を覗いてみると、各種プロパティはこのようになっています。最後に現れる"Read/Write State"というプロパティが、データのやりとりに直結しているもののようです。
>> bt
Bluetooth Object : Bluetooth-DSD TECH HC-05:1
(中略)
Read/Write State
TransferStatus: idle
BytesAvailable: 0
ValuesReceived: 0
ValuesSent: 0
>>
ちなみに、
>> fclose(bt)
>> fopen(bt)
とすると、データはリセットされてこの初期状態に戻ります。
ここで以下のように、MATLABからArduinoに向かって何らかの値を書き込み"Serial.println(0);"を行わせ1、再びbtの中身を覗いてみると、
>> fprintf(bt,0) %この値は何でもいいです
>> bt
Bluetooth Object : Bluetooth-DSD TECH HC-05:1
(中略)
Read/Write State
TransferStatus: idle
BytesAvailable: 14
ValuesReceived: 0
ValuesSent: 2
>>
となって、"bt.BytesAvailable"というメンバ変数が 14Bytes に変わっていることが分かります。ちなみに、"bt.ValuesSent"も 2Bytes に変わっています。
その後、fscanf()を使って以下のようにしてデータを読み込んでみると2、値が以下のように変わっていることが観察できます。
>> fscanf(bt,'%d')
>> bt
Bluetooth Object : Bluetooth-DSD TECH HC-05:1
(中略)
Read/Write State
TransferStatus: idle
BytesAvailable: 11
ValuesReceived: 3
ValuesSent: 2
>>
つまり、Arduino側でSerial.println()を呼ぶ度に、MATLABのbt.BytesAvailableという場所にデータが蓄積されているようです。それをfscanf(bt)で読み込めば、読み込んだ分のデータはbt.BytesAvailableからはなくなります。キューのようなデータ構造だと考えれば分かりやすいかもしれません。(実験したところスタックではないようです。)
ちなみに、bt.BytesAvailableにデータが全くない状態で読み取ろうとすると、以下のようにエラーを吐いたりします。
Warning: Unsuccessful read: A timeout occurred before the Terminator was reached.
'Bluetooth' unable to read any data. For more information on possible reasons, see Bluetooth Read
Warnings.
ここまでで、基本的なデータ構造、それぞれの関数がどうような操作をしているかは理解できました。
#問題点
しかし、ここで一つ大きな問題があります。
先ほど書き込まれたのは"0"と"1"だけのはずなのに、14Bytesも書き込まれています。(bt.ValuesReceivedの結果を見ても、一文字あたり3Bytesのはずです。3)
そこで、先ほどの操作をしたときのArduinoのシリアルモニタを見てみると、以下のようになっていました。
つまり、余計な"10"や"11"という数も書き込まれてしまうということです。これで、(3+3+4+4)=14Bytesとなっていたのです。(この原因については色々試してみたのですが、よく分かりませんでした。)
幸いにも"0"と"1"が最初に書き込まれるので、最初に読み出されるのはそれらなのですが、余分な値が書き込まれているとデータも膨らんでしまって嬉しくありません。
さらに厄介なのは、以下のように何度もデータを読み書きしたいときです。リアルタイムで二つのセンサーの値を読み込みたいと思ったら、以下のようにしますよね。
>> data = zeros(2,100);
>> for i=1:100
>> fprintf(bt,0);
>> data(1,i) = fscanf(bt);
>> data(2.i) = fscanf(bt);
>> end
このときのデータは以下のようになっています。
"First in, first out"なので、2回目以降のループでは、実際に読み出したい値以外を読み出してしまうことになります。これでは正確なセンシングができません。(下図は、2回目のループのfprintf(bt,0)の直後)
一つの解決策は、以下のように毎回毎回btを開け閉めする方法です。上で見たように、こうすることでデータ状態は元に戻ります。
>> data = zeros(2,100);
>> for i=1:100
>> fopen(bt);
>> fprintf(bt,0);
>> data(1,i) = fscanf(bt);
>> data(2.i) = fscanf(bt);
>> fclose(bt);
>> end
しかし、これだと処理が遅くなってしまいます。"fclose(bt);"の方はまだいいのですが、"fopen(bt);"には1秒ぐらいかかってしまうので、その時間を待たなくてはいけないのです。
そこで、データだけ消してくれる関数はないかなーと調べたところ、flushinput()という関数がありました! さすがはMATLABですね。
>> data = zeros(2,100);
>> for i=1:100
>> flushinput(bt);
>> fprintf(bt,0);
>> data(1,i) = fscanf(bt);
>> data(2.i) = fscanf(bt);
>> end
このように書くことで、ほとんど遅延もなくデータを受信することができました!!
そして、このようなデータ構造を持っているので、データの読み書きのタイミングはMATLAB側で制御した方が良さそうだという知見も得ることができました。
何が言いたいかというと、データの書き込みはpause(1000)などを使いながらArduino側でタイミング制御して、読み込みたいタイミングでMATLAB側で"flushinput()→fscanf()"をするという方法も可能ではあるのですが、どうせMATLAB側で読み込みのタイミングを決めるなら、データを読みたいタイミングで書き込ませるようにMATLAB側から指令を出した方が、データも膨らまず、よりロバストな設計になるということです。
void loop(){
if(bluetooth.available() > 0){
Serial.println(0);
}
/*
//これは微妙
void loop(){
Serial.println(0);
pause(1000);
}
*/
##補足
-
Serial.println()とbluetooth.println()の違い
多くのウェブサイトでは、bluetooth.println()というメソッドを使ってデータを書き込むように書かれていました。しかし、いくらやってもうまくいかず、試しにSerial.println()を使ったところ、データを書き込むことができましたが詳しいところは分かりません。逆に、Serial.println()でうまくいかないときは、bluetooth.println()を使ってみるとうまくいくかもしれません。 -
Serial.println()とSerial.print()の違い
シリアルモニタ上では以下の二つは同じように出力されます。また、データ数も変わりません。
Serial.println(0); //次のデータは次の行に書かれる
Serial.println(1); //次のデータは次の行に書かれる
Serial.print(0);
Serial.print("\n"); //改行を表す
Serial.print(1);
Serial.print("\n"); //改行を表す
ただ、それを読み込もうとすると、前者はちゃんと"0","1"と区切って読み込まれるのに対し、後者は"01"としていっぺんに読み込まれてしまいます。
おそらく、Serial.println();で出力されたデータがひとまとまりとして認識されるのだと思います。
-
上のArduinoのコードを見てもらうと分かると思いますが、"fprintf(bt,0)"を行うことでArduino側の"bluetooth.available()"に値が入り、"Serial.println(0);"が読み出されるというカラクリです。 ↩
-
fscanf()を使うためにはInstrument Control Toolboxをインストールしなければいけません。 ↩
-
正確には、書き込まれたのは「'0'」という文字列であるため、一つの数字あたり3Bytesも食べています。fscanf(bt,'%d')のようにフォーマット指定子をつけず単にfscanf(bt)とすると、文字として読み取られます。 ↩