ArduinoからのデータをRaspberry Piで受け取る by Simulink

## この記事について
リアルタイムに定サンプルレートでデータを取得したい場合は、OSが稼働するRaspberry Piより、ArduinoなどのOSのないデバイスの方が向いています。アルゴリズムはSimulinkで開発してRaspberry Piにデプロイしますが、データの採取はArduino側に任せ、USBでRaspberry Piに転送する構成を考えます。
USBのデータをSimulinkで受け取るには、いくつかの方法があります。

## USBポートからSimulinkにデータ受け取る(直接)
SimulinkのRaspberry Pi向けサポートパッケージには"Serial Read"ブロックがあり、USBポートの内容をそのまま読み取ることができます。

![Serial_Read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/3a74e351-cba5-0828-d13b-1a609cb3e6ca.png)

https://jp.mathworks.com/help/supportpkg/raspberrypi/ref/serialread.html


以下のようないくつかの条件がありますが、入力データが低レートで、Simulinkのサンプルレートごとに1つのデータを受け取ればいいような場合は、最も簡単で確実です。

1. 固定長のデータであること
2. データーにヘッダなどの不要なデータが含まれる場合は除外
3. Simulinkのサンプルレート(=読込タイミング)は入力データと同期させること

1と2は、Arduino側のプログラムでデータを加工することもできますね。

## USBポートからSimulinkにデータ受け取る(間接)
一方、100個のデータをフレームとして使用するような場合、上記のブロックを使えなくはないのですが、あまりシンプルになりません。フレームの頭出しや、そのタイミングでアルゴリズムの処理を開始する必要があるためです。"Rate Transition"ブロックで異なるタイミングの管理は可能ですが、レートが早くなると前節3.の同期が厳密に行えず、どうしてもデータの取りこぼしやフレームずれが発生します。
どうせ1と2のためにSimulinkの外でデータを加工するなら、フレームに纏めるところも外で処理したいところです。そこでデータを蓄積したりヘッダを除外する処理はLinuxのCLIコマンドで実施して、ダンプしたページデータを一括してSimulinkに転送することを考えます。Simulinkへの取り込みにはNNG(プロセス間通信)を利用します。
![USB_NNG_Simulink.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/651f0b97-1d13-c8a2-242e-80c7c9025e21.png)

Simulinkとプロセス間通信については以下をご覧ください。

https://qiita.com/kureta90/items/22ed24ba8b00064b0723

本記事では以降、この方法について見ていきます。

## Linuxでのデータ読み込み、ダンプ、NNG送信
Raspberry PiでのUSBポートへのアクセスは、Simulinkの外で行います。Simulinkとプロセス間通信するには前節のリンクの記事のようにCLIコマンド、C、Pythonと3通りの手段があって、最も簡単なのはCLIによるものです。また、LinuxでUSBのデータにアクセスするのもCLIで行えます。以下のシェルスクリプトでは、while中の3行のコードで、
 ・USBポートからLFまでのデータを取得 (データのダンプ)
 ・取得したデータをターミナルに表示(確認用)
 ・ダンプしたデータを1秒間隔でNNG転送
を実施しています。NNGのプロセス名は“ipc:///tmp/usbnng.ipc”としています。プロセスは"ipc:///tmp/~"以下に固有の名前で指定します。
``` usbnng.sh
#!/bin/bash

while :
do
  msg=$(cat /dev/ttyACM0|head –n1)
  echo ${msg}
  nngcat --pub --listen=“ipc:///tmp/usbnng.ipc” --data=“${msg}” --delay=1
done
```
シェルスクリプト実行の際は、シェルスクリプトにアクセス権を設定して、
$ sudo chmod 755 ./usbnng.sh
シェルスクリプトのパスを指定して実行します。
$ ./usbnng.sh
(上記は現在のパス直下に対象スクリプトがある場合)

## SimulinkでのNNG受け取り
ここはNNG Receiveブロックを置いて、必要な設定を行うだけです。IPC nameの欄に前節で指定したNNGのプロセス名"usbnng"を入力し、
![NNG_Block_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/17c1d2b3-2890-353d-c759-14541f1baaad.png)
asciiで受け取るためデータ型はuint8、データ長は5文字(4文字_スペース) X 100個とします。

## Arduinoでのデータ採取、USB送信
Arduino側の開発はSimulinkサポートパッケージもあってSimulinkでも可能ですが、今回はデータを取得して転送するだけであまりSimulinkのメリットはないため、豊富に関連記事があるArduino Sketchを利用します。SWを押すとアナログポートからデータを拾って文字数を固定し既定のサンプル数分を送付する、簡単なコード例を示します。

