DIY低成本的血氧仪

硬件:ESP8266(8.1RMB),MAX30102(8.5RMB),0.96寸IIC的OLED(10.7RMB)

连线:

SCL->D1(max30102与oled一样)

SDA->D2(max30102与oled一样)

按键一端接地,一端接D5

先上成品图(原谅它只是个原型机)

效果的话,这是与鱼跃的对比图,emmmmm,在数据稳定后,二者相差不大,效果图如下:

通过观察他们的算法应该是初始值设置成100,然后去拟合真实数据,而DIY的是由80去拟合真实数据,所以在数据开始到稳定的时间,DIY的会要30秒左右。这方面为了求数据真实性更好,就没做调整,因为如果将稳定时间缩短,会出现数据跳动幅度大的问题。

直接上源码:

#include <Wire.h>
#include "MAX30105.h" //sparkfun MAX3010X library
MAX30105 particleSensor;
#include <Arduino.h>
#include <U8g2lib.h>
#define bmp1_x 64
#define bmp1_y 64
 
static const unsigned char bmp1[] U8X8_PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD0,0x00,0x00,0x00,0x00,0x0F,0x00,
0x00,0x90,0x07,0x00,0x00,0xC0,0x08,0x00,0x00,0x08,0x0F,0x00,0x00,0x70,0x10,0x00,0x00,0x08,0x1E,0x00,0x00,0x38,0x10,0x00,0x00,0x08,0x7D,0x00,0x00,0x1E,0x10,0x00,
0x00,0x08,0xFA,0x00,0x00,0x0F,0x30,0x00,0x00,0x04,0xF4,0x03,0xC0,0x07,0x20,0x00,0x00,0x04,0xE8,0x07,0xE0,0x13,0x30,0x00,0x00,0x04,0xFA,0xFF,0xFF,0x0B,0x20,0x00,
0x00,0x44,0xD4,0xFF,0xFF,0x27,0x20,0x00,0x00,0x84,0xE8,0xFF,0xFF,0x9F,0x30,0x00,0x00,0x04,0xF1,0xFF,0xFF,0x5F,0x30,0x00,0x00,0x04,0xFA,0x3F,0xFE,0x3F,0x10,0x00,
0x00,0x80,0xFC,0x1F,0xFC,0x7F,0x10,0x00,0x00,0x08,0xFE,0x0F,0xF8,0x7F,0x10,0x00,0x00,0x08,0xFF,0x07,0xF0,0xFF,0x10,0x00,0x00,0x88,0xFF,0x03,0xE0,0xFF,0x11,0x00,
0x00,0x88,0x0F,0x03,0xE0,0xF0,0x09,0x00,0x00,0xD0,0x03,0x00,0x00,0xC0,0x0B,0x00,0x00,0xD0,0x01,0x00,0x00,0x80,0x07,0x00,0x00,0xE0,0xE1,0x09,0x80,0x07,0x07,0x00,
0x00,0xE0,0xF0,0x03,0xC0,0x0F,0x07,0x00,0x00,0xE0,0xF8,0x17,0xE0,0x1F,0x07,0x00,0x00,0xE0,0xF8,0x07,0xE0,0x1F,0x07,0x00,0x00,0xE0,0xF8,0x07,0xE0,0x1F,0x07,0x00,
0x00,0xE0,0xF8,0x07,0xE0,0x1F,0x07,0x00,0x00,0xE0,0xF1,0x03,0xC0,0x8F,0x07,0x00,0x00,0xE0,0xE1,0x91,0x11,0xC3,0x07,0x00,0x00,0xE0,0x03,0x00,0x20,0xE0,0x07,0x00,
0x00,0xC0,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xC0,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x40,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x02,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x00,0x00,
0x00,0x00,0x08,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x0C,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
#define TIMEOUT 30 //Time out second to sleep
#define BOOTSOUND 440 //Hz
#define BLIPSOUND 440*2 //Hz A
// beep sounder
#define LEDC_CHANNEL_2     2
#define LEDC_TIMER_13_BIT  13
#define LEDC_BASE_FREQ     5000
int reset_key=0;
double aveRed = 0;//DC component of RED signal
double aveIr = 0;//DC component of IR signal
double sumIrRMS = 0; //sum of IR square
double sumRedRMS = 0; // sum of RED square
unsigned int i = 0; //loop counter
#define SUM_CYCLE 50
int Num = SUM_CYCLE ; //calculate SpO2 by this sampling interval
double eSpO2 = 95.0;//initial value of estimated SpO2
double fSpO2 = 0.7; //filter factor for estimated SpO2
double fRate = 0.95; //low pass filter for IR/red LED value to eliminate AC component

