電子回路(Arduino)で構成された実験装置を自分で作成して、Matlab(PTB)と通信する方法をご説明します。
Arduinoについてはここでは詳しく説明しませんが、市販の本もたくさんあり、それほど専門的な知識を必要としません。例えばArduinoを利用すると、圧力や温度センサーからの情報を簡単にMatlabに読み込むことができます。
ここでは2つの圧力センサーを用いて、それぞれの出力をMatlab側で受け取る方法を例にあげてご説明します。
まず圧力センサーですが、インターリンク社のFSR 406 を使用しています。赤と黒の導線をはんだづけします。これを2組使用します。
次にArduino側です。(画像をクリックすると拡大表示されます)
今回は、5V、GND、A0、A1しか使用しません。
ArduinoとパソコンをUSBケーブルで接続します。
電源はUSBケーブルから取っています。
ブレッドボード側です。(画像をクリックすると拡大表示されます)
回路の配線としては以上になります。
ArduinoとMatlab(PTB)との通信には、シリアル通信を利用します。シリアル通信についてはこちらもご参照ください。(実際にはRS232cではなく、USBケーブルを使用します)
シリアルポートの名前は、Macであれば、ArduinoをUSBケーブルで接続した状態でターミナルを立ち上げて、
ls /dev/tty.usb*と入力すると確認できるはずです。
Windows ではCOM1などの表記になります。デバイスマネージャーから確認できます。
Arduinoのスケッチは次の通りです。
// 2つのセンサーをA0とA1に接続const int SENSOR1 = 0; // アナログ入力 A0const int SENSOR2 = 1; // アナログ入力 A1int sen1 = 0; // A0から読み込んだデータint sen2 = 0; // A1から読み込んだデータint inByte = 0; // バッファーから読み込んだ1バイト分のデータint SA = 0; // Serial.available();int flag = 0; // flagが1のときはパソコン(Matlab)にデータを送信void setup() { Serial.begin(38400); // 通信速度(bits/sec)はお使いの環境に合わせてください。 Serial.print("A\n"); // Arduinoの準備が整ったことをパソコンに通知}void loop() { SA = Serial.available(); if (SA > 0) { // バッファーにデータがたまっていたら inByte = Serial.read(); // バッファーのデータを1バイト読み込む(バッファーは空になる) if (inByte == 65) {// Matlabから'A'が送られていたら flag = 1; } else { flag = 0; } } if (flag == 1){ // アナログデータの読み取り sen1 = analogRead(SENSOR1); sen2 = analogRead(SENSOR2); // パソコンへのデータの送信 Serial.print(SA, DEC); Serial.print(","); Serial.print(inByte, DEC); Serial.print(","); Serial.print(sen1, DEC); Serial.print(","); Serial.print(sen2, DEC); Serial.print("\n"); //終端子 delay(10); // サンプリング間隔(ms)。チャタリング防止にもなっている。 }}Matlabのソースコードは次の通りです。
特に注意が必要なのは、常にArduinoからのデータを受け付けていると、入力バッファーにデータが蓄積されるという点です。BytesAvailableの値が大きいということは、それだけデータが蓄積されているということであり、実際の時間よりも遅れたデータを読み込んでいることになります。
function serialsample1% 実験参加者名の入力SubName = input('Name? ', 's'); % 名前をたずねるif isempty(SubName) % 名前の入力がなかったらプログラムを終了 return;end;% 出力ファイルの上書き確認を行うSaveFileName=[SubName '.csv']; % 出力ファイル名if exist(SaveFileName, 'file') % すでに同じ名前のファイルが存在していないかの確認 resp=input([SaveFileName 'はすでに存在します。上書きをしてよい場合は y を入力してエンターキーを押してください。'], 's'); if ~strcmp(resp,'y') disp('プログラムを強制終了しました。') return endend% 呼び出しておいたほうがよい関数たち。AssertOpenGL; KbName('UnifyKeyNames'); %OSで共通のキー配置にするListenChar(2); % Matlabに対するキー入力を無効GetSecs;WaitSecs(0.1);%HideCursor;openFlag = 0;try % シリアルポートオブジェクト myserial = serial('/dev/tty.usbmodemfa1311', 'BaudRate', 38400) % お使いの環境に合わせてシリアルポートの名前を書き換えてください。 % windowsでは serial('COM1'・・・などの表記になります。 fopen(myserial); myserial.Status; get(myserial) % 出力ファイルを開く Fid = fopen(SaveFileName, 'wt'); openFlag = 1; fprintf(Fid, '%s\n', SubName); fprintf(Fid, '%s\n', datestr(now, 'yy-mmdd-HH:MM')); fprintf(Fid, '%s\n', ''); % 出力ファイルの見出し fprintf(Fid, '%s\n', '通し番号,oldBA,BA,データ数,バイト数,SA,inByte,Sen1,Sen2'); % Arduinoとの通信を確立するため、データ'A'を受け取るのを待つ。 while myserial.BytesAvailable == 0 end; startChar = fscanf(myserial); % 入力バッファーからデータを読み込み %whos('startChar') %double(startChar) if startChar ~= 'A' error('通信を確立できませんでした。') end; for i = 1:2 while myserial.BytesAvailable fread(myserial, myserial.BytesAvailable); % バッファーを空に。 WaitSecs(0.5); end; fprintf('スペースキーを押すとシリアル通信を開始します。\n'); while KbCheck; end; % いずれのキーも押していないことを確認 while 1 % while 文の中をぐるぐる回ります。 [ keyIsDown, keyTime, keyCode ] = KbCheck; % キーが押されたか、そのときの時間、どのキーか、の情報を取得する if keyIsDown if keyCode(KbName('SPACE')) % fprintf(myserial, '%c', 'A'); % Arduinoに送信許可を送る。(終端子を含まない形で、1バイト送る) break; % while文を抜ける。 end; while KbCheck; end; end; end; %myserial.BytesAvailable fprintf('計測中・・・\n'); cnt = 1; baseTime = GetSecs; while GetSecs - baseTime < 5 oldBA = myserial.BytesAvailable; if oldBA > 0 % データの一部のみが入力バッファーにある可能性があることに注意。 %-------------------------------- % カンマ区切りのデータなどの扱い rawdata = fscanf(myserial); % 終端子が読み込まれるまで待つ。 tmpCell = textscan(rawdata, '%d%d%d%d', 'delimiter', ','); % Arduinoから送られてくるデータは4種類(%dの数に注意) [SA, inByte, sen1, sen2] = deal(tmpCell{:}); % BA = BytesAvailable。これが大きな値だと読み込んだデータに時間の遅れがあるということ。 % inByteが65のとき、これは「A」のASCIIコードを表している。 % inByteが10のとき、これは終端子「LF」のASCIIコードを表している。 info = whos('rawdata'); fprintf(Fid, '%d,%d,%d,%d,%d,%d,%d,%d,%d\n', cnt, oldBA, myserial.BytesAvailable, info.size(2), info.bytes, SA, inByte, sen1, sen2); cnt = cnt + 1; end; end; fprintf(myserial, '%c', 'B'); % Arduinoにデータの送信を停止させる。('A'ではなく'B'を送っていることに注意) fprintf(Fid, '\n'); cnt % 読み込んだデータ(行)の数 fprintf('計測を終了しました。\n'); WaitSecs(2); % 少し待ち時間をはさまないと次のブロックにデータが残ることがある。 end; %終了処理 fclose(Fid); % ファイルを閉じる。 fclose(myserial); delete(myserial); clear myserial; ShowCursor; ListenChar(0); catch % 以下はプログラムを中断したときのみ実行される。 %if exist('Fid', 'var') % ファイルを開いていたら閉じる。 if openFlag fclose(Fid); disp('fclose'); end; fclose(myserial); delete(myserial); clear myserial; ShowCursor; ListenChar(0); psychrethrow(psychlasterror);endMatlabのプログラムと同じフォルダのなかにCSVファイルが作成されます。
センサーの情報が正しく書き込まれていることを確認してください。また入力バッファーのデータ(BA)が大きな値になっていないことを確認してください。
Arduinodでは、300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 ビット/秒に、Matlabでは110、300、600、1200、2400、4800、9600、14400、19200、38400、57600、115200、128000、256000 ビット/秒に対応しているようです。Matlabの規定値は9600です。
上記のサンプルでは、1回あたりに送信されるデータ(終端子までのデータ)は10バイト前後になりますので次のように考えます。
10バイト=80ビット(1バイトは8ビット)
サンプリング間隔が10msの場合、1秒間には、
80ビット×(1000 ms / 10 ms) = 8000 ビット
この場合は9600ビット/秒以上の通信速度があれば十分なようです。
今回のサンプルでは、サンプリング間隔が10msで、5秒間データを計測しているので理論的には500個のデータが記録されるはずです。しかし実際に測定(cntの値)してみると、私の環境では460個でした。
ということは実際のサンプリング間隔は、5000 ms / 460 = 約10.87 ms になります。
おそらく1msほどが、データの書き込みなどに費やされているものと思われます。
今回のサンプルではPTBの機能はキー入力や時間取得にわずかに使用した程度です。自作の装置から受け取ったデータを、リアルタイムで刺激の描画に利用する方法については、Arduinoとの通信2をご覧ください。
参考資料