当前位置:   article > 正文

STM32HAL-血氧心率MAX30102模块_OLED显示心率图—蓝牙上传心率数据。

max30102

前言:本次要实现的功能是心率与血氧浓度的检测,在OLED显示心率与血样的数值以及心率的图新,通过蓝牙模块将检测到的数据发给手机。


目录:

目录

一.实验结果图片展示

二.心率血氧模块MAX3012

 1.概括:MAX30102是一个集成的脉搏血氧仪和监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指、耳垂和手腕等处。标准和12C兼容的通信接口可以将采集到的数值传输给

2.引脚介绍:

3.测量心率和血氧功能原理:

2.数字电路部分:

1.内部 ADC 是一个连续时间过采样的 sigma-delta 变换器,具 有 18 位分辨率。ADC 采样率为 10.24MHz。ADC 输出数据速率可以从 50sps(每秒样本)到 3200sps。滤波器用于去除噪声并提取有用的信号。

2.滤波器可以有效地处理各种复杂信号,从而得到更准确的测量结果 。

3.过滤之后的信号存储在数据寄存器中。     

3.模拟电路部分:

1.环境光消除 ALC 有一个内部轨道/保持电路来消除环境光的影响和增加有效的动态范围。

2.温度传感器,用于校准SpO2子系统的温度依赖性。

4.最外围:两个LED灯分别示发出红光的LED与发出红外的LED.

5.功能图中引脚介绍:

三.寄存器

1.中断使能:0x02~0x03

2.FIFO:        0X04~0X07

1. FIFO Write Pointer:

2.FIFO Overflow Counter:

3.FIFO Read  Pointer: 

4.FIFO Data Register:

5. FIFO Data:

6. FIFO Data is Left or Right Justified:

3.FIFO配置:0x08

 1.SMP_AVE[2:0]:

2.FIFO_ROLLOVER_EN:

3.FIFO_A_FULL[3:0]:

4.模式配置:0x09

1.SHDN:

​编辑

3.MODE[2:0]:

5.SPO2配置 : 0xA0

1.SPO2_ADC_RGE[1:0]:

2.SPO2_SR[2:0] :

3. LED_PW[1:0] :

6.LED脉冲幅度 :0x0C~0X10

四.代码讲解:

1.主函数:

 2.MAX初始化配置:

3.max30102读取心率血氧:

4.PPG算法:

五.代码资料获取方式



一.实验结果图片展示

没有检测界面
没有检测时无数据接收

有检测时接收数据
有检测界面

二.心率血氧模块MAX3012

原理图

 

 1.概括MAX30102是一个集成的脉搏血氧仪和监测仪生物传感器的模块。它集成了一个红光LED和一个红外光LED、光电检测器、光器件,以及带环境光抑制的低噪声电子电路。MAX30102采用一个1.8V电源和一个独立的5.0V用于内部LED的电源,应用于可穿戴设备进行心率和血氧采集检测,佩戴于手指、耳垂和手腕等处。标准和12C兼容的通信接口可以将采集到的数值传输给


2.引脚介绍

GND:接地线;