#define TIMETOBOOT 3000 // wait for this time(msec) to output SpO2
#define SCALE 88.0 //adjust to display heart beat and SpO2 in Arduino serial plotter at the same time
#define SAMPLING 1 //if you want to see heart beat more precisely , set SAMPLING to 1
#define FINGER_ON 50000 // if ir signal is lower than this , it indicates your finger is not on the sensor
#define MINIMUM_SPO2 80.0
#define MAX_SPO2 100.0
#define MIN_SPO2 80.0

void initSensor() {
  //Setup to sense a nice looking saw tooth on the plotter
  byte ledBrightness = 0x7F; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  //Options: 1 = IR only, 2 = Red + IR on MH-ET LIVE MAX30102 board
  int sampleRate = 200; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 16384; //Options: 2048, 4096, 8192, 16384
  // Set up the wanted parameters
  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
}
  char m_str[3];
  int start_time,end_time,flag;
void setup()
{

  Serial.begin(115200);
  Serial.println("Initializing...");
  u8g2.begin();
  u8g2.enableUTF8Print();
  pinMode(14, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(14), handleInterrupt, FALLING);
  BEGIN();
 // Wire.begin(6,10);
  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
  {
    Serial.println("MAX3010X was not found.");
    Serial.println("Go to sleep. Bye");
  }
  initSensor();
}
ICACHE_RAM_ATTR void handleInterrupt() {
  //interruptCounter++;
   reset_key=1;
}
//
// Heart Rate Monitor by interval of zero crossing at falling edge
// max 180bpm - min 45bpm
#define FINGER_ON 70000 // if ir signal is lower than this , it indicates your finger is not on the sensor
#define LED_PERIOD 100 // light up LED for this period in msec when zero crossing is found for filtered IR signal
#define MAX_BPS 180
#define MIN_BPS 45
double HRM_estimator( double fir , double aveIr)
{
  static double fbpmrate = 0.95; // low pass filter coefficient for HRM in bpm
  static uint32_t crosstime = 0; //falling edge , zero crossing time in msec
  static uint32_t crosstime_prev = 0;//previous falling edge , zero crossing time in msec
  static double bpm = 70.0;
  static double ebpm = 70.0;
  static double eir = 0.0; //estimated lowpass filtered IR signal to find falling edge without notch
  static double firrate = 0.85; //IR filter coefficient to remove notch , should be smaller than fRate
  static double eir_prev = 0.0;


  // Heart Rate Monitor by finding falling edge
  eir = eir * firrate + fir * (1.0 - firrate); //estimated IR : low pass filtered IR signal
  if ( ((eir - aveIr) * (eir_prev - aveIr) < 0 ) && ((eir - aveIr) < 0.0)) { //find zero cross at falling edge
    crosstime = millis();//system time in msec of falling edge
    //Serial.print(crosstime); Serial.print(","); Serial.println(crosstime_prev);
    if ( ((crosstime - crosstime_prev ) > (60 * 1000 / MAX_BPS)) && ((crosstime - crosstime_prev ) < (60 * 1000 / MIN_BPS)) ) {
      bpm = 60.0 * 1000.0 / (double)(crosstime - crosstime_prev) ; //get bpm
      //   Serial.println("crossed");
      ebpm = ebpm * fbpmrate + (1.0 - fbpmrate) * bpm;//estimated bpm by low pass filtered
    } else {
      //Serial.println("faild to find falling edge");
    }
    crosstime_prev = crosstime;
  }
  eir_prev = eir;
  return (ebpm);
}
int time_s=0; 
unsigned int loopCnt = 0;
double SpO2 = 0; //raw SpO2 before low pass filtered
double Ebpm;//estimated Heart Rate (bpm)
double max_SpO2,max_Ebpm;
void loop()
{
  if(reset_key==1){
    flag=0;
    initSensor();
    reset_key=0;
     BEGIN();
     start_time=0,end_time=0;
    }
  uint32_t ir, red ;//raw data
  double fred, fir; //floating point RED ana IR raw values
  SpO2=0;
  Ebpm=0;
  particleSensor.check(); //Check the sensor, read up to 3 samples
  if(flag!=2){
  while (particleSensor.available()) {//do we have new data
    red = particleSensor.getFIFOIR(); //why getFOFOIR output Red data by MAX30102 on MH-ET LIVE breakout board
    ir = particleSensor.getFIFORed(); //why getFIFORed output IR data by MAX30102 on MH-ET LIVE breakout board

    i++; loopCnt++;
    fred = (double)red;
    fir = (double)ir;
    aveRed = aveRed * fRate + (double)red * (1.0 - fRate);//average red level by low pass filter
    aveIr = aveIr * fRate + (double)ir * (1.0 - fRate); //average IR level by low pass filter
    sumRedRMS += (fred - aveRed) * (fred - aveRed); //square sum of alternate component of red level
    sumIrRMS += (fir - aveIr) * (fir - aveIr);//square sum of alternate component of IR level

    Ebpm = HRM_estimator(fir, aveIr); //Ebpm is estimated BPM

    if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino Serial plotter by decimation
      if ( millis() > TIMETOBOOT) {
        float ir_forGraph = 2.0 * (fir - aveIr) / aveIr * SCALE + (MIN_SPO2 + MAX_SPO2) / 2.0;
        float red_forGraph = 2.0 * (fred - aveRed) / aveRed * SCALE + (MIN_SPO2 + MAX_SPO2) / 2.0;
        //trancation to avoid Serial plotter's autoscaling
        if ( ir_forGraph > MAX_SPO2) ir_forGraph = MAX_SPO2;
        if ( ir_forGraph < MIN_SPO2) ir_forGraph = MIN_SPO2;
        if ( red_forGraph > MAX_SPO2 ) red_forGraph = MAX_SPO2;
        if ( red_forGraph < MIN_SPO2 ) red_forGraph = MIN_SPO2;
        //        Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print(".");
        if ( ir < FINGER_ON) eSpO2 = MINIMUM_SPO2; //indicator for finger detached
#define PRINT
#ifdef PRINT
        //Serial.print(bpm);// raw Heart Rate Monitor in bpm
        //Serial.print(",");
        if(ir > FINGER_ON&&flag==0){
          flag=1;
          Serial.print("开始测试");
          u8g2.clearBuffer();
          start_time=millis();
          }
          
          if(flag==1){
            end_time=millis();
           // time_s++;
            time_s=(int)((end_time-start_time)/350);
          if(max_SpO2<eSpO2){
            max_SpO2=eSpO2;
              }
          print_result();    
         //   TEST();
         }
         
     if(ir < FINGER_ON&&flag==1){
            flag=2;
            Errors();  
            break;
              }
        Serial.print(ir);// estimated Heart Rate Monitor in bpm
        Serial.print(",");
        Serial.print(Ebpm);// estimated Heart Rate Monitor in bpm
        Serial.print(",");
        //        Serial.print(Eir - aveIr);
        //        Serial.print(",");
        Serial.print(ir_forGraph); // to display pulse wave at the same time with SpO2 data
        Serial.print(","); Serial.print(red_forGraph); // to display pulse wave at the same time with SpO2 data
        Serial.print(",");
        Serial.print(eSpO2); //low pass filtered SpO2
        Serial.print("\n"); //low pass filtered SpO2

#else
        Serial.print(fred); Serial.print(",");
        Serial.print(aveRed); Serial.println();
        //    Serial.print(fir);Serial.print(",");
        //   Serial.print(aveIr);Serial.println();
#endif
      }
    }
    if ((i % Num) == 0) {
      double R = (sqrt(sumRedRMS) / aveRed) / (sqrt(sumIrRMS) / aveIr);
      // Serial.println(R);
      //#define MAXIMREFDESIGN
#ifdef MAXIMREFDESIGN
      //https://github.com/MaximIntegratedRefDesTeam/RD117_ARDUINO/blob/master/algorithm.h
      //uch_spo2_table is approximated as  -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
      SpO2 = -45.060 * R * R + 30.354 * R + 94.845 ;
      //      SpO2 = 104.0 - 17.0*R; //from MAXIM Integrated https://pdfserv.maximintegrated.com/en/an/AN6409.pdf
#else
#define OFFSET 0.0
      SpO2 = -23.3 * (R - 0.4) + 100 - OFFSET ; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf
      if (SpO2 > 100.0 ) SpO2 = 100.0;
#endif
      eSpO2 = fSpO2 * eSpO2 + (1.0 - fSpO2) * SpO2;//low pass filter
      //  Serial.print(SpO2);Serial.print(",");Serial.println(eSpO2);
      sumRedRMS = 0.0; sumIrRMS = 0.0; i = 0;//reset mean square at every interval
      break;
    }
    particleSensor.nextSample(); //We're finished with this sample so move to next sample
    //Serial.println(SpO2);
  }

}}
void BEGIN(){
  u8g2.clearBuffer();
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.drawXBMP(0,0, bmp1_x, bmp1_y, bmp1);
    u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
    u8g2.drawUTF8(65, 10, "1.坐下平静");
    u8g2.drawUTF8(65, 25, "2.放入食指");
    u8g2.drawUTF8(65, 40, "3.轻轻按压");
    u8g2.drawUTF8(65, 55, "4.等待30秒");
  } while ( u8g2.nextPage() );
  }
