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