赞
踩
在PulseSensor开发文档(一)DMA ADC数据采集中,具体介绍了如何用DMA和ADC采集心电数据并上传到上位机。传感器在采集到心电数据并经微处理器读取后,微处理器需要将数据转换成为可分析的生理指标——心率。顾名思义,心率指的就是一分钟内的心跳次数。计算心率的传统方法是统计一分钟内产生脉搏的总数。这种做法的缺点十分明显,一是效率极低,每次更新心率测量的时间间隔非常长,二是该种测量方法可能会忽略某些关键数据点,导致测量值与实际值相差较大。为了解决这种方式的弊端,本篇文章将介绍一种心率解析算法:动态阈值算法,用以从采样的心电信号中解析出实时心率。
动态阈值算法的核心思想为测量相邻的两次脉搏的时间间隔,再用心率的单位时间(一分钟)除以这个时间间隔得出心率。基于这种心率解析算法可以得到心率信号中的两个有效特征点。
IBI(Inter-Beat Intervals):心搏间隔,指的是两次有效脉搏之间的时间间隔。单位:ms。在心电图中的标记显示如下图(有多种选取标 准,效果相同):
BPM(Beat Per Minute):每分钟节拍数,这里特指心率值,与IBI之间的转换关系为BPM = 60000 / IBI;
无论是采用传统方法以及本篇文章中所提到的动态阈值算法,只有识别出一个脉搏,才能数出一分钟内脉搏数或者计算两个相邻的脉搏之间的时间间隔。我们立刻想到的方法可能会是从采集的电压波形数据中识别一个波峰/波谷,人为地在程序中设定一个阈值,当读取的电压数据超过这个阈值时系统便可认为检测到了一次脉搏。这种方法在应对输出的电压波形较为齐整时是可行的(波峰和波谷的值较为稳定且波形的频率分量较少),但是在心率的测算中以此种方式的测量会与实际的心率产生较大的误差,原因有以下两点:
既然固定阈值的方法不可取,那自然想到改变阈值——根据信号振幅调整阈值以适应不同信号的波峰检测。通过对一个周期内的信号多次采样,得出信号的最高与最低电压值,由此算出阈值,再用这个阈值对采集的电压值进行判断,考虑是否为波峰。也就是说电压信号的处理分两步,首先计算出参考阈值,然后用阈值对电压信号进行判定,识别一个波峰,实现思路如下图所示:
上面得出的是一段有效波形,而计算IBI只需要一个点。需要从一段有效信号上选取一个特征点,这个特征点的性质是:每一个有效波形仅有一个特征点存在,这个特征点代表着一个有效脉搏,只要能识别到这个特征点,就能在一个脉冲到来时触发任何动作(如计数、记录特征点时间)。通过记录两个相邻特征点的时间并求差值,计算IBI便水到渠成,在本节中我们将选取心电波形上升到振幅的一半作为特征点,我们可以捕获这个特征点并记录捕获时间进而计算IBI。
根据上述分析,可得动态阈值算法的整体思路如下:
算法代码实现如下:
/** *@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 = 66 Period = 15 or Size = 40 Period = 25。
按照正确的操作测量得到的心率数据在上位机中显示如下:
所测量的数据会自动保存在指定路径的txt文档中:
本节介绍了采用动态阈值算法获取心率值的核心思想以及实现步骤,较为精确地测量出了实时心率数据。上位机的部分本次采用Visual Studio完成,比较简单且定制化因素较多,故不再开辟额外章节探讨,实现方式多种多样,大家可以自行发挥想象~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。