void TEST(){
    char m_str[3];
    strcpy(m_str, u8x8_u8toa(time_s, 3));    /* convert m to a string with two digits */
    u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
    u8g2.drawUTF8(40, 10, "测试中....");
    u8g2.drawUTF8(25, 25, "请保持平静状态");
    u8g2.drawUTF8(15, 40, "以免测试结果不准确");
    u8g2.drawFrame(0,50,102,12);
    u8g2.drawLine(time_s,51,time_s, 61);
    u8g2.drawStr(103,60,m_str);
    u8g2.drawStr(122,60,"%");
    u8g2.sendBuffer();
  } 
void Errors(){
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_ncenB10_tr);
    u8g2.drawStr(20,20,"! ! Errors ! !");  // write something to the internal memory
    u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
    u8g2.drawUTF8(30, 40, "请重新检测");
    u8g2.drawUTF8(10, 55, "按下按键重新检测");       
    u8g2.sendBuffer(); 
  }
void RESULT(){
    u8g2.clearBuffer();
    char m_str[3];
    int eSP=0,BP=0;
    if(eSpO2>(int)max_SpO2){
      eSP=(int)max_SpO2+1;
      }
    else{
      eSP=(int)max_SpO2;
      }
    if(Ebpm>(int)Ebpm){
      BP=(int)Ebpm+1;
      }
    else{
      BP=(int)Ebpm;
      }
    u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
    u8g2.drawUTF8(10, 10, "测试结果如下");    
    strcpy(m_str, u8x8_u8toa((int)eSP, 3));
    u8g2.drawUTF8(10, 25, "血氧 :");
    u8g2.drawStr(40,25,m_str);   
    strcpy(m_str, u8x8_u8toa((int)BP, 3));
    u8g2.drawUTF8(30, 40, "心率 :");   
    u8g2.drawStr(70,40,m_str);   
    u8g2.drawUTF8(10, 55, "按下按键重新检测");   
    u8g2.sendBuffer(); 
  }
void print_result(){
    u8g2.clearBuffer();
    char m_str[3];
    int eSP=0,BP=0;
    if(eSpO2>(int)eSpO2){
      eSP=(int)eSpO2+1;
      }
    else{
      eSP=(int)eSpO2;
      }
    if(Ebpm>(int)Ebpm){
      BP=(int)Ebpm+1;
      }
    else{
      BP=(int)Ebpm;
      }
    u8g2.setFont(u8g2_font_wqy12_t_gb2312b);
    u8g2.drawUTF8(10, 10, "测试结果如下");    
    strcpy(m_str, u8x8_u8toa((int)eSP, 3));
    u8g2.drawUTF8(10, 25, "血氧 :");
    u8g2.drawStr(30,25,m_str);   
    strcpy(m_str, u8x8_u8toa((int)BP, 3));
    u8g2.drawUTF8(70, 25, "心率 :");   
    u8g2.drawStr(100,25,m_str);   
    u8g2.drawUTF8(10, 55, "按下按键重新检测");   
    u8g2.sendBuffer();   
  
  
  
  }

有什么问题可以提出改进

猜你喜欢

转载自blog.csdn.net/qq_50395641/article/details/128671145