一、前言

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

二、材料准备

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

三、引脚说明

        我们只用到了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 血氧调试笔记

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

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

------------------------------------修改于2023.4.12---------------------------------

我发现读取温湿度信息后,MAX30102的INT不能正常触发了,一种简单的不修改配置的方法如下

/**

* @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;

}

查看原文