STM32 MAX30102 血氧浓度(SpO2)和心率测量方案

MAX30102血氧检测解决方案,包含I2C驱动、数据采集、滤波算法、心率计算、血氧算法


一、MAX30102特性

参数 说明
LED波长 660nm(红), 880nm(IR) 双波长
ADC分辨率 18位 可调
采样率 50-3200Hz 可编程
接口 I2C 0x57地址
功耗 1.8V/600µA 低功耗
血氧范围 0-100% ±2%精度
心率范围 30-250BPM ±3BPM精度

推荐配置

  • 采样率:100Hz
  • LED电流:红光=6.4mA, IR=24mA
  • ADC分辨率:18位
  • 算法:PPG波形分析
  • 滤波:带通0.5-5Hz

二、硬件连接

复制代码
STM32F103       MAX30102
3.3V  →  VIN
GND   →  GND
PB6   →  SCL
PB7   →  SDA
PB5   →  INT (中断,可选)

外部电路:
VDD → 1.8V LDO (可选)
INT → 10k上拉电阻
SCL/SDA → 4.7k上拉电阻

三、程序架构

复制代码
MAX30102_SpO2/
├── max30102.h
├── max30102.c
├── spo2_algorithm.c
├── heart_rate.c
├── filter.c
└── main.c

四、MAX30102驱动程序

1、头文件定义

c 复制代码
// max30102.h
#ifndef MAX30102_H
#define MAX30102_H

#include "stm32f1xx_hal.h"

// MAX30102 I2C地址
#define MAX30102_I2C_ADDR    0x57

// 寄存器地址
#define MAX30102_REG_INT_STATUS     0x00
#define MAX30102_REG_INT_ENABLE     0x01
#define MAX30102_REG_FIFO_WR_PTR    0x02
#define MAX30102_REG_OVF_COUNTER    0x03
#define MAX30102_REG_FIFO_RD_PTR    0x04
#define MAX30102_REG_FIFO_DATA      0x05
#define MAX30102_REG_FIFO_CONFIG    0x06
#define MAX30102_REG_MODE_CONFIG    0x07
#define MAX30102_REG_SPO2_CONFIG    0x08
#define MAX30102_REG_LED1_PA        0x09
#define MAX30102_REG_LED2_PA        0x0A
#define MAX30102_REG_PILOT_PA       0x0B
#define MAX30102_REG_MULTI_LED_CTRL1 0x0C
#define MAX30102_REG_MULTI_LED_CTRL2 0x0D
#define MAX30102_REG_TEMP_INT       0x1F
#define MAX30102_REG_TEMP_FRAC      0x20
#define MAX30102_REG_TEMP_CONFIG    0x21
#define MAX30102_REG_REV_ID         0xFE
#define MAX30102_REG_PART_ID        0xFF

// 模式配置
#define MAX30102_MODE_HR            0x02
#define MAX30102_MODE_SPO2          0x03
#define MAX30102_MODE_MULTI_LED     0x07

// 中断配置
#define MAX30102_INT_A_FULL         (1<<7)
#define MAX30102_INT_PPG_RDY        (1<<6)
#define MAX30102_INT_ALC_OVF        (1<<5)
#define MAX30102_INT_TEMP_RDY       (1<<1)
#define MAX30102_INT_PWR_RDY        (1<<0)

// LED电流设置
#define MAX30102_LED_CURRENT_0MA    0x00
#define MAX30102_LED_CURRENT_6_4MA  0x24
#define MAX30102_LED_CURRENT_12_8MA 0x49
#define MAX30102_LED_CURRENT_19_2MA 0x6D
#define MAX30102_LED_CURRENT_24MA   0x7F

// 采样率
#define MAX30102_SAMPLERATE_50      0x00
#define MAX30102_SAMPLERATE_100     0x01
#define MAX30102_SAMPLERATE_200     0x02
#define MAX30102_SAMPLERATE_400     0x03
#define MAX30102_SAMPLERATE_800     0x04
#define MAX30102_SAMPLERATE_1000    0x05
#define MAX30102_SAMPLERATE_1600    0x06
#define MAX30102_SAMPLERATE_3200    0x07

// ADC分辨率
#define MAX30102_ADCRES_15BIT       0x00
#define MAX30102_ADCRES_16BIT       0x01
#define MAX30102_ADCRES_17BIT       0x02
#define MAX30102_ADCRES_18BIT       0x03

// FIFO平均采样数
#define MAX30102_SAMPLEAVG_1        0x00
#define MAX30102_SAMPLEAVG_2        0x01
#define MAX30102_SAMPLEAVG_4        0x02
#define MAX30102_SAMPLEAVG_8        0x03
#define MAX30102_SAMPLEAVG_16       0x04
#define MAX30102_SAMPLEAVG_32       0x05

// 数据结构
typedef struct {
    uint32_t red;      // 红光ADC值
    uint32_t ir;       // 红外ADC值
    uint32_t timestamp; // 时间戳
} MAX30102_Data_t;

// 心率和血氧结果
typedef struct {
    float heart_rate;      // 心率 (BPM)
    float spo2;           // 血氧饱和度 (%)
    uint8_t confidence;   // 置信度 (0-100)
    uint8_t valid;        // 数据是否有效
} MAX30102_Result_t;

// 滤波器状态
typedef struct {
    float red_dc;         // 红光直流分量
    float ir_dc;         // 红外直流分量
    float red_ac;         // 红光交流分量
    float ir_ac;         // 红外交流分量
} MAX30102_Filter_t;

// 函数声明
void MAX30102_Init(void);
void MAX30102_Reset(void);
uint8_t MAX30102_ReadID(void);
void MAX30102_Setup(void);
void MAX30102_SetLEDCurrent(uint8_t red_current, uint8_t ir_current);
void MAX30102_SetSamplingRate(uint8_t samplerate);
void MAX30102_SetADCRange(uint8_t adc_range);
void MAX30102_ReadFIFO(MAX30102_Data_t *data, uint8_t num_samples);
uint8_t MAX30102_GetNumSamples(void);
void MAX30102_ClearFIFO(void);
float MAX30102_ReadTemperature(void);
MAX30102_Result_t MAX30102_ProcessData(MAX30102_Data_t *data, uint16_t num_samples);
void MAX30102_StartReading(void);
void MAX30102_StopReading(void);

