一、系统核心原理
1.1 测量原理(PPG光电容积描记法)
MAX30102 包含两个LED(红光660nm和红外光880nm)和一个光电探测器。
- 血红蛋白对不同波长光的吸收率不同:
- 氧合血红蛋白(HbO₂)吸收更多的红外光。
- 脱氧血红蛋白(Hb)吸收更多的红光。
- 计算公式:
SpO2=110−25×R SpO_2 = 110 - 25 \times RSpO2=110−25×R
其中R值(调制比率)是关键:
R=ACred/DCredACir/DCir R = \frac{AC_{red}/DC_{red}}{AC_{ir}/DC_{ir}}R=ACir/DCirACred/DCred- ACACAC:脉搏波的交流成分(动态变化,代表心跳)。
- DCDCDC:直流成分(静态基线,代表组织吸收和外界光)。
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 寄存器地址#defineMAX30102_INT_STATUS0x00#defineMAX30102_FIFO_DATA0x07#defineMAX30102_FIFO_CONF0x08#defineMAX30102_MODE_CONF0x09#defineMAX30102_SPO2_CONF0x0A#defineMAX30102_LED_CONF0x0C#defineMAX30102_PART_ID0xFF// 算法相关#defineFIFO_DEPTH32// FIFO深度#defineSAMPLE_RATE100// 采样率100Hz#defineBUFFER_SIZE100// 数据处理缓冲区// 全局变量externvolatileuint32_tir_buffer[BUFFER_SIZE];externvolatileuint32_tred_buffer[BUFFER_SIZE];externvolatileuint8_tfifo_wptr;voidMAX30102_Init(void);voidMAX30102_ReadFIFO(void);floatMAX30102_CalcSpO2(void);#endif2.2 传感器初始化(max30102.c)
#include"max30102.h"#include"delay.h"#include"usart.h"volatileuint32_tir_buffer[BUFFER_SIZE];volatileuint32_tred_buffer[BUFFER_SIZE];volatileuint8_tdata_ready=0;/** * @brief MAX30102初始化 */voidMAX30102_Init(void){uint8_tpart_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=1Delay_ms(10);// 3. 配置FIFOI2C_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数据 */voidMAX30102_ReadFIFO(void){uint8_tfifo_data[6];staticuint8_tbuf_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"// 滑动平均滤波器staticvoidMovingAverageFilter(uint32_t*input,float*output,intsize){intwindow=5;for(inti=window;i<size;i++){floatsum=0;for(intj=0;j<window;j++){sum+=input[i-j];}output[i]=sum/window;}}// 直流分量提取(低通滤波)staticfloatExtractDC(float*signal,intsize){floatsum=0;for(inti=0;i<size;i++){sum+=signal[i];}returnsum/size;}// 交流分量提取(去除直流后的峰值检测)staticfloatExtractAC(float*signal,intsize){floatdc=ExtractDC(signal,size);floatmax_val=signal[0];floatmin_val=signal[0];for(inti=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 计算血氧饱和度 */floatMAX30102_CalcSpO2(void){floatred_filtered[BUFFER_SIZE];floatir_filtered[BUFFER_SIZE];if(!data_ready)return0;// 1. 滑动平均滤波去噪MovingAverageFilter((uint32_t*)red_buffer,red_filtered,BUFFER_SIZE);MovingAverageFilter((uint32_t*)ir_buffer,ir_filtered,BUFFER_SIZE);// 2. 计算R值floatred_ac=ExtractAC(red_filtered,BUFFER_SIZE);floatred_dc=ExtractDC(red_filtered,BUFFER_SIZE);floatir_ac=ExtractAC(ir_filtered,BUFFER_SIZE);floatir_dc=ExtractDC(ir_filtered,BUFFER_SIZE);if(red_dc==0||ir_dc==0)return0;floatR=(red_ac/red_dc)/(ir_ac/ir_dc);// 3. 转换为SpO2(经验公式,需校准)floatspo2=110.0f-25.0f*R;// 限制范围if(spo2>100.0f)spo2=100.0f;if(spo2<70.0f)spo2=0.0f;returnspo2;}2.4 主程序调用(main.c)
#include"stm32f10x.h"#include"delay.h"#include"usart.h"#include"i2c.h"#include"max30102.h"intmain(void){floatspo2_value;uint32_theart_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/contentcsu/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间期)和运动伪影消除算法,以获得更稳定的医疗级数据。