文章目录
-
- 1、摘要
- 2.算法核心思想和心率信号的有效特征
- 3.动态阈值算法分为思维分析
- 4.算法的整体实现
- 5.算法实现效果
- 6、小结
1、摘要
上一篇文章具体介绍了如何使用DMA和ADC收集心电数据并上传到上位机。在收集心电数据后,微处理器需要做的是将数据转换为可分析的生理指标心率。顾名思义,心率是指一分钟内的心跳次数。传统的心率计算方法是计时一分钟,测量一分钟内产生多少脉搏。这种做法的缺点很明显,一是效率很低,每次心率测量的时间间隔很长,二是容易分析错误,可能会忽略一些关键数据点。为了解决这种方法的缺点,本文将介绍一种心率分析算法:动态阈值算法,从采样的心电信号中分析实时心率,并将实时心率大小上传到上位机进行观察。
2.算法核心思想和心率信号的有效特征
算法的核心思想是测量相邻两个脉搏的时间间隔,然后用心率的单位时间(一分钟)除以这个时间间隔来获得心率。基于这种心率分析算法,可以在心率信号中获得两个有效的特征点
-
IBI(Inter-Beat Intervals):心搏间隔是指两次有效脉搏之间的时间间隔。ms。心电图中的标记如下图所示(有多种选择标准,效果相同):
-
BPM(Beat Per Minute):每分钟节拍数,这里指的是心率值IBI两者之间的转换关系是BPM = 60000 / IBI;
3.动态阈值算法分为思维分析
无论采用传统传统方法和动态阈值算法,只有识别一个脉搏,才能在一分钟内计算脉搏数或两个相邻脉搏之间的时间间隔。我们立即想到的方法可能是从收集的电压波形数据中识别峰/谷,并在程序中人工设置阈值。当读取的电压数据超过此阈值时,系统可以认为检测到脉搏。该方法在应对输出的电压波形相对完整时是可行的(峰谷值相对稳定,波形频率重量较小),但在心率计算中,这种测量会与实际心率产生较大的误差,原因如下:
- 心电波形电压输出不稳定,振幅大小,极易变化;
- 心电波形含有较多的频率重量,采用常规方法计算容易识别无效波形,导致结果与真实值之间的偏差。
由于固定阈值的方法不可取,因此自然会考虑根据信号振幅调整阈值以适应不同信号的峰值检测。通过对一个周期内的信号进行多次采样,得到信号的最高和最低电压值,从而计算阈值,然后判断采集的电压值是否为峰值。也就是说,电压信号的处理分为两个步骤。首先计算参考阈值,然后用阈值确定电压信号,识别峰值,如下图所示:
以上是计算有效波形的有效波形IBI只需要一点。需要从有效信号中选择特征点,其性质是:每个有效波形只有一个特征点,代表有效脉搏,只要能识别特征点,就能触发任何动作(如计数、记录特征点时间)。通过记录两个相邻特征点的时间并寻求差值来计算IBI在本节中,我们将选择心电波形上升到振幅的一半作为特征点。我们可以捕获这个特征点,记录捕获时间,然后计算它IBI。
4.算法的整体实现
动态阈值算法的总体思路如下:
- 在波形周期中缓存多次采样值,找出最大最小值,计算振幅中间值作为信号判定阈值;
- 将当前采样值与上一采样值与阈值进行比较,找出信号上升到振幅中间位置的特征点,记录当前时间;
- 找到下一个特征点并记录时间,计算两个点的时差,即相邻两个脉搏的时间间隔IBI;
- 由IBI计算心率值BPM;
算法代码实现如下:
/** *@brief 寻找当前数组的最大值 *@attention 无 *@param data[] 16.无符号整形数组 *@retval 目前数组最大值 */ static uint16_t MaxElement(uint16_t data[]) {
uint8_t i; uint16_t max = data[0]; for(i = 1; i < Size; i ) {
if(data[i] >= max) {
max = data[i]; } } return max; } /** *@brief 寻找当前数组的最小值 *@attention 无 *@param data[] 16.无符号整形数组 *@retval 目前数组最小值 */ static uint16_t MinElement(uint16_t data[])
{
uint8_t i;
uint16_t min = data[0];
for(i = 1; i < Size; i++)
{
if(data[i] <= min)
{
min = data[i];
}
}
return min;
}
/** *@brief 计算心率函数 *@attention 在主函数中以15Hz频率工作 *@param data 16位无符号整形数据 *@retval 无 */
void Rate_Calculate(uint16_t data)
{
static uint8_t index; //当前数组下标
static uint8_t pulseflag; //特征点数目
static uint16_t Input_Data[Size]; //缓存数据数组
static uint16_t predata, redata; //一个波形周期内读取上一次值、当前值
uint16_t Maxdata, Mindata; //最大值,最小值
static uint16_t Middata; //平均值
static uint16_t range = 1024; //幅度
static uint8_t prepulse, pulse; //衡量指标值,用以标记特征点
static uint16_t time1, time2, time; //检测时间
uint16_t IBI, BIM; //两个特征点间隔时间,心率大小
predata = redata; //保存上一次的值
redata = data; //读取当前值
if(redata - predata < range) //滤除突变噪声信号干扰
{
Input_Data[index] = redata; //填充缓存数组
index++;
}
if(index >= Size)
{
index = 0; //覆盖数组
Maxdata = MaxElement(Input_Data);
Mindata = MinElement(Input_Data);
Middata = (Maxdata + Mindata) / 2; //更新参考阈值
range = (Maxdata - Mindata) / 2; //更新突变阈值
}
prepulse = pulse; //保存当前脉冲状态
pulse = (redata > Middata) ? 1 : 0; //采样值大于中间值为有效脉冲
if(prepulse == 0 && pulse == 1) //当检测到两个有效脉冲时为一个有效心率周期
{
pulseflag++;
pulseflag = pulseflag % 2;
if(pulseflag == 1) //检测到第一个有效脉冲
{
time1 = time; //标记第一个特征点检测时间
}
if(pulseflag == 0) //检测到第二个有效脉冲
{
time2 = time; //标记第二个特征点检测时间
time = 0; //清空计时
if(time1 < time2)
{
IBI = (time2 - time1) * Period; //计算两个有效脉冲的时间间隔(单位:ms)
BIM = 60000 / IBI; //1分钟有60000ms
/**限制BIM最高/最低值*/
if(BIM > 200)
{
BIM = 200;
}
if(BIM < 30)
{
BIM = 30;
}
printf("Currented Heart Rate:%d ", BIM);
}
}
}
time++;
}
注意事项:
- Size的大小合适选取,原则是尽可能使采样点覆盖一个完整的波形,但又不适宜过大,导致过多的数据点被缓存,降低准确率,同时要控制采样时间Period,不宜采样时间过快或过慢,这里给出两组合适的方案:
Size = 66 Period = 15 or Size = 40 Period = 25。
- 传感器与手指之间的接触要保持稳定,频繁变动接触点及按压力度可能会对测量结果产生影响。
5、算法实现效果
按照正确的操作测量得到的心率数据在上位机中显示如下: 所测量的数据会自动保存在指定路径的txt文档中:
6、小结
本节介绍了采用动态阈值算法获取心率值的核心思想以及实现步骤,较为精确地测量出了实时心率数据。下一节将会介绍上位机的具体实现。