基于Arduino 开发 MAX30102 LM35 SSD1306 观察血氧、心率和温度血氧仪

2 篇文章 1 订阅
1 篇文章 2 订阅

 ↵本项目第一版本实现在arduino框架下通过MAX30102 对血氧和心率 进行实时监控,通过LM35 对温度进行监控 。所有数值在 ssd 1306 上进行显示。在血氧低过一定数值的时,设备会通过蜂鸣器发出警报。

第二版本实现手机实时监控并做数据分析(后续更新)

第三版本实现远程监控(后续更新)

**********************************************

本文面向完全新手的arduino及无编程经验人员。让大家低成本的制作一台血氧仪实时监控自己和家人的健康状态。

PS:单本设备不能替代专业医疗血氧仪,仅作补充使用

器材:

LM 35 温度传感器

SSD 1306 OLED 显示器

MAX30102 心率传感器

杜邦线 公母 公公 母母

蜂鸣器 micro bit51

arduino uno 

面包板

绝缘胶布

接线

 

 

LM35

arduinoLM35
5VVCC
GNDGND
A1S

 SSD1306

arduinoSSD1306
3V33V3
GNDGND
A4SDA
A5SCL

蜂鸣器

arduino蜂鸣器
5VVCC
GNDGND
A1S

MAX30102

arduinoMAX30102
3V33V3
GNDGND
A4SDA
A5SCL

******************************

如 max30102,ssd1306 同时需要连接A4 时,可以先连接面包板再连接进Arduino A4。

*****************************

按照上述接线完成后,需要用到软件arduino

Software | Arduino

 选择你要的版本;

搜索并安装以下库,点击install 安装 

 

 将代码复制进项目里:

#include <MAX3010x.h>
#include "filters.h"
#include <Adafruit_GFX.h>        //OLED   libraries
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//#include <MAX30105extra.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Sensor (adjust to your sensor type)
MAX30105 sensor;
//MAX30105extra particleSensor;
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;

// Finger Detection Threshold and Cooldown
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;

// Edge Detection Threshold (decrease for MAX30100)
const float kEdgeThreshold = -2000.0;

// Filters
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;

// Averaging
const bool kEnableAveraging = false;
const int kAveragingSamples = 5;
const int kSampleThreshold = 5;

// limitation of sop2
const int spo2limit =95;
void setup() {
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC,   0x3C); //Start the OLED display
  delay(3000);
  tone(3,1000);                                        //And   tone the buzzer for a 100ms you can reduce it it will be better
  delay(1000);
  noTone(3); 
  display.display();
  if(sensor.begin() && sensor.setSamplingRate(kSamplingRate)) { 
    Serial.println("Sensor initialized");
  }
  else {
    Serial.println("Sensor not found");  
    while(1);
  }
}

