当前位置:   article > 正文

【stm32】手把手用cubemx配置血氧传感器(MAX30102)

max30102

一、前言

        网上流传血氧传感器的代码有好几个版本,听说这个不准,那个不准的。突然间我看到了一篇好文章,大概是自己用软件测试测量结果是否准确,秀的我头皮发麻呀(外部中断触发),本文将通过他的例程来手把手教大家如何配置。本文适合小白,只讲如何应用,原理请大家查阅其他资料,文末分享我的工程关于血氧传感器优秀资料的链接。

二、材料准备

        某宝搜索关键字 “血氧传感器”,然后焊接成这个样子。

三、引脚说明

        我们只用到了6个引脚,RD、IRD为该模块LED有关的引脚,一般不接。之后在cubemx中可以看到具体怎么连接。

VIN:主电源输入端 1.8V-5V

SCL:接I2C总线的时钟     -->PB6(根据cubemx)

SDA:接I2C总线的数据    -->PB9(根据cubemx)

INT:芯片的中断引脚        -->PC0(根据cubemx)

GND:接地(有两个)

四、cubemx工程配置

1.选择自己单片机的芯片,把RCC、时钟、DEBUG啥的都配置好。(没啥要求,按平时来就行)

2.任选一个引脚作为外部中断触发的引脚,这样当我们放下手指时,血氧传感器才会检测。这里我选PC0。

3.配置PC0。下降沿触发、上拉、设置用户标签MAX30102_INT

 4.使能PC0中断

不同引脚中断号不一样,具体看数据手册,当然你也可以看配置完多出来哪一行来判断。

5.配置IIC

6.配置串口,我这里用串口10,参数默认就行

7.然后大家就可以生成工程了

五、keil工程配置

1.勾选MicroLIB

2.开启DSP

 

3.添加宏定义 ARM_MATH_CM7(<--这个根据自己内核的情况配置,我的是M7),__FPU_PRESENT 

六、代码

1.新建四个文件,将C文件添加到工程中

