一、项目背景
实时监测血氧浓度与心率对健康管理至关重要,血氧值低于 90% 提示缺氧风险,心率异常可反映心脏功能状态。本项目从便携健康监测的实际需求出发,结合 Beetle RP2350 开发板的微型化优势与高性能特性,利用其 I²C 接口连接 MAX30102 血氧传感器,打造轻量化、低功耗的生理指标实时监测方案,适用于可穿戴设备、移动医疗等空间受限场景,为个人健康管理提供便携解决方案。

二、学习目标
掌握 Beetle RP2350 开发板的Arduino C 编程方法。
理解 MAX30102 血氧传感器的工作原理,实现生理数据的采集与处理。
完成从硬件连接、程序编写到系统调试的全流程开发。
三、硬件准备
序号 | 名称 | 数量 | 连接说明 |
1 | Beetle RP2350 开发板 | 1 | 主控核心,提供 I²C 通信接口(SDA - 4、SCL - 5)与电源管理 |
2 | MAX30102 血氧传感器模块 | 1 | 通过 I²C 接口与 Beetle 连接,采集心率与血氧数据 |
3 | 锂电池(可选) | 1 | 通过 BAT 接口连接,支持便携供电 |
3.1 Beetle RP2350 开发板
Beetle RP2350 是一款基于树莓派 RP2350 芯片的高性能迷你开发板,专为嵌入式开发和可穿戴设备设计。其核心特点包括:
双核双架构设计:支持 Arm Cortex-M33 或 Hazard3 RISC-V 内核,主频 150MHz,可灵活配置计算资源
低功耗特性:休眠电流仅 25μA,集成锂电池充电管理(支持 0.5A 充电电流)和电池电压监测功能
高度集成:在 25mm×20.5mm 尺寸上引出 11 个 GPIO、I²C、SPI 等接口,支持 3.3V/5V 供电
大容量存储:配备 520KB SRAM 和 2MB Flash,可满足复杂算法需求
开发便捷:支持 Arduino/C++、MicroPython 编程,兼容标准树莓派 Pico 生态

引脚号 | 数字口 | 模拟口 | UART | I2C | SPI | 其它 |
---|---|---|---|---|---|---|
0 | D0 | TX1 | ||||
1 | D1 | RX1 | ||||
4 | D4 | SDA | ||||
5 | D5 | SCL | ||||
8 | D8 | TX2 | ||||
9 | D9 | RX2 | ||||
16 | D16 | SPI0/MISO | ||||
18 | D18 | SPI0/SCK | ||||
19 | D19 | SPI0/MOSI | ||||
26 | D26 | A0 | ||||
27 | D27 | A1 | ||||
29 | ADC,锂电池电压检测引脚,分压比为 1:2 | |||||
BAT | 锂电池输入接口 | |||||
GND | 接地引脚 | |||||
VCC | 该引脚接在USB电源引脚,作为输出时电压为USB电压,通常为5V | |||||
3V3 | 3.3V稳压电源输出 |
3.2 MAX30102 心率血氧传感器
MAX30102 心率血氧传感器模块搭载了美信(maxim)的MAX30102心率血氧芯片和一颗集成心率血氧算法的微控制器。模块内部集成了红外LED、红色光LED和光电检测器,通过红外LED和红色光LED照射皮肤,然后光电检测器采集反射光信号,进而进行血氧饱和度和心率的计算。
光学检测原理:
内置红光 LED(660nm)和红外 LED(880nm),通过皮肤组织发射光线
光电二极管捕捉反射光信号,检测血液容积变化产生的脉搏波
血氧饱和度(SpO2)计算:
氧合血红蛋白(HbO₂)和脱氧血红蛋白(HHb)对不同波长光的吸收特性差异
通过朗伯 - 比尔定律,分析红光 / 红外光吸收比例推算 SpO2 值
心率测量:
提取脉搏波信号的周期性变化,计算每分钟心跳次数
低功耗设计:
工作电流 < 5mA,待机电流仅 0.7μA,适合可穿戴设备应用
接口特性:
标准 I²C 通信接口,支持高速模式(400kHz)


四、接线图
Beetle RP2350 MAX30102 OLED
3.3V VCC VCC
GND GND GND
4 (SDA) SDA SDA
5 (SCL) SCL SCL
五、流程图

