エンジニア風味 (Engineer-taste)

電子系エンジニアのメモ帳

ATMEGA328P系Arduino とMATLABをシリアル通信するコツ

MATLABArduino 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ケ返されます。

f:id:engineer-paju:20200321154307p:plain
Arduino IDE シリアル

これを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のワーニングが出ます。

f:id:engineer-paju:20200321154440p:plain
エラー画面
同じ条件で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('出力値');

実行します。

f:id:engineer-paju:20200321154529p:plain
終了画面

動くようになりました。何なんでしょうか。
次に、この記事が見つかりました。
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を介して接続があります(赤部分)。

f:id:engineer-paju:20200321154713p:plain
リセット回路
このラインはシリアルポートにおいてDTR(データレディ)であり、ポートを開くとHighからLowに変化します。
コンデンサC5を含む接続部分のみをわかりやすく書くと下記のようになります。
f:id:engineer-paju:20200321154756p:plain
リセット回路2
コンデンサ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('出力値');


以上です。