``` USBserial.c
#define NUM_SMPL 100
void setup() {
  // put your setup code here, to run once:
  pinMode(12, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
}
void loop() {
  // put your main code here, to run repeatedly:
  int DAC;  // DA変換値(10bit)
  char IntChar[]="    ";  // (~1024 4文字)
  uint8_t cnt;
  if(digitalRead(12) == LOW){
    digitalWrite(LED_BUILTIN, HIGH);
    for(cnt=0;cnt<NUM_SMPL;cnt++){
      DAC=analogRead(0);  // アナログ入力アサイン
      sprintf(IntChar,"%04d",DAC); // 文字数固定のため0埋め 
      Serial.print(IntChar);
      Serial.print(' ');
      delayMicroseconds(200); // サンプリングレートの調整 50KHz
    }
    Serial.print('\n');    // 末尾区切り
  }
  digitalWrite(LED_BUILTIN, LOW);
  delay(500);   // SW不感時間
}
``` 

・使用基板はArduino Reopard、SWは#12ピンとGND、ADCの入力ポートはアナログ#0
・Serial.begin(ボーレート)でUSBシリアルを準備
・Serial.print(データ)でUSBポートにデータを出力
・SWを押すとNUM_SMPL(=100)個のデータを50KHzで採取
・10bit (~1023) -> 整数の文字にすると4文字
・0埋めしてどのサンプルも4文字になるように 0235 0246 0356・・・ 0457
・USBへの出力データは0346 0478 0499・・・ 0563 LF(改行コード)のようになる

## 実行
Arduinoのスケッチはwhile文なので、電源を入れたら動作します。Raspberry Pi側ではターミナルを開いて、USB_NNG.shを実行しておきます。.bashrcやcrontabから自動実行させても良いです。
Simulinkはまずエクスターナルモードで、デプロイ前に動作の確認をします。
(エクスターナルモードの詳細は以下のリンク参照)

https://qiita.com/kureta90/items/5554117f0a94df89480d

"ASCII to string"ブロックを追加した簡単なモデルで動作を確認します。
![NNG_ascii.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/a281ec6c-2231-0a84-a321-a21537bc8a9e.png)
0346 0478 0499・・・ 0563というように、Arduinoで拾ったアナログ値が読めているようです!

https://jp.mathworks.com/help/simulink/slref/asciitostring.html

# 取得したデータを配列に
前節で確認したのはASCII(uint8) で送信されるストリームデータでした。データとして使用するには、これをスペース区切りで配列に格納して、数値に変換する必要があります。下はNNGから新しいデータを受け取ると、Enabled Subsystemでデータを変換するモデルです。
![ASCII2Array.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583335/146718d8-f0be-36cc-eec0-92394f9c3115.png)
Enabled Subsystemの中身は2つのMATLAB Functionブロックで、前段で上記の変換、後段で数値を電圧に変換しています。まず前段ですが、
```
function data = fcn(Ascii)

num_sample = 100;
num_char_per_sample=5;
num_char=num_char_per_sample*num_sample;
CharMat=char(Ascii(1:num_char));
NumMat=reshape(CharMat(1:num_char),[num_char_per_sample,num_sample]);
A=NumMat';

data = zeros(num_sample,1);
for i=1:num_sample
  data(i) = real(str2double(A(i,:)));
end
```
char()で入力のAsciiを1文字ごとのベクトルにしてしまって、
reshape()で1データ5文字　X 100個の2次元配列にして、
for文内部のstr2double(複素数出力なので実部だけ取る)で数値にしています。

後段は5Vの電圧を10bitでAD変換しているだけで簡単です。、
```
function out = dac(in)
out=in*5/1024;   %DAC値->アナログ値
``` 
これで、100要素の数値配列に変換できました。

# システムとしての見通し
たかがデータを取ってくるだけでずいぶん面倒、と思われたかもしれません。しかしこれで、Simulinkのサンプルタイムはフレーム単位である1秒とすることができます。1秒あればRaspberry Piでかなり複雑な処理も可能です。
よく悩まされるデータ落ちやフレームずれも、OSのCLIを利用しているので確実です。
何より、Simulinkのモデルはデータ採取の高速なサイクルとは分離されて、肝心のアルゴリズム部だけをシンプルに構成できます。
またこの例は、NNG + CLIコマンド(Linuxシェルスクリプト)の基本的な活用例です。USBに限らず、他にCLIでもっと直接的にデバイスのデータにアクセスできる場合は、同じ手法でSimulinkからのアクセスが可能です。