#endif

2、I2C驱动函数

c 复制代码
// max30102_i2c.c
#include "max30102.h"

// I2C句柄
extern I2C_HandleTypeDef hi2c1;

// I2C写寄存器
uint8_t MAX30102_WriteReg(uint8_t reg, uint8_t value)
{
    uint8_t buffer[2] = {reg, value};
    
    HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, MAX30102_I2C_ADDR << 1, 
                                                       buffer, 2, 100);
    if(status != HAL_OK)
    {
        return 0;
    }
    return 1;
}

// I2C读寄存器
uint8_t MAX30102_ReadReg(uint8_t reg)
{
    uint8_t value = 0;
    
    // 发送寄存器地址
    HAL_I2C_Master_Transmit(&hi2c1, MAX30102_I2C_ADDR << 1, &reg, 1, 100);
    
    // 读取数据
    HAL_I2C_Master_Receive(&hi2c1, MAX30102_I2C_ADDR << 1, &value, 1, 100);
    
    return value;
}

// I2C批量读取
void MAX30102_ReadMultiReg(uint8_t reg, uint8_t *buffer, uint8_t len)
{
    // 发送寄存器地址
    HAL_I2C_Master_Transmit(&hi2c1, MAX30102_I2C_ADDR << 1, &reg, 1, 100);
    
    // 读取多个数据
    HAL_I2C_Master_Receive(&hi2c1, MAX30102_I2C_ADDR << 1, buffer, len, 100);
}

3、MAX30102初始化

c 复制代码
// max30102.c
#include "max30102.h"
#include <math.h>
#include <string.h>

// 全局变量
static uint8_t max30102_initialized = 0;
static uint32_t sample_counter = 0;
static float temperature = 0.0f;

// 初始化MAX30102
void MAX30102_Init(void)
{
    // 1. 复位
    MAX30102_Reset();
    HAL_Delay(10);
    
    // 2. 读取ID验证
    uint8_t part_id = MAX30102_ReadID();
    if(part_id != 0x15)  // MAX30102的PART ID是0x15
    {
        printf("MAX30102 ID错误: 0x%02X (应该是0x15)\r\n", part_id);
        return;
    }
    
    printf("MAX30102检测成功 (ID: 0x%02X)\r\n", part_id);
    
    // 3. 配置传感器
    MAX30102_Setup();
    
    // 4. 清除FIFO
    MAX30102_ClearFIFO();
    
    max30102_initialized = 1;
    
    printf("MAX30102初始化完成\r\n");
}

// 复位传感器
void MAX30102_Reset(void)
{
    MAX30102_WriteReg(MAX30102_REG_MODE_CONFIG, 0x40);
    HAL_Delay(10);
}

// 读取ID
uint8_t MAX30102_ReadID(void)
{
    uint8_t id = MAX30102_ReadReg(MAX30102_REG_PART_ID);
    return id;
}

// 配置传感器参数
void MAX30102_Setup(void)
{
    // 1. 设置FIFO平均采样数 (8个样本平均)
    uint8_t fifo_config = (MAX30102_SAMPLEAVG_8 << 5);
    MAX30102_WriteReg(MAX30102_REG_FIFO_CONFIG, fifo_config);
    
    // 2. 设置采样率(100Hz)和ADC分辨率(18位)
    uint8_t spo2_config = (MAX30102_SAMPLERATE_100 << 2) | MAX30102_ADCRES_18BIT;
    MAX30102_WriteReg(MAX30102_REG_SPO2_CONFIG, spo2_config);
    
    // 3. 设置LED电流 (红光6.4mA, 红外24mA)
    MAX30102_SetLEDCurrent(MAX30102_LED_CURRENT_6_4MA, MAX30102_LED_CURRENT_24MA);
    
    // 4. 设置多LED控制 (启用红光和红外LED)
    MAX30102_WriteReg(MAX30102_REG_MULTI_LED_CTRL1, 0x21);  // LED1=红光, LED2=红外
    MAX30102_WriteReg(MAX30102_REG_MULTI_LED_CTRL2, 0x00);
    
    // 5. 设置模式为血氧模式
    MAX30102_WriteReg(MAX30102_REG_MODE_CONFIG, MAX30102_MODE_SPO2);
    
    // 6. 启用FIFO几乎满中断
    MAX30102_WriteReg(MAX30102_REG_INT_ENABLE, MAX30102_INT_A_FULL);
    
    HAL_Delay(10);
}

// 设置LED电流
void MAX30102_SetLEDCurrent(uint8_t red_current, uint8_t ir_current)
{
    MAX30102_WriteReg(MAX30102_REG_LED1_PA, red_current);
    MAX30102_WriteReg(MAX30102_REG_LED2_PA, ir_current);
}

// 设置采样率
void MAX30102_SetSamplingRate(uint8_t samplerate)
{
    uint8_t spo2_config = MAX30102_ReadReg(MAX30102_REG_SPO2_CONFIG);
    spo2_config = (spo2_config & 0xE3) | (samplerate << 2);
    MAX30102_WriteReg(MAX30102_REG_SPO2_CONFIG, spo2_config);
}

// 设置ADC范围
void MAX30102_SetADCRange(uint8_t adc_range)
{
    uint8_t spo2_config = MAX30102_ReadReg(MAX30102_REG_SPO2_CONFIG);
    spo2_config = (spo2_config & 0xFC) | adc_range;
    MAX30102_WriteReg(MAX30102_REG_SPO2_CONFIG, spo2_config);
}

