아두이노(arduino) 심박센서 (heart rate sensor) 심박수 측정 example code

하드 정리 중 예전에 만든 아두이노에서 심박센서를 사용하여 심박수 측정하는 소스 코드가 있어, 이를 블로그 글로 남긴다.

인터넷 쇼핑몰에서 쉽게 구할 수 있는 저렴함 센서가 사용 되었으며, 연결은 아래 그림과 같이 연결 되었다. 
  
심박센서의 하트 부위에 손가락을 올려 놓고, 아날로그 A0값을 읽으면 심박의 움직임에 따라 A0값이 변하는 동작이 간단한 센서이다. 아래 그림의 왼쪽은 일정 시간 간격 마다 A0값을 읽어 시리얼 플로터로 본 심박에 따른 데이터이고, 오른쪽은 평균 필터를 사용해 노이즈를 제거한 후 시리얼 플로터로 본 데이터이다.
 
심박수는 수집된 데이터에서 threshold를 넘는 peak들의 시간 간격을 측정하여 계산한다. 
peak위치를 찾는 방법은 여러 가지 방법이 있으나, 소스에 구현된 방법은 신호의 기울기가 +에서 0또는 -로 바뀌는 위치를 peak으로 선정하도록 하였다.
심장이 t시간동안 N회 뛰는 경우 심박수는 heart rate bpm = (N/t)*60 을 표현 할 수 있다.
 
threshold는 데이터를 보고 임의로 정했으며, 측정 데이터에 따라 자동으로 정하고 싶을 때는 cfar같은 방법을 사용하면 된다. 
소스는 아래와 같다.


#define heartbit_adc_threshold      511
#define meanfilter_size             5
#define heartbit_tick_buffer_size   10
#define measure_buffer_size       3

float meanfilter[meanfilter_size] = {0,};
float measure_buffer[measure_buffer_size] = {0,};
unsigned long heartbit_tick[heartbit_tick_buffer_size] = {0,};
float heartrate_bpm = 0;
float prev_heartrate_bpm = 0;
unsigned long last_heartrate_bpm_update_tick = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  for (int i = 0; i < meanfilter_size; i++)
  {
    meanfilter[i] = 0;
  }

  clear_measure_buffer();

  heartrate_bpm = 0;
  prev_heartrate_bpm = 0;
}

float filtering(float val)
{
  int i = 0;
  float mean = 0;
  int cnt = 0;
  float max_val = 0;
  float min_val = 1024;

  for (i = 1; i < meanfilter_size; i++)
  {
    meanfilter[i - 1] = meanfilter[i];
  }

  meanfilter[meanfilter_size - 1] = val;

  for (int i = 0; i < meanfilter_size; i++)
  {
    if (meanfilter[i])
    {
      mean += meanfilter[i];
      cnt++;
      if (max_val < meanfilter[i])
      {
        max_val = meanfilter[i];
      }

      if (min_val > meanfilter[i])
      {
        min_val = meanfilter[i];
      }
    }
  }

  if (max_val)
  {
    mean -= max_val;
    cnt--;
  }

  if (min_val != 1024 && min_val != max_val)
  {
    mean -= min_val;
    cnt--;
  }

  if (cnt > 0)
  {
    return mean / cnt;
  }

  return val;
}

float get_trend(float *signals, int n)
{
  float x = 0, y = 0, a = 0, b = 0;
  float sy = 0.0,
        sxy = 0.0,
        sxx = 0.0;
  int i = 0;

  x = (-n / 2.0 + 0.5);

  while (i < n)
  {
    y = signals[i];
    sy += y;
    sxy += x * y;
    sxx += x * x;

    i++;
    x += 1.0;
  }

  b = sxy / sxx;
  a = sy / n;

  return b;
}

void clear_measure_buffer()
{
  for (int i = 0; i < heartbit_tick_buffer_size; i++)
  {
    heartbit_tick[i] = 0;
  }

}

float prev_trend = 0;
boolean found_high = false;

float measure_heartbitrate(float val, float threshold)
{
  int i = 0;
  float cur_trend = 0;

  found_high = false;

  for (i = 1; i < measure_buffer_size; i++)
  {
    measure_buffer[i - 1] = measure_buffer[i];
  }

  measure_buffer[measure_buffer_size - 1] = val;

  cur_trend = get_trend(measure_buffer, measure_buffer_size);


  if (prev_trend > 0 && cur_trend <= 0 && val > threshold )
  {
    found_high = true;
  }
  prev_trend = cur_trend;


  if (found_high)
  {
    for (i = 1; i < heartbit_tick_buffer_size; i++)
    {
      heartbit_tick[i - 1] = heartbit_tick[i];
    }

    heartbit_tick[heartbit_tick_buffer_size - 1] = millis();

    if (heartbit_tick[0] != 0 && heartbit_tick[heartbit_tick_buffer_size - 1] != 0)
    {
      float timedur = (float)heartbit_tick[heartbit_tick_buffer_size - 1] - (float)heartbit_tick[0];
      float bpm = (((heartbit_tick_buffer_size-1) * 1000) / timedur) * 60;

      if(bpm < 50 || bpm > 120)
      {
        // wrong bpm...
        clear_measure_buffer();
        return 0;
      }

      return bpm;
    }
  }

  return 0;
}

void loop() {
  // put your main code here, to run repeatedly:

  float cur_measure_val = 0;
  unsigned long cur_time_ms = millis();

  float heartbeat_level = filtering(analogRead(A0));

  cur_measure_val = measure_heartbitrate(heartbeat_level, heartbit_adc_threshold);
  if (cur_measure_val)
  {
    heartrate_bpm = cur_measure_val;
    last_heartrate_bpm_update_tick = cur_time_ms;
  }
  else
  {
    if (cur_time_ms - last_heartrate_bpm_update_tick >= 10 * 1000)
    {
      heartrate_bpm = 0;
      last_heartrate_bpm_update_tick = cur_time_ms;
      clear_measure_buffer();
    }
  }

  /*
  */
#if 0
  Serial.print(heartbeat_level);
  Serial.print(" ");
  //Serial.print(heartrate_bpm);
  //Serial.print(" ");

  if (found_high)
  {
    Serial.print(heartbeat_level);
  }
  else
  {
    Serial.print(heartbit_adc_threshold);

  }

  Serial.print("\n");
  #else
    if (prev_heartrate_bpm != heartrate_bpm)
  {
    if (heartrate_bpm)
    {
      Serial.print("HeartBeat Rate :");
      Serial.print(heartrate_bpm);
      Serial.println(" bpm");
    }
    else
    {
      Serial.println("HeartBeat Rate : ..... ");
    }
    
    prev_heartrate_bpm = heartrate_bpm;
  }
#endif

  /**/
  cur_time_ms = millis() - cur_time_ms;
  if (cur_time_ms < 10)
  {
    delay(10 - cur_time_ms);
  }
}


아래 그림은 실제 심박수를 측정한 결과다.


댓글

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

쉽게 설명한 파티클 필터(particle filter) 동작 원리와 예제

리눅스 디바이스 드라이버 기초와 예제

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법