血氧检测原理与算法
血氧饱和度测量原理
血氧饱和度(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;elsemedian_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; // 异常情况}
}