血氧检测原理与算法

发布于:2025-09-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

血氧饱和度测量原理

        血氧饱和度(SPO2)是指血液中氧和血红蛋白(HbO2)占总可结合血红蛋白(包括氧和血红蛋白和还原血红蛋白,即HbO2+Hb)的比例:

SPO2 = (HbO2/HbO2+Hb) * 100%

        临床上,SpO2的值通常不低于94%。

        血氧饱和度的测量方法可以分为两类:电化学法光学法电化学法通过动脉穿刺采集血液样本,利用血气分析仪分析动脉血中的氧分压,再通过计算得出血氧饱和度。该方法虽然测量精度高,但由于其有创性,操作复杂且实时性差,通常仅用于需要较高精度的特殊场合。与电化学法相比,光学法是一种连续的,无创的血氧饱和度测量方法,广泛应用于临床等领域。其原理是通过血氧探头检测血液对光吸收量的变化,计算氧和血红蛋白占总血红蛋白的百分比,,从而获得血氧饱和度。光学法具有操作简便,实时性强等优点,但其测量精度略低于电化学法。

        本实验采用光学法,用血氧指夹探头测量。

        关于血氧探头测量血氧的具体原理,读者可自行搜索。

血氧检测原理图

设计思路:

血氧探头发光管驱动电路

RED-,RED+,IR_CS,RED_CS均接单片机的引脚

压控恒流源电路

Ic = Ie = UTP11 / R111 = U- / R111,所以当UDA1增大是,IC也会增大

当IC增大时,UTp11也会增大,U-就会增大,但是UDA1没变,UTP10就会减小,进而Ib减小,那么IC就会受到负反馈的影响。

参考电压输出电路

信号放大滤波电路

完整的原理图

算法实现

滤波算法同呼吸和血氧

计算脉率

#define PEAK_MIN_INTERVAL  20  // 最小峰间隔(样本点)
#define DYNAMIC_THRESH_RATIO 0.7  // 动态阈值比例
double last_rate;
int find_peak(double *data,int* peak, int len)
{
    if(len < 3 || data == NULL) return 0;
    
    int count = 0;
    double max = 0;
    int16_t last_peak_index = -PEAK_MIN_INTERVAL; // 确保第一个峰值可被检测
    
    // 1. 计算全局最大值
    for(int i = 0; i < len; i++) {
        if(data[i] > max) max = data[i];
    }
    // 动态阈值(心率越高,阈值越低)
    double dynamic_thresh_ratio = 0.7 - 0.3 * (last_rate / 220.0);  // 线性调整
    if (dynamic_thresh_ratio < 0.4) dynamic_thresh_ratio = 0.4;      // 保护下限
    // 2. 动态阈值检测 + 峰间隔限制
    double dynamic_thresh = max * dynamic_thresh_ratio;
    
    for(int i = 1; i < len-1; i++) {
        if(data[i] > data[i-1] && 
           data[i] > data[i+1] && 
           data[i] > dynamic_thresh &&
           (i - last_peak_index) >= PEAK_MIN_INTERVAL) 
        {
            peak[count++] = i;
            last_peak_index = i;  // 更新最后峰值位置
            if(count >= RATE_BUFFER/2) break; // 防止数组溢出
        }
    }
    
    return count;
}
#define MEDIAN_FILTER_SIZE 5     // 中值滤波窗口大小
// 2. 生理范围限制 (30-200bpm)
const int min_interval = (60 * SAMPLE_RATE) / 210; // 最高心率间隔
const int max_interval = (60 * SAMPLE_RATE) / 25;  // 最低心率间隔