max30102.c

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file : max30102.c
  5. * @brief : 血氧传感器
  6. ******************************************************************************
  7. * @attention
  8. * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
  9. * 2.打开DSP
  10. * 3.main函数定义如下全局变量
  11. * uint8_t max30102_int_flag = 0; // 中断标志
  12. * float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
  13. * float ppg_data_cache_IR[CACHE_NUMS] = {0}; // 缓存区
  14. * uint16_t cache_counter = 0; // 缓存计数器
  15. *
  16. ******************************************************************************
  17. */
  18. /* USER CODE END Header */
  19. #include "./max30102/max30102.h"
  20. #include "./max30102/max30102_fir.h"
  21. #include "stdio.h"
  22. extern uint8_t max30102_int_flag;
  23. extern float ppg_data_cache_RED[CACHE_NUMS] ; // 缓存区
  24. extern float ppg_data_cache_IR[CACHE_NUMS] ; // 缓存区
  25. extern uint16_t cache_counter;
  26. /**
  27. * @brief IIC 写入
  28. * @retval None
  29. */
  30. void max30102_i2c_write(uint8_t reg_adder, uint8_t data)
  31. {
  32. uint8_t transmit_data[2];
  33. transmit_data[0] = reg_adder;
  34. transmit_data[1] = data;
  35. i2c_transmit(transmit_data, 2);
  36. }
  37. /**
  38. * @brief IIC 读取
  39. * @retval None
  40. */
  41. void max30102_i2c_read(uint8_t reg_adder, uint8_t *pdata, uint8_t data_size)
  42. {
  43. uint8_t adder = reg_adder;
  44. i2c_transmit(&adder, 1);
  45. i2c_receive(pdata, data_size);
  46. }
  47. /**
  48. * @brief max30102初始化
  49. * @retval None
  50. */
  51. void max30102_init(void)
  52. {
  53. uint8_t data;
  54. max30102_i2c_write(MODE_CONFIGURATION, 0x40); // reset the device
  55. delay_ms(5);
  56. max30102_i2c_write(INTERRUPT_ENABLE1, 0xE0);
  57. max30102_i2c_write(INTERRUPT_ENABLE2, 0x00); // interrupt enable: FIFO almost full flag, new FIFO Data Ready,
  58. // ambient light cancellation overflow, power ready flag,
  59. // internal temperature ready flag
  60. max30102_i2c_write(FIFO_WR_POINTER, 0x00);
  61. max30102_i2c_write(FIFO_OV_COUNTER, 0x00);
  62. max30102_i2c_write(FIFO_RD_POINTER, 0x00); // clear the pointer
  63. max30102_i2c_write(FIFO_CONFIGURATION, 0x4F); // FIFO configuration: sample averaging(1),FIFO rolls on full(0), FIFO almost full value(15 empty data samples when interrupt is issued)
  64. max30102_i2c_write(MODE_CONFIGURATION, 0x03); // MODE configuration:SpO2 mode
  65. max30102_i2c_write(SPO2_CONFIGURATION, 0x2A); // SpO2 configuration:ACD resolution:15.63pA,sample rate control:200Hz, LED pulse width:215 us
  66. max30102_i2c_write(LED1_PULSE_AMPLITUDE, 0x2f); // IR LED
  67. max30102_i2c_write(LED2_PULSE_AMPLITUDE, 0x2f); // RED LED current
  68. max30102_i2c_write(TEMPERATURE_CONFIG, 0x01); // temp
  69. max30102_i2c_read(INTERRUPT_STATUS1, &data, 1);
  70. max30102_i2c_read(INTERRUPT_STATUS2, &data, 1); // clear status
  71. }
  72. /**
  73. * @brief fifo区读取
  74. * @param output_data
  75. * @retval None
  76. */
  77. void max30102_fifo_read(float *output_data)
  78. {
  79. uint8_t receive_data[6];
  80. uint32_t data[2];
  81. max30102_i2c_read(FIFO_DATA, receive_data, 6);
  82. data[0] = ((receive_data[0] << 16 | receive_data[1] << 8 | receive_data[2]) & 0x03ffff);
  83. data[1] = ((receive_data[3] << 16 | receive_data[4] << 8 | receive_data[5]) & 0x03ffff);
  84. *output_data = data[0];
  85. *(output_data + 1) = data[1];
  86. }
  87. /**
  88. * @brief 获取心率
  89. * @param input_data cache_nums(缓存区的最大数字)
  90. * @retval (uint16_t)心率
  91. */
  92. uint16_t max30102_getHeartRate(float *input_data, uint16_t cache_nums)
  93. {
  94. float input_data_sum_aver = 0;
  95. uint16_t i, temp;
  96. for (i = 0; i < cache_nums; i++)
  97. {
  98. input_data_sum_aver += *(input_data + i);
  99. }
  100. input_data_sum_aver = input_data_sum_aver / cache_nums;
  101. for (i = 0; i < cache_nums; i++)
  102. {
  103. if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
  104. {
  105. temp = i;
  106. break;
  107. }
  108. }
  109. i++;
  110. for (; i < cache_nums; i++)
  111. {
  112. if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
  113. {
  114. temp = i - temp;
  115. break;
  116. }
  117. }
  118. if ((temp > 14) && (temp < 100))
  119. {
  120. return 3000 / temp;
  121. }
  122. else
  123. {
  124. return 0;
  125. }
  126. }
  127. /**
  128. * @brief 获取血氧
  129. * @param input_data red_input_data cache_nums(缓存区的最大数字)
  130. * @retval (float)血氧
  131. */
  132. float max30102_getSpO2(float *ir_input_data, float *red_input_data, uint16_t cache_nums)
  133. {
  134. float ir_max = *ir_input_data, ir_min = *ir_input_data;
  135. float red_max = *red_input_data, red_min = *red_input_data;
  136. float R;
  137. uint16_t i;
  138. for (i = 1; i < cache_nums; i++)
  139. {
  140. if (ir_max < *(ir_input_data + i))
  141. {
  142. ir_max = *(ir_input_data + i);
  143. }
  144. if (ir_min > *(ir_input_data + i))
  145. {
  146. ir_min = *(ir_input_data + i);
  147. }
  148. if (red_max < *(red_input_data + i))
  149. {
  150. red_max = *(red_input_data + i);
  151. }
  152. if (red_min > *(red_input_data + i))
  153. {
  154. red_min = *(red_input_data + i);
  155. }
  156. }
  157. R = ((ir_max + ir_min) * (red_max - red_min)) / ((red_max + red_min) * (ir_max - ir_min));
  158. return ((-45.060) * R * R + 30.354 * R + 94.845);
  159. }
  160. /**
  161. * @brief MAX30102服务函数
  162. * @param HeartRate(心率) SpO2(血氧) max30102_data fir_output
  163. * @retval (uint8_t)MAX30102_DATA_OK:结束读取 (uint8_t)!MAX30102_DATA_OK:还在读取
  164. */
  165. uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2])
  166. {
  167. if (max30102_int_flag) // 中断信号产生
  168. {
  169. max30102_int_flag = 0;
  170. max30102_fifo_read(max30102_data); // 读取数据
  171. ir_max30102_fir(&max30102_data[0], &fir_output[0]);
  172. red_max30102_fir(&max30102_data[1], &fir_output[1]); // 滤波
  173. if ((max30102_data[0] > PPG_DATA_THRESHOLD) && (max30102_data[1] > PPG_DATA_THRESHOLD)) // 大于阈值,说明传感器有接触
  174. {
  175. ppg_data_cache_IR[cache_counter] = fir_output[0];
  176. ppg_data_cache_RED[cache_counter] = fir_output[1];
  177. cache_counter++;
  178. }
  179. else // 小于阈值
  180. {
  181. cache_counter = 0;
  182. }
  183. if (cache_counter >= CACHE_NUMS) // 收集满了数据
  184. {
  185. *HeartRate = max30102_getHeartRate(ppg_data_cache_IR, CACHE_NUMS);
  186. *SpO2 = max30102_getSpO2(ppg_data_cache_IR, ppg_data_cache_RED, CACHE_NUMS);
  187. cache_counter = 0;
  188. return MAX30102_DATA_OK;
  189. }
  190. }
  191. return !MAX30102_DATA_OK;
  192. }
  193. /**
  194. * @brief MAX30102输入引脚外部中断触发
  195. * @param GPIO_Pin
  196. * @attention cubemx配置下降沿 上拉 允许中断
  197. * @retval None
  198. */
  199. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  200. {
  201. if (GPIO_Pin == MAX30102_INT_Pin)
  202. {
  203. max30102_int_flag = 1;
  204. }
  205. }