// 获取FIFO中样本数量
uint8_t MAX30102_GetNumSamples(void)
{
    uint8_t write_ptr = MAX30102_ReadReg(MAX30102_REG_FIFO_WR_PTR);
    uint8_t read_ptr = MAX30102_ReadReg(MAX30102_REG_FIFO_RD_PTR);
    
    uint8_t num_samples = (write_ptr - read_ptr) & 0x1F;
    
    return num_samples;
}

// 读取FIFO数据
void MAX30102_ReadFIFO(MAX30102_Data_t *data, uint8_t num_samples)
{
    if(num_samples == 0) return;
    
    uint8_t buffer[6 * num_samples];  // 每个样本6字节
    
    // 读取FIFO数据
    MAX30102_ReadMultiReg(MAX30102_REG_FIFO_DATA, buffer, 6 * num_samples);
    
    // 解析数据
    for(int i = 0; i < num_samples; i++)
    {
        uint8_t *sample = &buffer[i * 6];
        
        // 18位数据 (最高2位是无效的)
        data[i].ir = ((sample[0] << 16) | (sample[1] << 8) | sample[2]) & 0x3FFFF;
        data[i].red = ((sample[3] << 16) | (sample[4] << 8) | sample[5]) & 0x3FFFF;
        data[i].timestamp = HAL_GetTick();
        
        sample_counter++;
    }
}

// 清除FIFO
void MAX30102_ClearFIFO(void)
{
    MAX30102_WriteReg(MAX30102_REG_FIFO_WR_PTR, 0x00);
    MAX30102_WriteReg(MAX30102_REG_OVF_COUNTER, 0x00);
    MAX30102_WriteReg(MAX30102_REG_FIFO_RD_PTR, 0x00);
}

// 读取温度
float MAX30102_ReadTemperature(void)
{
    // 启动温度转换
    MAX30102_WriteReg(MAX30102_REG_TEMP_CONFIG, 0x01);
    
    // 等待转换完成
    HAL_Delay(100);
    
    // 读取温度整数部分
    uint8_t temp_int = MAX30102_ReadReg(MAX30102_REG_TEMP_INT);
    
    // 读取温度小数部分
    uint8_t temp_frac = MAX30102_ReadReg(MAX30102_REG_TEMP_FRAC);
    
    // 计算温度 (补码表示)
    int8_t temp_int_signed = (int8_t)temp_int;
    float temp = temp_int_signed + (temp_frac * 0.0625f);
    
    temperature = temp;
    return temp;
}

// 开始读取
void MAX30102_StartReading(void)
{
    // 清除FIFO
    MAX30102_ClearFIFO();
    
    // 设置模式为血氧模式
    MAX30102_WriteReg(MAX30102_REG_MODE_CONFIG, MAX30102_MODE_SPO2);
    
    printf("开始采集数据...\r\n");
}

// 停止读取
void MAX30102_StopReading(void)
{
    // 设置模式为待机
    MAX30102_WriteReg(MAX30102_REG_MODE_CONFIG, 0x00);
    
    printf("停止采集数据...\r\n");
}

4、滤波算法

c 复制代码
// filter.c
#include "filter.h"
#include <math.h>

// 移动平均滤波器
typedef struct {
    float buffer[8];
    uint8_t index;
    float sum;
    uint8_t size;
} MovingAverage_Filter;

// 巴特沃斯带通滤波器系数
typedef struct {
    float x[3];  // 输入
    float y[3];  // 输出
    float b[3];  // 分子系数
    float a[3];  // 分母系数
} Butterworth_Filter;

// DC移除滤波器
typedef struct {
    float alpha;
    float dc_w;
    float prev_w;
} DC_Remover;

// 初始化移动平均滤波器
void MovingAverage_Init(MovingAverage_Filter* filter, uint8_t size)
{
    memset(filter->buffer, 0, sizeof(filter->buffer));
    filter->index = 0;
    filter->sum = 0.0f;
    filter->size = (size > 8) ? 8 : size;
}

// 移动平均滤波
float MovingAverage_Filter_Update(MovingAverage_Filter* filter, float new_value)
{
    filter->sum -= filter->buffer[filter->index];
    filter->buffer[filter->index] = new_value;
    filter->sum += new_value;
    filter->index = (filter->index + 1) % filter->size;
    
    return filter->sum / filter->size;
}

// 初始化DC移除器
void DC_Remover_Init(DC_Remover* filter, float alpha)
{
    filter->alpha = alpha;
    filter->dc_w = 0.0f;
    filter->prev_w = 0.0f;
}

// DC移除滤波
float DC_Remover_Update(DC_Remover* filter, float input)
{
    float w = input + filter->alpha * filter->prev_w;
    float result = w - filter->prev_w;
    
    filter->prev_w = w;
    filter->dc_w = w;
    
    return result;
}

// 初始化巴特沃斯带通滤波器 (0.5-5Hz, 100Hz采样率)
void Butterworth_BP_Init(Butterworth_Filter* filter)
{
    // 二阶带通滤波器系数 (0.5-5Hz, 100Hz)
    filter->b[0] = 0.020083365564211;
    filter->b[1] = 0.0;
    filter->b[2] = -0.020083365564211;
    
    filter->a[0] = 1.0;
    filter->a[1] = -1.561018075800718;
    filter->a[2] = 0.641351538057563;
    
    // 清零历史数据
    for(int i = 0; i < 3; i++) {
        filter->x[i] = 0.0f;
        filter->y[i] = 0.0f;
    }
}

// 巴特沃斯带通滤波
float Butterworth_BP_Update(Butterworth_Filter* filter, float input)
{
    // 更新输入历史
    filter->x[2] = filter->x[1];
    filter->x[1] = filter->x[0];
    filter->x[0] = input;
    
    // 更新输出历史
    filter->y[2] = filter->y[1];
    filter->y[1] = filter->y[0];
    
    // 计算新输出
    filter->y[0] = filter->b[0] * filter->x[0] + 
                   filter->b[1] * filter->x[1] + 
                   filter->b[2] * filter->x[2] - 
                   filter->a[1] * filter->y[1] - 
                   filter->a[2] * filter->y[2];
    
    return filter->y[0];
}

