SPRESENSEメインボードで "Pulse Sensor" で有名なアナログ心拍センサーを動かしてみました。アナログの心拍センサーはお安いのに加え、心拍計のように波形もとれるので個人的には好きです。





SPRESENSEメインボードで心拍センサーを動かすことの注意点をいくつかまとめてみました。

■ 心拍センサーの出力3.3V出力を分圧して入力
心拍センサーは3.3V電源で動作しますが、SPRESENSEメインボードのADCは0.7Vなので心拍センサーの信号出力を分圧する必要があります。今回、分圧抵抗は省電力にするため330kΩと100kΩを使いました。


■ 割り込み関数内では ”analogRead” が使えない
最初、Pulse Sensor のサンプルプラグラム同様に、割り込み関数内で心拍数カウントをするプログラムを作りましたが、なんと analogRead が割り込み関数内で使うことができないことが発覚。チュートリアルを見ると SPRESENSE は割り込み内で使える関数に制限があるようなので要注意です。


■ 体動により大きく値が変動する
この方式は体動により値が大きく変動するので、それをキャンセル必要があります。心拍データに対して変動は非常に大きいので平均値をとって変動分を差し引く必要があります。





平均の算出は比較的大きなメモリが必要になります。SPRESENSEののマイク入力のA/DはAC入力でDC成分は排除できるので、心拍センサーを取り込むにはマイク入力のほうがいいかも知れません。


■ 心拍の心拍ピーク(P)と心拍基準値(T)の設定
プログラム内で心拍のピーク(P)と心拍の基準値(T)は、心拍の波形を確認してから値を決めてください。しきい値はP値とT値の半分を設定すれば安定して心拍を検出してくれるようです。
https://pulsesensor.com/pages/pulsesensor-playground-toolbox




■ SPRESENSEのLEDを心拍表示に使う
SPRESENSEのLEDは"analogWrite"に対応していないようです。ですので4つのLEDを使って表示を工夫しました。また、割り込み内で処理できなかったので、LED処理は task_create で別スレッドにしました。


■ SPRESENSE の Pulse Sensor 読み取りスケッチ(暫定版)
以上の点を踏まえて作成したスケッチです。


#define N (10)
#define INTERVAL (2000) // 2 msec

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

int Rate[N];
unsigned long CurrBeatTime = 0;
unsigned long LastBeatTime = 0;
int P = 570;
int T = 510;
int Threshold = 540;
int Amplifier = 100;

static bool bInterval = false;
int PulseSensorPin = 2;

#define AVE_WINDOW 64
#define SIG_WINDOW 4
int Average[AVE_WINDOW];
int Signals[SIG_WINDOW];
static int ave_num = 0;
static int sig_num = 0;
void pulse_check(void) {

CurrBeatTime = millis(); // msec
unsigned long interval = CurrBeatTime - LastBeatTime;

Signal = analogRead(PulseSensorPin);
Signals[sig_num] = Signal;
Average[ave_num] = Signal;
int mean_val = 0;
for (int i = 0; i < AVE_WINDOW; ++i) {
mean_val += Average[i];
}
mean_val /= AVE_WINDOW;
if (++ave_num >= AVE_WINDOW) ave_num = 0;

int sum = 0;
for (int i = 0; i < SIG_WINDOW; ++i) {
sum += Signals[i];
}
Signal = sum/SIG_WINDOW - mean_val + 512;
if (++sig_num >= SIG_WINDOW) sig_num = 0;

Serial.println(Signal);

// hold bottom
if ((Signal < Threshold) && (interval > (IBI*3) / 5)) {
if (Signal < T) T = Signal;
}

// hold peak
if (Signal > Threshold && Signal > P) {
P = Signal;
}

// calculate BPM
if (interval > 250 /* ms */) {

if ((Signal > Threshold) && !Pulse && (interval > (IBI*3) / 5)) {
Pulse = true;
IBI = interval;
LastBeatTime = CurrBeatTime;

if (Rate[0] < 0) { // first time
Rate[0] = 0;
return;
} else if (Rate[0] == 0) { // second time
for (int i = 0; i < N; ++i) Rate[i] = IBI;
}

uint16_t running_total = 0;
for (int i = 0; i < N-1; ++i) {
Rate[i] = Rate[i+1];
running_total += Rate[i];
}

Rate[N-1] = IBI;
running_total += IBI;
running_total /= N;
BPM = 60000 / running_total;
QS = true;
}
}

// check if Signal is under Threshold
if ((Signal < Threshold) && Pulse) {
Pulse = false;
Amplifier = P - T;
Threshold = Amplifier / 2 + T; // revise Threshold
P = Threshold;
T = Threshold;
}

// check if no Signal is over 2.5 sec
if (interval > 2500 /* ms */) {
Threshold = 515;
P = 520;
T = 510;
LastBeatTime = CurrBeatTime;
for (int i = 0; i < N; ++i) {
Rate[i] = -1;
}
}

return;
}

unsigned int interval_check() {
bInterval = true;
return INTERVAL;
}

void led_control() {
static int led_pattern[5] = {0, 0x01, 0x03, 0x07, 0x0f};
static int fade = 0;

while (true) {
if (QS) {
fade = 4;
QS = false;
}

digitalWrite(LED0, (led_pattern[fade] & 0x08));
digitalWrite(LED1, (led_pattern[fade] & 0x04));
digitalWrite(LED2, (led_pattern[fade] & 0x02));
digitalWrite(LED3, (led_pattern[fade] & 0x01));
if (--fade < 0) fade = 0;
usleep(100000);
}
}

void setup() {
Serial.begin(115200);
Serial.println("Start pulse sensor!");
LastBeatTime = millis(); // msec
attachTimerInterrupt(interval_check, INTERVAL);
task_create("led control", 120, 1024, led_control, NULL);

}

void loop() {
if (bInterval) {
pulse_check();
bInterval = false;
}
usleep(1000);
}



■ SPRESENSE で Pulse Sensor を動かしてみた

あまり綺麗なコードではありませんが、一応動きましたのでその様子を動画にしました。




そのうちマイク入力でも試してみたいと思います!
(^^)/~








Interface(インターフェース) 2015年 04 月号

  • 出版社/メーカー: CQ出版
  • 発売日: 2015/02/25
  • メディア: 雑誌