近來非常流行運動手錶的穿戴式裝置,除了有 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
張貼留言