// 中值滤波器
float Median_Filter(float* buffer, uint8_t size)
{
    float temp;
    
    // 冒泡排序
    for(int i = 0; i < size - 1; i++) {
        for(int j = 0; j < size - i - 1; j++) {
            if(buffer[j] > buffer[j + 1]) {
                temp = buffer[j];
                buffer[j] = buffer[j + 1];
                buffer[j + 1] = temp;
            }
        }
    }
    
    // 返回中值
    return buffer[size / 2];
}

// 低通滤波器
float LowPass_Filter(float input, float* prev_output, float alpha)
{
    float output = alpha * input + (1.0f - alpha) * (*prev_output);
    *prev_output = output;
    return output;
}

5、心率检测算法

c 复制代码
// heart_rate.c
#include "heart_rate.h"
#include <math.h>

// 心率检测器结构
typedef struct {
    float signal[BUFFER_SIZE];  // 信号缓冲区
    float filtered[BUFFER_SIZE]; // 滤波后信号
    uint32_t timestamps[BUFFER_SIZE]; // 时间戳
    uint16_t index;              // 当前索引
    uint8_t buffer_full;         // 缓冲区是否已满
    
    // 峰值检测
    float threshold;            // 检测阈值
    uint32_t last_peak_time;    // 上一个峰值时间
    float peak_values[10];      // 峰值存储
    uint32_t peak_times[10];    // 峰值时间
    uint8_t peak_index;         // 峰值索引
    
    // 心率计算
    float heart_rate;           // 当前心率
    float heart_rate_smooth;    // 平滑心率
    uint8_t confidence;         // 置信度
} HeartRate_Detector;

static HeartRate_Detector hr_detector = {0};

// 初始化心率检测器
void HeartRate_Init(void)
{
    memset(&hr_detector, 0, sizeof(HeartRate_Detector));
    hr_detector.threshold = 1000.0f;  // 初始阈值
    hr_detector.confidence = 0;
}

// 添加新数据点
void HeartRate_AddData(float value, uint32_t timestamp)
{
    // 保存数据
    hr_detector.signal[hr_detector.index] = value;
    hr_detector.timestamps[hr_detector.index] = timestamp;
    
    // 更新索引
    hr_detector.index++;
    if(hr_detector.index >= BUFFER_SIZE) {
        hr_detector.index = 0;
        hr_detector.buffer_full = 1;
    }
}

// 检测峰值
uint8_t HeartRate_DetectPeak(float value, uint32_t timestamp)
{
    static float last_value = 0;
    static float max_value = 0;
    static uint8_t rising = 0;
    static uint32_t last_cross_time = 0;
    
    uint8_t peak_detected = 0;
    
    // 寻找上升沿
    if(value > hr_detector.threshold && !rising) {
        rising = 1;
        max_value = value;
        last_cross_time = timestamp;
    }
    
    // 在上升沿中寻找最大值
    if(rising && value > max_value) {
        max_value = value;
    }
    
    // 检测下降沿
    if(rising && value < hr_detector.threshold) {
        rising = 0;
        
        // 确认这是有效的峰值
        if(max_value > hr_detector.threshold * 1.2f) {
            // 保存峰值
            hr_detector.peak_values[hr_detector.peak_index] = max_value;
            hr_detector.peak_times[hr_detector.peak_index] = last_cross_time;
            
            // 更新阈值 (自适应)
            hr_detector.threshold = 0.8f * hr_detector.threshold + 0.2f * (max_value * 0.6f);
            
            // 计算心率
            if(hr_detector.peak_index > 0) {
                uint32_t time_diff = timestamp - hr_detector.last_peak_time;
                if(time_diff > 0) {
                    // 计算瞬时心率 (BPM)
                    float instant_hr = 60000.0f / time_diff;  // 60秒/时间差(ms)
                    
                    // 有效性检查
                    if(instant_hr >= 30.0f && instant_hr <= 250.0f) {
                        // 平滑心率
                        hr_detector.heart_rate = 0.8f * hr_detector.heart_rate + 0.2f * instant_hr;
                        hr_detector.confidence = 100;  // 高置信度
                        
                        peak_detected = 1;
                    }
                }
            }
            
            hr_detector.last_peak_time = last_cross_time;
            hr_detector.peak_index = (hr_detector.peak_index + 1) % 10;
        }
    }
    
    last_value = value;
    return peak_detected;
}

// 计算心率
float HeartRate_Calculate(void)
{
    if(hr_detector.confidence < 50) {
        return 0.0f;  // 置信度太低
    }
    
    return hr_detector.heart_rate;
}

// 获取置信度
uint8_t HeartRate_GetConfidence(void)
{
    return hr_detector.confidence;
}

// 通过自相关计算心率
float HeartRate_Calculate_Autocorrelation(void)
{
    if(!hr_detector.buffer_full) {
        return 0.0f;
    }
    
    float acf[BUFFER_SIZE/2] = {0};
    float max_correlation = 0;
    uint16_t max_lag = 0;
    
    // 计算自相关
    for(uint16_t lag = 0; lag < BUFFER_SIZE/2; lag++) {
        float sum = 0;
        for(uint16_t i = 0; i < BUFFER_SIZE - lag; i++) {
            sum += hr_detector.filtered[i] * hr_detector.filtered[i + lag];
        }
        acf[lag] = sum;
        
        // 寻找第一个正峰值 (跳过lag=0)
        if(lag > 20 && acf[lag] > max_correlation) {
            max_correlation = acf[lag];
            max_lag = lag;
        }
    }
    
    if(max_lag > 0) {
        // 计算心率 (BPM)
        float sampling_interval = (hr_detector.timestamps[1] - hr_detector.timestamps[0]);
        float heart_rate = 60000.0f / (max_lag * sampling_interval);
        
        // 有效性检查
        if(heart_rate >= 30.0f && heart_rate <= 250.0f) {
            return heart_rate;
        }
    }
    
    return 0.0f;
}

