近來非常流行運動手錶的穿戴式裝置,除了有 GPS計算距離及速度外,還有偵測心跳次數的功能。最近上網購買 ESP8266 無線網卡相關的物品,也順手買了一個脈搏心率感測器,試著要來模擬運動手錶偵測脈搏次數的方法。
這個脈搏計量感測器( Pulse Sensor) 是開源的硬體,可以讓開發人員設計程式出和心率有關的互動作品。感測器可以戴在手指或者耳垂上,通過線路與Arduino相連。實質是一款集成了放大電路和雜訊消除電路的光學心率感測器。它還可以透過 Arduino 的 SerialPlot ,將即時的把您的心率用圖線顯示出來。除了Serial Plot顯示心律圖外,還可以用 OLED或其他顯示器呈現心律的狀況,以下就是直接將檢測到的心律狀況,直接透過 OLED呈現出來。
0.96吋 OLED顯示器預設的螢幕解析度是 128 x 32,如何更改 Adafruit_SSD1306.h來調整解析度成 128 x 64,請參考 Arduino筆記(30):NodeMCU 與 OLED 顯示器 這篇文章。
若您手邊沒有 OLED的顯示器,可以參考 Library 中的範例,使用 Serial Plotter 畫出心律圖,本篇實作就不再繼續使用範例說明。
以下說明使用 OLED 顯示心律相關實作的過程。
(2) Beats Per Minute(簡稱 BPM),每一分鐘的心跳數
串列埠執行的狀況 :
這個脈搏計量感測器( Pulse Sensor) 是開源的硬體,可以讓開發人員設計程式出和心率有關的互動作品。感測器可以戴在手指或者耳垂上,通過線路與Arduino相連。實質是一款集成了放大電路和雜訊消除電路的光學心率感測器。它還可以透過 Arduino 的 SerialPlot ,將即時的把您的心率用圖線顯示出來。除了Serial Plot顯示心律圖外,還可以用 OLED或其他顯示器呈現心律的狀況,以下就是直接將檢測到的心律狀況,直接透過 OLED呈現出來。
0.96吋 OLED顯示器預設的螢幕解析度是 128 x 32,如何更改 Adafruit_SSD1306.h來調整解析度成 128 x 64,請參考 Arduino筆記(30):NodeMCU 與 OLED 顯示器 這篇文章。
若您手邊沒有 OLED的顯示器,可以參考 Library 中的範例,使用 Serial Plotter 畫出心律圖,本篇實作就不再繼續使用範例說明。
以下說明使用 OLED 顯示心律相關實作的過程。
[名詞解釋]
(1) InterBeat Interval(簡稱 IBI),是 心臟的各個節拍之間的時間間隔,使用間隔間隔是一個科學術語。IBI通常以毫秒為單位測量。(2) Beats Per Minute(簡稱 BPM),每一分鐘的心跳數
[材料]
- NodeMCU ESP-12E x 1
- OLED 顯示幕 0.96寸 12864 IIC x 1
- PulseSensor 脈衝傳感器 x 1
- 連接線 x 7條
[線路圖]
| OLED 12864 | NodeNCU | Pulse Sensor |
|---|---|---|
| VCC | 3.3V | VCC |
| GND | GND | GND |
| SCK/SCL | Pin D1 | - |
| SDA | Pin D2 | - |
| - | A0 | Sensor |
[程式]
#include <Ticker.h> #include <SPI.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define OLED_RESET 0 Adafruit_SSD1306 display(OLED_RESET); // 設定 Pulse 的顯示畫面 const int WIDTH=128; const int HEIGHT=64; const int LENGTH=WIDTH; // 使用中斷法(interrupt 暫停現在執行中的
// 程序去處理中斷事件, 控制權移轉到中斷處理函數
volatile int rate[10]; // 保留最後 10個 IBI 值
volatile unsigned long sampleCounter = 0;
volatile unsigned long lastBeatTime = 0;
volatile int P =512;
volatile int T = 512;
volatile int thresh = 512;
volatile int amp = 100;
volatile boolean firstBeat = true;
volatile boolean secondBeat = false;
volatile int BPM; // 存放心律變數
volatile int Signal; // 保留讀進來的原始資料
volatile int IBI = 600;
volatile boolean Pulse = false;
volatile boolean QS = false;
// The Ticker/flipper routine
Ticker flipper;
int fadePin = 12;
int fadeRate = 0;
// 顯示用
int x;
int y[LENGTH];
void clearY(){
for(int i=0; i≤LENGTH; i++){
y[i] = -1;
}
}
void drawY(){
display.drawPixel(0, y[0], WHITE);
for(int i=1; i≤LENGTH; i++){
if(y[i]!=-1){
display.drawLine(i-1, y[i-1], i, y[i], WHITE);
}else{
break;
}
}
}
void setup(){
// 初始設定 OLED顯示 I2C addr 0x3C (for the 128x64)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(20);
// 清除緩衝區
display.clearDisplay();
x = 0;
clearY();
pinMode(fadePin,OUTPUT);
Serial.begin(115200);
interruptSetup(); // 設定每 2mS讀取 Pulse Sensor 的訊號
}
void loop(){
// Leave some screen for the text.....
y[x] = map(Signal, 0, 1023, HEIGHT-14, 0);
drawY();
x++;
if(x ≥= WIDTH){
display.clearDisplay();
display.drawLine(0, 51, 127, 51, WHITE);
display.drawLine(0, 63, 127, 63, WHITE);
display.setTextSize(0);
display.setTextColor(WHITE);
display.setCursor(0,54);
display.print(" BPM = ");
display.print(BPM);
display.print(" IBI = ");
display.print(IBI);
display.print(" ");
x = 0;
clearY();
}
sendDataToProcessing('S', Signal);
// 當 arduino發現一個心跳時 Quantified Self(QS)為真
if (QS == true){
fadeRate = 255;
// 送出心律,前面帶一個'B'
sendDataToProcessing('B',BPM);
// 送出心跳間的時間,前面帶一個'Q'
sendDataToProcessing('Q',IBI);
QS = false; // 重新設定
}
display.display();
delay(10);
}
void sendDataToProcessing(char symbol, int data ){
Serial.print(symbol); // 前置符號告知進來的資料為何種型態
Serial.println(data);
}
void interruptSetup(){
// 初始化Ticker 讓 flipper 每 2mS 執行 ISR
flipper.attach_ms(2, ISRTr);
}
// 以下是 TICKER 中斷服務的歷程
// Ticker確認每2 miliseconds 讀一次
void ISRTr(){
cli(); // 停止中斷
Signal = analogRead(A0); // 讀取 Pulse Sensor
sampleCounter += 2;
// 兩個 Beat 間的時間差,避免雜訊
int N = sampleCounter - lastBeatTime;
// 找到脈衝波的最高值
// 等待 3/5 of last IBI
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;
IBI = sampleCounter - lastBeatTime;
lastBeatTime = sampleCounter;
if(secondBeat){ // 假使是第二個心跳, secondBeat == TRUE
secondBeat = false;
for(int i=0; i<=9; i++){
rate[i] = IBI;
}
}
if(firstBeat){ // 第一次的心跳, firstBeat == TRUE
firstBeat = false;
secondBeat = true;
sei(); // 再次啟動interrupts
return;
}
// 保留最後 10個 IBI 值的加總
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){
Pulse = false;
amp = P - T;
thresh = amp/2 + T; // 設定 thresh 為振幅的 50%
P = thresh;
T = thresh;
}
if (N > 2500){ // 假使 2.5 秒沒有脈搏跳動
thresh = 512; // 將變數設為預設值
P = 512;
T = 512;
lastBeatTime = sampleCounter;
firstBeat = true;
secondBeat = false;
}
sei(); // 結束時啟動 interrupts
}
[執行結果]

[參考資料]
- pulsesensor 官方網站:https://pulsesensor.com/pages/code-and-guide
- Instructables: Online Heart Rate Monitor Using NodeMCU and Cayenne
- Github:Pulse-Sensor-ESP8266-ADC0



張貼留言