Arduino筆記(42):NodeMCU-使用OLED顯示心律脈搏感測 Pulse Sensor

近來非常流行運動手錶的穿戴式裝置,除了有 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 顯示心律相關實作的過程。

[名詞解釋]

(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 12864NodeNCUPulse Sensor
VCC3.3VVCC
GNDGNDGND
SCK/SCLPin D1-
SDAPin D2-
-A0Sensor

[程式]

#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
} 
 

[執行結果]

串列埠執行的狀況 :

[參考資料]

Post a Comment

較新的 較舊