Arduinoとの通信2
Arduinoとの通信1の動作確認がまだの場合は、そちらを先に行ってください。
先のサンプルでは、PTBの機能をほとんど利用していませんでしたが、次にご紹介するサンプルでは、圧力センサーを押さえると画面上の円(PTBの関数で描画されたもの)が大きくなります。
左側に描画される円がセンサー1から入力されたもので、右側に描画される円がセンサー2から入力されたものです。
またサンプル1では、送受信の開始時にのみ許可信号(文字A)を送っていましたが、今回のサンプルではデータを受信したいタイミングで必ずパソコンからArduinoに対して許可信号を送るようにしています。(ハンドシェイク、フロー制御と呼ばれます)この理由は、Flipなどが実行されるわずかな時間のあいだに、バッファーにデータが蓄積されるのを防ぐためです。こちらの記事も参考になります。
Arduinoのスケッチです。
// 2つのセンサーをA0とA1に接続
const int SENSOR1 = 0; // アナログ入力 A0
const int SENSOR2 = 1; // アナログ入力 A1
int 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){
flag = 0; // サンプル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のソースコードです。
function serialsample2
% 実験参加者名の入力
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
end
end
% 呼び出しておいたほうがよい関数たち。
AssertOpenGL;
KbName('UnifyKeyNames'); %OSで共通のキー配置にする
ListenChar(2); % Matlabに対するキー入力を無効
GetSecs;
WaitSecs(0.1);
%HideCursor;
openFlag = 0;
% 背景色
bgColor = [128 128 128]; %RGBの値
% 文字色
strColor = [0 0 0];
fontSize = 20; % 文字サイズ(整数)
try
% 刺激を呈示するディスプレイ(ウィンドウ)の設定
screenNumber = max(Screen('Screens'));
% デバッグ用。ウィンドウでの呈示
%[windowPtr, windowRect] = Screen('OpenWindow', screenNumber, bgColor, [10 50 1200 1000]);
% 実験用。フルスクリーン
[windowPtr, windowRect] = Screen('OpenWindow', screenNumber, bgColor);
% 1フレームの時間 (inter flame interval)
ifi = Screen('GetFlipInterval', windowPtr);
% 画面の中央の座標
[centerX, centerY] = RectCenter(windowRect);
%------------------------------
% フォント設定
if IsWin
%Screen('TextFont', windowPtr, 'メイリオ');
Screen('TextFont', windowPtr, 'Courier New');
end;
if IsOSX
% DrawHighQualityUnicodeTextDemoを参照。
allFonts = FontInfo('Fonts');
foundfont = 0;
for idx = 1:length(allFonts)
%if strcmpi(allFonts(idx).name, 'Hiragino Mincho Pro W3')
if strcmpi(allFonts(idx).name, 'Hiragino Kaku Gothic Pro W3')
foundfont = 1;
break;
end
end
if ~foundfont
error('Could not find wanted japanese font on OS/X !');
end
Screen('TextFont', windowPtr, allFonts(idx).number);
end;
Screen('TextSize', windowPtr, fontSize);
% シリアルポートオブジェクト
myserial = serial('/dev/tty.usbmodemfa1311', 'BaudRate', 38400)
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', '通し番号,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;
DrawFormattedText(windowPtr, double('スペースキーを押すとシリアル通信を開始します。'), 'center', 'center', strColor);
Screen('Flip', windowPtr);
while KbCheck; end; % いずれのキーも押していないことを確認
while 1 % while 文の中をぐるぐる回ります。
[ keyIsDown, keyTime, keyCode ] = KbCheck; % キーが押されたか、そのときの時間、どのキーか、の情報を取得する
if keyIsDown
if keyCode(KbName('SPACE')) %
break; % while文を抜ける。
end;
while KbCheck; end;
end;
end;
cnt = 1;
baseTime = GetSecs;
while 1
fprintf(myserial, '%c', 'A'); % Arduinoに送信許可を送る。(終端子を含まない形で、1バイト送る)
onTime = GetSecs; % 送信を許可した時間
while myserial.BytesAvailable == 0 % Arduinoからデータを受け取るまでループする。
end;
%--------------------------------
% カンマ区切りのデータなどの扱い
rawdata = fscanf(myserial); % 終端子が読み込まれるまで待つ。
timelag = GetSecs - onTime;
tmpCell = textscan(rawdata, '%d%d%d%d', 'delimiter', ','); % Arduinoから送られてくるデータは4種類(%dの数に注意)
[SA, inByte, sen1, sen2] = deal(tmpCell{:})
dist = 300; % 2つの円の間隔
radius = double(sen1)/2; % 左の円の半径(センサー1からの入力)doubleで倍精度に変換する必要あり。
tmpRect = [centerX - dist - radius, centerY - radius, centerX - dist + radius, centerY + radius];
Screen('FillOval', windowPtr, [255 255 255], tmpRect);
radius = double(sen2)/2; % 右の円の半径(センサー2からの入力)doubleで倍精度に変換する必要あり。
tmpRect = [centerX + dist - radius, centerY - radius, centerX + dist + radius, centerY + radius];
Screen('FillOval', windowPtr, [255 255 255], tmpRect);
Screen('Flip', windowPtr);
% BA = BytesAvailable。これが大きな値だと読み込んだデータに時間の遅れがあるということ。
% inByteが65のとき、これは「A」のASCIIコードを表している。
% inByteが10のとき、これは終端子「LF」のASCIIコードを表している。
info = whos('rawdata');
fprintf(Fid, '%d,%d,%d,%d,%d,%d,%d,%d,%f\n', cnt, myserial.BytesAvailable, info.size(2), info.bytes, SA, inByte, sen1, sen2, timelag*1000);
cnt = cnt + 1;
if GetSecs - baseTime > 5
break;
end;
end;
Screen('Flip', windowPtr);
%fprintf(myserial, '%c', 'B'); % Arduinoにデータの送信を停止させる。('A'ではなく'B'を送っていることに注意)
fprintf(Fid, '\n');
cnt % 読み込んだデータ(行)の数
%fprintf('計測を終了しました。\n');
%WaitSecs(2); % 少し待ち時間をはさまないと次のブロックにデータが残っている。
end;
DrawFormattedText(windowPtr, double('実験は終わりです。'), 'center', 'center', strColor);
Screen('Flip', windowPtr);
KbWait([], 3);
%終了処理
fclose(Fid); % ファイルを閉じる。
fclose(myserial);
delete(myserial);
clear myserial;
Screen('CloseAll');
ShowCursor;
ListenChar(0);
catch % 以下はプログラムを中断したときのみ実行される。
%if exist('Fid', 'var') % ファイルを開いていたら閉じる。
if openFlag
fclose(Fid);
disp('fclose');
end;
fclose(myserial);
delete(myserial);
clear myserial;
Screen('CloseAll');
ShowCursor;
ListenChar(0);
psychrethrow(psychlasterror);
end