22BF学会講習会

導入
 近年、Arduino等の安価なマイクロコンピュータを用いた電子工作が広く普及しています。これらを利用し、バイオフィードバック装置を自作し、利用する方法を紹介します。
 マイクロコンピュータの多くはAD変換機能を持っているため、様々なセンサーを接続することで測定装置として利用可能です。センサーを読み取るだけでなく、LEDやスピーカー、液晶ディスプレイなどに測定結果を出力することも可能です。シングルタスクOSを搭載しているため、測定値のリアルタイム処理できる点や、比較的コンパクトかつ低価格で装置を構成可能な点も、バイオフィードバックへの応用に適しています。
 本講習会では、末梢皮膚温変化を題材に、ハードウェア・ソフトウェアの両面から、バイオフィードバック装置の動作を学びます。 

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

測定の方法
 センサーはメンディングテープを用い、非利き手人差し指の腹側に装着する。テープをきつく巻き過ぎて指が圧迫されないように注意する。計測中は手のひらを上に向ける。下側に向けると机の温度を測ることになるので注意。室内温度は23~25℃程度に調節し、エアコンの風が直接指先にあたらないよう、風量・風向を調節する。


学習の手順
 ここでは下記の順序でバイオフィードバック機器の動作を学んでいきます。

プログラム名:ADC・・・AD変換の基礎
プログラム名:temp01・・・基本的な皮膚温測定
プログラム名:temp02・・・より細かく皮膚温を測定する方法
プログラム名:Tone・・・聴覚フィードバックに用いるスピーカーの使い方
プログラム名:ColorLED・・・視覚フィードバックに用いるスピーカーの使い方
プログラム名:TempABF・・・聴覚を用いた皮膚温バイオフィードバックの方法
プログラム名:TempVBF・・・視覚を用いた皮膚温バイオフィードバックの方法
プログラム名:TempAVBF・・・視聴覚を用いた皮膚温バイオフィードバックの方法

 使用するプログラム一式は下記のアドレスからダウンロードできます。Zip形式で圧縮されているため、必ず展開してから使用してください(圧縮されたままだとプログラムが動作しません)。

 ハードウェアの組み立ては、各プログラムの動画と下の画像をご覧ください。

プログラム:ADC
 AD変換は生体計測をする上で欠かすことのできない方法です。ここでは、ArduinoUNOを用い、皮膚温センサーから得られる電圧をAD変換し、数値として表示する方法を学びます。

//A0に入力された値をAD変換し表示
void setup() {
  Serial.begin(115200);        // シリアル通信の初期化
}

void loop() {
  int val = analogRead(0);
  Serial.println(val);        //analog0の値を表示
  delay(1000);
}

プログラム:temp01
 AD変換された値には単位がありません。デジタル化された値をmVに換算することで、温度を表示する方法を学びます。今回用いる皮膚温センサーは、1℃あたり10mVの出力があるため、mV値を0.1倍することで温度を得ることができます。

//1秒間隔でデジタル値を取得して温度に変換し表示
double mv,temp;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1Vに設定
}

void loop() {
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  temp=mv*0.1;           //電圧を温度に変換
  Serial.print(mv);  
  Serial.print(",");
  Serial.print(temp);   //温度表示
  Serial.println();
  delay(1000);          //1000ms停止
}

プログラム:temp02
 皮膚温の精神的な変化は微弱であるため、自己制御を実現するためには、0.01℃程度の変化を測定する必要があります。ここでは、1秒間の平均値を算出することで、より細かく皮膚温を調べる方法を学びます。

//1秒間隔で可能な限りデジタル値を取得して平均を表示
double mv,temp;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);        // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
}

void loop() {
  t0=t;
  t=millis();   //現在時刻をms単位で取得
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  sum=sum+mv;   //mv値の足し込み
  cnt++;
  if(t0/1000 !=t/1000){       //1秒(1000ms)毎に平均算出
    average=sum/(double)cnt;  //平均を計算
    temp=average*0.1;         //電圧を温度に変換
    Serial.print(cnt);        //サンプル数表示
    Serial.print(",");
    Serial.print(average);    //平均電圧を表示
    Serial.print(",");
    Serial.print(temp);       //温度を表示
    Serial.println();
    sum=0;cnt=0;              //合計値とカウントを初期化
  }
}

プログラム:Tone
 聴覚フィードバックに用いるスピーカーの使用方法を学びます。tone命令を用いることで、出力する音の周波数と、長さを指定できます。プログラム中の12は、デジタルポートの12番から音を出す事を意味します。

