ShigetaMasahiro のすべての投稿

皮膚温バイオフィードバック装置-プログラム解説

導入
 バイオフィードバック(以下,BF)は,心身のリラクセーション法として有効な手法であり,脳波や心拍数,筋電図,血圧などの生理的指標を自己制御可能にする訓練方法です。しかし,従来のBF装置は高価であり,専門施設でしか利用できないため,その普及が阻まれています。そこで本研究は,これに対する解決策として,オープンソースハードウェアであるArduino型マイクロコンピューターとデジタルファブリケーション技術を活用し,容易に皮膚温BF訓練を実施できる新たな低コストデバイスの開発を行いました。

装置の構成
 マイクロコンピュータはMakerNanoを,皮膚温センサーはLM35DZを用います。フィードバック部分は,視覚フィードバックにカラーLEDモジュールと液晶ディスプレイを,聴覚フィードバックにスピーカーを用います。

使用した電子部品の入手先
 使用した部品の入手先を以下に示しました。なお,各部品名の「こちら」を選択すると,販売先のサイトに移動します。

・MakerNano:こちら
・LM35DZ:こちら
・スピーカー:こちら
・RGBLED:こちら
・液晶ディスプレイ:こちら
・DCDCコンバータ:こちら
・ピンヘッダー:こちら
・熱収縮チューブ:こちら
・細径3心並列線:こちら
・電池ボックス:こちら
・単4電池:こちら

測定方法
 温度センサは,指サックを用いて,非利き手の第2指腹測部に直接固定します。次に,右側面(下部)にある電源スイッチを入れて装置を起動します。安静時は,装置左下部のスイッチを右にスライドさせFBを無効にし,訓練時は左にスライドしてFBを有効にします。注意点として,温度センサに風が直接当たらない場所で訓練をしてください。詳しい訓練方法については,下記の2分程度の動画をご覧ください。

プログラムの解説
 ここでは,皮膚温BF装置の開発に用いたソフトウェアの説明を行います。使用したソフトウェアは,長野(2022)を参考にArduino開発環境1.8.19と,長野(2016)を参考にProcessing開発環境3.5.4のプログラムの作成を行いました。

1. Arduino開発環境

プログラム名説明
st7032液晶ディスプレイに皮膚温度を表示
Adpatesion安静期間の自動管理化
switchFB刺激の有無の切り替え
BFst7032_230705プログラムの全体像

2. Processing開発環境

プログラム名説明
SBF230707皮膚温のグラフ表示とCSV形式の発行
Subroutine1現在時刻と経過時間

1. Arduino開発環境

プログラム:st7032
 液晶ディスプレイ部分に該当するプログラムです。GNDと5Vで電源供給を行い,アナログピンの4,5番からI2C通信(Inter-Integrated Circuit)を行い,マイコンと液晶ディスプレイの間でデータの送受信を行っています。1行目で,液晶ディスプレイの制御に必要な「ST7032_asukiaaa.h」ライブラリをインクルードし,バージョン1.0.5を使用しました。3行目で,液晶ディスプレイの制御を行うために「lcd」とクラスをインスタンス化しています。これにより,11行目で表示領域8列2行とし,12行目でコントラスト(文字の濃さ)0~63を指定するなど液晶ディスプレイを制御する機能を呼び出しています。6行目のsetup関数でこれらの初期化を行っています。5行目のtemp1,tempdは,現在温度,1秒間で生じた温度の変化量を格納しています。15~24行目は安静期間の自動管理化を行っています。安静期間終了の条件を満たした場合,液晶ディスプレイの7列0行(画面右上)に「*」を表示し,満たされない場合,何も表示しませんでした。26~28行目は,Stringオブジェクトのbuf1,buf2を用いることで,temp1,tempdのデータを文字列に変換し,表示するための準備を行っています。30~33行目では,液晶ディスプレイの画面1行目にbuf1から現在温度,2行目にbuf2から1秒間で生じた温度の変化量をStringオブジェクトから「.c_str()」メソッドを用いて表示しました。これらを15行目のloop関数で繰り返し実行しました。

#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ

ST7032_asukiaaa lcd;  //液晶ディスプレイのライブラリを使用

double temp1, tempd;   //温度データを格納する変数

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

