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

  1. 基准校准:使用医用级指夹血氧仪作为基准,对比你的设备读数。
  2. 公式微调
    • 如果你的读数普遍偏高:增大公式中的 25.0f 系数。
    • 如果你的读数普遍偏低:减小公式中的 25.0f 系数。
  3. 分段校准:不同的人种、肤色对光的吸收率不同,建议在85%-99%区间分别校准。

3.3 硬件优化建议

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

四、总结

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

posted @ 2026-04-27 09:25  康帅服  阅读(9)  评论(0)    收藏  举报