//スピーカーから500Hz,1000Hzの音を表示
void setup() {
}

void loop() {
  tone(12, 500, 100);   //500Hz 100ms
  delay(1000);          //1000ms停止
  
  tone(12, 1000, 100);  //1000Hz 100ms
  delay(1000);          //1000ms停止
}

プログラム:ColorLED
 視覚フィードバックに用いるカラーLEDの使用方法を学びます。カラーLEDの制御には、Adafruit_NeoPixelライブラリを用います(ライブラリのインストール方法は動画をご覧ください)。プログラム冒頭で、デジタルポートの13番を制御用に使うこと、LEDの数が1個であることを定義しています。プログラムは、setup関数で初期化を行い、loop関数でLEDの点灯を処理しています。色のセットは、pixels.clear→pixels.setPixelColor→pixels.showの順で行います。pixels.Color(R, G, B)の形式で、色の強さは0~255で指定します。

//カラーLEDを用い、赤・緑・青色を点灯
#include <Adafruit_NeoPixel.h>
#define PIN 13        //制御用ピン
#define NUMPIXELS 1   //LEDの数
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();     //LEDを初期化
}

void loop() {
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(20, 0, 0));  //赤
  pixels.show();
  delay(1000);    //1000ms停止

  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 20, 0));  //緑
  pixels.show();
  delay(1000);    //1000ms停止

  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 0, 20));  //青
  pixels.show();
  delay(1000);    //1000ms停止
}

プログラム:TempABF
 皮膚温変化を聴覚刺激として2値(上昇もしくは下降)でフィードバックします。上昇・下降は23行において求められる皮膚温変化量によって判定します。25行から27行では、温度変化に応じて上昇下降の方向を求めています。29行から34行では、変化の方向に応じてフィードバックする音を、500Hzもしくは1000Hzに振り分けています。36行から41行は、現在温度や変化量をシリアルモニタに表示しています。

//皮膚温を聴覚でフィードバック
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
}

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;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    if(dir==1){
      if(mag>0){tone(12, 500, 10);}                     //温度上昇時は500Hz
    }
    if(dir==-1){
      if(mag>0){tone(12, 1000, 10);}                    //温度下降時は1000Hz
    }

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

プログラム:TempVBF
 皮膚温変化を視覚刺激として色と強さでフィードバックします。具体的には、上昇時は赤、下降時は青でLEDが点灯し、皮膚温の変化量が大きいほど明るく光ります。変化量の算出、上昇・下降の判定方法はTempABFと同様です。視覚フィードバックを処理する34行から41行では、上昇の場合に

1
pixels.Color(mag, 0, 0)
で赤色に、下降の場合には
1
pixels.Color( 0, 0,mag)
で青色に、LEDを点灯させています。magには、皮膚温変化量tempdを100倍した値が入っていますので、変化量が大きいほど強く光ります。それ以外の処理は、TempABFと同様です。

//皮膚温を視覚でフィードバック
#include <Adafruit_NeoPixel.h>
#define PIN        13 
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
  pixels.begin();               //LEDを初期化
}

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;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    pixels.clear();
    if(dir==1){
      pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
    }
    if(dir==-1){
      pixels.setPixelColor(0, pixels.Color( 0, 0,mag)); //温度下降を青で表示
    }
    pixels.show();

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

プログラム:TempVABF
 TempVABFは、視覚・聴覚、双方のフィードバックを行うものです。内容は、TempVBFに聴覚フィードバック処理(37・41行部分)を追加しただけです。

//皮膚温を視聴覚でフィードバック
#include <Adafruit_NeoPixel.h>
#define PIN        13 
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
  pixels.begin();               //LEDを初期化
}

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;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    pixels.clear();
    if(dir==1){
      pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
      if(mag>0){tone(12, 500, 10);}                     //温度上昇時は500Hz
    }
    if(dir==-1){
      pixels.setPixelColor(0, pixels.Color( 0, 0,mag)); //温度下降を青で表示
      if(mag>0){tone(12, 1000, 10);}                    //温度下降時は1000Hz
    }
    pixels.show();

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

補足

・温度センサーに関して
現在温度センサーLM35DZは入手が難しくなっており、TMP36GT9Zを代用することができます。その場合は、温度算出部分を下記のように変更してください。

temp1=average*0.1-50;    //電圧を温度に変換

・使用した電子部品の入手先
ArduinoUNO
TMP36GT9Z
カラーLEDモジュール
スピーカー
ピンヘッダー
収縮チューブ
細径3心並列線