【STM32】手把手教你用CubeMX配置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
/* 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…知道的小伙伴可以在评论区留言。
七、结束语
移植有问题的小伙伴可以在评论区留言,最后分享一下我借鉴的资料。