MAX30102 + STM32 人体血氧饱和度(SpO₂)测量方案
一、系统核心原理
1.1 测量原理(PPG光电容积描记法)
MAX30102 包含两个LED(红光660nm 和 红外光880nm)和一个光电探测器。
- 血红蛋白对不同波长光的吸收率不同:
- 氧合血红蛋白(HbO₂)吸收更多的红外光。
- 脱氧血红蛋白(Hb)吸收更多的红光。
- 计算公式:\[SpO_2 = 110 - 25 \times R \]其中 R值(调制比率) 是关键:\[R = \frac{AC_{red}/DC_{red}}{AC_{ir}/DC_{ir}} \]
- \(AC\):脉搏波的交流成分(动态变化,代表心跳)。
- \(DC\):直流成分(静态基线,代表组织吸收和外界光)。
1.2 硬件连接表
| MAX30102 | STM32F103C8T6 | 说明 |
|---|---|---|
| VIN | 3.3V | 电源输入 |
| GND | GND | 共地 |
| SDA | PB7 | I2C1数据线(需4.7k上拉) |
| SCL | PB6 | I2C1时钟线(需4.7k上拉) |
| INT | PA0 | 中断引脚(低电平有效,可选) |
| RD | NC | 未使用 |
二、STM32驱动与算法实现
2.1 核心宏定义与变量(max30102.h)
#ifndef __MAX30102_H
#define __MAX30102_H
#include "stm32f10x.h"
#include "i2c.h" // 假设已有I2C底层驱动
// MAX30102 寄存器地址
#define MAX30102_INT_STATUS 0x00
#define MAX30102_FIFO_DATA 0x07
#define MAX30102_FIFO_CONF 0x08
#define MAX30102_MODE_CONF 0x09
#define MAX30102_SPO2_CONF 0x0A
#define MAX30102_LED_CONF 0x0C
#define MAX30102_PART_ID 0xFF
// 算法相关
#define FIFO_DEPTH 32 // FIFO深度
#define SAMPLE_RATE 100 // 采样率100Hz
#define BUFFER_SIZE 100 // 数据处理缓冲区
// 全局变量
extern volatile uint32_t ir_buffer[BUFFER_SIZE];
extern volatile uint32_t red_buffer[BUFFER_SIZE];
extern volatile uint8_t fifo_wptr;
void MAX30102_Init(void);
void MAX30102_ReadFIFO(void);
float MAX30102_CalcSpO2(void);
#endif
2.2 传感器初始化(max30102.c)
#include "max30102.h"
#include "delay.h"
#include "usart.h"
volatile uint32_t ir_buffer[BUFFER_SIZE];
volatile uint32_t red_buffer[BUFFER_SIZE];
volatile uint8_t data_ready = 0;
/**
* @brief MAX30102初始化
*/
void MAX30102_Init(void)
{
uint8_t part_id;
// 1. 读取Part ID,确认通信正常
part_id = I2C_ReadReg(MAX30102_PART_ID);
printf("MAX30102 Part ID: 0x%02X\r\n", part_id);
// 2. 复位传感器
I2C_WriteReg(0x09, 0x40); // MODE_CONFIG: RESET=1
Delay_ms(10);
// 3. 配置FIFO
I2C_WriteReg(MAX30102_FIFO_CONF, 0x0F); // FIFO_A_FULL=0, FIFO_ROLLOVER_EN=1
// 4. 配置模式:SpO2模式
I2C_WriteReg(MAX30102_MODE_CONF, 0x03); // MODE=011 (SpO2 mode)
// 5. 配置SpO2:100Hz采样,411us脉宽,18位分辨率
I2C_WriteReg(MAX30102_SPO2_CONF, 0x27); // SPO2_ADC_RGE=01(411us), SPO2_SR=111(100Hz)
// 6. 配置LED电流(非常重要!)
// 红光: 6.4mA, 红外: 6.4mA (根据手指透光率调整)
I2C_WriteReg(MAX30102_LED_CONF, 0x24); // LED1_PA=0x2, LED2_PA=0x4
// 7. 清中断
I2C_ReadReg(MAX30102_INT_STATUS);
printf("MAX30102 Init OK!\r\n");
}
/**
* @brief 读取FIFO数据
*/
void MAX30102_ReadFIFO(void)
{
uint8_t fifo_data[6];
static uint8_t buf_index = 0;
// 读取3个字节的RED数据 + 3个字节的IR数据
I2C_ReadMulti(MAX30102_FIFO_DATA, fifo_data, 6);
// 拼接数据 (18-bit)
red_buffer[buf_index] = ((uint32_t)fifo_data[0] << 16) |
((uint32_t)fifo_data[1] << 8) |
(uint32_t)fifo_data[2];
red_buffer[buf_index] &= 0x3FFFF; // 掩码18位
ir_buffer[buf_index] = ((uint32_t)fifo_data[3] << 16) |
((uint32_t)fifo_data[4] << 8) |
(uint32_t)fifo_data[5];
ir_buffer[buf_index] &= 0x3FFFF;
buf_index++;
if(buf_index >= BUFFER_SIZE) {
buf_index = 0;
data_ready = 1; // 标记数据已满,可以进行计算
}
}
2.3 核心算法:血氧计算(spo2_algorithm.c)
这是最关键的部分,包含滤波和峰值检测。
#include "max30102.h"
#include "math.h"
// 滑动平均滤波器
static void MovingAverageFilter(uint32_t *input, float *output, int size)
{
int window = 5;
for(int i = window; i < size; i++) {
float sum = 0;
for(int j = 0; j < window; j++) {
sum += input[i - j];
}
output[i] = sum / window;
}
}
// 直流分量提取(低通滤波)
static float ExtractDC(float *signal, int size)
{
float sum = 0;
for(int i = 0; i < size; i++) {
sum += signal[i];
}
return sum / size;
}
// 交流分量提取(去除直流后的峰值检测)
static float ExtractAC(float *signal, int size)
{
float dc = ExtractDC(signal, size);
float max_val = signal[0];
float min_val = signal[0];
for(int i = 0; i < size; i++) {
if(signal[i] > max_val) max_val = signal[i];
if(signal[i] < min_val) min_val = signal[i];
}
return (max_val - min_val); // AC幅度 = 峰峰值
}
/**
* @brief 计算血氧饱和度
*/
float MAX30102_CalcSpO2(void)
{
float red_filtered[BUFFER_SIZE];
float ir_filtered[BUFFER_SIZE];
if(!data_ready) return 0;
// 1. 滑动平均滤波去噪
MovingAverageFilter((uint32_t*)red_buffer, red_filtered, BUFFER_SIZE);
MovingAverageFilter((uint32_t*)ir_buffer, ir_filtered, BUFFER_SIZE);
// 2. 计算R值
float red_ac = ExtractAC(red_filtered, BUFFER_SIZE);
float red_dc = ExtractDC(red_filtered, BUFFER_SIZE);
float ir_ac = ExtractAC(ir_filtered, BUFFER_SIZE);
float ir_dc = ExtractDC(ir_filtered, BUFFER_SIZE);
if(red_dc == 0 || ir_dc == 0) return 0;
float R = (red_ac / red_dc) / (ir_ac / ir_dc);
// 3. 转换为SpO2(经验公式,需校准)
float spo2 = 110.0f - 25.0f * R;
// 限制范围
if(spo2 > 100.0f) spo2 = 100.0f;
if(spo2 < 70.0f) spo2 = 0.0f;
return spo2;
}
2.4 主程序调用(main.c)
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "i2c.h"
#include "max30102.h"
int main(void)
{
float spo2_value;
uint32_t heart_rate = 0;
// 系统初始化
Delay_Init();
USART_Init(115200);
I2C_Init();
printf("System Start...\r\n");
// 传感器初始化
MAX30102_Init();
while(1)
{
// 读取FIFO数据
MAX30102_ReadFIFO();
// 数据满了就计算
if(data_ready) {
spo2_value = MAX30102_CalcSpO2();
// 输出结果
printf("SpO2: %.1f%%, HR: %lu BPM\r\n", spo2_value, heart_rate);
data_ready = 0; // 清空标志位
}
Delay_ms(10); // 控制循环速度
}
}
参考代码 使用MAX30102搭配stm32开发板对人体血氧饱和度进行测量 www.youwenfan.com/contentcnu/56129.html
三、关键调试与校准指南
3.1 常见问题排查
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 读数一直为0 | LED电流太小 | 增大 LED_CONF 寄存器值(如 0x7F 对应最大电流) |
| 读数跳动剧烈 | 手指未压紧或漏光 | 使用遮光罩,手指用力按压传感器 |
| 血氧低于90% | 环境光干扰 | 确保传感器被完全覆盖,避免强光直射 |
| FIFO溢出 | 读取速度太慢 | 提高I2C时钟(400kHz),或增加FIFO中断 |
3.2 校准方法
- 基准校准:使用医用级指夹血氧仪作为基准,对比你的设备读数。
- 公式微调:
- 如果你的读数普遍偏高:增大公式中的
25.0f系数。 - 如果你的读数普遍偏低:减小公式中的
25.0f系数。
- 如果你的读数普遍偏高:增大公式中的
- 分段校准:不同的人种、肤色对光的吸收率不同,建议在85%-99%区间分别校准。
3.3 硬件优化建议
- 遮光处理:MAX30102必须配合黑色硅胶指套使用,否则环境光会淹没微弱的血氧信号。
- 电源稳定性:在VIN引脚并联100nF和10µF电容,防止电源噪声导致数据抖动。
- 手指接触:确保手指指甲盖朝上,指腹紧贴传感器透镜,不要用力按压导致血流阻断。
四、总结
本方案实现了基于STM32和MAX30102的完整血氧监测系统。核心难点不在于I2C驱动,而在于信号处理算法。实际应用中,建议加入心率计算(通过PPG间期) 和 运动伪影消除算法,以获得更稳定的医疗级数据。

浙公网安备 33010602011771号