Arduinoと心拍センサーを使って生体信号である脈拍を計測してみる
この記事はこんな人にオススメです
  • 脈拍測定器を低価格で実装したい人
  • 生体信号を研究している人
  • Arduinoで何か試作したい人

こんにちは.けんゆー(@kenyu0501_)です.
Arduinoで使用できるセンサーは沢山のものがありますよね!
光センサーや人感センサーなどは有名ですが,心拍センサーはあまり馴染みがないのではないでしょうか.
(商品が心拍センサーで出ているので,そうしてます.)

今日はそんな心拍センサー(パルスセンサーともいう)とArduinoを使用して,指先の脈拍を計測しました!
(心拍と脈拍は厳密には異なりますが,不整脈でない限り,心臓の拍動によって血液が体を巡るため,健康体だと同じです.)

こちらです!各LEDの色は以下に対応してます

  • 緑 →  脈拍のビート自体
  • 赤 →  緑のビートの間隔

緑と赤は,ほとんど同様です.
緊張度が高いと,パルスが出現する間隔が狭くなるので,緑も赤も早くなります.

このブログでは,心拍センサの計測原理からプログラムまでを網羅したいと思います.

心拍センサの計測原理

光を皮膚に当て計測する「光学式」

血液の流れの変化を捉えることで計測する方法で,手軽かつ比較的正確に計測が可能です.
手軽というのは,「計測するための金銭的コストと,痛くない(非侵襲)計測」を言います.

人の目に,血液が赤く映るのは,血が赤色を反射しているわけで,言い換えれば,緑などの色はすごく吸収されるのですよね.

血中のヘモグロビンはセンシティブに良く緑色を吸収しますが,血液は流れているので計測箇所のヘモグロビンの量は変化します.

そのため,吸収度合いと,反射度合いは血液の流れによって決まります.

また血液の流れは,不整脈ではない場合,心臓の拍動に寄るので,だいたい1秒間に1回のピークを持った,光の反射度合いならぬ脈拍値が計測されるということになります.

また,光学式は割と皮膚接触がなされていたら,精度良く取れます.
(Youtube動画を見てください)

そのため,Apple Watch 3でも4でも,ヘルスモニタリングに光学式の心拍計が入ってます.
(Apple Watch 4では,同時に電気信号も取ってるので更に高精度!)

 

個人差による精度の問題点

常々,光学式の心拍計で懸念されることは,個人差による問題点でしょうか.

ヘモグロビンの量によって脈拍が計測されるので,ヘモグロビン量が多い人と少ない人では,脈拍の計測のされ方が違うことですね.

推定脈拍の計測値の最大値と最小値をとって,状態を把握しておけば問題ないと考えますが,工夫次第ではなんとかなりそうです.

計測機器の回路と動作プログラム

試作回路図

さてさて,先ほどYoutube動画で実際に脈拍を計測した際の回路をプログラムを示していきます.

配線このような感じです!
脈拍をパルスセンサで計測して,その出力としてLED点滅をさせるということを行います.

プログラミング

プログラミングに関しては,こちらのgithubから転用しました.
自分のアーカイブ用に載せてますが,git様からダウンロードしてください.

Arduinoで使用するときは,以下3つのプログラムを使用します.
それぞれ

  • PulseSensorAmped_Arduino_1.5.0
  • Interrupt
  • AllSerialHandling

です.同階層上のフォルダに入れて使ってください.

#define PROCESSING_VISUALIZER 1
#define SERIAL_PLOTTER  2

int pulsePin = 0;
int blinkPin = 13;
int fadePin = 5;
int fadeRate = 0;

volatile int BPM;
volatile int Signal;
volatile int IBI = 600;
volatile boolean Pulse = false;
volatile boolean QS = false;

static int outputType = SERIAL_PLOTTER;


void setup(){
  pinMode(blinkPin,OUTPUT); 
  pinMode(fadePin,OUTPUT);
  Serial.begin(115200); 
  interruptSetup();
}

void loop(){

    serialOutput() ;

  if (QS == true){

        fadeRate = 255;
        serialOutputWhenBeatHappens();
        QS = false;
  }

  ledFadeToBeat();
  delay(20);
}


void ledFadeToBeat(){
    fadeRate -= 15;
    fadeRate = constrain(fadeRate,0,255);
    analogWrite(fadePin,fadeRate);
  }
volatile int rate[10];
volatile unsigned long sampleCounter = 0;
volatile unsigned long lastBeatTime = 0;
volatile int P =512;
volatile int T = 512;
volatile int thresh = 530;
volatile int amp = 0;
volatile boolean firstBeat = true;
volatile boolean secondBeat = false;


void interruptSetup(){
  TCCR2A = 0x02;
  TCCR2B = 0x06;
  OCR2A = 0X7C;
  TIMSK2 = 0x02;
  sei();
}

ISR(TIMER2_COMPA_vect){
  cli();
  Signal = analogRead(pulsePin);
  sampleCounter += 2;
  int N = sampleCounter - lastBeatTime;
  
  if(Signal < thresh && N > (IBI/5)*3){
    if (Signal < T){
      T = Signal;
    }
  }

  if(Signal > thresh && Signal > P){
    P = Signal;
  }

  if (N > 250){
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ){
      Pulse = true;
      digitalWrite(blinkPin,HIGH);
      IBI = sampleCounter - lastBeatTime;
      lastBeatTime = sampleCounter;

      if(secondBeat){
        secondBeat = false;
        for(int i=0; i<=9; i++){
          rate[i] = IBI;
        }
      }

      if(firstBeat){
        firstBeat = false;
        secondBeat = true;
        sei();
        return; 
      }

      word runningTotal = 0;
      for(int i=0; i<=8; i++){
        rate[i] = rate[i+1];
        runningTotal += rate[i];
      }

      rate[9] = IBI;
      runningTotal += rate[9];
      runningTotal /= 10;
      BPM = 60000/runningTotal; 
      QS = true;
    }
  }

  if (Signal < thresh && Pulse == true){
    digitalWrite(blinkPin,LOW);
    Pulse = false;
    amp = P - T;
    thresh = amp/2 + T;
    P = thresh;
    T = thresh;
  }

  if (N > 2500){
    thresh = 530;
    P = 512;
    T = 512;
    lastBeatTime = sampleCounter;
    firstBeat = true;
    secondBeat = false;
  }

  sei();
}
void serialOutput(){
  switch(outputType){
    case PROCESSING_VISUALIZER:
      sendDataToSerial('S', Signal);
      break;
    case SERIAL_PLOTTER: 
      Serial.print(BPM);
      Serial.print(",");
      Serial.print(IBI);
      Serial.print(",");
      Serial.println(Signal);
      break;
    default:
      break;
  }

}

//  Decides How To OutPut BPM and IBI Data
void serialOutputWhenBeatHappens(){
  switch(outputType){
    case PROCESSING_VISUALIZER:
      sendDataToSerial('Q',IBI);
      break;

    default:
      break;
  }
}

void sendDataToSerial(char symbol, int data ){
    Serial.print(symbol);
    Serial.println(data);
  }

何回も記述しますが,プログラミングに関しては,他作成者が無料で後悔しているgithubから転用しましたので,そちらから取ってくださいね!

他にも,Switch Science公式HPにも各種パルスセンサの利用についてリンクがあるので,そちらの有益な情報が乗っていました.

また,こちらのページには,心拍センサを使ったいろんなプロジェクトのライブラリがありました!

データを取得して数値化する方法

また,Arduinoから取得したファイルをtxtデータで数値化する方法を書いています.
興味があれば読んでみてください.