MAX30102 + STM32 人体血氧饱和度(SpO₂)测量方案

MAX30102 + STM32 人体血氧饱和度(SpO₂)测量方案

一、系统核心原理

1.1 测量原理(PPG光电容积描记法)

MAX30102 包含两个LED(红光660nm红外光880nm)和一个光电探测器。

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 校准方法

  1. 基准校准:使用医用级指夹血氧仪作为基准,对比你的设备读数。

  2. 公式微调

    • 如果你的读数普遍偏高:增大公式中的 25.0f 系数。
    • 如果你的读数普遍偏低:减小公式中的 25.0f 系数。
  3. 分段校准:不同的人种、肤色对光的吸收率不同,建议在85%-99%区间分别校准。

3.3 硬件优化建议

  1. 遮光处理:MAX30102必须配合黑色硅胶指套使用,否则环境光会淹没微弱的血氧信号。
  2. 电源稳定性:在VIN引脚并联100nF和10µF电容,防止电源噪声导致数据抖动。
  3. 手指接触:确保手指指甲盖朝上,指腹紧贴传感器透镜,不要用力按压导致血流阻断。

四、总结

本方案实现了基于STM32和MAX30102的完整血氧监测系统。核心难点不在于I2C驱动,而在于信号处理算法。实际应用中,建议加入心率计算(通过PPG间期)运动伪影消除算法,以获得更稳定的医疗级数据。

专注于matlab/simulink,电子电路,编程