【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…知道的小伙伴可以在评论区留言。

 

七、结束语

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

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

MAX30102 血氧调试笔记

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32】手把手教你用CubeMX配置MAX30102血氧传感器

发表评论