好久之前有一期关于使用ESP32利用MAX30100/30102制作一个血氧测试仪。 这里声明一下,我这个
血氧模块的INT引脚并没有接,所以没办法使用INT引脚来触发采集 。如果哪位朋友可以提供一下更好,简便的算法欢迎指正。
但是ESP3 2中使用MAX30100比较简单,利用官方美信的库(C++)就可以非常简单的使用血氧模块。
但是,当我们使用STM32来驱动MAX30100时,尤其是使用Keil这种不支持C++的IDE时,我们就会陷入一个非常头疼的处境,去修改心率算法。
这里不赘述30100初始化的操作以及如何进行读取数据,我将通讯用的I2C修改为硬件I2C方便不同设备之间的移植。
之后利用rawIRValue和rawRedValue来存放每次的数据。
在定时器中设置15ms读取一次心率数据。
我们将读取的心率数据进行打印。
可以看到,红外光数据会有一个一个波峰(后来我取了负)并且值通常大于50。
但是,当我们第一次把手放上去的时候,这个值会趋向无穷大。 而这个波峰的地方,就是我们的心跳的位置 。 ** 算法简述 **
- 峰值统计
我开始采取了一个算法,统计某个时间内波峰的数量。
for(int i = 0;i<BuffLenth;i++) { if(Red[i]>50&&Red[i]<300) { number++; while(Red[i]>0) { i++; } } }
当我们的值在50~100之间,我们认为统计到了一次波峰,并且等待下一个波谷的出现(0位置)
之后等待下一个波峰的出现。之后利用统计的数字,例如我这里总共采集了5s的时间,将这个数*12就是一分钟的心率。
但是这有一个非常的问题,我的分辨率注定卡在了12,如果需要提高分辨率就需要提高采样长度。
所以我就舍弃了这个算法。
- 间距法
第二种算法我想到了使用间距法,利用前一个峰值的思路,统计一下两个峰值之间的间距(多少个采样点)。结合采样周期计算心率。
float Heart; int index = 0; for(int i = 10;i<size;i++) { if(smooth[i]>40&&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:%drn",index); if(Heart<180&&Heart>40) { printf("A:%frn",Heart); } } } }
我们统计两个间距之间的距离,并且结合采样率来计算心率。
这种方法的分辨率可以得到很好的提示,并且分辨率来自于采样率的缩短并且响应率可以比较高。
但是这种方法仍可能出现一些偶然值,所以我们可以进一步完善算法,例如利用去掉最大最小值的方法来平衡。
- 离散傅里叶变换
第三种方法也是我觉得最应该去使用的方法,既是使用离散傅里叶变换(DFT)将时域图转换成频谱图。 计算频率最大值是多少。
但是事实上我们的信号杂波很多,并且有很多干扰,试了一下离散傅里叶变换的效果好像不是很好。
这里我还没有做的好的效果,后续会单独出一期STM32利用DSP库进行离散傅里叶变换。