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

2023-05-16

 ↵本项目第一版本实现在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中。

备注:

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

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

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

对于MAX30102,最坑的地方在于,如果使用sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library,那么读取到的IR和RED是反的。表现为可以读取到稳定的心率,却始终无法获取稳定的血氧, SPO2经常是-999。拿Example8_SPO2举例,原始代码为:


  
  1. redBuffer[i] = particleSensor.getRed();

  2. irBuffer[i] = particleSensor.getIR();

应改为


  
  1. redBuffer[i] = particleSensor.getIR();

  2. irBuffer[i] = particleSensor.getRed();

似乎是因为max30102和max30105的这个信息是反的。

这部分可以用下面的链接验证,RED应该要比IR的波动小才对:coniferconifer/ESP32_MAX30102_simple-SpO2_plotter: Simple SpO2 plotter from scratch for Arduno IDE by reading MH-ET LIVE MAX30102 raw RED/IR data (github.com)

---------------------
作者:winddoll
来源:CSDN
原文:https://blog.csdn.net/winddoll/article/details/128602470
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 加解密篇 - 对称加密算法 (DES、3DES、AES、RC)

    这篇文章来讲讲对称加密 xff0c 对称加密在开发中用的很多 xff0c 如 AES xff0c DES xff0c 3DES xff0c RC 目录 介绍优点和缺点DES算法3DES算法AES算法RC算法 1 介绍 1 1 概念 采用单钥
  • 加解密篇 - 非对称加密算法 (RSA、DSA、ECC、DH)

    最近的文章中多次出现了非对称加密 xff0c 今天就来分析一下非对称加密的算法 目录 xff1a 简介RSA算法DSA算法ECC算法DH算法 1 简介 1 1 概念 非对称加密需要两个密钥 xff1a 公钥 publickey 和私钥 pr
  • 跨平台技术篇 - 使用 Flutter 与原生技术混合开发示例

    目前主流的混合开发方案有两种集成方式 xff1a 源码集成 xff1a 也就是谷歌官方提供的方案 https github com flutter flutter wiki Add Flutter to existing apps 产物集成
  • 音视频篇 - Android 音视频涉及到的技术

    前言 现在市面上的图像 xff0c 音视频软件越来越多 xff0c 最近两年也是直播 xff0c 短视频的红利期 而图像 音视频一直是互联网视觉的入口 xff0c 掌握并熟练运用音视频 图像技术已经是当前互联网时代不可或缺的技能 xff0c
  • sockaddr_in的一个小理解

    之前一直认为是sockaddr 设计时有缺陷 xff0c 在编写网络通信时 xff0c 都使用sockaddr in xff0c 因为它将sockaddr 中的 char sa data 14 拆分为了 unsigned short sin
  • Linux虚拟机下WWW(HTTP)服务器的搭建与使用(详细)

    1 简介 1 1 关于www服务器 WWW服务器是被动程序 xff0c 只有接收到互联网中其他计算机发出的请求后才会响应 xff0c 然后WWW服务器才会使用HTTP或者HTTPS将指导文件传输到客户机的浏览器上 1 2 关于HTTP协议
  • 非抢占式优先级调度算法

    Priority scheduling is a type of scheduling algorithm used by the operating system to schedule the processes for executi
  • Eclipse中使用jstl标签库

    在MyEclipse中使用jstl标签只需导读jstl jar就能使用 xff0c 但是在Eclipse中还需要一点小套路 步骤 xff1a 一 导入jstl jar 二 导入导入standard jar 三 web xml加上如下配置 l
  • hibernate HQL 投影查询 + 多表关联

    64 Test public void testQueryProjection Session session 61 HibernateUtils openSession session beginTransaction 操作 hql 默认
  • NetEQ 算法

    NetEQ 算法中集成了自适应抖动控制算法以及语音包丢失隐藏算法 这项技术使其能够快速且高解析度地适应不断变化的网络环境 xff0c 确保音质优美且缓冲延迟最小 研究的重点是 NetEQ 模块 xff0c 其中所涉及的处理过程包括抖动消除
  • 数组的形参与实参,通过引用传递数组

    数组形参 xff1a void printValues int void printValues int void printValues int 10 以上三种是等价的 通常 xff0c 将数组形参直接定义为指针要比使用语法定义更好 第三
  • java 简单员工管理系统

    package cn test import java io import java util class Emp 64 Override public String toString return 34 Emp empNo 61 34 4
  • 关闭虚拟机vmware自动挂起

    1 xff0c 桌面右键 属性 xff0c 里的屏幕保护程序 xff0c 选 无 2 xff0c 控制面板的电源选项 xff0c 方案为 一直开着 xff0c 具体选项选 从不关机 3 xff0c 在Windows 2003 Server点
  • GTK+2.0之初始学习篇(二)—— g_signal_connect宏解释及HelloWorld

    GTK 43 2 0中利用信号 回调函数机制来处理窗口外部传来的事件 消息或信号 以下实现的是单击窗口关闭按钮 xff0c 窗口自动关闭 在程序中调用了gtk main quit 函数实现退出主循环 include lt gtk gtk h
  • linux-----页、页表、页框(块)

    基本介绍 我们知道 xff0c 在linux操作系统中 xff0c CPU在执行一个进程的时候 xff0c 都会访问内存 但CPU并不是直接访问物理内存地址 xff0c 而是通过虚拟地址空间来间接的访问物理内存地址 所谓的虚拟地址空间 xf
  • CMake交叉编译配置

    罗列一下cmake常用的命令 CMake支持大写 小写 混合大小写的命令 1 添加头文件目录INCLUDE DIRECTORIES 语法 xff1a include directories AFTER BEFORE SYSTEM dir1
  • 8086CPU的14个寄存器全称

    通用寄存器 xff1a ax accumulate register 累加器 bx based register 基地址寄存器 cx count register 计数器 dx data registered 数据寄存器 段寄存器 xff1
  • 项目开发-后台管理框架

    开发中几乎的平台都需要一个后台管理 xff0c 但是从零开发一套后台控制面板并不容易 xff0c 幸运的是有很多开源免费的后台控制面板可以给开发者使用 xff0c 那么有哪些优秀的开源免费的控制面板呢 xff1f 我在 Github 上收集
  • 项目经理必备工具-个人推荐

    点击链接 项目经理必备工具 进入详细说明
  • 如何绕过CDN找源站ip?

    如何绕过CDN找源站ip xff1f 这是一个总结帖 xff0c 查了一下关于这个问题的国内外大大小小的网站 xff0c 对其中说的一些方法总结归纳形成 xff0c 里面具体发现ip的方法不是原创 xff0c 所有参考的原贴都也贴在了后面