RD:MAX30102芯片的RED,LED接地端,一般不接(驱动红色LED

IRD: MAX30102芯片的RLED接地端,一般不接(驱动红外LED

INT:MAX30102芯片的中断引脚

 VIN:主电源电源输入端, 1.8V-5V;3位焊盘:选择总线的上拉电平,取决于引脚主控电压,可选1.8V端或者3.3V端(此端包含3.3V及以上);

SCL:接I2C总线的时钟;

SDA:接12C总线的数据;

在本此工程量只用到了引脚:VIN,SDA,SCL,GND,INT。

接线图



3.测量心率和血氧功能原理:

功能图

1.通讯部分:MAX30102通过I2C与MCU通讯。

I2C时序图
2.数字电路部分
1.内部 ADC 是一个连续时间过采样的 sigma-delta 变换器,具 有 18 位分辨率。ADC 采样率为 10.24MHz。ADC 输出数据速率可以从 50sps(每秒样本)到 3200sps。滤波器用于去除噪声并提取有用的信号。
2.滤波器可以有效地处理各种复杂信号,从而得到更准确的测量结果 。
3.过滤之后的信号存储在数据寄存器中。     
3.模拟电路部分
1.环境光消除 ALC 有一个内部轨道/保持电路来消除环境光的影响和增加有效的动态范围。
2.温度传感器,用于校准SpO2子系统的温度依赖性。
4.最外围:两个LED灯分别示发出红光的LED与发出红外的LED.
5.功能图中引脚介绍

引脚名称                      功能
1、7、8、14N.C.没有连接。连接 PCB 板以实现机械稳定性
2SCLI2C 时钟输入
3SDAI2C数据双向传输
4PGNDLED 驱动器块的电源接地
5R_DRV红色 LED 驱动程序
6IR_DRV红外 LED 驱动程序
9,10V LED+LED电源(阳极连接)。使用旁路电容到PGND以获得最佳性能。
11VDD模拟电源输入,使用旁路电容到GND以获得最佳性能。
12GND模拟接地
13~INTActive-Low中断(开漏)低电平有效,用上拉电阻连接到外部电压。

三.寄存器

1.中断使能:0x02~0x03


2.FIFO:        0X04~0X07

1. FIFO Write Pointer:

FIFO写指针FIFO写指针指向MAX30102写入下一个示例的位置。该指针为推进到FIFO的每个样本前进。当MODE[2:0]为010、011或111时,也可以通过i2c接口进行更改。

2.FIFO Overflow Counter:

FIFO溢出计数器当 FIFO 满时,样品没有被推进到 FIFO,样品丢失。OVF_COUNTER 计算丢失的样本数目。它在 0 xf 浸透。当一个完整的样本被“弹出”(即,删除旧的 FIFO 数据,并将样本从 FIFO(当读取指针前进时),OVF_COUNTER 重置为零。

3.FIFO Read  Pointer: 

FIFO读指针指向处理器从FIFO获取下一个样本的位置i2c接口。每次从FIFO中取出样本时,这种情况都会发生。如果有数据通信错误,处理器也可以在读取样本后写入该指针,以允许从FIFO重新读取样本.

4.FIFO Data Register:

FIFO数据寄存器圆形FIFO深度为32,最多可容纳32个数据样本。样本大小取决于配置为活动的LED通道(又称通道)的数量。由于每个通道信号被存储为一个3字节的数据信号,FIFO的宽度可以是3字节或6字节的大小.

i2c寄存器映射中的fif_data寄存器指向要从FIFO读取的下一个样本。FIFO_RD_PTR指向这个示例。读取FIFO_DATA寄存器,不会自动增加i2c寄存器地址。突然读取这个寄存器,一遍又一遍地读取相同的地址。每个采样是每个通道3字节的数据(即,3字节用于RED, 3字节用于IR,等等)。

FIFO寄存器(0x04-0x07)都可以被写入和读取,但实际上只有fif_rd_ptr寄存器应该被写入操作中。其他的由MAX30102自动递增或填充数据。当开始一个新的SpO 2或心率转换,建议首先FIFO_WR_PTR, OVF_COUNTER和FIFO_RD_PTR寄存器清除为全零(0x00),以确保FIFO为空并处于已知状态。当在一个突发读I2C事务中读取MAX30102寄存器时,寄存器地址指针通常会增加,以便发送的数据的下一个字节是从将进入和退出邻近模式(当PROX_INT_EN = 1时)通过设置写指针和读指针彼此相等来清除FIFO。

5. FIFO Data:

FIFO数据

6. FIFO Data is Left or Right Justified:

FIFO数据的左对齐或右对,左对齐,无论ADC分辨率设置如何,MSB位始终位于第17位数据位置。右对齐,无论ADC分辨率设置如何,MSB位始终位于第0位数据位置。


3.FIFO配置:0x08

 1.SMP_AVE[2:0]:

样本平均为了减少数据吞吐量,相邻的样本(在每个单独的通道中)可以通过设置这个寄存器在芯片上平均和抽取。

样本平均对应表:

2.FIFO_ROLLOVER_EN:

当FIFO完全被数据填充时,这个位控制FIFO的行为。如果设置了FIFO_ROLLOVER_EN (1), FIFO地址滚到零,FIFO继续用新数据填充。如果该位未设置(0),则FIFO不会更新,直到读取FIFO_DATA或更改WRITE/ read指针位置。

3.FIFO_A_FULL[3:0]:

这个寄存器设置了当中断发出时,FIFO中剩余的数据样本数(3字节/样本)。例如,如果该字段设置为0x0,则当FIFO中剩余的数据样本为0(全部为32个)时发出中断先进先出字有未读数据)。此外,如果该字段设置为0xF,则当FIFO中剩余15个数据样本(17个FIFO数据样本有未读数据)时发出中断。 


4.模式配置:0x09

1.SHDN:

关机控制位该部件可以通过将该位设置为1来进入省电模式。当处于省电模式时,所有寄存器保留它们的值,并且写/读操作正常运行。在这种模式下,所有中断都被清除为零。 

 2.RESET:
​​​​​​​

复位控制位当RESET位设置为1时,所有配置、阈值和数据寄存器通过上电复位复位到它们的上电状态。复位顺序完成后,复位位自动清零。注意:设置RESET位不会触发PWR_RDY中断事件。

3.MODE[2:0]:

模式控制位这些位设置了MAX30102的工作状态。更改模式不会更改任何其他设置,也不会擦除数据寄存器中先前存储的任何数据。


5.SPO2配置 : 0xA0

1.SPO2_ADC_RGE[1:0]:

SpO 2 ADC量程控制位设置SpO 2传感器ADC的量程

2.SPO2_SR[2:0] :

 SpO 2采样率控制位 这些位定义了一个由一个红外脉冲/转换和一个红色脉冲/转换组成的样本的有效采样率。

采样率和脉冲宽度是相关的,因为采样率设置了脉冲宽度时间的上界。如果用户选择的采样率对于所选的LED_PW设置太高,则将最高可能的采样率编程到寄存器中。

3. LED_PW[1:0] :

LED脉宽控制和ADC分辨率这些位设置LED脉冲宽度(IR和Red具有相同的脉冲宽度),因此,间接设置每个采样中ADC的积分时间,ADC分辨率与积分时间直接相关。


6.LED脉冲幅度 :0x0C~0X10

这些位设置每个LED的当前电平.

LED配置表

PILOT_PA[7:0]的目的是在接近模式和多LED模式下设置LED功率。


四.代码讲解:

接线图;

1.主函数:

  1. /**
  2. * @brief The application entry point.
  3. * @retval int
  4. */
  5. int main(void)
  6. {
  7. int32_t n_heart_rate = 0; //获取心率值
  8. int32_t n_sp02 = 0; //获取血氧值
  9. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  10. HAL_Init();
  11. /* Configure the system clock */
  12. SystemClock_Config();
  13. /* Initialize all configured peripherals */
  14. MX_GPIO_Init();
  15. MX_USART1_UART_Init(); //蓝牙初始化
  16. Delay_Init(); //延时初始化
  17. OLED_Init(); //OLED初始化
  18. OLED_ON();
  19. OLED_Clear();
  20. max30102_init(); //30102初始化,里面有个while循环
  21. /* Infinite loop */
  22. /* USER CODE BEGIN WHILE */
  23. while (1)
  24. {
  25. //读取心率血氧
  26. max30102_Read_Data(&n_heart_rate, &n_sp02);
  27. //OLED显示,心率血氧
  28. OLED_Start_Interface((uint8_t )n_heart_rate,(uint8_t )n_sp02);
  29. //led灯翻转
  30. HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
  31. }
  32. }

 2.MAX初始化配置:

  1. /**
  2. * 作用 :max30102初始化配置
  3. * 名称 :max30102_init();
  4. * 参数 :无
  5. * 返回值:无
  6. **/
  7. void max30102_init(void)
  8. {
  9. uint32_t un_min, un_max; //缓存新信号最小值,最大值
  10. uint32_t un_prev_data; //红色LED器数据缓冲区
  11. int i;
  12. uint8_t temp[6];
  13. IIC_Init();
  14. //max30102模式配置
  15. max30102_reset();
  16. //寄存器配置
  17. max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0); // INTR setting
  18. max30102_Bus_Write(REG_INTR_ENABLE_2,0x00);
  19. max30102_Bus_Write(REG_FIFO_WR_PTR,0x00); // FIFO_WR_PTR[4:0]
  20. max30102_Bus_Write(REG_OVF_COUNTER,0x00); // OVF_COUNTER[4:0]
  21. max30102_Bus_Write(REG_FIFO_RD_PTR,0x00); // FIFO_RD_PTR[4:0]
  22. max30102_Bus_Write(REG_FIFO_CONFIG,0x0f); // sample avg = 1, fifo rollover=false, fifo almost full = 17
  23. max30102_Bus_Write(REG_MODE_CONFIG,0x03); // 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
  24. max30102_Bus_Write(REG_SPO2_CONFIG,0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
  25. max30102_Bus_Write(REG_LED1_PA,0x24); // Choose value for ~ 7mA for LED1
  26. max30102_Bus_Write(REG_LED2_PA,0x24); // Choose value for ~ 7mA for LED2
  27. max30102_Bus_Write(REG_PILOT_PA,0x7f); // Choose value for ~ 25mA for Pilot LED
  28. un_min=0x3FFFF;
  29. un_max=0;
  30. n_ir_buffer_length=500; //buffer length of 100 stores 5 seconds of samples running at 100sps
  31. for(i=0; i<n_ir_buffer_length; i++)
  32. {
  33. //等待,直到中断引脚断言
  34. while(MAX30102_INT==1)
  35. {
  36. Max30102_Wait_Interface(); //等待检测界面
  37. }
  38. //读取FIFO内的数据
  39. max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp);
  40. //对从FIFO中读到的数据进行整合
  41. aun_red_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2]; // Combine values to get the actual number
  42. aun_ir_buffer [i] = (long)((long)((long)temp[3] & 0x03)<<16) | (long)temp[4]<<8 | (long)temp[5]; // Combine values to get the actual number
  43. if(un_min>aun_red_buffer[i])
  44. un_min=aun_red_buffer[i]; //更新信号最小值
  45. if(un_max<aun_red_buffer[i])
  46. un_max=aun_red_buffer[i]; //更新信号最大值
  47. }
  48. //红色LED器数据缓
  49. un_prev_data=aun_red_buffer[i];
  50. un_prev_data=un_prev_data;
  51. // 计算心率和SpO2水平
  52. maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer,
  53. &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
  54. }