// Filter Instances
LowPassFilter low_pass_filter_red(kLowPassCutoff, kSamplingFrequency);
LowPassFilter low_pass_filter_ir(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
MovingAverageFilter<kAveragingSamples> averager_bpm;
MovingAverageFilter<kAveragingSamples> averager_r;
MovingAverageFilter<kAveragingSamples> averager_spo2;

// Statistic for pulse oximetry
MinMaxAvgStatistic stat_red;
MinMaxAvgStatistic stat_ir;

// R value to SpO2 calibration factors
// See https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
float kSpO2_A = 1.5958422;
float kSpO2_B = -34.6596622;
float kSpO2_C = 112.6898759;

// Timestamp of the last heartbeat
long last_heartbeat = 0;

// Timestamp for finger detection
long finger_timestamp = 0;
bool finger_detected = false;

// Last diff to detect zero crossing
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;

int lowsopcount =0;

void loop() {

  unsigned int val; //定义变量val
  unsigned int dat;//定义变量dat
  val=analogRead(1);//将val设置为读取到的A0的数值
  dat=(500 * val) /1024; //计算出当前温度数字dat
  auto sample = sensor.readSample(1000);
  float current_value_red = sample.red;
  float current_value_ir = sample.ir;
  // Detect Finger using raw sensor value
  if(sample.red > kFingerThreshold) {
    if(millis() - finger_timestamp > kFingerCooldownMs) {
      finger_detected = true;
    }
  }
  else {
    // Reset values if the finger is removed
    differentiator.reset();
    averager_bpm.reset();
    averager_r.reset();
    averager_spo2.reset();
    low_pass_filter_red.reset();
    low_pass_filter_ir.reset();
    high_pass_filter.reset();
    stat_red.reset();
    stat_ir.reset();
    
    finger_detected = false;
    finger_timestamp = millis();
  }

  if(finger_detected) {
    current_value_red = low_pass_filter_red.process(current_value_red);
    current_value_ir = low_pass_filter_ir.process(current_value_ir);

    // Statistics for pulse oximetry
    stat_red.process(current_value_red);
    stat_ir.process(current_value_ir);

    // Heart beat detection using value for red LED
    float current_value = high_pass_filter.process(current_value_red);
    float current_diff = differentiator.process(current_value);

    // Valid values?
    if(!isnan(current_diff) && !isnan(last_diff)) {
      
      // Detect Heartbeat - Zero-Crossing
      if(last_diff > 0 && current_diff < 0) {
        crossed = true;
        crossed_time = millis();
      }
      
      if(current_diff > 0) {
        crossed = false;
      }
  
      // Detect Heartbeat - Falling Edge Threshold
      if(crossed && current_diff < kEdgeThreshold) {
        if(last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
          // Show Results
          int bpm = 60000/(crossed_time - last_heartbeat);
          float rred = (stat_red.maximum()-stat_red.minimum())/stat_red.average();
          float rir = (stat_ir.maximum()-stat_ir.minimum())/stat_ir.average();
          float r = rred/rir;
          float spo2 = kSpO2_A * r * r + kSpO2_B * r + kSpO2_C;
          
          if(bpm > 50 && bpm < 250) {
            // Average?
            if(kEnableAveraging) {
              int average_bpm = averager_bpm.process(bpm);
              int average_r = averager_r.process(r);
              int average_spo2 = averager_spo2.process(spo2);
              
              // Show if enough samples have been collected
              if(averager_bpm.count() >= kSampleThreshold) {
                Serial.print("Time (ms): ");
                Serial.println(millis()); 
                Serial.print("Heart Rate (avg, bpm): ");
                Serial.println(average_bpm);
                Serial.print("R-Value (avg): ");
                Serial.println(average_r);  
                Serial.print("SpO2 (avg, %): ");
                Serial.println(average_spo2);
                if( average_spo2 >100) average_spo2 = 100;  
                display.clearDisplay();                                   //Clear the display       
                display.setTextSize(2);                                   //Near   it display the average BPM you can display the BPM if you want
                display.setTextColor(WHITE);   
                display.setCursor(15,0);                
                display.println("BPM");              
                display.setCursor(70,0);                
                display.println(bpm);
                display.setCursor(15,18);                
                display.println("SpO2");              
                display.setCursor(70,18);                
                display.println((int)average_spo2);  
                display.setCursor(15,36);                
                display.println("TMP");              
                display.setCursor(70,36);                
                display.println((int)dat);  

                display.display();

                if ((int)average_spo2 < spo2limit){
                  lowsopcount++;
                  if (lowsopcount >3) {
                      tone(3,1000);                                        //And   tone the buzzer for a 100ms you can reduce it it will be better
                      delay(1000);
                      noTone(3);                    
                  }
                }
                if((int)average_spo2 >spo2limit)    lowsopcount = 0;             
              }
            }
            else {
              Serial.print("Time (ms): ");
              Serial.println(millis()); 
              Serial.print("Heart Rate (current, bpm): ");
              Serial.println(bpm);  
              Serial.print("R-Value (current): ");
              Serial.println(r);
              Serial.print("SpO2 (current, %): ");
              Serial.println(spo2);
              if( spo2 >100) spo2 = 100;   
              display.clearDisplay();                                   //Clear the display       
              display.setTextSize(2);                                   //Near   it display the average BPM you can display the BPM if you want
              display.setTextColor(WHITE);   
              display.setCursor(15,0);                
              display.println("BPM");              
              display.setCursor(70,0);                
              display.println(bpm);
              display.setCursor(15,18);                
              display.println("SpO2");              
              display.setCursor(70,18);                
              display.println((int)spo2);
              display.setCursor(15,36);                
              display.println("TMP");              
              display.setCursor(70,36);                
              display.println((int)dat);    
              display.display();
              if ((int)spo2 < spo2limit){
                lowsopcount++;
                  if (lowsopcount >3) {
                      tone(3,1000);                                        //And   tone the buzzer for a 100ms you can reduce it it will be better
                      delay(1000);
                      noTone(3);                    
                  }
                }
              if((int)spo2 >spo2limit)    lowsopcount = 0;  
            }
          }

          // Reset statistic
          stat_red.reset();
          stat_ir.reset();
        }
  
        crossed = false;
        last_heartbeat = crossed_time;
      }
    }

    last_diff = current_diff;
  }
}

将arduino 插入电脑中

 选择你所用的arduino uno板

 选择你的Port 

***********************************

每台电脑的Port 口可能不一样,不影响代码导入

**********************************

 点击上传按钮将代码烧录进arduino uno里

 显示upload success,显示器显示adafruit的图案(杨桃),蜂鸣器发出声音表示代码正常导入arduino中。

将下面的代码命名为filters.h 并放在一起

#ifndef FILTERS_H
#define FILTERS_H

/**
 * @brief Statistic block for min/nax/avg
 */
class MinMaxAvgStatistic {
  float min_;
  float max_;
  float sum_;
  int count_;
public:
  /**
   * @brief Initialize the Statistic block
   */
  MinMaxAvgStatistic() :
    min_(NAN),
    max_(NAN),
    sum_(0),
    count_(0){}

  /**
   * @brief Add value to the statistic
   */
  void process(float value) {  
    min_ = min(min_, value);
    max_ = max(max_, value);
    sum_ += value;
    count_++;
  }

  /**
   * @brief Resets the stored values
   */
  void reset() {
    min_ = NAN;
    max_ = NAN;
    sum_ = 0;
    count_ = 0;
  }

  /**
   * @brief Get Minimum
   * @return Minimum Value
   */
  float minimum() const {
    return min_;
  }

  /**
   * @brief Get Maximum
   * @return Maximum Value
   */
  float maximum() const {
    return max_;
  }

  /**
   * @brief Get Average
   * @return Average Value
   */
  float average() const {
    return sum_/count_;
  }
};

/**
 * @brief High Pass Filter 
 */
class HighPassFilter {
  const float kX;
  const float kA0;
  const float kA1;
  const float kB1;
  float last_filter_value_;
  float last_raw_value_;
public:
  /**
   * @brief Initialize the High Pass Filter
   * @param samples Number of samples until decay to 36.8 %
   * @remark Sample number is an RC time-constant equivalent
   */
  HighPassFilter(float samples) :
    kX(exp(-1/samples)),
    kA0((1+kX)/2),
    kA1(-kA0),
    kB1(kX),
    last_filter_value_(NAN),
    last_raw_value_(NAN){}

  /**
   * @brief Initialize the High Pass Filter
   * @param cutoff Cutoff frequency
   * @pram sampling_frequency Sampling frequency
   */
  HighPassFilter(float cutoff, float sampling_frequency) :
    HighPassFilter(sampling_frequency/(cutoff*2*PI)){}

  /**
   * @brief Applies the high pass filter
   */
  float process(float value) { 
    if(isnan(last_filter_value_) || isnan(last_raw_value_)) {
      last_filter_value_ = 0.0;
    }
    else {
      last_filter_value_ = 
        kA0 * value 
        + kA1 * last_raw_value_ 
        + kB1 * last_filter_value_;
    }
    
    last_raw_value_ = value;
    return last_filter_value_;
  }

  /**
   * @brief Resets the stored values
   */
  void reset() {
    last_raw_value_ = NAN;
    last_filter_value_ = NAN;
  }
};

/**
 * @brief Low Pass Filter 
 */
class LowPassFilter {
  const float kX;
  const float kA0;
  const float kB1;
  float last_value_;
public:
  /**
   * @brief Initialize the Low Pass Filter
   * @param samples Number of samples until decay to 36.8 %
   * @remark Sample number is an RC time-constant equivalent
   */
  LowPassFilter(float samples) :
    kX(exp(-1/samples)),
    kA0(1-kX),
    kB1(kX),
    last_value_(NAN){}

  /**
   * @brief Initialize the Low Pass Filter
   * @param cutoff Cutoff frequency
   * @pram sampling_frequency Sampling frequency
   */
  LowPassFilter(float cutoff, float sampling_frequency) :
    LowPassFilter(sampling_frequency/(cutoff*2*PI)){}

  /**
   * @brief Applies the low pass filter
   */
  float process(float value) {  
    if(isnan(last_value_)) {
      last_value_ = value;
    }
    else {  
      last_value_ = kA0 * value + kB1 * last_value_;
    }
    return last_value_;
  }

  /**
   * @brief Resets the stored values
   */
  void reset() {
    last_value_ = NAN;
  }
};

/**
 * @brief Differentiator
 */
class Differentiator {
  const float kSamplingFrequency;
  float last_value_;
public:
  /**
   * @brief Initializes the differentiator
   */
  Differentiator(float sampling_frequency) :
    kSamplingFrequency(sampling_frequency),
    last_value_(NAN){}

  /**
   * @brief Applies the differentiator
   */
  float process(float value) {  
      float diff = (value-last_value_)*kSamplingFrequency;
      last_value_ = value;
      return diff;
  }

  /**
   * @brief Resets the stored values
   */
  void reset() {
    last_value_ = NAN;
  }
};

/**
 * @brief MovingAverageFilter
 * @tparam buffer_size Number of samples to average over
 */
template<int kBufferSize> class MovingAverageFilter {
  int index_;
  int count_;
  float values_[kBufferSize];
public:
  /**
   * @brief Initalize moving average filter
   */
  MovingAverageFilter() :
    index_(0),
    count_(0){}

  /**
   * @brief Applies the moving average filter
   */
  float process(float value) {  
      // Add value
      values_[index_] = value;

      // Increase index and count
      index_ = (index_ + 1) % kBufferSize;
      if(count_ < kBufferSize) {
        count_++;  
      }

      // Calculate sum
      float sum = 0.0;
      for(int i = 0; i < count_; i++) {
          sum += values_[i];
      }

      // Calculate average
      return sum/count_;
  }

  /**
   * @brief Resets the stored values
   */
  void reset() {
    index_ = 0;
    count_ = 0;
  }

  /**
   * @brief Get number of samples
   * @return Number of stored samples
   */
  int count() const {
    return count_;
  }
};

#endif // FILTERS_H

备注:

Max30102可以在外围一圈包裹上绝缘胶布以提高其精准性

杜邦线也可以用绝缘胶布进行稳定

代码在upload 的时候可能会出错显示有的库未找到,再次upload就行。

  • 21
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
Arduino心率血氧检测是一种基于Arduino平台的健康监测系统,可用于测量人体的心率血氧饱和度。该系统主要由Arduino开发板、心率传感器和血氧传感器组成。 心率传感器是一种通过测量心跳信号来检测心率的器件。它通常通过放置在用户的皮肤上,检测皮肤上的微弱电信号来获取心率数据。传感器会将检测到的信号传输给Arduino开发板,然后开发板通过运行相应的程序处理信号并计算出心率值。 血氧传感器则通过测量氧气在人体组织中的浓度来检测血氧饱和度。该传感器通常通过红外光和红外光散射来测量氧气的浓度。传感器将测量到的数据传输给Arduino开发板处理。 Arduino开发板是整个系统的核心。它负责接收传感器的数据,并使用预先编写的程序进行处理和计算。开发板上的微控制器可以实现数据的采集、处理和存储,还可以将结果以可视化的形式显示在LCD屏幕上或通过与计算机通信进行储存和分析。 Arduino心率血氧检测系统具有体积小巧、成本低廉、易于操控等优点。它可以在家庭环境中进行个体心脏健康的监测,也可以在医疗机构中进行心电图的采集和分析。此外,由于Arduino平台开放源代码,用户可以根据自身需要进行二次开发。 总之,Arduino心率血氧检测系统是一种便携、实用的健康监测工具,可以帮助用户更好地了解自身的心脏健康状况,并及时采取相应的措施。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值