随机推荐

  • Navicat修改MySQL数据库密码就是这么简单

    方法1 xff1a 用SET PASSWORD命令 首先登录MySQL 格式 xff1a mysql gt set password for 用户名 64 localhost 61 password 39 新密码 39 例子 xff1a m
  • Axure 元件库-原型

    元件名称 xff1a 1 Axure交互原型设计指南 rp 2 PC和移动原型常用元件 rp 3 后台模板 rp 4 全局说明 rp https pan baidu com s 1vmac 08MZAKj6qsdjwIZlg 提取码 xff
  • Node.js 通过http调用外部接口

    通过http request发送带参数的post请求 data xff1a 发送的内容 opt xff1a 描述将要发出的请求 data xff1a 事件在数据到达时被触发 end xff1a 请求结束时触发 error xff1a 发生错
  • 想成为出色的 CTO,你要具备这七大能力

    编者按 xff1a 首席技术官这一职位在20世纪80年代出现于美国 起于做很多技术研发的大公司 xff0c 如General Electric 主要职能是将科研成果转化实际效益 简单地说 xff0c 就是一个企业中技术的最高负责人 要扮演好
  • Keil中添加自己的头文件

    xfeff xfeff 方法一 在keil的开发环境下添加 xff1a 请注意上面的系统生成的头文件目录是 xff1a xff0c 即 dd jj pp kk xff0c 其中 是相对于项目文件 uvproj 而言的 其中 表示项目文件所在
  • http authorization 基本认证

    最近做的一个项目需要与其它系统对接接口 xff0c 对方提供的是webservice的接口 xff0c 并且需要Basic Authorization基本认证 xff0c 一开始都是用postman请求 xff0c 用户名和密码需要填在Ba
  • 终端命令安装 chrome for linux

    终端里安装chrome for linux 备注 xff1a 我是在Linux Mint 17 1 64位系统下安装的chrome for linux xff0c 其它Debian衍生版应该也是一样的 1 在终端里输入下载命令 xff1a
  • Arduino串口函数详解

    本文总结了Arduino常用串口操作函数 xff0c 函数说明部分来源于Arduino 官网串口使用指南 xff0c 示例与实验部分为自编 本文是对Arduino串口操作函数的较全面总结 xff0c 可作为工具贴查找使用 1 串口设置 xf
  • STM32CubeMX——霍尔编码器、L298N驱动电机

    前言 人生如逆旅 xff0c 我亦是行人 苏轼 临江仙 送钱穆父 目录 xff1a L298N电机驱动介绍编码器介绍电机介绍新建工程编写代码实验结果 一 L298N电机驱动介绍 B站 视频讲解 xff1a l298n电机驱动模块 电机正反转
  • AT命令拨电话,如何判断手机的状态?

    我使用AT命令拨电话 xff0c 如 xff1a ATD10086 我怎么知道我拨打的电话是否成功了呢 xff1f 比如SIM卡欠费了 xff0c 那么肯定算是没有拨通 xff1b 再比如网络有问题 xff0c 被叫方没有收到来电 xff0
  • GPS模块(GPS-NEO-6M)

    ATK NEO 6M GPS 模块简介 ATK NEO 6M V23 模块 xff0c 是 ALIENTEK 生产的一款高性能 GPS 模块 xff0c 模块核心采用 UBLOX公司的 NEO 6M 模组 xff0c 具有 50 个 通道
  • toCharArray()

    toCharArray 是将一个字符串内容转换为字符数组 xff0c 例如 String str 61 34 abc 34 System out println str toCharArray 43 34 34 将输出a b c 转载于 h
  • 基于Arduino的GPS数据解析程序

    这篇博客讲了我利用arduino来解析和转发原始nema 0813数据的思想和实现方法 因为arduino比较简单 xff0c 无法实现串口数据接收中断 xff0c 所以都写在主循环里面了 不知道代码存在何种缺陷和漏洞 xff0c 欢迎大家
  • VsCode安装和配置c/c++环境(超完整,小白专用)

    文章目录 1 vsCode配置C C 43 43 环境 1 vsCode下载和安装 1 下载Microsoft vsCode2 安装vsCode3 下载中文插件2 MinGW编译器下载和配置 1 下载MinGW2 下载后放到自己方便的目录
  • 各版本esp32和esp8266开发板引脚图(附各开发板特殊通信接口如IIC、SPI接口等默认引脚查看方法)

    目录 esp32 GPIO可用资源 1 esp32开发板 2 esp32开发板 查看特殊通信接口的方法 esp8266 esp32 GPIO可用资源 GPIO 6 11 连接到SPI Flash GPIO 34 39 只能作为输入且没有内部
  • GPS数据解析、可视化及经纬度距离计算

    一 GPS数据解析 根据NMEA协议 xff0c 我们从传感器上接收到的GPS经纬度数据格式如下 xff1a 例 xff1a GPRMC 024813 640 A 3158 4608 N 11848 3737 E 10 05 324 27
  • C语言知识点小结 | 指针 数组 结构体 堆栈 内存分配

    不掌握指针就是没有掌握C的精华 地址指向该变量单元 xff0c 地址即指针 在C C 43 43 语言中定义一个指针 xff0c 就是在栈区开辟一个内存空间用来存放它指向的内存地址 xff0c 然后给指针赋值 xff0c 就是把地址值赋值给
  • 西门子PLC S7-200SMART Modbus TCP通讯的步骤和要点

    Modbus TCP是一个非常传统 xff0c 应用广泛的通讯协议 xff0c 很多智能设备都支持该协议 西门子S7 200SMART及1200 1500系列都免费支持 xff08 300和400还是要高昂收费 xff09 xff0c 并且
  • GPRM/GNRMC定位信息的读取与解析

    GPRM GNRMC定位信息的读取与解析 参考网址 xff1a http www cnblogs com 88223100 p GPRM GNRMC Transform html 帧头 UTC时间 状态 纬度 北纬 南纬 经度 东经 西经
  • 基于Arduino 开发 MAX30102 LM35 SSD1306 观察血氧、心率和温度血氧仪

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