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

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

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

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

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

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

器材:

LM 35 温度传感器

SSD 1306 OLED 显示器

MAX30102 心率传感器

杜邦线 公母 公公 母母

蜂鸣器 micro bit51

arduino uno 

面包板

绝缘胶布

接线

LM35

arduino LM35
5V VCC
GND GND
A1 S

SSD1306

arduino SSD1306
3V3 3V3
GND GND
A4 SDA
A5 SCL

蜂鸣器

arduino 蜂鸣器
5V VCC
GND GND
A1 S

MAX30102

arduino MAX30102
3V3 3V3
GND GND
A4 SDA
A5 SCL

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

如 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 displaydelay(3000);tone(3,1000);                                        //And   tone the buzzer for a 100ms you can reduce it it will be betterdelay(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; //定义变量valunsigned int dat;//定义变量datval=analogRead(1);//将val设置为读取到的A0的数值dat=(500 * val) /1024; //计算出当前温度数字datauto sample = sensor.readSample(1000);float current_value_red = sample.red;float current_value_ir = sample.ir;// Detect Finger using raw sensor valueif(sample.red > kFingerThreshold) {if(millis() - finger_timestamp > kFingerCooldownMs) {finger_detected = true;}}else {// Reset values if the finger is removeddifferentiator.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 oximetrystat_red.process(current_value_red);stat_ir.process(current_value_ir);// Heart beat detection using value for red LEDfloat 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-Crossingif(last_diff > 0 && current_diff < 0) {crossed = true;crossed_time = millis();}if(current_diff > 0) {crossed = false;}// Detect Heartbeat - Falling Edge Thresholdif(crossed && current_diff < kEdgeThreshold) {if(last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {// Show Resultsint 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 collectedif(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 wantdisplay.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 betterdelay(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 wantdisplay.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 betterdelay(1000);noTone(3);                    }}if((int)spo2 >spo2limit)    lowsopcount = 0;  }}// Reset statisticstat_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 valuevalues_[index_] = value;// Increase index and countindex_ = (index_ + 1) % kBufferSize;if(count_ < kBufferSize) {count_++;  }// Calculate sumfloat sum = 0.0;for(int i = 0; i < count_; i++) {sum += values_[i];}// Calculate averagereturn 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就行。

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

  1. 查看gpio状态_基于Arduino开发,借助blinker平台,让NodeMCU实现8路继电器APP端状态监视和控制...

    基于Arduino开发,借助blinker物联网平台,让NodeMCU实现8路继电器APP端状态监视和控制 先上开发板功能图: 本文利用了NodeMCU上的D0-D3,D5-D9,总共 8个 GPIO ...

  2. 利用HFS软件一分钟搭建好ESP8266基于Arduino开发环境

    利用HFS一分钟搭建好ESP8266基于Arduino开发环境

  3. arduino编码器计数_基于Arduino开发环境的光电编码器检测仪设计方案 - 全文

    0 引言 Arduino是一款基于单片机系统的电子产品开发平台,它的软硬件系统都具有高度的模块化,而且软件系统是完全开源的.其硬件系统也是高度模块化的,在核心控制板的外围有开关量输入/输出模块.各种模 ...

  4. Mixly(米思齐)的安装以及基于Arduino开发板实现电容触摸控制灯

    Mixly(米思齐)的安装以及基于Arduino开发板实现电容触摸控制灯 1.Mixly下载 http://mixly.org/bnu-maker/mixly-arduino-win Mixly软件安 ...

  5. 基于Arduino开发板的火焰报警器

    ** 基于Arduino开发板的火焰报警器* 概述 本文将通过使用火焰传感器与Arduino UNO组成一个简单的火灾报警系统.该火焰传感器是基于IR(红外线 Infrared Radiation)的 ...

  6. 基于STM32F030、MAX30102血氧心率监测仪的设计(三)

    本篇主要记录一下开发过程中遇到的问题与解决. max30102模块是某宝上购买的,价格不贵5元左右,如下图所示. 使用引脚为SDA.SCL.INT.VIN.GND 拿到模块后,看着资料开始移植程序,我 ...

  7. STM32WB55大半年开发记录,血氧心率检测手环

    1.STM32WB55开发经验 在长达大半年的STM32WB55蓝牙手环开发的过程当中,让我感受到了这款芯片的魅力和ST为其倾力打造的生态环境是真的很不错! 不过在开发STM32WB55这款芯片的时候 ...

  8. 血氧心率测量仪(带温湿度测量功能)OLED显示

    血氧心率测量仪(带温湿度测量功能)OLED显示 原理图资料 模块说明 测试数据处理图 部分代码展示 资料包 原理图资料 模块说明 数字温湿度传感 DHT11 ►相对湿度和温度测量 ►全部校准,数字输出 ...

  9. spo2数据集_自己翻译的Max30100寄存器中文资料(血氧心率传感器IC)

    Max30100 可穿戴光电式的血氧心率传感器IC 总体描述: Max30100是一款集成的脉搏血氧和心率检测传感器.它使用了两个LED灯,一个用来优化光学的光电探测器,和低噪声模拟信号处理器,用来检 ...

最新文章

  1. linux中断共享程序实现,如何在非实时linux上实现实时应用程序与内核模块之间共享存储器...
  2. 史上最全ajax全套讲解
  3. 一线大厂在机器学习方向的面试题(一)
  4. css:hover伪类的使用
  5. 我是一个请求,我是如何被发送的?
  6. Lucene入门程序
  7. IT人的春节对联集锦
  8. Mysql入门经典.pdf下载
  9. PyQt设置右下角弹窗
  10. 《解读基金》 基金中统计指标含义-平均回报、标准差、夏普比率、阿尔法系数、贝塔系数、R平方
  11. springcloud以及四大神兽面试涉及知识总结(持续更新)
  12. 网络与信息安全-第三章-对称秘钥加密算法
  13. SHELL DATE 命令详解
  14. android qq apk,仿QQ获取手机中的APK并分享的实现
  15. 嵌入式Linux之我行——C+CGI+Ajax在S3C244
  16. Vue_实现五星好评效果
  17. 金融行业IT运维现状问题和发展方向
  18. PingCAP 与 DSG 达成战略合作,共同开启数据智能管理新篇章
  19. JS 键盘事件、触摸事件
  20. 《性能优化》并发与并行

热门文章

  1. mybatis的一级缓存详解
  2. 画中画效果怎么给视频快速的制作?
  3. 微信H5支付----报undened index openid
  4. 微信小程序计算圆周长和面积
  5. iOS 与 Android 系统十年之战,究竟谁是赢家?
  6. CDH平台DATANODE数据块阀值参数设置
  7. 数论概论读书笔记 21.-1是模p平方剩余吗? 2呢
  8. unity 3d物体显示和2dUI显示相结合
  9. 为什么要有认证及其实现方式
  10. Windows上安装MySQL数据库(完整版)