max30102.h

  1. #ifndef __MAX30102_H
  2. #define __MAX30102_H
  3. /*******************************以下根据实际情况设置*******************************/
  4. #include "main.h"
  5. extern I2C_HandleTypeDef hi2c1;
  6. #define i2c_transmit(pdata,data_size) HAL_I2C_Master_Transmit(&hi2c1,I2C_WRITE_ADDR,pdata,data_size,10)
  7. #define i2c_receive(pdata,data_size) HAL_I2C_Master_Receive(&hi2c1,I2C_READ_ADDR,pdata,data_size,10)
  8. #define delay_ms(ms) HAL_Delay(ms)
  9. /***********************************************************************************/
  10. #define CACHE_NUMS 150//缓存数
  11. #define PPG_DATA_THRESHOLD 100000 //检测阈值
  12. #define I2C_WRITE_ADDR 0xAE
  13. #define I2C_READ_ADDR 0xAF
  14. #define INTERRUPT_STATUS1 0X00
  15. #define INTERRUPT_STATUS2 0X01
  16. #define INTERRUPT_ENABLE1 0X02
  17. #define INTERRUPT_ENABLE2 0X03
  18. #define FIFO_WR_POINTER 0X04
  19. #define FIFO_OV_COUNTER 0X05
  20. #define FIFO_RD_POINTER 0X06
  21. #define FIFO_DATA 0X07
  22. #define FIFO_CONFIGURATION 0X08
  23. #define MODE_CONFIGURATION 0X09
  24. #define SPO2_CONFIGURATION 0X0A
  25. #define LED1_PULSE_AMPLITUDE 0X0C
  26. #define LED2_PULSE_AMPLITUDE 0X0D
  27. #define MULTILED1_MODE 0X11
  28. #define MULTILED2_MODE 0X12
  29. #define TEMPERATURE_INTEGER 0X1F
  30. #define TEMPERATURE_FRACTION 0X20
  31. #define TEMPERATURE_CONFIG 0X21
  32. #define VERSION_ID 0XFE
  33. #define PART_ID 0XFF
  34. #define MAX30102_DATA_OK 1
  35. void max30102_init(void);
  36. void max30102_fifo_read(float *data);
  37. void max30102_i2c_read(uint8_t reg_adder,uint8_t *pdata, uint8_t data_size);
  38. uint16_t max30102_getHeartRate(float *input_data,uint16_t cache_nums);
  39. float max30102_getSpO2(float *ir_input_data,float *red_input_data,uint16_t cache_nums);
  40. uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2]);
  41. void MAX30102_LCD_Data(uint16_t HeartRate,float SpO2,char *PHeartRate,char *PSpO2);
  42. #endif /* __MAX30102_H */

