第四节:信号采集与预处理——ADC采样配置、数字滤波设计与去基线漂移算法
好,咱们进入正题。信号采集与预处理,这步要是没做好,后面算法再牛也白搭。我见过太多项目,ADC配置错了,滤波参数没调好,结果血压心率算出来全是错的。今天咱们就把这块彻底讲透。
4.1 ADC采样配置——别小看这一步
ADC采样,说白了就是把模拟的脉搏波信号变成数字信号。你想想看,传感器出来的信号是连续的电压变化,但单片机只能处理0和1。所以ADC就是那个翻译官。
我个人习惯,血压计用的ADC分辨率至少12位。为什么?因为脉搏波信号动态范围不大,但细节很重要。8位ADC的话,一个心跳周期可能就几十个采样点,波形细节全丢了。
采样率也是个关键。我记得有一次,一个学生用100Hz采样率去做血压测量,结果收缩压怎么算都不准。我一看,波形都变形了。为什么?因为脉搏波的主峰宽度大概100-200毫秒,100Hz采样率一个主峰才10-20个点,够干啥的?
我建议采样率至少250Hz,最好500Hz。这样每个心跳周期能采到300-500个点,波形细节就丰富多了。
ADC配置要点:
- 分辨率:12位或以上
- 采样率:250Hz-500Hz
- 参考电压:根据传感器输出范围选择,一般3.3V或5V
- 采样模式:连续扫描模式,避免丢点
代码示例,我用STM32的ADC配置给大家看看:
// ADC初始化配置
void ADC_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 使能ADC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
// ADC结构体配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 通道数
ADC_Init(ADC1, &ADC_InitStructure);
// 配置采样时间,保证采样率
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 使能ADC
ADC_Cmd(ADC1, ENABLE);
// 校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
避坑指南:我曾经遇到过一个问题,ADC采样值总是跳变。查了半天,发现是参考电压没加去耦电容。记住,ADC的Vref引脚一定要加10uF+0.1uF的电容,不然噪声会直接耦合进采样结果。
4.2 数字滤波设计——低通、高通、带通
原始信号进来了,但里面全是噪声。工频干扰50Hz、肌电干扰、呼吸干扰...你想想看,这些噪声叠加在脉搏波上,算法根本没法用。
数字滤波,说白了就是用数学方法把不要的频率成分滤掉。我一般用IIR滤波器,因为计算量小,适合嵌入式实时处理。
4.2.1 低通滤波器
低通滤波器用来滤除高频噪声。脉搏波的主要能量集中在0.5-10Hz,所以低通截止频率设在15-20Hz就够了。
我记得有一次,一个项目里用了30Hz的截止频率,结果波形上还有毛刺。降到15Hz后,波形平滑多了,而且脉搏波的特征点(主峰、重搏波)都保留得很好。
二阶巴特沃斯低通滤波器实现:
// 二阶IIR低通滤波器,截止频率15Hz,采样率250Hz
typedef struct {
float b0, b1, b2;
float a1, a2;
float x1, x2; // 输入历史
float y1, y2; // 输出历史
} IIR_Filter;
void IIR_LowPass_Init(IIR_Filter *filt)
{
// 系数由双线性变换法计算得到
filt->b0 = 0.0201f;
filt->b1 = 0.0402f;
filt->b2 = 0.0201f;
filt->a1 = -1.5610f;
filt->a2 = 0.6414f;
filt->x1 = filt->x2 = 0.0f;
filt->y1 = filt->y2 = 0.0f;
}
float IIR_Filter_Process(IIR_Filter *filt, float input)
{
float output;
output = filt->b0 * input +
filt->b1 * filt->x1 +
filt->b2 * filt->x2 -
filt->a1 * filt->y1 -
filt->a2 * filt->y2;
// 更新历史值
filt->x2 = filt->x1;
filt->x1 = input;
filt->y2 = filt->y1;
filt->y1 = output;
return output;
}
4.2.2 高通滤波器
高通滤波器用来去除直流分量和极低频漂移。脉搏波信号中,直流分量占了很大比重,但对我们分析波形没用。高通截止频率一般设在0.3-0.5Hz。
你想想看,如果不去掉直流分量,ADC采到的值可能一直在2000-3000之间跳动,但我们要的是波形的变化量,不是绝对值。
高通滤波器设计要点:
- 截止频率:0.3-0.5Hz
- 阶数:二阶足够,阶数太高会引入相位失真
- 类型:巴特沃斯或切比雪夫,巴特沃斯更常用(通带平坦)
4.2.3 带通滤波器
有时候,我会把低通和高通串起来,形成一个带通滤波器。这样既能滤除高频噪声,又能去掉基线漂移。说白了,就是只保留0.5-15Hz这个频段,这正是脉搏波的主要能量范围。
我建议的做法是:先高通,再低通。为什么?因为高通滤波后信号幅度会变小,再低通时计算误差会小一些。
4.3 去基线漂移算法
基线漂移,就是信号整体在缓慢上下移动。原因很多:呼吸、肢体运动、传感器压力变化...如果不处理,血压计算会出大问题。
举个例子,收缩压是根据波形峰值来算的。如果基线在漂移,峰值也跟着漂,算出来的血压值就不准了。
去基线漂移,我常用的方法有三种:
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 高通滤波法 | 用高通滤波器直接滤除低频成分 | 实现简单,实时性好 | 会损失部分低频脉搏波成分 |
| 多项式拟合 | 用多项式拟合基线,然后减去 | 精度高,适合离线处理 | 计算量大,不适合实时 |
| 中值滤波 | 用中值滤波器提取基线 | 对脉冲噪声不敏感 | 窗口大小需要调试 |
我个人最常用的是高通滤波法,因为嵌入式系统讲究实时性。但要注意,高通滤波器的截止频率不能太高,否则会把有用的脉搏波信息也滤掉。
警告:去基线漂移时,千万不要用滑动平均法!我曾经踩过这个坑。滑动平均虽然能平滑基线,但它会引入相位延迟,导致波形特征点(比如主峰位置)发生偏移。血压计算对相位非常敏感,偏移几个采样点,收缩压可能就差5-10mmHg。
还有一种方法,我称之为「分段线性去基线」。原理很简单:把信号分成若干段,每段内用线性拟合出基线,然后减去。这个方法在呼吸干扰严重时特别好用。
// 分段线性去基线漂移
void Remove_Baseline_Drift(float *signal, int len, int segment_len)
{
int i, j;
int num_segments = len / segment_len;
float baseline_start, baseline_end;
float slope;
for(i = 0; i < num_segments; i++)
{
// 取每段的首尾点作为基线参考点
baseline_start = signal[i * segment_len];
baseline_end = signal[(i + 1) * segment_len - 1];
// 计算斜率
slope = (baseline_end - baseline_start) / (segment_len - 1);
// 减去基线
for(j = 0; j < segment_len; j++)
{
signal[i * segment_len + j] -= (baseline_start + slope * j);
}
}
}
经验之谈:分段长度怎么选?我一般取2-3秒的数据长度。太短了会把心跳波形也当成基线去掉,太长了又跟不上呼吸漂移的速度。嗯,这个参数需要根据实际信号调试。
好了,信号采集与预处理这块就讲这么多。ADC配置、数字滤波、去基线漂移,这三步做好了,后面的特征提取和血压计算就轻松多了。记住一句话:预处理做得好,算法事半功倍;预处理做得差,后面全是坑。
下一节,咱们讲脉搏波特征点提取,包括主峰、重搏波、切迹这些关键点的定位方法。到时候我会分享一些我在项目中用过的「土办法」,虽然简单,但特别实用。