void setup() {
  lcd.begin(8, 2);     //液晶ディスプレイで8列2行で指定表示
  lcd.setContrast(5);  //液晶ディスプレイのコントラスト
}

void loop() {
  //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
  if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
    lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
    lcd.print("*");             //「*」表示
    readyFlg=true;
  } else {                      //条件を満たさなかった場合
    lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
    lcd.print(" ");             //何も表示しない
  }

  String buf1, buf2;
  buf1 += temp1;      //buf1は現在温度
  buf2 += tempd;      //buf2は温度変化量

  lcd.setCursor(0, 0); //液晶ディスプレイの1行目にカーソルを移動
  lcd.print(buf1.c_str()); //1行目に現在温度を表示
  lcd.setCursor(0, 1);    //液晶ディスプレイの2行目にカーソルを移動
  lcd.print(buf2.c_str());  //2行目に温度変化量を表示
}

プログラム:Adpatesion 
 このプログラムは、安静期間の自動管理化を行うため、大河内(1990)の安静期間の処理を参考に行っています。具体的には、1分ごとの皮膚温の平均値が連続する3分間で増減せず、その範囲が0.4℃以内である場合に安静期間の終了を示す「*」を液晶ディスプレイに表示します。プログラム内では、現在の温度を格納する変数temp1、平均値計算用のsum、average、cntが定義され、連続する3分間(180サンプル)の温度データを保持するための配列historyと、そのデータを格納するインデックスhistory Indexが用意されています。history Indexは180を超えると自動的に最初の位置に戻り、配列の境界をループしながらデータを保存します。初期化として、最小・最大温度を0℃で設定し、取得した温度データをhistory配列に格納、次のインデックスを更新します。履歴内のデータと比較して、現在の最小・最大温度を更新し、連続する180サンプルが増減なく0.4℃以内であるかをチェックします。条件が満たされた場合はArduinoのシリアルモニタ内に「***」を表示し、満たされない場合は「—」を表示しました。温度データの取得と処理は10msごとに行われ、安静期間の判定と表示が繰り返されることで、安静状態をモニタリングします。この処理により、安静期間の終了を正確に管理し、リアルタイムで液晶ディスプレイにフィードバックを提供します。

double temp1;   //現在温度のデータを格納する変数
double sum, average;    //温度データの合計値と平均値を格納
long cnt;      //温度データのサンプリング数

const int HISTORY_SIZE = 180; // 履歴のサイズ
double history[HISTORY_SIZE]; // 測定値の履歴
int historyIndex = 0;         // 履歴のインデックス

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

bool readyFlg=false;  //安静期間のフラグ


void loop() {

    if(readyFlg==true){
      Serial.print(",***"); //安静期間終了時に「***」表示
    }
    else{
      Serial.print(",---"); //安静期間が終了していない場合「---」表示
    }
    
    Serial.println();   //シリアルモニタに「***/---」を表示するための空行
    sum = 0;    //合計値の初期化
    cnt = 0;
    delay(10);  //10ms単おきに行う

    history[historyIndex] = temp1;  //温度データを記録するための配列
    historyIndex = (historyIndex + 1) % HISTORY_SIZE;  //新しい温度データを保存

    minTemp = history[0];   //最小温度をデータの最初の要素で初期化
    maxTemp = history[0];   //最大温度をデータの最初の要素で初期化

    for (int i = 1; i < HISTORY_SIZE; i++) {  //温度データから最小・最大温度を算出
      if (history[i] < minTemp) {   //データ内で最小温度より低い場合
        minTemp = history[i];       //最小温度を更新
      }
      if (history[i] > maxTemp) {    //データ内で最大温度より低い場合
        maxTemp = history[i];        //最大温度を更新
      }
    }

    //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
    if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print("*");             //「*」表示
      //Serial.println("***");
      readyFlg=true;
    } else {                      //条件を満たさなかった場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print(" ");             //何も表示しない
    }