六、程序编写
6.1 开发环境
1. Arduino IDE安装Beetle RP2350 开发板
步骤 1:添加开发板管理器网址
打开 Arduino IDE 后,点击菜单栏中的 文件 -> 首选项。
在弹出的 首选项 窗口中,找到 附加开发板管理器网址 输入框。
输入 Beetle RP2350 开发板的支持包链接。通常可以在开发板的官方文档或者社区中找到对应的链接。对于 RP2350 开发板,一般使用的链接是 https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json。
点击 确定 保存设置。

步骤 2:安装 Beetle RP2350 开发板支持
点击菜单栏中的 工具 -> 开发板 -> 开发板管理器,在开发板管理器窗口中,等待索引更新完成。
在搜索框中输入 RP2350。
在搜索结果中找到 Raspberry Pi Pico/RP2040/RP2350 并点击 安装 按钮。

等待安装过程完成,这可能需要一些时间,具体取决于你的网络速度。
步骤 3:选择 Generic RP2350 开发板
安装完成后,点击菜单栏中的 工具 -> 开发板,在开发板列表中选择 Generic RP2350。

步骤 4:选择端口
将 Beetle RP2350 开发板通过 USB 线连接到计算机。点击菜单栏中的 工具 -> 端口,选择与开发板对应的端口。
完成以上步骤后,你就可以在 Arduino IDE 中使用 Beetle RP2350 开发板进行编程和开发了。你可以编写代码并上传到开发板上运行。
2. 安装库文件
在 Arduino IDE 的菜单栏中,选择 工具 -> 管理库,这将打开库管理器窗口。
1.搜索并安装MAX30102
在库管理器的搜索框中输入 MAX30102,然后在搜索结果中找到对应的库。点击 安装 按钮,等待安装完成。这个库是专门为 MAX30102 这类生物传感器设计的驱动库,支持对传感器进行数据读取与配置操作。通过该库,能够方便地从 MAX30102 传感器获取心率和血氧饱和度等生理数据。

2. 搜索并安装 Adafruit GFX Library
在库管理器的搜索框中输入 Adafruit GFX Library,然后在搜索结果中找到对应的库。点击 安装 按钮,等待安装完成。这个库是 Adafruit 图形库,提供了基本的图形绘制功能,许多 Adafruit 的显示设备都会依赖这个库。

3. 搜索并安装 Adafruit SSD1306
在库管理器的搜索框中输入 Adafruit SSD1306,在搜索结果中找到该库后,点击 安装 按钮进行安装。这个库专门用于驱动基于 SSD1306 芯片的 OLED 显示屏,项目中使用的 0.96 寸 OLED 显示屏正是基于此芯片。