6、血氧算法 (SpO2)

c 复制代码
// spo2_algorithm.c
#include "spo2_algorithm.h"
#include <math.h>

// 血氧计算结构
typedef struct {
    float red_ac;        // 红光交流分量
    float red_dc;        // 红光直流分量
    float ir_ac;         // 红外交流分量
    float ir_dc;         // 红外直流分量
    
    float red_buffer[BUFFER_SIZE];  // 红光缓冲区
    float ir_buffer[BUFFER_SIZE];   // 红外缓冲区
    uint16_t buffer_index;
    
    // 统计
    float R_value;       // R = (red_ac/red_dc) / (ir_ac/ir_dc)
    float spo2;          // 血氧饱和度
    uint8_t confidence;  // 置信度
} SpO2_Calculator;

static SpO2_Calculator spo2_calc = {0};

// 初始化血氧计算器
void SpO2_Init(void)
{
    memset(&spo2_calc, 0, sizeof(SpO2_Calculator));
    spo2_calc.confidence = 0;
}

// 添加新数据点
void SpO2_AddData(float red_value, float ir_value)
{
    // 保存原始数据
    spo2_calc.red_buffer[spo2_calc.buffer_index] = red_value;
    spo2_calc.ir_buffer[spo2_calc.buffer_index] = ir_value;
    
    spo2_calc.buffer_index = (spo2_calc.buffer_index + 1) % BUFFER_SIZE;
}

// 计算DC分量
void SpO2_CalculateDC(void)
{
    if(spo2_calc.buffer_index < BUFFER_SIZE) {
        return;  // 缓冲区未满
    }
    
    // 计算红光DC分量 (平均值)
    float red_sum = 0, ir_sum = 0;
    for(int i = 0; i < BUFFER_SIZE; i++) {
        red_sum += spo2_calc.red_buffer[i];
        ir_sum += spo2_calc.ir_buffer[i];
    }
    
    spo2_calc.red_dc = red_sum / BUFFER_SIZE;
    spo2_calc.ir_dc = ir_sum / BUFFER_SIZE;
}

// 计算AC分量
void SpO2_CalculateAC(void)
{
    if(spo2_calc.buffer_index < BUFFER_SIZE) {
        return;
    }
    
    // 计算红光AC分量 (标准差)
    float red_ac_sum = 0, ir_ac_sum = 0;
    for(int i = 0; i < BUFFER_SIZE; i++) {
        float red_diff = spo2_calc.red_buffer[i] - spo2_calc.red_dc;
        float ir_diff = spo2_calc.ir_buffer[i] - spo2_calc.ir_dc;
        
        red_ac_sum += red_diff * red_diff;
        ir_ac_sum += ir_diff * ir_diff;
    }
    
    spo2_calc.red_ac = sqrtf(red_ac_sum / BUFFER_SIZE);
    spo2_calc.ir_ac = sqrtf(ir_ac_sum / BUFFER_SIZE);
}

// 计算R值
void SpO2_CalculateR(void)
{
    if(spo2_calc.red_dc < 1.0f || spo2_calc.ir_dc < 1.0f) {
        return;
    }
    
    // 计算R = (red_ac/red_dc) / (ir_ac/ir_dc)
    float red_ratio = spo2_calc.red_ac / spo2_calc.red_dc;
    float ir_ratio = spo2_calc.ir_ac / spo2_calc.ir_dc;
    
    if(ir_ratio > 0.01f) {  // 避免除零
        spo2_calc.R_value = red_ratio / ir_ratio;
    }
}

// 计算血氧饱和度
float SpO2_Calculate(void)
{
    // 计算分量
    SpO2_CalculateDC();
    SpO2_CalculateAC();
    SpO2_CalculateR();
    
    // 有效性检查
    if(spo2_calc.red_dc < 1000.0f || spo2_calc.ir_dc < 1000.0f) {
        spo2_calc.confidence = 0;
        return 0.0f;
    }
    
    if(spo2_calc.R_value < 0.1f || spo2_calc.R_value > 3.0f) {
        spo2_calc.confidence = 0;
        return 0.0f;
    }
    
    // 通过经验公式计算血氧
    // 这里使用经验公式: SpO2 = 110 - 25 * R
    // 注意: 实际应用中需要校准
    
    float spo2 = 110.0f - 25.0f * spo2_calc.R_value;
    
    // 限制范围
    if(spo2 < 0.0f) spo2 = 0.0f;
    if(spo2 > 100.0f) spo2 = 100.0f;
    
    // 计算置信度
    float ac_ratio = spo2_calc.red_ac / spo2_calc.ir_ac;
    if(ac_ratio > 0.5f && ac_ratio < 2.0f) {
        spo2_calc.confidence = 100;
    } else {
        spo2_calc.confidence = 50;
    }
    
    spo2_calc.spo2 = spo2;
    return spo2;
}

// 通过查找表计算血氧
float SpO2_Calculate_LookupTable(float R)
{
    // 经验查找表 (R值 -> SpO2)
    static const float lookup_table[][2] = {
        {0.4, 100.0},  // R=0.4 -> 100%
        {0.8, 97.0},   // R=0.8 -> 97%
        {1.0, 95.0},   // R=1.0 -> 95%
        {1.2, 93.0},   // R=1.2 -> 93%
        {1.4, 90.0},   // R=1.4 -> 90%
        {1.6, 87.0},   // R=1.6 -> 87%
        {1.8, 84.0},   // R=1.8 -> 84%
        {2.0, 80.0},   // R=2.0 -> 80%
        {2.2, 75.0},   // R=2.2 -> 75%
        {2.4, 70.0},   // R=2.4 -> 70%
        {3.0, 50.0}    // R=3.0 -> 50%
    };
    
    // 查找最近的R值
    for(int i = 0; i < 10; i++) {
        if(R <= lookup_table[i][0]) {
            if(i == 0) {
                return lookup_table[0][1];
            }
            
            // 线性插值
            float R1 = lookup_table[i-1][0];
            float R2 = lookup_table[i][0];
            float SpO2_1 = lookup_table[i-1][1];
            float SpO2_2 = lookup_table[i][1];
            
            float spo2 = SpO2_1 + (SpO2_2 - SpO2_1) * (R - R1) / (R2 - R1);
            return spo2;
        }
    }
    
    return lookup_table[10][1];  // 返回最后一个值
}

