** ZigBee、NBIOT等无线通信技术交流学习,可以加入QQ技术交流群:575036716
可以到CSDN的下载版块下载该源码包: 点击打开链接
最近有朋友需要通过Pulse Sensor也就是心率传感器结合ZigBee进行开发,ZigBee是Ti的方案,主控是CC2530,因此写了这款传感器的驱动程序!
Pulse Sensor是一款用来检测心率的传感器,使用方法还是比较方便的,用户只需要用手指按住传感器就可以,传感器的实物图如下:
正面 背面
Pulse Sensor检测原理是通过发射光源,然后根据光源返回的效果输出ADC数据。由于手指上布满毛细血管,而毛细血管会随着心率而跳动,这就导致传感器发射和接收光的效果不同,从而达到ADC输出效果不同,比如下图就是ADC的输出图:
因此检测心率的方式很简单:测出两个ADC峰值间的时长,假设这个时长为T,单位是毫秒,那么1分钟的心率假设为S,则:
S = 60000/T 次/秒
程序设计需要考虑的地方:
1. 传感器检测时,必须手指按在传感器上,否则检测的数据是不对的,所以首先要判断传感器是否有手指接触。
2. 如何正确的获取两次峰值数据?
程序逻辑:
- if(halPulseSensorTouch())
- {
- if( (refPeak = halPulseSensorPeakFitting()) == 0 )
- goto RET;
- // First Beat
- if(halPulseSensorGetNextBeat(refPeak) == 0)
- goto RET;
- // Clear and Start Timer
- HAL_PULSE_SENSOR_TIMER_CLEAR();
- HAL_PULSE_SENSOR_TIMER_START();
- // Skip the peak value
- HAL_PULSE_SENSOR_DELAY_MS(200);
- // Second Beat
- secondBeat = halPulseSensorGetNextBeat(refPeak);
- // Stop Timer and get the delay time
- HAL_PULSE_SENSOR_TIMER_STOP();
- if(secondBeat == 0)
- goto RET;
- if( (beatTime = (uint16)HAL_PULSE_SENSOR_TIMER_GET_TIME()) == 0 )
- goto RET;
- if( (dat.heartRate = (uint16)60000 / beatTime) != 0 )
- dat.ok = TRUE;
- }
首先我们通过函数halPulseSensorTouch()检测是否手指是触摸在传感器上的,如果是我们才进行数据读取!
然后函数halPulseSensorPeakFitting()是用来获取ADC峰值的数据,这个函数会拟合出一个峰值的数值作为参考数值,
具体的做法是不断地获取峰值数据,然后筛选出相近的一组数值求取平均值,这个平均值将作为峰值参考数值!
然后确定一个峰值点,作为时间的起始点,这时候开启定时器,定时时间直到获取到下一个峰值点,这段时间就是一次心跳
的时长:
- // First Beat
- if(halPulseSensorGetNextBeat(refPeak) == 0)
- goto RET;
开启定时器并尝试获取第二个峰值:
- // Clear and Start Timer
- HAL_PULSE_SENSOR_TIMER_CLEAR();
- HAL_PULSE_SENSOR_TIMER_START();
- // Skip the peak value
- HAL_PULSE_SENSOR_DELAY_MS(200); // <-- 获取一个峰值后,如果直接读取数据,可能瞬间读到的数据还是峰值数据,所以通过延时跳过峰值阶段
- // Second Beat
- secondBeat = halPulseSensorGetNextBeat(refPeak); // <---------------- 获取第二个峰值
- // Stop Timer and get the delay time
- HAL_PULSE_SENSOR_TIMER_STOP();
剩下的数据就是读取这段时间定时器时长,然后计算出心率就OK了:
- if( (beatTime = (uint16)HAL_PULSE_SENSOR_TIMER_GET_TIME()) == 0 )
- goto RET;
- if( (dat.heartRate = (uint16)60000 / beatTime) != 0 ) // <------- 计算出心率
- dat.ok = TRUE;
判断手指是否按下的方法: 先获取一个ADC的数据作为参考点,然后连续再获取20个数据(数量可调整)作为采样点,如果说这些采样点有一半以上和参考点是相近的,可以认为是没有手指按下!
- static BOOL halPulseSensorTouch(void)
- {
- uint8 i;
- uint16 timeout = 0;
- uint8 cnt = 0;
- uint8 adc8Next;
- uint8 adc8;
- /* First touch or Realease */
- while( (adc8 = HAL_PULSE_SENSOR_ADC8()) < HAL_PULSE_SENSOR_TOUCH_ADC8 ||
- adc8 > HAL_PULSE_SENSOR_REALEASE_ADC8)
- {
- HAL_PULSE_SENSOR_DELAY_MS(10);
- if(++timeout >= 100)
- goto ERROR;
- }
- /* Touching */
- adc8 = HAL_PULSE_SENSOR_ADC8();
- for(i = 0; i < HAL_PULSE_SENSOR_SPACING_CMP_TIMES; i++)
- {
- HAL_PULSE_SENSOR_DELAY_MS(20);
- adc8Next = HAL_PULSE_SENSOR_ADC8();
- if( ( adc8Next >= adc8 && adc8Next < (adc8 + HAL_PULSE_SENSOR_SPACING) ) ||
- ( adc8Next < adc8 && (adc8Next + HAL_PULSE_SENSOR_SPACING) > adc8 ) )
- {
- continue;
- }
- cnt++;
- }
- if(cnt >= HAL_PULSE_SENSOR_SPACING_CMP_TIMES/2)
- return TRUE;
- ERROR:
- return FALSE;
- }
获取峰值的方法: 由于人的心率是有范围的,正常人是60~100次/分钟,也有可能50、120这样,如果我们取一个极限值30次/Min,也就是说两个峰值的时长是2秒,再极端点算3秒,时长可自行调整,也就是说3秒内一定最少有一个峰值数据出现,我们只需要在这段时间内不断的获取ADC数据然后取最大值即可!当然这个过程还需要考虑其他情况,比如手指突然松开等等。
- static uint8 halPulseSensorGetPeak(void)
- {
- uint16 i;
- uint8 adc8;
- uint8 adc8Peak = 0;
- /* Get and compare the ADC value in 3 Second */
- for(i = 0; i < 300; i++)
- {
- HAL_PULSE_SENSOR_DELAY_MS(10);
- adc8 = HAL_PULSE_SENSOR_ADC8();
- if(adc8 >= adc8Peak)
- {
- adc8Peak = adc8;
- }
- }
- if(adc8Peak <= HAL_PULSE_SENSOR_TOUCH_ADC8 ||
- adc8Peak >= HAL_PULSE_SENSOR_REALEASE_ADC8)
- {
- adc8Peak = 0;
- }
- return adc8Peak;
- }
拟合峰值数据的方法:可以在每次获取心率时,由于每次都需要获取峰值来作为参考峰值数据,所以我们可以保存起来,比如保存10个数据,然后把这10个数据中相近的数值保留起来,其他删除等到下次更新,直到10个数据都是相近数据,然后我们可以算出平均值,这个平均值就会一直作为我们的参考峰值数据了!
- static uint8 halPulseSensorPeakFitting(void)
- {
- uint8 peak;
- uint8 index;
- for(index = 0; index < HAL_PULSE_SENSOR_PEAK_LEN; index++)
- {
- if(halPulseSensorPeak_g[index] == 0)
- break;
- }
- if(index < HAL_PULSE_SENSOR_PEAK_LEN)
- {
- peak = halPulseSensorGetPeak();
- halPulseSensorPeak_g[index] = peak;
- }
- else
- {
- for(uint8 i = 0; i < HAL_PULSE_SENSOR_PEAK_LEN; i++)
- {
- uint8 cnt = 0;
- if( halPulseSensorPeak_g[i] == 0 )
- continue;
- for(uint8 j = 0; j < HAL_PULSE_SENSOR_PEAK_LEN; j++)
- {
- if( halPulseSensorPeak_g[j] == 0 )
- continue;
- if( halPulseSensorPeak_g[i] < (halPulseSensorPeak_g[j] - HAL_PULSE_SENSOR_FITTING_ERR) ||
- halPulseSensorPeak_g[i] > (halPulseSensorPeak_g[j] + HAL_PULSE_SENSOR_FITTING_ERR) )
- {
- cnt++;
- }
- }
- if(cnt >= (HAL_PULSE_SENSOR_PEAK_LEN/2))
- halPulseSensorPeak_g[i] = 0;
- }
- uint16 tmp = 0;
- uint8 tmpCnt = 0;
- for(uint8 i = 0; i < HAL_PULSE_SENSOR_PEAK_LEN; i++)
- {
- if( halPulseSensorPeak_g[i] == 0 )
- continue;
- tmp += halPulseSensorPeak_g[i];
- tmpCnt++;
- }
- peak = (uint8)(tmp/tmpCnt);
- }
- return peak;
- }
根据参考峰值数据获取心跳峰值点方法:我们会设置一段时间,比如3秒,然后在这段时间内不断的获取数据,如果获取的数据和参考峰值数据相近,我们会标记为峰值点记为A,然后继续获取,如果数据比A大,那么更新A,直到数据比A小,也就是呈现出下降趋势,说明已经到峰值点了,那么我们就会停止获取数据的动作并立即返回!
- static uint8 halPulseSensorGetNextBeat(uint8 refPeak)
- {
- uint16 i;
- uint8 adc8, peak = 0;
- for(i = 0; i < 300; i++)
- {
- HAL_PULSE_SENSOR_DELAY_MS(10);
- adc8 = HAL_PULSE_SENSOR_ADC8();
- if( peak == 0 &&
- ( adc8 < (refPeak - HAL_PULSE_SENSOR_HEARTBEAT_ERR) ||
- adc8 > (refPeak + HAL_PULSE_SENSOR_HEARTBEAT_ERR) ) )
- {
- continue;
- }
- if(adc8 >= peak)
- {
- peak = adc8;
- continue;
- }
- return peak;
- }
- return 0;
- }