MATLABとArduino LeonardのようなATMEGA32U系のArduinoとシリアル通信してデータのやり取りをする場合は問題ないのに、Arduino UnoとかArduino NanoのようなATMEGA328系のArduinoとシリアル通信すると、なぜかTimeOutするという問題が出るので調査してみました。
Arduino IDEでのプログラムは下記のように書いたとします。
char serial_recv; void setup() { Serial.begin(9600) ; } void loop() { while(Serial.available() > 0){ serial_recv = Serial.read(); // シリアル通信を受信 switch(serial_recv){ case '1': int adout; for (int i=0; i<10; i++){ Serial.println(i) ; // シリアル通信を送信 } } } }
forループで変数 i をインクリメントして、PCに送るプログラムです。
書き込みして、IDEのシリアルモニタを開き、「1」を送信すると、データが10ケ返されます。
これをMATLABに取り込むプログラムを下記のように書きます。ここではアドオンのMATLAB Support Package for Arduino Hardwareではなく、古くからあるserialコマンドでシリアルポートに接続されたデバイスを制御する方法で書いています。
s=serial('COM6'); set(s,'BaudRate',9600); s.Timeout = 1; fopen(s); out=zeros(1,10); fprintf(s,'1'); u=1; while u<11 outdum=fscanf(s); if isempty(outdum) == 0 out(u)=str2num(outdum); u=u+1; end end fclose(s); delete(s); clear s; figure(1); plot(out); xlabel('データNo.'); ylabel('出力値');
プログラムを実行しますと、ポートを開いたところで、止まってしまい、Timeoutのワーニングが出ます。同じ条件でArduino Leonardで動かしますと、何の問題もなく動作します。
この問題について、誰かが言及していないかググってみました。
対策:その1
まず、MATLAB Answerにあった、この記事です。
www.mathworks.com
受け入れられた回答(Accepted Answer :自己解決したようです)を見ますと、
fprintf(s,'0'); を送る前に、2秒ほどウェイト(pause(2))をすると、timeoutが起こらなくなったと述べています。
プログラムを修正します。
s=serial('COM6'); set(s,'BaudRate',9600); s.Timeout = 1; fopen(s); out=zeros(1,10); pause(2);% <----------------追加 fprintf(s,'1'); u=1; while u<11 outdum=fscanf(s); if isempty(outdum) == 0 out(u)=str2num(outdum); u=u+1; end end fclose(s); delete(s); clear s; figure(1); plot(out); xlabel('データNo.'); ylabel('出力値');
実行します。
動くようになりました。何なんでしょうか。
次に、この記事が見つかりました。
arduino.stackexchange.com
「Arduino UnoとArduino Leonardのシリアルポートは異なる方法で初期化されますか?」で、「異なる」という回答がついています。
UnoにはUSB-UARTブリッジとしてATMega16U2があります。シリアルポートを開くと、ATMega16U2はATMega328pに信号を送り、スケッチをリセットして最初から開始します。
Leonardoには、USBポートに直接接続されているメインMCUとしてATMega32U4があります。シリアルポートを開くと、その時点でプログラムに接続します。
重要なのは、Unoのポートを開くと、ボードがリセットし、スケッチがリセットすることです。Leonardoのポートを開くと、ボードはリセットされず、スケッチはどこにでもあります(常に動く状態にある)。
Arduino Unoの回路図https://www.arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf
を見てみます。
下の図が関係ある部分で、USB接続のためのATMEGA16U2からメインMPUのATMEGA328PのリセットピンにコンデンサC5を介して接続があります(赤部分)。このラインはシリアルポートにおいてDTR(データレディ)であり、ポートを開くとHighからLowに変化します。
コンデンサC5を含む接続部分のみをわかりやすく書くと下記のようになります。コンデンサC5のATMEGA328P側は10kΩの抵抗RN1Dで+5Vにプルアップしていますので、コンデンサC5の右側(ATMEGA328Pのリセット端子)は、一度Lowになり、ある時間を経てHighに戻ります。Highに戻ってからATMEGA328Pはスケッチを再起動することになります。
この再起動に要する時間が2秒(pause(2) )となるわけです。
この再起動はMATLABからポートを開く場合に限らず発生します。例えばArduino IDEのシリアルモニタを立ち上げたときにも、再起動は発生します。
対策:その2
上記のようにポートを開いてから2秒待てば、正常に動作しますが、2秒待てない場合にはどうすればよいのでしょうか。
ATMEGA328PのResetピンがLowにならなければ、リセットはかからないわけなのです。
ATMEGA16U2とATMEGA328Pの間には基板上にパターンによる端子があります。
この2つの端子間はパターンで接続されているので、ここをカッターなどで分離すれば、リセットはかからなくなります。
実際にやってみますと、リセットがかからず、2秒待たなくとも動作することを確認しました。
ですが、この方法は問題があります。
Arduino IDEからの書き込みができなくなることです(他にもあるかもしれませんが)。
また、Arduino Nanoの場合は、リセットラインを切る場所が設けてないので、難しいかと思います。
対策:その3
リセット線を切らずに、再起動がかからないようにすればよいわけです。
MATLABのserialコマンドのプロパティ一覧を見てみます。
Control Pin Propertiesの中に、DataTerminalReady というのがあり、デフォルトではONになっています。これをOFFにします。
s=serial('COM6'); set(s,'BaudRate',9600,'DataTerminalReady','off');% <------------- s.Timeout = 1; fopen(s); %以下、略。
こうすると、DTR信号が使われなくなるので、リセット線を切らずに再起動がかからなくなります。
余談ですが、serialコマンドは今後、推奨されないそうで、serialportコマンドを使ってね、ということらしいです。
ですが、serialコマンドは、それ単体でシリアル接続を行なってしまいます。serialport コマンドにはDTR信号のためのオプションはないので、DataTerminalReadyのような使い方はできません。2秒待つしかないようです。
s = serialport('COM6',9600); pause(2); write(s,'1',"char") u=1; out=zeros(1,10); while u<11 outdum = readline(s); if isempty(outdum) == 0 out(u)=str2num(outdum); u=u+1; end end clear s; figure(1); plot(out); xlabel('データNo.'); ylabel('出力値');
以上です。