// 获取R值
float SpO2_GetR(void)
{
    return spo2_calc.R_value;
}

// 获取置信度
uint8_t SpO2_GetConfidence(void)
{
    return spo2_calc.confidence;
}

7、 主数据处理

c 复制代码
// max30102_processor.c
#include "max30102.h"

// 滤波器实例
static MovingAverage_Filter red_filter;
static MovingAverage_Filter ir_filter;
static DC_Remover red_dc_remover;
static DC_Remover ir_dc_remover;
static Butterworth_Filter hr_filter;

// 数据处理
MAX30102_Result_t MAX30102_ProcessData(MAX30102_Data_t *data, uint16_t num_samples)
{
    MAX30102_Result_t result = {0};
    
    if(num_samples == 0) {
        return result;
    }
    
    // 处理每个样本
    for(int i = 0; i < num_samples; i++) {
        float red_value = (float)data[i].red;
        float ir_value = (float)data[i].ir;
        
        // 1. 移动平均滤波
        red_value = MovingAverage_Filter_Update(&red_filter, red_value);
        ir_value = MovingAverage_Filter_Update(&ir_filter, ir_value);
        
        // 2. DC移除
        float red_ac = DC_Remover_Update(&red_dc_remover, red_value);
        float ir_ac = DC_Remover_Update(&ir_dc_remover, ir_value);
        
        // 3. 带通滤波 (心率检测)
        float hr_signal = Butterworth_BP_Update(&hr_filter, red_ac);
        
        // 4. 心率检测
        HeartRate_AddData(hr_signal, data[i].timestamp);
        HeartRate_DetectPeak(hr_signal, data[i].timestamp);
        
        // 5. 血氧计算
        SpO2_AddData(red_value, ir_value);
    }
    
    // 计算心率
    result.heart_rate = HeartRate_Calculate();
    result.confidence = HeartRate_GetConfidence();
    
    // 计算血氧
    if(num_samples >= 100) {  // 收集足够样本
        result.spo2 = SpO2_Calculate();
        if(result.confidence < SpO2_GetConfidence()) {
            result.confidence = SpO2_GetConfidence();
        }
    }
    
    result.valid = (result.confidence > 70) ? 1 : 0;
    
    return result;
}

// 初始化处理器
void MAX30102_Processor_Init(void)
{
    // 初始化滤波器
    MovingAverage_Init(&red_filter, 4);
    MovingAverage_Init(&ir_filter, 4);
    DC_Remover_Init(&red_dc_remover, 0.95f);
    DC_Remover_Init(&ir_dc_remover, 0.95f);
    Butterworth_BP_Init(&hr_filter);
    
    // 初始化算法
    HeartRate_Init();
    SpO2_Init();
}

8、主程序

c 复制代码
// main.c
#include "main.h"
#include "max30102.h"

// 全局变量
MAX30102_Data_t sample_buffer[32];
MAX30102_Result_t latest_result = {0};
uint8_t new_data_available = 0;

int main(void)
{
    // HAL初始化
    HAL_Init();
    SystemClock_Config();
    
    // 外设初始化
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();
    MX_TIM2_Init();  // 用于定时采集
    
    printf("MAX30102 血氧和心率检测系统\r\n");
    printf("===========================\r\n");
    
    // 初始化MAX30102
    MAX30102_Init();
    
    // 初始化处理器
    MAX30102_Processor_Init();
    
    // 开始采集
    MAX30102_StartReading();
    
    // 启动定时器 (100Hz采样)
    HAL_TIM_Base_Start_IT(&htim2);
    
    while(1)
    {
        // 检查是否有新数据
        if(new_data_available)
        {
            new_data_available = 0;
            
            // 获取FIFO中的样本数
            uint8_t num_samples = MAX30102_GetNumSamples();
            if(num_samples > 0)
            {
                // 读取数据
                MAX30102_ReadFIFO(sample_buffer, num_samples);
                
                // 处理数据
                latest_result = MAX30102_ProcessData(sample_buffer, num_samples);
                
                // 显示结果
                if(latest_result.valid)
                {
                    DisplayResults(&latest_result);
                }
            }
        }
        
        // 处理其他任务
        ProcessUserInput();
        
        HAL_Delay(10);
    }
}

// 定时器中断回调 (100Hz)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        new_data_available = 1;
    }
}

// 显示结果
void DisplayResults(MAX30102_Result_t *result)
{
    static uint32_t last_display_time = 0;
    
    if(HAL_GetTick() - last_display_time < 1000) {
        return;  // 每秒显示一次
    }
    
    last_display_time = HAL_GetTick();
    
    printf("心率: %.1f BPM, 血氧: %.1f%%, 置信度: %d%%\r\n",
           result->heart_rate, result->spo2, result->confidence);
    
    // 读取并显示温度
    float temp = MAX30102_ReadTemperature();
    printf("温度: %.2f°C\r\n", temp);
}

// 处理用户输入
void ProcessUserInput(void)
{
    // 处理按键等用户输入
    // ...
}

9、校准功能

c 复制代码
// calibration.c
#include "calibration.h"

// 校准状态
typedef struct {
    float red_dc_offset;
    float ir_dc_offset;
    float red_gain;
    float ir_gain;
    float temperature_offset;
    uint8_t calibrated;
} Calibration_Data;

static Calibration_Data calib = {0};