プログラム:switch
 スライドスイッチを用いて,光や音のFB有無の切り替えを行うプログラムです。1行目では視覚FBに用いるLEDの制御にAdafruit_NeoPixelライブラリ(Ver1.10.7)をインクルードし,2行目では液晶ディスプレイの制御にST7032_asukiaaa.hライブラリ(Ver1.0.5)をインクルードしました。4,5行目では,LEDの制御ピン番号,使用するLEDの個数を指定し,7行目ではこれらをもとにAdafruit_NeoPixelクラスをインスタンス化しています。9,10行目で,FB有無の切り替えに用いるスイッチピン7,ブザーピン2など制御に用いるピン番号を指定しています。13行目で,スイッチの状態を入力モードにして,「INPUT_PULLUP」から内部プルアップ抵抗を有効にして,ピンの状態がHigh(オン)になっています。14行目で,ブザーのピンを出力モードに設定して,制御するための処理を行い,これらをsetup関数から初期化しています。17行目では,スイッチの状態(オン/オフ)を読み取ります。19行目で,スイッチの状態がオフの時に19~32行目で実行するFB有の状態にしました。温度が上昇した際(dir==1)に視覚FBのLEDライトが赤く点灯し,聴覚FBのブザー500Hzを10ms間出力しました(19~26行目)。温度が下降した際(dir==-1)に視覚FBのLEDライトが青く点灯し,聴覚FBのブザー1000Hzを10ms間出力しました(27~32行目)。33~35行目では,FB無しの状態を割り当て,スイッチの状態がオフの時にLEDライトが消灯し,ブザーが消音するようにFBの有無の切り替えを行いました。

#include <Adafruit_NeoPixel.h>  //LEDのライブラリ
#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ
 
#define PIN        3   //LEDの制御ピン
#define NUMPIXELS 1    //LEDの数
 
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);   //LEDのライブラリを使用

const int switchPin = 7;    //FB有無の切り替えに用いるスイッチ
int Bpin = 2;  //ブザーPIN2

