好久之前有一期关于使用ESP32利用MAX30100/30102制作一个血氧测试仪。
这里声明一下,我这个血氧模块的INT引脚并没有接,所以没办法使用INT引脚来触发采集。如果哪位朋友可以提供一下更好,简便的算法欢迎指正。
但是ESP32中使用MAX30100比较简单,利用官方美信的库(C++)就可以非常简单的使用血氧模块。
但是,当我们使用STM32来驱动MAX30100时,尤其是使用Keil这种不支持C++的IDE时,我们就会陷入一个非常头疼的处境,去修改心率算法。
这里不赘述30100初始化的操作以及如何进行读取数据,我将通讯用的I2C修改为硬件I2C方便不同设备之间的移植。
之后利用rawIRValue和rawRedValue来存放每次的数据。

在定时器中设置15ms读取一次心率数据。
我们将读取的心率数据进行打印。
可以看到,红外光数据会有一个一个波峰(后来我取了负)并且值通常大于50。

但是,当我们第一次把手放上去的时候,这个值会趋向无穷大。而这个波峰的地方,就是我们的心跳的位置。
算法简述
- 峰值统计
我开始采取了一个算法,统计某个时间内波峰的数量。
for(int i = 0;i50&&Red[i]<300)
{
number++;
while(Red[i]>0)
{
i++;
}
}
}
当我们的值在50~100之间,我们认为统计到了一次波峰,并且等待下一个波谷的出现(0位置)
之后等待下一个波峰的出现。之后利用统计的数字,例如我这里总共采集了5s的时间,将这个数*12就是一分钟的心率。
但是这有一个非常的问题,我的分辨率注定卡在了12,如果需要提高分辨率就需要提高采样长度。
所以我就舍弃了这个算法。
- 间距法
第二种算法我想到了使用间距法,利用前一个峰值的思路,统计一下两个峰值之间的间距(多少个采样点)。结合采样周期计算心率。
float Heart;
int index = 0;
for(int i = 10;i40&&smooth[i]<300)
{
index = i;
while(smooth[i]>0)//等待波谷
{
i++;
if(i==size-1)
{
break;
}
}
while(smooth[i]<40)//等待第二个波峰
{
i++;
if(i==size-1)
{
break;
}
}
if(i<80)
{
index = i-index;
Heart = (float)(index)*0.015;
Heart = 60/Heart;
//printf("A:%d\r\n",index);
if(Heart<180&&Heart>40)
{
printf("A:%f\r\n",Heart);
}
}
}
}
我们统计两个间距之间的距离,并且结合采样率来计算心率。

这种方法的分辨率可以得到很好的提示,并且分辨率来自于采样率的缩短并且响应率可以比较高。

但是这种方法仍可能出现一些偶然值,所以我们可以进一步完善算法,例如利用去掉最大最小值的方法来平衡。
- 离散傅里叶变换
第三种方法也是我觉得最应该去使用的方法,既是使用离散傅里叶变换(DFT)将时域图转换成频谱图。 计算频率最大值是多少。
但是事实上我们的信号杂波很多,并且有很多干扰,试了一下离散傅里叶变换的效果好像不是很好。
这里我还没有做的好的效果,后续会单独出一期STM32利用DSP库进行离散傅里叶变换。


登录 或 注册 后才可以进行评论哦!