Arduino -> MATLABのBluetooth送受信の仕組み

#前置き
Arduinoは、センサーと組み合わせることで簡単になんちゃってIoTができてしまう、非常に素晴らしいデバイスです。MATLABは、ツールボックスが非常に豊富で、インタラクティブなシステムもすぐに書けてしまうので、欲しい機能をすぐに実装したりするのに非常に優しい言語です。
よって、センシングをArduinoに行わせて、Wirelessに送信したデータをMATLABで処理するというのは、比較的ニーズのある方法だと思います。
Wirelessでデータを送る主要な方法は、Bluetoothを使う方法と、Wifiを使う方法がありますが、今回は前者の方法を選択しました。
PCにダイレクトに繋いだArduinoからデータを読み取る方法（例えば[この動画](https://jp.mathworks.com/videos/plotting-live-data-of-a-temperature-sensor-using-arduino-and-matlab-121317.html)）や、MATLAB側からArduinoに向かってデータを送信する方法（例えば[このサイト](https://circuitdigest.com/microcontroller-projects/how-to-use-bluetooth-with-matlab-for-wireless-communication##targetText=Here%2C%20the%20MATLAB%20uses%20the,anything%20connected%20to%20the%20Arduino.)）は検索するとたくさん出てくるのですが、**Arduino→MATLABをBluetooth経由で行う**方法がなかなか分からなかったので、その方法を書いておきます。
一応公式[ドキュメント](https://jp.mathworks.com/help/instrument/reading-and-writing-data-over-the-bluetooth-interface.html)はあるのですが、どのようにデータが行き来しているのかはよく分からなかったので、その中身の仕組みについて書き残しておきます。

#やりたいこと
Arduinoからデータ(具体的には数値)を送信し、MATLABでそれを受信する。
同時に複数のデータを送受信できたら成功とする。

#環境
- [Arduino UNO R3](https://www.amazon.com/Arduino-Uno-R3-Microcontroller-A000066/dp/B008GRTSV6/ref=sr_1_4?ie=UTF8&qid=1532009862&sr=8-4&keywords=%E2%80%A2%09Arduino+Uno++usb&dpID=61GN8biQEHL&preST=_SX300_QL70_&dpSrc=srch)
- [Bluetooth Module HC-05](https://www.amazon.com/gp/product/B01G9KSAF6/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1) ←MATLABがサポートしているのは"HC-05"と"HC-06"だけです。([参照](https://jp.mathworks.com/help/supportpkg/arduinoio/ug/arduino-bluetooth-setup-issues.html))
- MATLAB R2019a ([Instrument Control Toolbox](https://jp.mathworks.com/products/instrument.html)をインストールする必要あり)

ArduinoとBluetooth Moduleは[このサイト](https://circuitdigest.com/microcontroller-projects/how-to-use-bluetooth-with-matlab-for-wireless-communication)にあるように、次の画像のように接続します。図にあるように、HC-05のTXDはArduinoのRXDに、TXDはRXDに、とクロスして繋がなければいけないので注意しましょう。(LEDはあってもなくてもいいですが、LEDを繋ぐときは多分抵抗を挟まないといけないと思います。)
<img width="700" alt="fig1.png" src="https://circuitdigest.com/sites/default/files/circuitdiagram_mic/Circuit-Diagram-for-Sending-data-from-MATLAB-to-Arduino-via-Bluetooth.png">

#Arduino側のコード
以下のコードをArduinoに書き込みます。今回は簡単に、"0"と"1"という値を送信するだけにします。
上で、「同時に」と言いましたが、ほぼ時間ディレイはないので、このようなシリアル通信で良いこととします。(おそらくArduino UNOはパラレル通信ができません。)

```c++:sending_data.ino
#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'はデバイスによって微妙に異なっていたりするので、正しく打ち込みましょう。一行目はそれを確認するためのコマンドです。)

```matlab:MATLABコマンドライン

>> instrhwinfo('Bluetooth','DSD TECH HC-05');
>> bt = Bluetooth('DSD TECH HC-05', 1);
>> fopen(bt);
```
この状態でbtの中身を覗いてみると、各種プロパティはこのようになっています。最後に現れる"Read/Write State"というプロパティが、データのやりとりに直結しているもののようです。

```matlab:MATLABコマンドライン

>> bt

   Bluetooth Object : Bluetooth-DSD TECH HC-05:1
   (中略)
   Read/Write State  
          TransferStatus:     idle
          BytesAvailable:     0
          ValuesReceived:     0
          ValuesSent:         0
>> 
```
ちなみに、

```matlab:MATLABコマンドライン

>> fclose(bt)
>> fopen(bt)
```
とすると、データはリセットされてこの初期状態に戻ります。

ここで以下のように、MATLABからArduinoに向かって何らかの値を書き込み"Serial.println(0);"を行わせ[^1]、再びbtの中身を覗いてみると、

```matlab:MATLABコマンドライン

>> 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()](https://www.mathworks.com/help/instrument/fscanf.html)を使って以下のようにしてデータを読み込んでみると[^2]、値が以下のように変わっていることが観察できます。


```matlab:MATLABコマンドライン

>> 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からはなくなります。キューのようなデータ構造だと考えれば分かりやすいかもしれません。(実験したところスタックではないようです。)
<img width="700" alt="スクリーンショット 2019-11-17 18.45.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/e3090186-dece-6de4-1a3b-274e67885dcb.png">
ちなみに、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のシリアルモニタを見てみると、以下のようになっていました。
<img width="700" alt="スクリーンショット 2019-11-17 18.53.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/a0003077-02ea-db7b-a989-69545a55d0e8.png">
つまり、余計な"10"や"11"という数も書き込まれてしまうということです。これで、(3+3+4+4)=14Bytesとなっていたのです。(この原因については色々試してみたのですが、よく分かりませんでした。)
幸いにも"0"と"1"が最初に書き込まれるので、最初に読み出されるのはそれらなのですが、余分な値が書き込まれているとデータも膨らんでしまって嬉しくありません。
さらに厄介なのは、以下のように何度もデータを読み書きしたいときです。リアルタイムで二つのセンサーの値を読み込みたいと思ったら、以下のようにしますよね。

```matlab:read_data.m

>> 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"なので、２回目以降のループでは、実際に読み出したい値以外を読み出してしまうことになります。これでは正確なセンシングができません。(下図は、２回目のループのfprintf(bt,0)の直後)
<img width="750" alt="スクリーンショット 2019-11-17 18.59.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/ac24b9e8-769d-9d22-c06b-b36e4d7284b1.png">

一つの解決策は、以下のように毎回毎回btを開け閉めする方法です。上で見たように、こうすることでデータ状態は元に戻ります。

```matlab:read_data.m

>> 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()](https://www.mathworks.com/help/instrument/flushinput.html)という関数がありました！ さすがはMATLABですね。

```matlab:read_data.m

>> 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側から指令を出した方が、データも膨らまず、よりロバストな設計になるということです。

```c++:arduino側のコード

void loop(){
  if(bluetooth.available() > 0){    
    Serial.println(0);
  }

/*
//これは微妙
void loop(){   
  Serial.println(0); 
  pause(1000);
  }
*/
```

##補足
1. **Serial.println()とbluetooth.println()の違い**  
多くのウェブサイトでは、bluetooth.println()というメソッドを使ってデータを書き込むように書かれていました。しかし、いくらやってもうまくいかず、試しにSerial.println()を使ったところ、データを書き込むことができましたが詳しいところは分かりません。逆に、Serial.println()でうまくいかないときは、bluetooth.println()を使ってみるとうまくいくかもしれません。

2. **Serial.println()とSerial.print()の違い**  
シリアルモニタ上では以下の二つは同じように出力されます。また、データ数も変わりません。

```c++
Serial.println(0);   //次のデータは次の行に書かれる
Serial.println(1);   //次のデータは次の行に書かれる     
``` 
```c++
Serial.print(0);       
Serial.print("\n");  //改行を表す
Serial.print(1);       
Serial.print("\n");  //改行を表す
``` 
ただ、それを読み込もうとすると、前者はちゃんと"0","1"と区切って読み込まれるのに対し、後者は"01"としていっぺんに読み込まれてしまいます。
おそらく、Serial.println();で出力されたデータがひとまとまりとして認識されるのだと思います。

[^1]: 上のArduinoのコードを見てもらうと分かると思いますが、"fprintf(bt,0)"を行うことでArduino側の"bluetooth.available()"に値が入り、"Serial.println(0);"が読み出されるというカラクリです。
[^2]: fscanf()を使うためには[Instrument Control Toolbox](https://jp.mathworks.com/products/instrument.html)をインストールしなければいけません。
[^3]: 正確には、書き込まれたのは「'0'」という文字列であるため、一つの数字あたり3Bytesも食べています。fscanf(bt,'%d')のようにフォーマット指定子をつけず単にfscanf(bt)とすると、文字として読み取られます。