好久之前有一期关于使用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库进行离散傅里叶变换。

嘉立创PCB
全部评论 默认 最新
已折叠部分评论 展开
没有更多啦~