3.max30102读取心率血氧:

  1. /**
  2. * 作用 :max30102读取心率血氧
  3. * 名称 :max30102_Read_Data();
  4. * 参数 :heart_rate:存储心率
  5. sp02:存血氧
  6. * 返回值:无
  7. **/
  8. uint8_t max30102_FLAG=0;
  9. uint8_t max30102_Tem=1; //用来清oled屏
  10. uint8_t heart_Data[3]={0};
  11. uint8_t heart_Picture[100]={0},heart_count=0;
  12. void max30102_Read_Data(int32_t *heart_rate, int32_t *sp02)
  13. {
  14. //variables to calculate the on-board LED brightness that reflects the heartbeats
  15. //变量来计算反映心跳的板载LED亮度
  16. uint32_t un_min, un_max, un_prev_data;
  17. int i;
  18. uint8_t temp[6];
  19. float f_temp;
  20. int32_t n_brightness;
  21. while(1)
  22. {
  23. i=0;
  24. un_min=0x3FFFF;
  25. un_max=0;
  26. for(i=100; i<500; i++)
  27. {
  28. aun_red_buffer[i-100] = aun_red_buffer[i];
  29. aun_ir_buffer [i-100] = aun_ir_buffer[i];
  30. //update the signal min and max 更新信号最小值和最大值
  31. if(un_min>aun_red_buffer[i])
  32. un_min=aun_red_buffer[i];
  33. if(un_max<aun_red_buffer[i])
  34. un_max=aun_red_buffer[i];
  35. }
  36. //take 100 sets of samples before calculating the heart rate.
  37. //在计算心率前,取100组样本
  38. for(i=400; i<500; i++)
  39. {
  40. un_prev_data=aun_red_buffer[i-1];
  41. // printf("un_prev_data=%d\r\n", un_prev_data); //串口测试点
  42. //等待,直到中断引脚断言
  43. while(MAX30102_INT==1)
  44. {
  45. if(max30102_FLAG==0)
  46. {
  47. Max30102_Wait_Interface(); //等待检测界面
  48. }
  49. }
  50. //读取FIFO内的数据
  51. max30102_FIFO_ReadBytes(REG_FIFO_DATA,temp);
  52. // 将红色led值组合以获得实际数字
  53. aun_red_buffer[i] = (long)((long)((long)temp[0]&0x03)<<16) | (long)temp[1]<<8 | (long)temp[2];
  54. // 将红外led值组合以获得实际数字
  55. aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03)<<16) | (long)temp[4]<<8 | (long)temp[5];
  56. //对取到的样本进行处理
  57. if(aun_red_buffer[i]>un_prev_data)
  58. {
  59. f_temp=aun_red_buffer[i]-un_prev_data;
  60. f_temp/=(un_max-un_min);
  61. f_temp*=MAX_BRIGHTNESS;
  62. n_brightness-=(int)f_temp;
  63. if(n_brightness<0)
  64. n_brightness=0;
  65. }
  66. else
  67. {
  68. f_temp=un_prev_data-aun_red_buffer[i];
  69. f_temp/=(un_max-un_min);
  70. f_temp*=MAX_BRIGHTNESS;
  71. n_brightness+=(int)f_temp;
  72. if(n_brightness>MAX_BRIGHTNESS)
  73. n_brightness=MAX_BRIGHTNESS;
  74. }
  75. // printf("n_brightness=%d\r\n", n_brightness); //串口测试点
  76. //心率图
  77. if(max30102_FLAG==1)
  78. {
  79. if((n_brightness>=30)&&(n_brightness<=250))
  80. {
  81. n_brightness-=30;
  82. n_brightness/= 5;
  83. heart_count++;
  84. if(heart_count>100)
  85. {
  86. heart_count=0;
  87. }
  88. heart_Picture[heart_count]=n_brightness;
  89. //oled显示心率图
  90. OLED_heart_Picture(heart_Picture[heart_count-1],heart_Picture[heart_count]);
  91. }
  92. }
  93. //send samples and calculation result to terminal program through UART
  94. //通过UART将采样和计算结果发送到终端程序
  95. if(ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<150 && n_sp02<101)//**/ ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<120 && n_sp02<101
  96. {
  97. heart = n_heart_rate;
  98. }
  99. else
  100. {
  101. n_heart_rate = 0;
  102. heart = 0;
  103. }
  104. if(ch_hr_valid == 0)
  105. {
  106. n_heart_rate = 0;
  107. heart = 0;
  108. }
  109. }
  110. //计算心率和SpO2水平
  111. maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
  112. //对采集到的数据进行筛选
  113. if((ch_hr_valid == 1) && (ch_spo2_valid ==1) && (n_heart_rate>50) && (n_heart_rate<150) && (n_sp02<101) && (n_sp02>80))//**/ ch_hr_valid == 1 && ch_spo2_valid ==1 && n_heart_rate<120 && n_sp02<101
  114. {
  115. *heart_rate = n_heart_rate;
  116. *sp02 = n_sp02;
  117. // 心率和血氧浓度初始数据打印
  118. printf("HR=%d, spo2:%d%%\r\n", n_heart_rate, n_sp02);
  119. //清除oled旧界面
  120. if(max30102_Tem==1)
  121. {
  122. OLED_Clear();
  123. max30102_Tem=0;
  124. }
  125. max30102_FLAG=1;
  126. break;
  127. }
  128. else
  129. {
  130. //清除oled旧界面
  131. if(max30102_Tem==0)
  132. {
  133. OLED_Clear();
  134. }
  135. max30102_Tem=1;
  136. max30102_FLAG=0;
  137. }
  138. }
  139. }