// 开始校准
void Calibration_Start(void)
{
    printf("开始校准...\r\n");
    printf("1. 请移开手指,等待10秒...\r\n");
    
    // 清空FIFO
    MAX30102_ClearFIFO();
    
    // 等待稳定
    HAL_Delay(10000);
    
    // 采集暗电流
    Calibrate_DarkCurrent();
    
    printf("2. 请放置手指,等待10秒...\r\n");
    HAL_Delay(10000);
    
    // 采集参考信号
    Calibrate_Reference();
    
    calib.calibrated = 1;
    printf("校准完成!\r\n");
}

// 校准暗电流
void Calibrate_DarkCurrent(void)
{
    MAX30102_Data_t samples[100];
    float red_sum = 0, ir_sum = 0;
    
    // 读取100个样本
    for(int i = 0; i < 100; i++) {
        MAX30102_ReadFIFO(&samples[i], 1);
        red_sum += samples[i].red;
        ir_sum += samples[i].ir;
        HAL_Delay(10);
    }
    
    calib.red_dc_offset = red_sum / 100.0f;
    calib.ir_dc_offset = ir_sum / 100.0f;
    
    printf("暗电流校准: 红光=%.0f, 红外=%.0f\r\n", 
           calib.red_dc_offset, calib.ir_dc_offset);
}

// 校准参考值
void Calibrate_Reference(void)
{
    MAX30102_Data_t samples[100];
    float red_sum = 0, ir_sum = 0;
    
    // 读取100个样本
    for(int i = 0; i < 100; i++) {
        MAX30102_ReadFIFO(&samples[i], 1);
        red_sum += samples[i].red;
        ir_sum += samples[i].ir;
        HAL_Delay(10);
    }
    
    // 计算增益
    calib.red_gain = 10000.0f / (red_sum/100.0f - calib.red_dc_offset);
    calib.ir_gain = 10000.0f / (ir_sum/100.0f - calib.ir_dc_offset);
    
    printf("增益校准: 红光=%.6f, 红外=%.6f\r\n", 
           calib.red_gain, calib.ir_gain);
}

// 应用校准
void Apply_Calibration(float *red, float *ir)
{
    if(!calib.calibrated) {
        return;
    }
    
    // 减去偏移
    *red = *red - calib.red_dc_offset;
    *ir = *ir - calib.ir_dc_offset;
    
    // 应用增益
    *red = *red * calib.red_gain;
    *ir = *ir * calib.ir_gain;
    
    // 确保非负
    if(*red < 0) *red = 0;
    if(*ir < 0) *ir = 0;
}

10、上位机通信

c 复制代码
// serial_protocol.c
#include "serial_protocol.h"

// 数据包结构
typedef struct {
    uint8_t header[2];  // 0xAA 0x55
    uint8_t type;       // 数据类型
    uint8_t length;     // 数据长度
    uint8_t data[16];   // 数据
    uint8_t checksum;   // 校验和
} Serial_Packet;

// 发送数据到上位机
void Send_To_Host(MAX30102_Result_t *result, MAX30102_Data_t *raw_data, uint8_t num_samples)
{
    Serial_Packet packet;
    
    // 1. 发送结果数据
    packet.header[0] = 0xAA;
    packet.header[1] = 0x55;
    packet.type = 0x01;  // 结果数据
    packet.length = 9;   // 4(心率) + 4(血氧) + 1(置信度)
    
    memcpy(&packet.data[0], &result->heart_rate, 4);
    memcpy(&packet.data[4], &result->spo2, 4);
    packet.data[8] = result->confidence;
    
    // 计算校验和
    packet.checksum = Calculate_Checksum(&packet);
    
    // 发送
    HAL_UART_Transmit(&huart1, (uint8_t*)&packet, sizeof(packet), 1000);
    
    // 2. 发送原始数据
    if(num_samples > 0) {
        packet.type = 0x02;  // 原始数据
        packet.length = num_samples * 6;  // 每个样本6字节
        
        for(int i = 0; i < num_samples; i++) {
            memcpy(&packet.data[i*6], &raw_data[i].red, 3);
            memcpy(&packet.data[i*6+3], &raw_data[i].ir, 3);
        }
        
        packet.checksum = Calculate_Checksum(&packet);
        HAL_UART_Transmit(&huart1, (uint8_t*)&packet, sizeof(packet), 1000);
    }
}

参考代码 max30102获取血氧浓度 www.youwenfan.com/contentcsu/70139.html

五、Python上位机示例

python 复制代码
# max30102_gui.py
import serial
import struct
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from collections import deque
import time

