赞
踩
网上流传血氧传感器的代码有好几个版本,听说这个不准,那个不准的。突然间我看到了一篇好文章,大概是自己用软件测试测量结果是否准确,秀的我头皮发麻呀(外部中断触发),本文将通过他的例程来手把手教大家如何配置。本文适合小白,只讲如何应用,原理请大家查阅其他资料,文末分享我的工程和关于血氧传感器优秀资料的链接。
某宝搜索关键字 “血氧传感器”,然后焊接成这个样子。
我们只用到了6个引脚,RD、IRD为该模块LED有关的引脚,一般不接。之后在cubemx中可以看到具体怎么连接。
VIN:主电源输入端 1.8V-5V
SCL:接I2C总线的时钟 -->PB6(根据cubemx)
SDA:接I2C总线的数据 -->PB9(根据cubemx)
INT:芯片的中断引脚 -->PC0(根据cubemx)
GND:接地(有两个)
1.选择自己单片机的芯片,把RCC、时钟、DEBUG啥的都配置好。(没啥要求,按平时来就行)
2.任选一个引脚作为外部中断触发的引脚,这样当我们放下手指时,血氧传感器才会检测。这里我选PC0。
3.配置PC0。下降沿触发、上拉、设置用户标签MAX30102_INT
4.使能PC0中断
不同引脚中断号不一样,具体看数据手册,当然你也可以看配置完多出来哪一行来判断。
5.配置IIC
6.配置串口,我这里用串口10,参数默认就行
7.然后大家就可以生成工程了
1.勾选MicroLIB
2.开启DSP
3.添加宏定义 ARM_MATH_CM7(<--这个根据自己内核的情况配置,我的是M7),__FPU_PRESENT
1.新建四个文件,将C文件添加到工程中
max30102.c
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : max30102.c
- * @brief : 血氧传感器
- ******************************************************************************
- * @attention
- * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
- * 2.打开DSP
- * 3.main函数定义如下全局变量
- * uint8_t max30102_int_flag = 0; // 中断标志
- * float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
- * float ppg_data_cache_IR[CACHE_NUMS] = {0}; // 缓存区
- * uint16_t cache_counter = 0; // 缓存计数器
- *
- ******************************************************************************
- */
- /* USER CODE END Header */
- #include "./max30102/max30102.h"
- #include "./max30102/max30102_fir.h"
- #include "stdio.h"
-
- extern uint8_t max30102_int_flag;
- extern float ppg_data_cache_RED[CACHE_NUMS] ; // 缓存区
- extern float ppg_data_cache_IR[CACHE_NUMS] ; // 缓存区
- extern uint16_t cache_counter;
- /**
- * @brief IIC 写入
- * @retval None
- */
- void max30102_i2c_write(uint8_t reg_adder, uint8_t data)
- {
- uint8_t transmit_data[2];
- transmit_data[0] = reg_adder;
- transmit_data[1] = data;
- i2c_transmit(transmit_data, 2);
- }
-
- /**
- * @brief IIC 读取
- * @retval None
- */
- void max30102_i2c_read(uint8_t reg_adder, uint8_t *pdata, uint8_t data_size)
- {
- uint8_t adder = reg_adder;
- i2c_transmit(&adder, 1);
- i2c_receive(pdata, data_size);
- }
-
- /**
- * @brief max30102初始化
- * @retval None
- */
- void max30102_init(void)
- {
- uint8_t data;
-
- max30102_i2c_write(MODE_CONFIGURATION, 0x40); // reset the device
-
- delay_ms(5);
-
- max30102_i2c_write(INTERRUPT_ENABLE1, 0xE0);
- max30102_i2c_write(INTERRUPT_ENABLE2, 0x00); // interrupt enable: FIFO almost full flag, new FIFO Data Ready,
- // ambient light cancellation overflow, power ready flag,
- // internal temperature ready flag
-
- max30102_i2c_write(FIFO_WR_POINTER, 0x00);
- max30102_i2c_write(FIFO_OV_COUNTER, 0x00);
- max30102_i2c_write(FIFO_RD_POINTER, 0x00); // clear the pointer
-
- 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)
-
- max30102_i2c_write(MODE_CONFIGURATION, 0x03); // MODE configuration:SpO2 mode
-
- max30102_i2c_write(SPO2_CONFIGURATION, 0x2A); // SpO2 configuration:ACD resolution:15.63pA,sample rate control:200Hz, LED pulse width:215 us
-
- max30102_i2c_write(LED1_PULSE_AMPLITUDE, 0x2f); // IR LED
- max30102_i2c_write(LED2_PULSE_AMPLITUDE, 0x2f); // RED LED current
-
- max30102_i2c_write(TEMPERATURE_CONFIG, 0x01); // temp
-
- max30102_i2c_read(INTERRUPT_STATUS1, &data, 1);
- max30102_i2c_read(INTERRUPT_STATUS2, &data, 1); // clear status
- }
-
- /**
- * @brief fifo区读取
- * @param output_data
- * @retval None
- */
- void max30102_fifo_read(float *output_data)
- {
- uint8_t receive_data[6];
- uint32_t data[2];
- max30102_i2c_read(FIFO_DATA, receive_data, 6);
- data[0] = ((receive_data[0] << 16 | receive_data[1] << 8 | receive_data[2]) & 0x03ffff);
- data[1] = ((receive_data[3] << 16 | receive_data[4] << 8 | receive_data[5]) & 0x03ffff);
- *output_data = data[0];
- *(output_data + 1) = data[1];
- }
-
- /**
- * @brief 获取心率
- * @param input_data cache_nums(缓存区的最大数字)
- * @retval (uint16_t)心率
- */
- uint16_t max30102_getHeartRate(float *input_data, uint16_t cache_nums)
- {
- float input_data_sum_aver = 0;
- uint16_t i, temp;
-
- for (i = 0; i < cache_nums; i++)
- {
- input_data_sum_aver += *(input_data + i);
- }
- input_data_sum_aver = input_data_sum_aver / cache_nums;
- for (i = 0; i < cache_nums; i++)
- {
- if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
- {
- temp = i;
- break;
- }
- }
- i++;
- for (; i < cache_nums; i++)
- {
- if ((*(input_data + i) > input_data_sum_aver) && (*(input_data + i + 1) < input_data_sum_aver))
- {
- temp = i - temp;
- break;
- }
- }
- if ((temp > 14) && (temp < 100))
- {
- return 3000 / temp;
- }
- else
- {
- return 0;
- }
- }
-
- /**
- * @brief 获取血氧
- * @param input_data red_input_data cache_nums(缓存区的最大数字)
- * @retval (float)血氧
- */
- float max30102_getSpO2(float *ir_input_data, float *red_input_data, uint16_t cache_nums)
- {
- float ir_max = *ir_input_data, ir_min = *ir_input_data;
- float red_max = *red_input_data, red_min = *red_input_data;
- float R;
- uint16_t i;
- for (i = 1; i < cache_nums; i++)
- {
- if (ir_max < *(ir_input_data + i))
- {
- ir_max = *(ir_input_data + i);
- }
- if (ir_min > *(ir_input_data + i))
- {
- ir_min = *(ir_input_data + i);
- }
- if (red_max < *(red_input_data + i))
- {
- red_max = *(red_input_data + i);
- }
- if (red_min > *(red_input_data + i))
- {
- red_min = *(red_input_data + i);
- }
- }
-
- R = ((ir_max + ir_min) * (red_max - red_min)) / ((red_max + red_min) * (ir_max - ir_min));
- return ((-45.060) * R * R + 30.354 * R + 94.845);
- }
-
- /**
- * @brief MAX30102服务函数
- * @param HeartRate(心率) SpO2(血氧) max30102_data fir_output
- * @retval (uint8_t)MAX30102_DATA_OK:结束读取 (uint8_t)!MAX30102_DATA_OK:还在读取
- */
- uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2])
- {
- if (max30102_int_flag) // 中断信号产生
- {
- max30102_int_flag = 0;
- max30102_fifo_read(max30102_data); // 读取数据
- ir_max30102_fir(&max30102_data[0], &fir_output[0]);
- red_max30102_fir(&max30102_data[1], &fir_output[1]); // 滤波
- if ((max30102_data[0] > PPG_DATA_THRESHOLD) && (max30102_data[1] > PPG_DATA_THRESHOLD)) // 大于阈值,说明传感器有接触
- {
- ppg_data_cache_IR[cache_counter] = fir_output[0];
- ppg_data_cache_RED[cache_counter] = fir_output[1];
- cache_counter++;
- }
- else // 小于阈值
- {
- cache_counter = 0;
- }
- if (cache_counter >= CACHE_NUMS) // 收集满了数据
- {
- *HeartRate = max30102_getHeartRate(ppg_data_cache_IR, CACHE_NUMS);
- *SpO2 = max30102_getSpO2(ppg_data_cache_IR, ppg_data_cache_RED, CACHE_NUMS);
- cache_counter = 0;
- return MAX30102_DATA_OK;
- }
- }
- return !MAX30102_DATA_OK;
- }
-
- /**
- * @brief MAX30102输入引脚外部中断触发
- * @param GPIO_Pin
- * @attention cubemx配置下降沿 上拉 允许中断
- * @retval None
- */
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- if (GPIO_Pin == MAX30102_INT_Pin)
- {
- max30102_int_flag = 1;
- }
- }
-
max30102.h
- #ifndef __MAX30102_H
- #define __MAX30102_H
-
- /*******************************以下根据实际情况设置*******************************/
- #include "main.h"
- extern I2C_HandleTypeDef hi2c1;
- #define i2c_transmit(pdata,data_size) HAL_I2C_Master_Transmit(&hi2c1,I2C_WRITE_ADDR,pdata,data_size,10)
- #define i2c_receive(pdata,data_size) HAL_I2C_Master_Receive(&hi2c1,I2C_READ_ADDR,pdata,data_size,10)
- #define delay_ms(ms) HAL_Delay(ms)
- /***********************************************************************************/
-
- #define CACHE_NUMS 150//缓存数
- #define PPG_DATA_THRESHOLD 100000 //检测阈值
-
-
- #define I2C_WRITE_ADDR 0xAE
- #define I2C_READ_ADDR 0xAF
-
- #define INTERRUPT_STATUS1 0X00
- #define INTERRUPT_STATUS2 0X01
- #define INTERRUPT_ENABLE1 0X02
- #define INTERRUPT_ENABLE2 0X03
-
- #define FIFO_WR_POINTER 0X04
- #define FIFO_OV_COUNTER 0X05
- #define FIFO_RD_POINTER 0X06
- #define FIFO_DATA 0X07
-
- #define FIFO_CONFIGURATION 0X08
- #define MODE_CONFIGURATION 0X09
- #define SPO2_CONFIGURATION 0X0A
- #define LED1_PULSE_AMPLITUDE 0X0C
- #define LED2_PULSE_AMPLITUDE 0X0D
-
- #define MULTILED1_MODE 0X11
- #define MULTILED2_MODE 0X12
-
- #define TEMPERATURE_INTEGER 0X1F
- #define TEMPERATURE_FRACTION 0X20
- #define TEMPERATURE_CONFIG 0X21
-
- #define VERSION_ID 0XFE
- #define PART_ID 0XFF
-
- #define MAX30102_DATA_OK 1
-
- void max30102_init(void);
- void max30102_fifo_read(float *data);
- void max30102_i2c_read(uint8_t reg_adder,uint8_t *pdata, uint8_t data_size);
- uint16_t max30102_getHeartRate(float *input_data,uint16_t cache_nums);
- float max30102_getSpO2(float *ir_input_data,float *red_input_data,uint16_t cache_nums);
- uint8_t MAX30102_Get_DATA(uint16_t *HeartRate,float *SpO2,float max30102_data[2],float fir_output[2]);
- void MAX30102_LCD_Data(uint16_t HeartRate,float SpO2,char *PHeartRate,char *PSpO2);
- #endif /* __MAX30102_H */
max30102_fir.c
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : max30102_fir.c
- * @brief : 滤波算法实现
- ******************************************************************************
- * @attention
- * 1.要宏定义 ARM_MATH_CM7,__FPU_PRESENT
- * 2.打开DSP
-
- ******************************************************************************
- */
- /* USER CODE END Header */
- #include "./max30102/max30102_fir.h"
-
- #define BLOCK_SIZE 1 /* 调用一次arm_fir_f32处理的采样点个数 */
- #define NUM_TAPS 29 /* 滤波器系数个数 */
-
- uint32_t blockSize = BLOCK_SIZE;
- uint32_t numBlocks = BLOCK_SIZE; /* 需要调用arm_fir_f32的次数 */
- arm_fir_instance_f32 S_ir, S_red;
- static float firStateF32_ir[BLOCK_SIZE + NUM_TAPS - 1]; /* 状态缓存,大小numTaps + blockSize - 1*/
- static float firStateF32_red[BLOCK_SIZE + NUM_TAPS - 1]; /* 状态缓存,大小numTaps + blockSize - 1*/
- /* 低通滤波器系数 通过fadtool获取*/
- const float firCoeffs32LP[NUM_TAPS] = {
- -0.001542701735, -0.002211477375, -0.003286228748, -0.00442651147, -0.004758632276,
- -0.003007677384, 0.002192312852, 0.01188309677, 0.02637642808, 0.04498152807,
- 0.06596207619, 0.0867607221, 0.1044560149, 0.1163498312, 0.1205424443,
- 0.1163498312, 0.1044560149, 0.0867607221, 0.06596207619, 0.04498152807,
- 0.02637642808, 0.01188309677, 0.002192312852, -0.003007677384, -0.004758632276,
- -0.00442651147, -0.003286228748, -0.002211477375, -0.001542701735};
-
- void max30102_fir_init(void)
- {
- arm_fir_init_f32(&S_ir, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_ir[0], blockSize);
- arm_fir_init_f32(&S_red, NUM_TAPS, (float32_t *)&firCoeffs32LP[0], &firStateF32_red[0], blockSize);
- }
-
- void ir_max30102_fir(float *input, float *output)
- {
- arm_fir_f32(&S_ir, input, output, blockSize);
- }
-
- void red_max30102_fir(float *input, float *output)
- {
- arm_fir_f32(&S_red, input, output, blockSize);
- }
max30102_fir.h
- #ifndef __MAX30102_FIR_H
- #define __MAX30102_FIR_H
-
- #include "./math/arm_const_structs.h"
-
-
- void max30102_fir_init(void);
- void ir_max30102_fir(float *input,float *output);
- void red_max30102_fir(float *input,float *output);
- #endif /* __MAX30102_FIR_H */
-
2.代码添加完毕,在main.c中使用
- /* USER CODE BEGIN Includes */
- #include "stdio.h"
- #include "./max30102/max30102.h"
- #include "./max30102/max30102_fir.h"
- /* USER CODE END Includes */
3.main.c定义全局变量
- /* USER CODE BEGIN PV */
- uint8_t max30102_int_flag = 0; // 中断标志
- float ppg_data_cache_RED[CACHE_NUMS] = {0}; // 缓存区
- float ppg_data_cache_IR[CACHE_NUMS] = {0}; // 缓存区
- uint16_t cache_counter = 0; // 缓存计数器
- /* USER CODE END PV */
4.begin1这里定义心率 血氧
- /* USER CODE BEGIN 1 */
- uint16_t HeartRate = 0;
- float SpO2 = 0;
- /* USER CODE END 1 */
5.begin2这里初始化
- /* USER CODE BEGIN 2 */
- max30102_init();
- max30102_fir_init();
- float max30102_data[2], fir_output[2];
- printf("Max30102 Init\r\n");
- /* USER CODE END 2 */
6.begin while这里添加服务函数
- if(MAX30102_Get_DATA(&HeartRate,&SpO2,max30102_data,fir_output) == MAX30102_DATA_OK)
- {
- printf("心率:%d 次/min ", HeartRate);
- printf("血氧:%.2f %%\r\n", SpO2);
- }
7.最后别忘了添加串口重定向,我用的是串口10,在begin0添加
- /* USER CODE BEGIN 0 */
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
- //采用轮询方式发送一个字节的数据,没有发送成功就一直等待
- return ch;
- }
- int fgetc(FILE *f)
- //int fgetc(int ch, FILE *F)
- {
- uint8_t ch;
- HAL_UART_Receive (&huart10 ,(uint8_t *)&ch,1,HAL_MAX_DELAY );
- return ch;
- }
- /* USER CODE END 0 */
8.移植完毕,效果图如下
不知道第一次测的结果为啥是0...知道的小伙伴可以在评论区留言。
移植有问题的小伙伴可以在评论区留言,最后分享一下我借鉴的资料。
MAX30102脉搏血氧仪和心率传感器(四)血氧+心率完整版(STM32)
需求有点大,那就把工程发出来
链接:https://pan.baidu.com/s/1VJ551_J-mVwxHWPCOqNBPQ
提取码:max3
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。