// 整数比较函数(用于qsort)
int compare_int(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}
double SPO2Rate(int* peak,int count)
{
   
    double median_interval = 0.0;
    // 1. 无效数据检查
    if(count < 2) return -1;    
    // 3. 计算有效间隔
    int valid_intervals[50] = {0};
    int valid_count = 0;
    for(int i = 1; i < count; i++) {
        int interval = peak[i] - peak[i-1];
        if(interval >= min_interval && interval <= max_interval) {
            valid_intervals[valid_count++] = interval;
        }
    }
    if(valid_count < 1) return -1;
    // 4. 中值滤波
    qsort(valid_intervals, valid_count, sizeof(int), compare_int);
    if(valid_count%2==0 )
        median_interval = (valid_intervals[valid_count/2]+valid_intervals[(valid_count/2)-1])/2;
    else
        median_interval = valid_intervals[valid_count/2];
    // 5. 计算心率并滑动平均
    double heartrate = (60.0 * SAMPLE_RATE) / median_interval;
    last_rate = heartrate;
  
    return heartrate;
}

计算血氧饱和度

typedef enum {
    FINGER_THIN,      // 细手指
    FINGER_NORMAL,    // 正常手指
    FINGER_THICK,     // 粗手指
    FINGER_UNKNOWN    // 无法识别
} FingerType;
double redADRng ;
double irADRng ;
//movingAverageFilter_Red_max等参考这个函数
double movingAverageFilter_HearteRate(double newSample, double *buffer)
{
    static int index = 0;
    static float sum = 0;
    
    // 减去最旧的值
    sum -= buffer[index];
    // 添加最新的值
    sum += newSample;
    // 更新缓冲区
    buffer[index] = newSample;
    // 更新索引
    index = (index + 1) % 10;
    
    // 返回平均值
    return sum / 10;
}
double rate(double red_buffer[],double ir_buffer[],int RR_TABLE[])
{

    // 初始化(避免依赖 buffer[0])
    double red_max = -INFINITY;
    double red_min = INFINITY;
    double ir_max = -INFINITY;
    double ir_min = INFINITY;
    for(int i = 0;i<BUFFER_THEREAD;i++)
    {
        if(red_max < red_buffer[i]) red_max = red_buffer[i];
        if(red_min > red_buffer[i]) red_min = red_buffer[i];
    }
    for(int i = 0;i<BUFFER_THEREAD;i++)
    {
        if(ir_max < ir_buffer[i]) ir_max = ir_buffer[i];
        if(ir_min > ir_buffer[i]) ir_min = ir_buffer[i];
    }
    red_max = movingAverageFilter_Red_max(red_max,red_max_buffer);
    red_min = movingAverageFilter_Red_min(red_min,red_min_buffer);
    ir_max = movingAverageFilter_ir_max(ir_max,ir_max_buffer);
    ir_min = movingAverageFilter_ir_min(ir_min,ir_min_buffer);
    redADRng = red_max - red_min;
    irADRng = ir_max-ir_min;

    double red_DC = (red_max + red_min)/2;
    double ir_DC  = (ir_max + ir_min)/2;
    if(redADRng < 5.0 || irADRng < 5.0)
    {
        figurestate = FIGURE_OFF;
    }
    else 
    { 
        figurestate = FIGURE_ON;
    }

    double light_intensity = get_current_light_intensity(red_DC, ir_DC)*1000.0;
    double dynamic_offset = get_dynamic_compensation(light_intensity);
    double R1 = redADRng * 1000.0/ irADRng -dynamic_offset;
    R1 = movingAverageFilter_R1(R1,R1Buffer);
    //double spo2 = 110.0 - 25.0 * R1;  // 示例公式,需校准
    int index = 1; 
    while(R1>=(double)RR_TABLE[index-1]&&index<11)
    {
        index++;
    }
    int spo2 = 101 - index;
    char str[20];     
    sprintf(str,"%.02lf,%.02lf,%.02lf",R1,redADRng,irADRng);
    LCD_WriteAsciiString(50,50,24,(uint8_t*)str,BLACK,WHITE);
    return spo2;
}
FingerType classify_finger_type() {
    if (irADRng <35 &&irADRng > 18.5) {
        return FINGER_THIN;      // 细手指
    } else if (irADRng <= 18.5 && irADRng >= 13.0) {
        return FINGER_NORMAL;    // 正常手指
    } else if ( irADRng <=16) {
        return FINGER_THICK;    // 粗手指
    } else {
        return FINGER_UNKNOWN;  // 异常情况
    }
}