class MAX30102_Visualizer:
    def __init__(self, port='COM3', baudrate=115200):
        self.serial = serial.Serial(port, baudrate, timeout=1)
        self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(10, 8))
        
        # 数据缓冲区
        self.buffer_size = 200
        self.red_data = deque(maxlen=self.buffer_size)
        self.ir_data = deque(maxlen=self.buffer_size)
        self.time_data = deque(maxlen=self.buffer_size)
        
        # 心率和血氧
        self.heart_rate = 0
        self.spo2 = 0
        self.confidence = 0
        
        # 初始化时间
        self.start_time = time.time()
        
        # 初始化绘图
        self.setup_plot()
        
    def setup_plot(self):
        # 设置子图1:原始数据
        self.ax1.set_title('MAX30102 Raw Data')
        self.ax1.set_xlabel('Time (s)')
        self.ax1.set_ylabel('ADC Value')
        self.ax1.grid(True)
        
        self.red_line, = self.ax1.plot([], [], 'r-', label='Red', alpha=0.7)
        self.ir_line, = self.ax1.plot([], [], 'b-', label='IR', alpha=0.7)
        self.ax1.legend(loc='upper right')
        
        # 设置子图2:心率血氧
        self.ax2.set_title('Heart Rate & SpO2')
        self.ax2.axis('off')
        
        # 文本显示
        self.hr_text = self.ax2.text(0.1, 0.7, 'Heart Rate: -- BPM', 
                                     fontsize=20, fontweight='bold')
        self.spo2_text = self.ax2.text(0.1, 0.4, 'SpO2: -- %', 
                                       fontsize=20, fontweight='bold')
        self.conf_text = self.ax2.text(0.1, 0.1, 'Confidence: -- %', 
                                       fontsize=12)
        
    def parse_packet(self, data):
        if len(data) < 5:
            return None
            
        # 检查包头
        if data[0] != 0xAA or data[1] != 0x55:
            return None
            
        packet_type = data[2]
        length = data[3]
        
        if len(data) < 5 + length:
            return None
            
        # 校验和
        checksum = sum(data[:-1]) & 0xFF
        if checksum != data[-1]:
            return None
            
        return packet_type, data[4:4+length]
    
    def update_plot(self, frame):
        # 读取串口数据
        while self.serial.in_waiting:
            data = self.serial.read(self.serial.in_waiting)
            result = self.parse_packet(data)
            
            if result:
                packet_type, payload = result
                
                if packet_type == 0x01:  # 结果数据
                    self.parse_result(payload)
                elif packet_type == 0x02:  # 原始数据
                    self.parse_raw_data(payload)
        
        # 更新绘图
        current_time = time.time() - self.start_time
        
        if len(self.time_data) > 0:
            self.red_line.set_data(self.time_data, self.red_data)
            self.ir_line.set_data(self.time_data, self.ir_data)
            
            # 自动调整X轴范围
            self.ax1.set_xlim(current_time - 10, current_time)
            
            # 自动调整Y轴范围
            all_data = list(self.red_data) + list(self.ir_data)
            if all_data:
                y_min = min(all_data) * 0.9
                y_max = max(all_data) * 1.1
                self.ax1.set_ylim(y_min, y_max)
        
        # 更新文本
        self.hr_text.set_text(f'Heart Rate: {self.heart_rate:.1f} BPM')
        self.spo2_text.set_text(f'SpO2: {self.spo2:.1f} %')
        self.conf_text.set_text(f'Confidence: {self.confidence} %')
        
        return self.red_line, self.ir_line, self.hr_text, self.spo2_text, self.conf_text
    
    def parse_result(self, data):
        if len(data) >= 9:
            self.heart_rate = struct.unpack('<f', data[0:4])[0]
            self.spo2 = struct.unpack('<f', data[4:8])[0]
            self.confidence = data[8]
            
            print(f"HR: {self.heart_rate:.1f}, SpO2: {self.spo2:.1f}, Conf: {self.confidence}")
    
    def parse_raw_data(self, data):
        num_samples = len(data) // 6
        
        for i in range(num_samples):
            red_bytes = data[i*6:i*6+3] + b'\x00'
            ir_bytes = data[i*6+3:i*6+6] + b'\x00'
            
            red = struct.unpack('<I', red_bytes)[0] & 0x3FFFF
            ir = struct.unpack('<I', ir_bytes)[0] & 0x3FFFF
            
            self.red_data.append(red)
            self.ir_data.append(ir)
            self.time_data.append(time.time() - self.start_time)
    
    def run(self):
        ani = FuncAnimation(self.fig, self.update_plot, interval=50, blit=True)
        plt.tight_layout()
        plt.show()

if __name__ == "__main__":
    visualizer = MAX30102_Visualizer('COM3', 115200)
    visualizer.run()

六、测试和验证

c 复制代码
void Test_MAX30102(void)
{
    printf("=== MAX30102 测试程序 ===\r\n");
    
    // 1. 基本功能测试
    printf("1. 读取ID...\r\n");
    uint8_t id = MAX30102_ReadID();
    printf("   ID: 0x%02X\r\n", id);
    
    // 2. 温度测试
    printf("2. 读取温度...\r\n");
    float temp = MAX30102_ReadTemperature();
    printf("   温度: %.2f°C\r\n", temp);
    
    // 3. 采集测试
    printf("3. 采集测试...\r\n");
    MAX30102_Data_t samples[10];
    
    for(int i = 0; i < 10; i++) {
        MAX30102_ReadFIFO(samples, 1);
        printf("   样本%d: 红光=%lu, 红外=%lu\r\n", 
               i, samples[0].red, samples[0].ir);
        HAL_Delay(100);
    }
    
    // 4. 心率和血氧测试
    printf("4. 心率和血氧测试...\r\n");
    printf("   请放置手指...\r\n");
    
    MAX30102_StartReading();
    HAL_Delay(10000);  // 采集10秒
    
    MAX30102_Result_t result = MAX30102_ProcessData(samples, 10);
    printf("   心率: %.1f BPM\r\n", result.heart_rate);
    printf("   血氧: %.1f%%\r\n", result.spo2);
    
    MAX30102_StopReading();
    
    printf("测试完成!\r\n");
}
相关推荐
aini_lovee1 小时前
基于STM32F103的数控电源设计方案
stm32·单片机·嵌入式硬件
LCG元1 小时前
STM32实战:基于STM32F103的智能鱼缸监控投喂系统(水质监测+自动换水)
stm32·单片机·嵌入式硬件
深圳市晨芯阳科技有限公司1 小时前
晨芯阳科技HC358-N双通道运算放大IC
科技·单片机·嵌入式硬件
一路往蓝-Anbo1 小时前
第四章:手撕协议栈 —— 缓冲区与结构体数据的 Mock 技巧
网络·stm32·单片机·嵌入式硬件·软件工程·tdd
jghhh012 小时前
STM32指纹密码锁的程序
stm32·单片机·嵌入式硬件
Achou.Wang2 小时前
从 Atomic 到 Futex:深入解析并发同步的三重境界
单片机·嵌入式硬件
不怕犯错,就怕不做2 小时前
linux的notifier_block内核通知链
linux·驱动开发·嵌入式硬件
时空自由民.2 小时前
Arm Coretex-M核MCU做IAP/OTA升级时候为什么要做中断向量表地址偏移?
arm开发·单片机·嵌入式硬件
不脱发的程序猿2 小时前
MCU升级固件合并和转换工具
单片机·嵌入式硬件