心脏通过一系列电活动驱动其收缩和放松,这些电活动可以通过电极在身体表面检测到。心脏的电活动主要包括:
心电图(ECG)波形中的各种波代表了心脏电活动的不同阶段。标准的心电图波形包括P波、QRS波群和T波。每种波形反映了心脏电活动的特定部分。

定义:QRS波群代表心室的去极化过程,是心电图中最显著的波形。
组成:
Q波:QRS波群的第一个向下的波(负波),并不是所有心电图中都有Q波。 R波:QRS波群的第一个向上的波(正波),通常最为显著。
S波:R波后的向下波(负波),跟在R波后面。
特点:
形状:QRS波群通常为尖锐而且幅度较高。
持续时间:正常情况下,QRS波群的持续时间小于0.12秒(120毫秒)。
振幅:通常较大,尤其是R波。
意义:QRS波群的出现表示心室肌肉的去极化,即心室收缩。如果QRS波群的形状、持续时间或幅度异常,可能指示心室的电活动异常,如心室肥厚、心室早搏或心室传导阻滞。
综上所述,心电图的各个波形反映了心脏不同部位的电活动情况:
标准双极导联是心电图(ECG)中常用的配置之一。它使用两个电极来记录心脏的电活动,并且每个导联都是双极的,即通过两个电极之间的电位差来测量心脏信号。以下是常见的标准双极导联配置:
标准双极导联的特点是能够广泛地反映心脏的大致情况,如后壁心肌梗死、心律失常等,在导联Ⅱ或导联Ⅲ中可记录到清晰的波形改变。但标准导联只能说明两个肢体间的电位差,不能记录到单个电极处的电位变化。
在心电图(ECG)信号的测量和分析中,硬件和软件滤波器用于提高信号的质量,去除噪声,增强信号的可读性。

将原理图绘制好后进行PCB打板和贴片,通过板子实测模拟器发出的心电信号,打印出来得到原始心电波形:


这是心电信号进行归一化处理后得到的图形,能够清晰地看到不同频率在心电信号中的占比情况。图中可见一些干扰需要对其进行算法处理。
软件滤波是指在信号采集之后,通过计算机算法对信号进行处理。这种处理可以在信号记录后的后期分析中进行,本次实践中,只完成了50Hz陷波器、IIR低通滤波器、IIR高通滤波器、滑动窗口平滑器。
50Hz陷波器的本质在于其能够精准地滤除特定频率(50Hz)的干扰信号,而对其他频率的信号影响最小。它是一种带阻滤波器,设计用来降低或消除中心频率为50Hz的干扰,同时尽可能保留其余信号。
关于50Hz陷波器的理论原理详见陷波器及其算法(基于C语言)
以下为具体的设计步骤:
找到滤波器设计器进行滤波器设计

设计好滤波器后,找到相应的系数


Fs是采样频率,这里是500Hz。上述步骤是作为设计一个50Hz陷波器的步骤参考,最终得到的系数非下面代码所使用的系数,下面代码所使用的Fstop1和Fstop2的频率与上图不一致因此系数不一样。
//50Hz陷波器
static IIR_2OR_DATA_DEF notching_data = {
Notching_filter_50Hz_GAIN,
Notching_filter_50Hz_B0,
Notching_filter_50Hz_B1,
Notching_filter_50Hz_B2,
Notching_filter_50Hz_A1,
Notching_filter_50Hz_A2,
0, 0, 0, 0
};
//陷波滤波器
#define Notching_filter_50Hz_GAIN 0.888856976280745336715938265115255489945
#define Notching_filter_50Hz_B0 1
#define Notching_filter_50Hz_B1 -1.61816175199937850592846189101692289114
#define Notching_filter_50Hz_B2 1
#define Notching_filter_50Hz_A1 -1.438314362015320924115258094388991594315
#define Notching_filter_50Hz_A2 0.777713952561490673431876530230510979891
double iir_50Hz_2or_func(IIR_2OR_DATA_DEF *filter_date, double target)


从归一化图中可以看出,50Hz工频干扰滤除的效果不错,但高于50Hz的频率干扰没有滤除,需要进行IIR低通滤波。
IIR低通滤波器的作用是允许低频信号通过,同时抑制高频信号。关于原理详细可见嵌入式常用的算法 - 二阶IIR低通滤波器
static u16 lowFilters_IIR(u16 data)
{
static double x1=0,x2=0,y,y1=0,y2=0;
y=0.064591*(data +2.0*x1+1.0*x2)+1.401314*y1-0.659679*y2;
x2=x1;
x1=data;
y2=y1;
y1=y;
return y;
}
对于滤波器的系数同50Hz陷波器步骤可得,只不过需要改变滤波器类型和截止频率,根据具体要求设计相应功能的滤波器。


由图可见,50Hz后的干扰基本滤除了。
基线漂移通常表现为信号中的低频偏移或漂移。IIR高通滤波器可以去除基线漂移,因为它能够抑制低频信号成分。高通滤波器允许高频成分通过,同时对低频成分进行衰减,从而去除这些低频漂移,使信号的基线更稳定。
关于IIR高通滤波器这部分具体参考了一篇文献

这篇文献中具体展示了IIR高通滤波器对基线漂移的抑制效果

static u16 highFilters_IIR(u16 data)
{
static double x1=0,x2=0,y,y1=0,y2=0;
y=0.992173*(data -1.999993*x1+ 1.0*x2)+1.984303*y1-0.984382*y2; //IIR4阶二型切比雪夫 0.55 30dB
x2=x1;
x1=data;
y2=y1;
y1=y;
return y+2000;
}
由于处理后打印出来的数据比较小所以加2000可以放大波形,当然不加也没有太大关系。


滑动窗口平滑器(也称为滑动平均滤波器)是一种常用的信号处理技术,主要用于减少数据中的噪声和波动,平滑波形。
u16 sliding_average_filter(u16 value)
else
{
memcpy(&buffer[0], &buffer[1], (int)(10 - 1) * 4); //将1之后的数据移到0之后,即移除头部
buffer[10 - 1] = value; //即添加尾部
for (i = 0; i < 10; i++) //每一次都计算,可以避免累计浮点计算误差
{
sum += buffer[i];
}
output = sum / 10;
}
return output;
}

u16 ECG_Calculate(u16 data[Len] ,u16 len)
T = (Max_data - Min_data) * 0.9 + Min_data; //求出阈值T
//第2步找出数组中所有大于阈值T的索引,并存入新的数组中
for(i = 0 , k = 0 ;i < len;i++)
}
if(Feak_Flag == 1) //最终判定该数据是波峰
{
Feak_Index[k] = i;
k += 1;
Feak_Num += 1;
i = i + n_min; //提高代码运行速度,找到一个波峰索引后跳到下一个波峰索引的大概位置
}
}
}
}
//第3步计算Feak数组两相邻元素之间的差值,即两波峰之间的数据个数
if(Feak_Num > 1)
for(i = 1 ,j = 0; i < Feak_Num; i++ ,j++)
{
Index_num[j] = Feak_Index[i] - Feak_Index[i - 1];
}
else
Index_num[0] = Feak_Index[0];
//第4步将Index_num中的数据按从小到大的顺序进行排序
if(Feak_Num > 1)
for (i = 0; i < Feak_Num - 1; i++)
}
// 如果在这一轮遍历中没有发生交换,说明数组已经有序
if (!swapped)
break;
}
//第5步取中值作为R-R波之间的数据个数,计算心率
if(Feak_Num >= 2)
else //波峰数量小于2,为1或0
n = Index_num[0];
heart_rate = 30000 / n;
return heart_rate;
}