void setup() {
  pinMode(switchPin, INPUT_PULLUP);  // 内部プルアップ抵抗を有効にする
  pinMode(Bpin, OUTPUT);

void loop() {
  int switchState = digitalRead(switchPin);
 
  if (switchState == LOW) {   //FBスイッチがオフの時に
    pixels.clear();
    if (dir == 1) {
        pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
      if (mag > 0) {
        tone(Bpin, 500, 10);    //温度上昇時は500Hz
      }
    }
    if (dir == -1) {
      pixels.setPixelColor(0, pixels.Color(0, 0, mag)); //温度下降を青で表示
      if (mag > 0) {
        tone(Bpin, 1000, 10);   //温度下降時は1000Hz
      }
    }
  } else if (switchState == HIGH) {         //左下のスイッチを入れるとFBなし
    pixels.setPixelColor(0, pixels.Color(0, 0, 0));    //LEDを消す
    noTone(Bpin);         //ブザー音を消す
  }

プログラム:BFst7032_230705
 長野(2022)が作成した皮膚温BFプログラムと,上記で開発したプログラムを統合した全体像です。温度センサーから入力された値をもとに,「現在温度,温度変化量,変化の強さ,サンプリング数,安静期間終了の判定」が算出されます。温度が上昇したときに,LEDが赤く点灯し,ブザーから500HzでFBが行われます。温度が下降したときにLEDが青く点灯し,ブザーから1000HzでFBが行われました。温度変化が顕著に行われない場合LEDの色は変化せず,温度の変化量が大きい場合はLEDがより強く点灯しました。1秒毎に平均値の算出および各種FBなどが行われました。これらの機能を用いて,BF装置のプログラムを作成しました。

#include <Adafruit_NeoPixel.h>  //LEDのライブラリ
#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ

#define PIN        3   //LEDの制御ピン
#define NUMPIXELS 1    //LEDの数

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);   //LEDのライブラリを使用
ST7032_asukiaaa lcd;        //液晶ディスプレイのライブラリを使用

const int switchPin = 7;    //FB有無の切り替えに用いるスイッチ

double mv, temp0, temp1, tempd;   //温度データを格納する変数
double sum, average;    //温度データの合計値と平均値を格納
long t, t0, cnt;      //現在の時刻・温度データをサンプリングした時の時刻・温度データのサンプリング数
int Bpin = 2;  //ブザーPIN2

const int HISTORY_SIZE = 180; // 履歴のサイズ
double history[HISTORY_SIZE]; // 測定値の履歴
int historyIndex = 0;         // 履歴のインデックス

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

bool readyFlg=false;  //安静準備フラグ

void setup() {
  pinMode(switchPin, INPUT_PULLUP);  // 内部プルアップ抵抗を有効にする

  pinMode(Bpin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(115200);  //シリアル通信115200
  analogReference(INTERNAL); //参照電圧を1.1Vに設定
  cnt = sum = 0;
  pixels.begin();      //LEDを初期化

  lcd.begin(8, 2);     //液晶ディスプレイで8列2行で指定表示
  lcd.setContrast(5);  //液晶ディスプレイのコントラスト
}

void loop() {
  t0 = t;
  t = millis();        //現在時刻をms単位で取得
  mv = (double)1100 / (double)1024 * analogRead(0);  //デジタル値を電圧に変換
  sum = sum + mv;
  cnt++;        //サンプリング数のカウント
  if (t0 / 1000 != t / 1000) {    //1秒(100ms)毎に平均算出
    average = sum / (double)cnt;  //平均を計算

    temp0 = temp1;                //前回の温度を保存
    temp1 = average * 0.1;        //電圧を温度に変換
    tempd = temp1 - temp0;        //温度変化量を算出

    int mag, dir;                 //変化の程度(mag)と方向(dir)
    if (tempd > 0) {
      mag = abs(tempd * 100);
      dir = 1;                    //温度上昇時
    } else if (tempd <= 0) {
      mag = abs(tempd * 100);
      dir = -1;                   //温度下降時
    }

    int switchState = digitalRead(switchPin);

    if (switchState == LOW) {   //FBスイッチがオフの時に
      pixels.clear();
      if (dir == 1) {
        pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
        if (mag > 0) {
          tone(Bpin, 500, 10);    //温度上昇時は500Hz
        }
      }
      if (dir == -1) {
        pixels.setPixelColor(0, pixels.Color(0, 0, mag)); //温度下降を青で表示
        if (mag > 0) {
          tone(Bpin, 1000, 10);   //温度下降時は1000Hz
        }
      }
    } else if (switchState == HIGH) {         //左下のスイッチを入れるとFBなし
      pixels.setPixelColor(0, pixels.Color(0, 0, 0));    //LEDを消す
      noTone(Bpin);         //ブザー音を消す
    }

    pixels.show();

    Serial.print(temp1); //現在温度
    Serial.print(",");   //カンマ区切り
    Serial.print(tempd); //温度変化量
    Serial.print(",");
    Serial.print(mag);   //変化の強さ
    Serial.print(",");
    Serial.print(cnt);   //サンプリング数

    if(readyFlg==true){
      Serial.print(",***"); //安静期間終了時に「***」表示
    }
    else{
      Serial.print(",---"); //安静期間が終了していない場合「---」表示
    }
    
    Serial.println();   //シリアルモニタに「***/---」を表示するための空行
    sum = 0;    //合計値の初期化
    cnt = 0;
    delay(10);  //10ms単おきに行う

    history[historyIndex] = temp1;  //温度データを記録するための配列
    historyIndex = (historyIndex + 1) % HISTORY_SIZE;  //新しい温度データを保存

    minTemp = history[0];   //最小温度をデータの最初の要素で初期化
    maxTemp = history[0];   //最大温度をデータの最初の要素で初期化

    for (int i = 1; i < HISTORY_SIZE; i++) {  //温度データから最小・最大温度を算出
      if (history[i] < minTemp) {   //データ内で最小温度より低い場合
        minTemp = history[i];       //最小温度を更新
      }
      if (history[i] > maxTemp) {    //データ内で最大温度より低い場合
        maxTemp = history[i];        //最大温度を更新
      }
    }

    //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
    if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print("*");             //「*」表示
      //Serial.println("***");
      readyFlg=true;
    } else {                      //条件を満たさなかった場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print(" ");             //何も表示しない
    }

    String buf1, buf2;
    buf1 += temp1;      //buf1は現在温度
    buf2 += tempd;      //buf2は温度変化量

    lcd.setCursor(0, 0); //液晶ディスプレイの1行目にカーソルを移動
    lcd.print(buf1.c_str()); //1行目に現在温度を表示
    lcd.setCursor(0, 1);    //液晶ディスプレイの2行目にカーソルを移動
    lcd.print(buf2.c_str());  //2行目に温度変化量を表示
  }
}

2. Processing開発環境

プログラム:SBF230707
 1つ目は、シリアル通信を介して受信した皮膚温データを処理し、リアルタイムでグラフ表示しながら、そのデータをCSVファイルに保存するプログラムです。1行目では、Processing開発環境のシリアル通信ライブラリをインポートして、3行目ではシリアル通信を行うための準備として、Serialオブジェクトを宣言しています。4、5行目では、BF装置とシリアル通信を行うため、Arduino開発環境で表示されたシリアルポートの指定および通信速度115200ポートレートを使用しています。6、7行目では、ST波形などを表示するためのエリアとして、幅640、高さ200と指定しています。8~10行目では、受信したデータ「現在温度、温度変化量、変化の強さ、サンプリング数、安静期間終了の判定」の5つのデータについて、float型の配列の宣言からデータ格納を行い、String関数からデータ文字列を格納するための宣言を行っています。また、現在データと前のデータを線でつなぐため、prevDate配列を使用しています。11~15行目は、グラフを描画するために、赤・青・透明色の指定を行っています。17行目は、8~10行目のデータの初期値を0と指定し、18行目はCSVファイルの出力に使用するファイル名の格納を行っています。19行目は、一時的に取得したデータを格納するため、buf2という変数を指定。20行目では、CSVファイルにデータを書き込むためにオブジェクトの指定を行っています。21行目では、プログラムの開始時刻をミリ秒単位で格納するため、startMills変数を指定しています。27行目では、プログラムの実行開始時に表示されるウィンドウサイズを幅600ピクセル、高さ500ピクセルに設定しています。28行目では、myPortオブジェクトを作成し、4、5行目で指定したポート名とポートレートで初期化を行っています。30、31行目は、現在時刻および経過時間を描画するプログラム(図3-6)内で用いたgetTimestamp関数から取得した現在日時のタイムスタンプに基づいて、CSVファイル名を指定しています。32行目は、新しいデータが一定数蓄積するたびにファイル内に書き込むため初期化を行っています。

 36~92行目では、draw関数を用いて取得したデータをグラフに描画するための処理を行っています。39~43行目は、データの表示設定を行っており、変数datecountを用いてデータの受信回数が600の倍数の時に(39行目)次の条件を実行しました。background(255)からウィンドウ全体を白く塗りつぶし、fill(0, 0, 0)からテキスト描画を黒色で塗りつぶしました。さらにテキストサイズを12として、センサーから取得された温度データの単位を示すため、画面左上に摂氏40℃を表す40degを表示しました。46行目では、myPortオブジェクトから指定したシリアル通信が可能な場合、次の処理を行いました。48行目のmyPort.readStringUntiでデータを読み込み、取得したデータがnull表記ではない場合(50行目)、trim(inString)からデータ前後の空白を削除し、データ処理を行いました(52行目)。シリアルポートから読み込まれた1行のテキストデータを格納するためのInstring関数をCSV形式のデータ入力のため、カンマで分割して、得られた文字列をparts配列に格納しており(53行目)、取得中のデータに不足はないか確認しています(55、56行目)。57行目では、現在のSTをグラフに描画する際に、前回のデータが紛失しないようにデータの保存が行われています。60行目では、グラフの縦軸の範囲を可視化するため、元の範囲20~40の値を0~500に変換しています。これらデータをCSVファイルに記録するため、dataMV配列を使用しています(61行目)。64、65行目では、getTimestamp2から現在時刻、getElapsedTimeから経過時間を取得しています。67、68行目では、Processing開発環境のコンソール画面に表示するために、bufという変数を用いて「タイムスタンプ、経過時間」とdataMV配列に格納された温度データがカンマ区切りで表示されました。これにより、Arduino開発環境のシリアルモニタなどから温度データを確認する手間を省きました。69行目では、連続して受信したデータbufをbuf2に追加し、一度に大量のデータをファイルに書き込む回数を減らしました。70行目では、取得したデータ数が10の倍数ごとにCSVファイルに、writer.flushからリアルタイムで書き込む処理を行っています(71行目)。その後、ファイル内の残りのデータを書き込み(72行目)、73行目でbuf2を空にしました。これにより、ファイルのサイズを一定に保ちつつ、連続してデータを記録することができました。76~82行目では、グラフを描画するための処理を行っています。受信したデータ数を600で割った値からグラフのx座標を計算して(76行目)、描画する線の太さを1に設定(77行目)。78行目で実際に描画を行い、11~16行目で設定した色を用いて、描画する線の輪郭線の色を設定しています。81行目では、直前のデータから現在のデータまでを、グラフ上での位置を計算して線で結ぶ描画処理を行っています。「xp-1」から線の始点のx座標、「500-prevData[i]」から線の始点のy座標、「xp」から線の終点のx座標、「500 – data[i]」から線の終点のy座標をそれぞれ表しています。84、85行目では、現在時刻と経過時間を描画し、描画された文字が潰れないようにデータのカウントの更新を行っています(88行目)。94~98行目では、プログラムの終了時にファイルに書き込めてないデータをCSVファイルに強制的に書き込み(95行目)、ファイルを閉じるための処理を行っています(96、97行目)。

import processing.serial.*;

Serial myPort;
String portName = "COM6"; //BT serial port
int baudRate = 115200;
int dataPlotWidth = 640;
int dataPlotHeight = 200;
float[] data = new float[5];
String[] dataMV = new String[5];
float[] prevData = new float[5];
color[] colors = {
  color(255, 0, 0), 
  color(0, 0, 255), 
  color(0, 0, 0, 0), 
  color(0, 0, 0, 0)
};
int datacount=0;
String OutFileName;
String buf2;
PrintWriter writer;
int startMillis;


void setup() 
{
  //size(dataPlotWidth, dataPlotHeight);
  size(600,500);
  myPort = new Serial(this, portName, baudRate);
  
  OutFileName=getTimestamp()+".csv";
  writer = createWriter("./data/"+OutFileName); // Create a new file in the sketch directory
  buf2="";

}

void draw()
{
  
  if((datacount % 600) == 0) {
    background(255);  // clear screen    
    fill(0, 0, 0); // set the fill color to black
    textSize(12);
    text("40 deg", 10, 20); // 
  }
  
  while (myPort.available() > 0) 
  {
    String inString = myPort.readStringUntil('\n');
    
    if (inString != null) 
    {
      inString = trim(inString);
      String[] parts = split(inString, ',');
      
      if(parts.length >3) 
      {
        for (int i = 0; i < 5; i++) 
        {
          prevData[i] = data[i];
          data[i] = map(float(parts[i]), 20, 40, 0, 500);  // Assuming your data is in the range 0-200
          dataMV[i] = parts[i];
          
        }
        String stamp=getTimestamp2();
        String etime= getElapsedTime();
        String buf=stamp+","+etime+","+dataMV[0] + "," + dataMV[1] + "," + dataMV[2] + "," + dataMV[3] + "," + dataMV[4];
        println(buf);
        buf2=buf2+buf+"\n";
        
        if(datacount%10==0){
          writer.print(buf2);
          writer.flush();
          buf2="";
        }

        int xp = datacount%600;
        strokeWeight(1);
        for (int i = 0; i < 1; i++) 
        {
          stroke(colors[i]);
          line(xp-1, 500 - prevData[i], xp, 500 - data[i]);
        }
        
        drawCurrentTime();
        drawElapsedTime();

    
        datacount++;
      }
    }
  }
}

void stop() {
  writer.flush(); // Writes the remaining data to the file
  writer.close(); // Finishes the file
  super.stop();
}

プログラム:Subroutine1
 2つ目は、現在時刻および経過時間を描画するプログラムです(図3-6)。1行目は、現在の日時情報をフォーマットして文字列で返すために、getTimestampというString型の関数の宣言を行っています。2~7行目では、現在の日時を「年、月、日、時、分、秒」から取得して、それぞれ変数に代入しています。10行目では、2~7行目で取得した現在日時の情報を連結した文字列のタイムスタンプを作成しています。12行目では、作成した文字列のタイムスタンプは、getTimestamp関数内のtimestampに格納されているため、その関数内でしか使用できません。そのため、return timestampを使用し、timestampを関数の外に返すことでgetTimestampの呼び出し元に、作成した文字列のタイムスタンプが出力できます。15行目では、getTimestam2というString型の関数の宣言を行っています。16~18行目では、現在時刻「時、分、秒」を取得し、それぞれ変数に代入しています。21行目では、現在時刻の情報を連結した文字列のタイムスタンプを作成しています。23行目では、12行目と同様にreturn timestampを使用し、getTimestam2の呼び出し元に、作成した文字列のタイムスタンプを出力する処理を行っています。

26~42行目は、Processing開発環境の実行ボタンを選択した際、ウィンドウ内に表示する現在時刻および経過時間の描画設定を行っています。26行目では、現在時刻を描画するため、drawCurrentTimeという関数の宣言を行っています。28行目では、getTimestam2から現在時刻をstamp2に格納しています。29行目のfill(255)は描画される形状の塗りつぶしを白色に設定し、noStroke()は形状の輪郭線を非表示に設定しています。これにより、白色の背景が表示されます。30行目では、rect関数を用いて、位置(450, 5)に幅130、高さ50の現在時刻を表示するための長方形の背景を描画しています。31行目では、描画の属性を設定しており、fill(0)から黒色のテキストに設定し、Stroke(0)は形状の輪郭線を黒色に設定しています。また、textSize(24)からテキストサイズを24としました。32行目では、text関数を使用して、28行目のstamp2から画面右上(x:450, y:30)に現在時刻を表示しました。35~42行目も同様の処理を行い、経過時間を描画するための関数をdrawElapsedTimeと定義し(35行目)、経過時刻を取得するための変数をelapsedTimeに格納しました(37行目)。経過時刻を描画するための背景色やテキストサイズは同様です(38~40行目)。描画位置に関しては、画面右上の現在時刻の下の段(x:450, y:60)に表示しました(41行目)。

44~55行目は、経過時間の計算を行い、「時、分、秒」のフォーマットに整形するString型の関数getElapsedTime(44行目)を用いたプログラムです。45行目では、millis()関数を使用してプログラムの実行開始からの経過時間をミリ秒数単位で取得し、elapsedMillis変数に格納しています。47~49行目は、取得したミリ秒数を元に、経過時間を「時間、分、秒」に変換しています。経過ミリ秒数を1000で割った値を「秒」、秒を60で割った値を「分」、分を60で割った値を時間に変換しています。51、52行目では、秒数や分数で60を超えた値を再計算できるように、60未満の範囲に収めています。54行目では、nf()関数を使用して、表示される経過時間の間にコロンで区切り「時間:分:秒」の形式で表示する文字列を形成しています。以上から、プログラムの実行開始と同時に設定したウィンドウ内に現在時刻および経過時間を表示することが可能となりました。

String getTimestamp() {
  int y = year();
  int m = month();
  int d = day();
  int h = hour();
  int min = minute();
  int s = second();

  // Use nf() to add leading zeroes to each time component if needed
  String timestamp = nf(y, 4) + nf(m, 2) + nf(d, 2) + "_" + nf(h, 2) + nf(min, 2) + nf(s, 2);
  
  return timestamp;
}

String getTimestamp2() {
  int h = hour();
  int min = minute();
  int s = second();

  // Use nf() to add leading zeroes to each time component if needed
  String timestamp =  nf(h, 2) +":"+ nf(min, 2) +":"+ nf(s, 2);
  
  return timestamp;
}

void drawCurrentTime()
{
  String stamp2=getTimestamp2();
  fill(255); noStroke();
  rect(450, 5,130,50); 
  fill(0); stroke(0);textSize(24); 
  text(stamp2, 450, 30); //   
}

void drawElapsedTime()
{
  String elapsedTime = getElapsedTime();
  fill(255); noStroke();
  rect(450, 55,130,50); 
  fill(0); stroke(0);textSize(24); 
  text(elapsedTime, 450, 60);
}

String getElapsedTime() {
  int elapsedMillis = millis() - startMillis;

  int s = elapsedMillis / 1000;
  int m = s / 60;
  int h = m / 60;

  s = s % 60;
  m = m % 60;

  return nf(h, 2) + ":" + nf(m, 2) + ":" + nf(s, 2);
}

====================================================================

引用文献
長野祐一郎(2016). 自作測定装置で学ぶ皮膚温バイオフィードバック バイオフィードバック研究,43,49-51.
長野祐一郎(2022). 自作測定機器を用いたバイオフィードバック バイオフィードバック研究,49,77-81.
大河内浩人(1990). 皮膚温制御におよぼす訓練課題とフィードバックの効果 バイオフィードバック研究,17,8-14.