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