LoginSignup
7
6

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-18

前置き

Arduinoは、センサーと組み合わせることで簡単になんちゃってIoTができてしまう、非常に素晴らしいデバイスです。MATLABは、ツールボックスが非常に豊富で、インタラクティブなシステムもすぐに書けてしまうので、欲しい機能をすぐに実装したりするのに非常に優しい言語です。
よって、センシングをArduinoに行わせて、Wirelessに送信したデータをMATLABで処理するというのは、比較的ニーズのある方法だと思います。
Wirelessでデータを送る主要な方法は、Bluetoothを使う方法と、Wifiを使う方法がありますが、今回は前者の方法を選択しました。
PCにダイレクトに繋いだArduinoからデータを読み取る方法(例えばこの動画)や、MATLAB側からArduinoに向かってデータを送信する方法(例えばこのサイト)は検索するとたくさん出てくるのですが、Arduino→MATLABをBluetooth経由で行う方法がなかなか分からなかったので、その方法を書いておきます。
一応公式ドキュメントはあるのですが、どのようにデータが行き来しているのかはよく分からなかったので、その中身の仕組みについて書き残しておきます。

やりたいこと

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

環境

ArduinoとBluetooth Moduleはこのサイトにあるように、次の画像のように接続します。図にあるように、HC-05のTXDはArduinoのRXDに、TXDはRXDに、とクロスして繋がなければいけないので注意しましょう。(LEDはあってもなくてもいいですが、LEDを繋ぐときは多分抵抗を挟まないといけないと思います。)
fig1.png

Arduino側のコード

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

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コマンドライン

>> instrhwinfo('Bluetooth','DSD TECH HC-05');
>> bt = Bluetooth('DSD TECH HC-05', 1);
>> fopen(bt);

この状態でbtの中身を覗いてみると、各種プロパティはこのようになっています。最後に現れる"Read/Write State"というプロパティが、データのやりとりに直結しているもののようです。

MATLABコマンドライン

>> bt

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

ちなみに、

MATLABコマンドライン

>> fclose(bt)
>> fopen(bt)

とすると、データはリセットされてこの初期状態に戻ります。

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

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()を使って以下のようにしてデータを読み込んでみると2、値が以下のように変わっていることが観察できます。

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からはなくなります。キューのようなデータ構造だと考えれば分かりやすいかもしれません。(実験したところスタックではないようです。)
スクリーンショット 2019-11-17 18.45.06.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のシリアルモニタを見てみると、以下のようになっていました。
スクリーンショット 2019-11-17 18.53.48.png
つまり、余計な"10"や"11"という数も書き込まれてしまうということです。これで、(3+3+4+4)=14Bytesとなっていたのです。(この原因については色々試してみたのですが、よく分かりませんでした。)
幸いにも"0"と"1"が最初に書き込まれるので、最初に読み出されるのはそれらなのですが、余分な値が書き込まれているとデータも膨らんでしまって嬉しくありません。
さらに厄介なのは、以下のように何度もデータを読み書きしたいときです。リアルタイムで二つのセンサーの値を読み込みたいと思ったら、以下のようにしますよね。

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"なので、2回目以降のループでは、実際に読み出したい値以外を読み出してしまうことになります。これでは正確なセンシングができません。(下図は、2回目のループのfprintf(bt,0)の直後)
スクリーンショット 2019-11-17 18.59.20.png

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

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

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()の違い

    シリアルモニタ上では以下の二つは同じように出力されます。また、データ数も変わりません。

Serial.println(0);   //次のデータは次の行に書かれる
Serial.println(1);   //次のデータは次の行に書かれる     
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をインストールしなければいけません。 

  3. 正確には、書き込まれたのは「'0'」という文字列であるため、一つの数字あたり3Bytesも食べています。fscanf(bt,'%d')のようにフォーマット指定子をつけず単にfscanf(bt)とすると、文字として読み取られます。 

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6