4.PPG算法:

  1. /**
  2. * \brief 计算心率和SpO2水平
  3. * \par Details
  4. * By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
  5. * Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
  6. * Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
  7. *
  8. * \param[in] *pun_ir_buffer - 红外传感器数据缓冲区
  9. * \param[in] n_ir_buffer_length - 红外数据存储长度
  10. * \param[in] *pun_red_buffer - 红色LED器数据缓冲区
  11. * \param[out] *pn_spo2 - 血氧数值
  12. * \param[out] *pch_spo2_valid - 如果计算的SpO2值有效,则为1
  13. * \param[out] *pn_heart_rate - 计算心率值
  14. * \param[out] *pch_hr_valid - 如果计算的心率值有效,则为1
  15. *
  16. * \retval None
  17. */
  18. void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
  19. int32_t *pn_heart_rate, int8_t *pch_hr_valid)
  20. {
  21. uint32_t un_ir_mean ,un_only_once ;
  22. int32_t k ,n_i_ratio_count;
  23. int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx;
  24. int32_t n_th1, n_npks,n_c_min;
  25. int32_t an_ir_valley_locs[15] ;
  26. int32_t an_exact_ir_valley_locs[15] ;
  27. int32_t an_dx_peak_locs[15] ;
  28. int32_t n_peak_interval_sum;
  29. int32_t n_y_ac, n_x_ac;
  30. int32_t n_spo2_calc;
  31. int32_t n_y_dc_max, n_x_dc_max;
  32. int32_t n_y_dc_max_idx, n_x_dc_max_idx;
  33. int32_t an_ratio[5],n_ratio_average;
  34. int32_t n_nume, n_denom ;
  35. // remove DC of ir signal 去除ir信号的直流
  36. un_ir_mean =0;
  37. for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
  38. un_ir_mean =un_ir_mean/n_ir_buffer_length ;
  39. for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ;
  40. // 4 pt Moving Average pt移动平均线
  41. for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
  42. n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]);
  43. an_x[k]= n_denom/(int32_t)4;
  44. }
  45. // get difference of smoothed IR signal 得到平滑的红外信号差
  46. for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++)
  47. an_dx[k]= (an_x[k+1]- an_x[k]);
  48. // 2-pt Moving Average to an_dx pt移动平均线到an_dx
  49. for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){
  50. an_dx[k] = ( an_dx[k]+an_dx[k+1])/2 ;
  51. }
  52. // hamming window
  53. // flip wave form so that we can detect valley with peak detector
  54. //翻转波形,这样我们就可以用峰值检测器检测山谷
  55. for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){
  56. s= 0;
  57. for( k=i; k<i+ HAMMING_SIZE ;k++){
  58. s -= an_dx[k] *auw_hamm[k-i] ;
  59. }
  60. an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm 除以auw_hamm的和
  61. }
  62. n_th1=0; // threshold calculation 门坎值计算分割
  63. for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){
  64. n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ;
  65. }
  66. n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE);
  67. // peak location is acutally index for sharpest location of raw signal since we flipped the signal
  68. //峰值位置实际上是我们翻转信号后原始信号最尖锐位置的索引
  69. maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks
  70. n_peak_interval_sum =0;
  71. if (n_npks>=2){
  72. for (k=1; k<n_npks; k++)
  73. {
  74. n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]);
  75. // printf ("n_peak_interval_sum=%d\r\n",n_peak_interval_sum);//测试点
  76. }
  77. n_peak_interval_sum=n_peak_interval_sum/(n_npks-1);
  78. *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes 每分钟心跳数
  79. *pch_hr_valid = 1;
  80. // printf ("pn_heart_rate=%d\r\n",*pn_heart_rate);//测试点
  81. }
  82. else {
  83. *pn_heart_rate = -999;
  84. *pch_hr_valid = 0;
  85. }
  86. for ( k=0 ; k<n_npks ;k++)
  87. an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2;
  88. // raw value : RED(=y) and IR(=X)
  89. // we need to assess DC and AC value of ir and red PPG. 我们需要评估ir和红色PPG的直流和交流值。
  90. for (k=0 ; k<n_ir_buffer_length ; k++ ) {
  91. an_x[k] = pun_ir_buffer[k] ;
  92. an_y[k] = pun_red_buffer[k] ;
  93. }
  94. // find precise min near an_ir_valley_locs 在an_ir_valley_locs附近找到精确的min
  95. n_exact_ir_valley_locs_count =0;
  96. for(k=0 ; k<n_npks ;k++){
  97. un_only_once =1;
  98. m=an_ir_valley_locs[k];
  99. n_c_min= 16777216;//2^24;
  100. if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){
  101. for(i= m-5;i<m+5; i++)
  102. if (an_x[i]<n_c_min){
  103. if (un_only_once >0){
  104. un_only_once =0;
  105. }
  106. n_c_min= an_x[i] ;
  107. an_exact_ir_valley_locs[k]=i;
  108. }
  109. if (un_only_once ==0)
  110. n_exact_ir_valley_locs_count ++ ;
  111. }
  112. }
  113. if (n_exact_ir_valley_locs_count <2 ){
  114. *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range 不使用SPO2,因为信号比超出了范围
  115. *pch_spo2_valid = 0;
  116. return;
  117. }
  118. // 4 pt MA
  119. for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
  120. an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4;
  121. an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4;
  122. }
  123. //using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio、
  124. //使用an_exact_ir_valley_locs,找到红色直流和红色交流的SPO2校准比
  125. //finding AC/DC maximum of raw ir * red between two valley locations
  126. //发现两个山谷位置之间的AC/DC最大原始ir *红
  127. n_ratio_average =0;
  128. n_i_ratio_count =0;
  129. for(k=0; k< 5; k++) an_ratio[k]=0;
  130. for (k=0; k< n_exact_ir_valley_locs_count; k++){
  131. if (an_exact_ir_valley_locs[k] > BUFFER_SIZE ){
  132. *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range 不使用SPO2,因为山谷loc在范围之外
  133. *pch_spo2_valid = 0;
  134. return;
  135. }
  136. }
  137. // find max between two valley locations
  138. // 求两个山谷之间的最大值
  139. // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
  140. //以及Ir & Red的交流分量与Ir & Red的直流分量对SPO2的利用率
  141. for (k=0; k< n_exact_ir_valley_locs_count-1; k++){
  142. n_y_dc_max= -16777216 ;
  143. n_x_dc_max= - 16777216;
  144. if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){
  145. for (i=an_exact_ir_valley_locs[k]; i< an_exact_ir_valley_locs[k+1]; i++){
  146. if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; }
  147. if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;}
  148. }
  149. n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red
  150. n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]) ;
  151. n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw 从原始电路中减去线性直流分量
  152. n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir
  153. n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]);
  154. n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
  155. n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value 准备X100以保留浮动值
  156. n_denom= ( n_x_ac *n_y_dc_max)>>7;
  157. if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0)
  158. {
  159. an_ratio[n_i_ratio_count]= (n_nume*20)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ; ///*************************n_nume原来是*100************************//
  160. n_i_ratio_count++;
  161. }
  162. }
  163. }
  164. maxim_sort_ascend(an_ratio, n_i_ratio_count);
  165. n_middle_idx= n_i_ratio_count/2;
  166. if (n_middle_idx >1)
  167. n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median
  168. else
  169. n_ratio_average = an_ratio[n_middle_idx ];
  170. if( n_ratio_average>2 && n_ratio_average <184){
  171. n_spo2_calc= uch_spo2_table[n_ratio_average] ;
  172. *pn_spo2 = n_spo2_calc ;
  173. *pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table
  174. }
  175. else{
  176. *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
  177. *pch_spo2_valid = 0;
  178. }
  179. }

五.代码资料获取方式

程序在微信公主号“嵌入式电子小白凯”发送“MAX30102资料”就可获取到程序资料下载。

各位读者如果感觉这篇文章对你有帮住的话,希望能点赞支持。 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/1020347
推荐阅读
相关标签
  

闽ICP备14008679号