6.2 主要程序代码及说明
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include "heartRate.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED分辨率
#define SCREEN_HEIGHT 64 // OLED分辨率
#define OLED_RESET -1 // 复位引脚,不接
#define BATT_PIN 29 // 电池检测引脚(IO29)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
MAX30105 particleSensor;
// 数据缓冲区和计算结果
#define SAMPLE_SIZE 100
uint32_t irBuffer[SAMPLE_SIZE]; // 红外光数据缓冲区
uint32_t redBuffer[SAMPLE_SIZE]; // 红光数据缓冲区
int32_t heartRateValue = 0; // 心率值
int8_t validHeartRate = 0; // 心率计算有效性标志
int32_t spo2Value = 0; // 血氧饱和度值
int8_t validSPO2 = 0; // 血氧计算有效性标志
float temperatureValue = 0.0; // 温度值
const float TEMP_CALIBRATION = 0.5; // 温度校准偏移量
int32_t signalStrength = 0; // 信号强度指标
// 心率计算相关变量
const byte RATE_SIZE = 4; // 心率平均值计算窗口大小
byte rates[RATE_SIZE]; // 心率值数组
byte rateSpot = 0;
long lastBeat = 0; // 上次心跳时间戳
uint32_t beatCount = 0; // 心跳计数
// 显示区域定义
#define STATUS_AREA_Y 0
#define BATTERY_AREA_X 100
#define SPO2_AREA_Y 15
#define HR_AREA_Y 35
#define SIGNAL_AREA_Y 55
// 手指检测稳定性参数
const byte FINGER_STABILIZE_COUNT = 3;
byte fingerStableCount = 0;
// 状态评估常量
enum Status {
STATUS_NORMAL, // 正常
STATUS_MILD_HYPOXIA, // 轻度缺氧
STATUS_SEVERE_HYPOXIA, // 重度缺氧
STATUS_TACHYCARDIA, // 心动过速
STATUS_BRADYCARDIA, // 心动过缓
STATUS_INVALID, // 测量中
STATUS_FINGER_MISSING // 手指未放置
};
// 初始化传感器
bool initializeSensor() {
Serial.println("MAX30102健康监测系统初始化中...");
// 初始化传感器
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("错误:未找到MAX30102传感器,请检查接线和电源!");
return false;
}
// 配置传感器参数(优化信号质量)
byte ledBrightness = 40; // 降低LED亮度,避免信号饱和
byte sampleAverage = 8; // 增加采样平均,提高稳定性
byte ledMode = 2; // 双模模式(红光+红外)
byte sampleRate = 100; // 采样率(100Hz)
int pulseWidth = 411; // 脉冲宽度
int adcRange = 4096; // ADC范围
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);
particleSensor.enableDIETEMPRDY(); // 启用温度测量
Serial.println("传感器就绪");
return true;
}
// 初始化显示屏
void initializeDisplay() {
// 初始化OLED显示屏
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306显示屏初始化失败"));
for(;;);
}
// 显示初始化画面(英文)
clearDisplayArea(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("MAX30105 Health Monitor");
display.setTextSize(2);
display.setCursor(30, 20);
display.println("Initializing...");
display.setTextSize(1);
display.setCursor(0, 50);
display.println("Place finger on sensor");
display.display();
delay(2000);
}
// 清除指定区域
void clearDisplayArea(int16_t x, int16_t y, int16_t w, int16_t h) {
display.fillRect(x, y, w, h, SSD1306_BLACK);
}
// 检测手指是否正确放置(增强稳定性)
bool isFingerPresent() {
while (!particleSensor.available())
particleSensor.check();
uint32_t irValue = particleSensor.getIR();
signalStrength = irValue / 1000; // 计算信号强度指标
// 连续稳定检测到手指才返回true
if (irValue > 50000) {
fingerStableCount = min(fingerStableCount + 1, FINGER_STABILIZE_COUNT);
} else {
fingerStableCount = 0;
}
return fingerStableCount >= FINGER_STABILIZE_COUNT;
}
// 获取电池电量百分比(适配分压电路)
int getBatteryLevel() {
int adcValue = analogRead(BATT_PIN);
float voltage = adcValue * (3.3 / 1023.0) * 2.0;
int batteryPercent = constrain(map(voltage * 100, 300, 370, 0, 100), 0, 100);
return batteryPercent;
}
// 获取血氧饱和度
int getSPO2() {
return validSPO2 ? spo2Value : 0;
}
// 获取心率(添加数据过滤)
int getHeartbeat() {
int hr = validHeartRate ? heartRateValue : 0;
// 过滤异常心率值(正常范围:20-200 BPM)
return (hr >= 20 && hr <= 200) ? hr : 0;
}
// 获取温度(摄氏度)
float getTemperature_C() {
return temperatureValue;
}
// 计算平均心率
int calculateAverageRate() {
byte sum = 0;
byte count = 0;
for (byte i = 0; i < RATE_SIZE; i++) {
if (rates[i] > 20 && rates[i] <= 200) { // 只统计有效心率
sum += rates[i];
count++;
}
}
return count > 0 ? sum / count : 0;
}
// 评估综合状态
Status evaluateStatus() {
if (!isFingerPresent()) return STATUS_FINGER_MISSING;
if (!validHeartRate || !validSPO2) return STATUS_INVALID;
int hr = getHeartbeat();
int spo2 = getSPO2();
if (spo2 >= 95 && hr >= 60 && hr <= 100) {
return STATUS_NORMAL;
} else if (spo2 >= 90 && spo2 < 95) {
return STATUS_MILD_HYPOXIA;
} else if (spo2 < 90) {
return STATUS_SEVERE_HYPOXIA;
} else if (hr > 100 && hr <= 200) {
return STATUS_TACHYCARDIA;
} else if (hr < 60 && hr >= 20) {
return STATUS_BRADYCARDIA;
} else {
return STATUS_INVALID;
}
}
// 获取状态文本描述(中文,用于串口输出)
const char* getStatusTextCN(Status status) {
switch(status) {
case STATUS_NORMAL: return "正常";
case STATUS_MILD_HYPOXIA: return "轻度缺氧";
case STATUS_SEVERE_HYPOXIA: return "重度缺氧";
case STATUS_TACHYCARDIA: return "心动过速";
case STATUS_BRADYCARDIA: return "心动过缓";
case STATUS_INVALID: return "测量中...";
case STATUS_FINGER_MISSING: return "请放手指";
default: return "未知";
}
}
// 获取状态文本描述(英文,用于OLED显示)
const char* getStatusTextEN(Status status) {
switch(status) {
case STATUS_NORMAL: return "Normal";
case STATUS_MILD_HYPOXIA: return "Mild Hypoxia";
case STATUS_SEVERE_HYPOXIA: return "Severe Hypoxia";
case STATUS_TACHYCARDIA: return "Tachycardia";
case STATUS_BRADYCARDIA: return "Bradycardia";
case STATUS_INVALID: return "Measuring";
case STATUS_FINGER_MISSING: return "Place Finger";
default: return "Unknown";
}
}
// 更新OLED显示内容(英文)
void updateOLEDDisplay() {
Status currentStatus = evaluateStatus();
// 清屏准备显示新数据
display.clearDisplay();
// 显示状态文本(英文)
clearDisplayArea(0, STATUS_AREA_Y, SCREEN_WIDTH, 10);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, STATUS_AREA_Y);
display.print("Stat:");
display.println(getStatusTextEN(currentStatus));
// 显示电池电量
clearDisplayArea(BATTERY_AREA_X, STATUS_AREA_Y, SCREEN_WIDTH - BATTERY_AREA_X, 10);
display.setTextColor(SSD1306_WHITE);
display.setCursor(BATTERY_AREA_X + 3, STATUS_AREA_Y);
display.print(getBatteryLevel());
display.println("%");
// 显示血氧数据
clearDisplayArea(0, SPO2_AREA_Y, SCREEN_WIDTH, 15);
display.setTextSize(2);
display.setCursor(0, SPO2_AREA_Y);
display.print("SPO2: ");
if (validSPO2 && spo2Value >= 70 && spo2Value <= 100) {
display.setTextColor(SSD1306_WHITE);
display.print(spo2Value);
display.println("%");
} else {
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.println("-- %");
}
// 显示心率数据
clearDisplayArea(0, HR_AREA_Y, SCREEN_WIDTH, 15);
display.setTextSize(2);
display.setCursor(0, HR_AREA_Y);
display.print("HR: ");
if (validHeartRate && heartRateValue >= 20 && heartRateValue <= 200) {
display.setTextColor(SSD1306_WHITE);
display.print(heartRateValue);
display.print(" BPM");
} else {
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.println("-- BPM");
}
// 显示信号强度
clearDisplayArea(0, SIGNAL_AREA_Y, SCREEN_WIDTH, 10);
display.setTextSize(1);
display.setCursor(0, SIGNAL_AREA_Y);
display.print("Signal: ");
display.print(min(signalStrength, 100)); // 限制最大值为100
display.print("/100");
// 显示心跳动画
if (validHeartRate && beatCount % 2 == 0) {
clearDisplayArea(115, SIGNAL_AREA_Y, 10, 10);
display.setTextSize(1);
display.setCursor(115, SIGNAL_AREA_Y);
display.print("♥");
} else {
clearDisplayArea(115, SIGNAL_AREA_Y, 10, 10);
}
// 将缓冲区内容刷新到屏幕
display.display();
}
// 串口输出监测数据(中文)
void printSensorDataToSerial() {
Status currentStatus = evaluateStatus();
// 输出时间戳
Serial.print("时间: ");
Serial.print(millis() / 1000);
Serial.println("秒");
// 输出状态
Serial.print("状态: ");
Serial.println(getStatusTextCN(currentStatus));
// 输出血氧
Serial.print("血氧饱和度: ");
if (validSPO2 && spo2Value >= 70 && spo2Value <= 100) {
Serial.print(spo2Value);
Serial.println(" %");
} else {
Serial.println("未检测到");
}
// 输出心率
Serial.print("心率: ");
if (validHeartRate && heartRateValue >= 20 && heartRateValue <= 200) {
Serial.print(heartRateValue);
Serial.print(" BPM (平均: ");
Serial.print(calculateAverageRate());
Serial.println(" BPM)");
} else {
Serial.println("未检测到");
}
// 输出信号强度
Serial.print("信号强度: ");
Serial.print(min(signalStrength, 100)); // 限制最大值为100
Serial.println("/100");
// 输出电池电量
Serial.print("电池电量: ");
Serial.print(getBatteryLevel());
Serial.println("%");
// 输出温度
Serial.print("传感器温度: ");
Serial.print(getTemperature_C(), 1);
Serial.println(" °C");
// 分隔线
Serial.println("===================================");
Serial.println();
}
// 采集一次完整数据
void collectSensorData() {
// 检测手指是否放置
if (!isFingerPresent()) {
validHeartRate = 0;
validSPO2 = 0;
return;
}
// 数据缓冲区滑动
for (byte i = 25; i < SAMPLE_SIZE; i++) {
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
// 采集新数据
for (byte i = SAMPLE_SIZE - 25; i < SAMPLE_SIZE; i++) {
while (!particleSensor.available())
particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
}
// 计算心率和血氧
maxim_heart_rate_and_oxygen_saturation(irBuffer, SAMPLE_SIZE, redBuffer, &spo2Value, &validSPO2, &heartRateValue, &validHeartRate);
// 过滤异常心率值
if (validHeartRate && (heartRateValue < 20 || heartRateValue > 200)) {
validHeartRate = 0;
}
// 过滤异常血氧值
if (validSPO2 && (spo2Value < 70 || spo2Value > 100)) {
validSPO2 = 0;
}
// 更新心率平均值计算
if (validHeartRate) {
long irValue = particleSensor.getIR();
if (checkForBeat(irValue) == true) {
beatCount++;
long delta = millis() - lastBeat;
lastBeat = millis();
float bpm = 60 / (delta / 1000.0);
if (bpm > 20 && bpm <= 200) {
rates[rateSpot++] = (byte)bpm;
rateSpot %= RATE_SIZE;
}
}
}
// 读取温度数据
temperatureValue = particleSensor.readTemperature() + TEMP_CALIBRATION;
}
// 显示错误信息(英文)
void displayError(const char* message) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("ERROR");
display.setTextSize(2);
display.setCursor(0, 20);
display.println(message);
display.setTextSize(1);
display.setCursor(0, 50);
display.println("Please restart device");
display.display();
}
void setup() {
Serial.begin(115200);
pinMode(BATT_PIN, INPUT);
initializeDisplay();
if (!initializeSensor()) {
displayError("Sensor Connection Failed");
while (1) delay(1000); // 保持错误状态
}
lastBeat = millis();
memset(rates, 0, sizeof(rates)); // 初始化心率数组
// 串口输出初始化完成信息
Serial.println("初始化完成,请将手指放在传感器上");
Serial.println("开始测量...");
Serial.println("===================================");
}
void loop() {
static unsigned long lastUpdateTime = 0;
static unsigned long lastSensorCheckTime = 0;
static unsigned long lastSerialOutputTime = 0;
// 每100ms检查传感器状态
if (millis() - lastSensorCheckTime >= 100) {
collectSensorData();
lastSensorCheckTime = millis();
}
// 每秒更新一次OLED显示
if (millis() - lastUpdateTime >= 1000) {
updateOLEDDisplay();
lastUpdateTime = millis();
}
// 每2秒串口输出一次数据
if (millis() - lastSerialOutputTime >= 2000) {
printSensorDataToSerial();
lastSerialOutputTime = millis();
}
// 低功耗延时
delay(10);
}
七、系统运行结果
这个系统整合了 MAX30102 生物传感器和 0.96 寸 OLED 显示屏,能够实时监测并显示人体脉率、血氧饱和度等生理参数。以下是硬件运行时的详细效果描述:
1. 初始化阶段
OLED 显示
启动时显示初始化画面:

若传感器连接成功,OLED 会继续显示相关提示信息,等待用户操作。
若连接失败(如 I2C 通信异常),OLED 显示错误信息:

串口输出

2. 测量阶段
2.1 手指未放置或信号弱
OLED 显示

串口输出

2.2 正常测量状态
OLED 显示(动态更新)

串口输出

3. 不同健康状态下的显示效果
3.1 正常状态
脉率:60 - 100 BPM
血氧:≥95%
OLED 显示

文字颜色为白色(默认)。
3.2 轻度缺氧
脉率:可能升高(身体代偿反应)
血氧:90 - 94%
OLED 显示

3.3 重度缺氧
脉率:可能显著升高或不规则
血氧:< 90%
OLED 显示

4. 硬件交互效果
硬件交互主要体现在手指放置和移除时系统的响应。当手指放置在传感器上且信号稳定时,系统开始正常测量并显示数据;当手指移除时,系统会提示手指未放置,增强了用户与系统之间的互动性。
八、项目扩展与优化
接入Wifi网络:利用ESP8266模块通过串口通信让 Beetle RP2350 与 ESP8266 进行数据交互,实现接入Wifi网络。
数据可视化:通过 MQTT 协议将数据上传至物联网平台(如 SIoT),实现远程监控与历史数据查询。
低功耗设计:利用 Beetle 的睡眠模式与传感器待机模式,延长电池续航。
九、附件
附件
评论