max30102_fir.c

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file : max30102_fir.c
  5. * @brief : 滤波算法实现
  6. ******************************************************************************
  7. * @attention
  8. * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
  9. * 2.打开DSP
  10. ******************************************************************************
  11. */
  12. /* USER CODE END Header */
  13. #include "./max30102/max30102_fir.h"
  14. #define BLOCK_SIZE 1 /* 调用一次arm_fir_f32处理的采样点个数 */
  15. #define NUM_TAPS 29 /* 滤波器系数个数 */
  16. uint32_t blockSize = BLOCK_SIZE;
  17. uint32_t numBlocks = BLOCK_SIZE; /* 需要调用arm_fir_f32的次数 */
  18. arm_fir_instance_f32 S_ir, S_red;
  19. static float firStateF32_ir[BLOCK_SIZE + NUM_TAPS - 1]; /* 状态缓存,大小numTaps + blockSize - 1*/
  20. static float firStateF32_red[BLOCK_SIZE + NUM_TAPS - 1]; /* 状态缓存,大小numTaps + blockSize - 1*/
  21. /* 低通滤波器系数 通过fadtool获取*/
  22. const float firCoeffs32LP[NUM_TAPS] = {
  23. -0.001542701735, -0.002211477375, -0.003286228748, -0.00442651147, -0.004758632276,
  24. -0.003007677384, 0.002192312852, 0.01188309677, 0.02637642808, 0.04498152807,
  25. 0.06596207619, 0.0867607221, 0.1044560149, 0.1163498312, 0.1205424443,
  26. 0.1163498312, 0.1044560149, 0.0867607221, 0.06596207619, 0.04498152807,
  27. 0.02637642808, 0.01188309677, 0.002192312852, -0.003007677384, -0.004758632276,
  28. -0.00442651147, -0.003286228748, -0.002211477375, -0.001542701735};
  29. void max30102_fir_init(void)
  30. {
  31. arm_fir_init_f32(&S_ir, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_ir[0], blockSize);
  32. arm_fir_init_f32(&S_red, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_red[0], blockSize);
  33. }
  34. void ir_max30102_fir(float *input, float *output)
  35. {
  36. arm_fir_f32(&S_ir, input, output, blockSize);
  37. }
  38. void red_max30102_fir(float *input, float *output)
  39. {
  40. arm_fir_f32(&S_red, input, output, blockSize);
  41. }

max30102_fir.h

  1. #ifndef __MAX30102_FIR_H
  2. #define __MAX30102_FIR_H
  3. #include "./math/arm_const_structs.h"
  4. void max30102_fir_init(void);
  5. void ir_max30102_fir(float *input,float *output);
  6. void red_max30102_fir(float *input,float *output);
  7. #endif /* __MAX30102_FIR_H */

2.代码添加完毕,在main.c中使用

  1. /* USER CODE BEGIN Includes */
  2. #include "stdio.h"
  3. #include "./max30102/max30102.h"
  4. #include "./max30102/max30102_fir.h"
  5. /* USER CODE END Includes */

 3.main.c定义全局变量

  1. /* USER CODE BEGIN PV */
  2. uint8_t max30102_int_flag = 0; // 中断标志
  3. float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
  4. float ppg_data_cache_IR[CACHE_NUMS] = {0}; // 缓存区
  5. uint16_t cache_counter = 0; // 缓存计数器
  6. /* USER CODE END PV */

 4.begin1这里定义心率 血氧

  1. /* USER CODE BEGIN 1 */
  2. uint16_t HeartRate = 0;
  3. float SpO2 = 0;
  4. /* USER CODE END 1 */

5.begin2这里初始化

  1. /* USER CODE BEGIN 2 */
  2. max30102_init();
  3. max30102_fir_init();
  4. float max30102_data[2], fir_output[2];
  5. printf("Max30102 Init\r\n");
  6. /* USER CODE END 2 */

6.begin while这里添加服务函数

  1. if(MAX30102_Get_DATA(&HeartRate,&SpO2,max30102_data,fir_output) == MAX30102_DATA_OK)
  2. {
  3. printf("心率:%d 次/min ", HeartRate);
  4. printf("血氧:%.2f %%\r\n", SpO2);
  5. }

7.最后别忘了添加串口重定向,我用的是串口10,在begin0添加

  1. /* USER CODE BEGIN 0 */
  2. int fputc(int ch, FILE *f)
  3. {
  4. HAL_UART_Transmit (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
  5. //采用轮询方式发送一个字节的数据,没有发送成功就一直等待
  6. return ch;
  7. }
  8. int fgetc(FILE *f)
  9. //int fgetc(int ch, FILE *F)
  10. {
  11. uint8_t ch;
  12. HAL_UART_Receive (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
  13. return ch;
  14. }
  15. /* USER CODE END 0 */

8.移植完毕,效果图如下

不知道第一次测的结果为啥是0...知道的小伙伴可以在评论区留言。

七、结束语

移植有问题的小伙伴可以在评论区留言,最后分享一下我借鉴的资料。

MAX30102脉搏血氧仪和心率传感器(四)血氧+心率完整版(STM32)

MAX30102 血氧调试笔记

需求有点大,那就把工程发出来

链接:https://pan.baidu.com/s/1VJ551_J-mVwxHWPCOqNBPQ 
提取码:max3

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/81973
推荐阅读
相关标签
  

闽ICP备14008679号