本文是基于小熊派Hi3861书写记录应用MAX30102传感器使用,这里只记录怎么进行调用要用的,其余配置自行借鉴推理
传感器使用设定需要设备地址和内部寄存器地址,根据设备地址再找寻到内部寄存器地址,要根据不同的寄存器官方进行地址的使用,还有传感器相应的协议。
max30102是使用I2C协议进行传输信息。
根据官方默认,MAX30102设备地址为0x57,是7位存放数据,1位设置读写,共8位。
中断状态1寄存器,地址为0x00
中断状态2寄存器,地址为0x01
设置模式寄存器,地址为0x09
SPO2模式设置寄存器,地址为0x0A
红外LED监测寄存器,地址为0x0C
红光LED监测寄存器,地址位0X0D
根据这些寄存器为进行初始化设定测试。
传感器中对数据的存储 :


这里对于数据存储有4块地址
0x04是写指针,0x05是计算溢出值,0x06是读指针,0x07是数据寄存器,这里面存在两个指针,作用就是对数据存储器的读写。
可以手动对这个数据存储器进行清空操作,就是把写指针直接写入0x00,这样在你复位写指针后,读指针会自动复位,但是你如果只复位读指针,写指针不会自动复位,而数据存储器,对于我来说,要开启心率血氧模式,它会自动记录红外光和红光的数据,这两个数据组成一个样本,一个样本占6个空间大小。FIFO存储数据的结构好像是循环队列。
对于队列数据存储模式的设置:

因为我只用处理它存储的情况,不用管理中断之类的,所以我在这个0x08直接设置0x0F即可,好像说7-5位是设置计算均值的,第4位是配置空间用完自动覆盖的旧数据的,3-0位是关于存储数据涉及中断的一些配置。
选择传感器的不同模式:
先看看启动模式寄存器后,怎么设置模式

从官方文档中可以看见,第6为为复位位置,写入1即可,所以要复位要传入的数据是0x40,复位的定义翻译在下方

从表4中可以看见对模式的控制,当启动心率模式的时候在模式寄存器中写入010也就是0x02,当启动血氧模式的时候要往模式寄存器中写入011即0x03,这就是模式的控制了。
对传感器的采样率和脉宽配置:

根据官方文档,0x0A为寄存器地址设置采样率和脉宽,可以看见第6-5位是设置ADC的,第4-2位是设置采样率的,第1-0是设置,再根据表格进行选择配置。

上图是关于ADC的配置

上图是关于采样率的配置

上图是关于脉宽的配置

配置两个不同LED灯的电流大小,用于检测,电流大的光强,好像就是穿透效果好。
max30102驱动:
根据官方文档,先对max30102进行宏定义头文件,上方根据官方文档进行了一次分析。
主要处理的是传感器传输的数据类型,传感器从机地址,传感器寄存器地址,传感器一些相应模式的物理设置数据。
#ifndef __max30102_H__ #define __max30102_H__ //只要处理传输的数据就行了 /* max30102传感器数据类型定义 ------------------------------------------------------------*/ typedef struct { int heart_rate; int spo2; } max30102_Data_TypeDef; //只需要心率和血氧的数据就够了 /* 传感器地址、寄存器宏定义 --------------------------------------------------------------------*/ //使用max30102传感器默认设备地址 #define max30102_i2c_addr 0x57 //max30102传感器中寄存器的地址 #define max30102_FIFO_config 0x08 #define max30102_mode_addr 0x09 //模式选择寄存器地址 #define max30102_spo2_addr 0x0A //配置采样率脉宽寄存器的地址 #define max30102_led1_addr 0x0C //红外Led功率 #define max30102_led2_addr 0x0D //红光Led 功率 //MAX30102中存储红外和红光的寄存器地址,总共可以存32个样本,每个样本6个字节,前3个字节红光,后三个字节是红外 #define max30102_WD_addr 0x04 //写指针地址位置 #define max30102_RD_addr 0x06 //读指针地址位置 #define max30102_FIFO_data_addr 0x07 //这个地址是FIFO Data Register,可以存储32个数据样本 //配置不同模式 #define HR_mode 0x02 //心率模式下只启动红外光 #define Spo2_mode 0x03 //血氧模式 有红光有红外光 void max30102_init(void); //初始化传感器 void max30102_read_data(max30102_Data_TypeDef *max30102_Data); //读取max30102传感器数据 #endif
写完宏定义详细处理宏定义问题:
根据HI3861对于传感器硬件的使用案例进行I2C协议的开发使用
调用的包为
#include <stdio.h> #include <string.h> #include <unistd.h> #include <math.h> #include <stdlib.h> #include <stdbool.h> #include "cmsis_os2.h" #include "max30102.h" #include "wifiiot_errno.h" #include "wifiiot_gpio.h" #include "wifiiot_gpio_ex.h" #include "wifiiot_i2c.h" #include "wifiiot_i2c_ex.h" //设定数据类型宏定义 #define u8 uint8_t #define u16 uint16_t #define u32 uint32_t
首先初始化开发板的I2C管脚:
复用管脚为I2C协议传输数据模式,0管脚作为SDA,1管脚作为SCL,根据HI3861给的示例进行管脚初始化就行了,自行设置,也不一定是要使用0、1管脚作为复用。
/*************************************************************** * 函数名称: max30102_IO_Init * 说 明: max30102_GPIO初始化 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ static void max30102_IO_Init(void){ //开发板初始化管脚 GpioInit(); //GPIO_0复用为I2C1_SDA IoSetFunc(WIFI_IOT_IO_NAME_GPIO_0, WIFI_IOT_IO_FUNC_GPIO_0_I2C1_SDA); //GPIO_1复用为I2C1_SCL IoSetFunc(WIFI_IOT_IO_NAME_GPIO_1, WIFI_IOT_IO_FUNC_GPIO_1_I2C1_SCL); //baudrate: 400kbps I2cInit(WIFI_IOT_I2C_IDX_1, 400000); I2cSetBaudrate(WIFI_IOT_I2C_IDX_1, 400000); }
再结合上方所说的模式与寄存器复位设置书写初始的软复位函数:
/*************************************************************** * 函数名称: reset_max30102 * 说 明: 软复位max30102,系统初始化使用,恢复寄存器默认值,全局复位 * 参 数: 无 * 返 回 值: ret 为I2C通讯写入返回值,通讯成功返回 WIFI_IOT_SUCCESS 0 ***************************************************************/ u32 reset_max30102(void){ u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; u8 reg_val; //处理复位 //发送信息[0]要设置为寄存器地址 send_data[0] = max30102_mode_addr; send_data[1] = 0x40; // 0x40为复位命令 i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){//这个单纯验证I2C通讯协议是否发送成功 printf("\nret的值位%u\n",&ret); printf("\n初始复位失败!!!!\n"); }else{ //测试复位成功数据,直接读复位后的数据 do{ send_data[0] = max30102_mode_addr; i2cdata.sendBuf = send_data; i2cdata.sendLen = 1; i2cdata.receiveBuf = ®_val; i2cdata.receiveLen = 1; ret = I2cRead(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x01, &i2cdata); printf("here\n"); if (ret != WIFI_IOT_SUCCESS) { printf("\n寄存器读取失败: %u\n", ret); return ret; //读取失败就证明通讯协议失败直接退出就行 } // 短暂延时,避免频繁读取 osDelay(10); }while (reg_val & 0x40); // 等待Bit6变为1,设置了复位会先将寄存器内部值变成0x40,下一步再运行清零操作,寄存器内部值变为0x00 // 3. 寄存器复位判断 if(reg_val == 0x00 )printf("\n复位成功完成!\n"); else{ printf("\n最终复位失败!\n"); } } return ret;//正常通讯成功ret返回0 }
从这里可以对I2C协议的书写格式进行分析,发送I2C协议对寄存器内部进行书写是通过调用I2cWrite函数,传入的第一个参数为初始开发板中设置的I2C协议中的WIFI_IOT_I2C_IDX_1,第二个参数是传感器的从机地址的设置,在我的max30102宏定义中定义了从机地址为max30102_i2c_addr,传入的第二个参数是(max30102_i2c_addr << 1) | 0x00,这个表示的就是对传感器进行I2C写入操作,第3个就是它要求规定的数据类型WifiIotI2cData i2cdata;注意当使用读操作的时候第二个参数就要改为(max30102_i2c_addr << 1) | 0x01
可以查看一些这个数据类型的定义:
typedef struct { /** Pointer to the buffer storing data to send */ unsigned char *sendBuf; /** Length of data to send */ unsigned int sendLen; /** Pointer to the buffer for storing data to receive */ unsigned char *receiveBuf; /** Length of data received */ unsigned int receiveLen; } WifiIotI2cData;
I2C协议注意点:
这个结构体封装了4个数据可以进行书写,一个是I2C协议传输的数据unsigned char *sendBuf;一个是表示这个指针数据的长度是多少unsigned int sendLen;最后两个是对I2C协议处理后调用函数接收的反馈,一般来说只有在读取数据的时候用到这两个unsigned char *receiveBuf;指向数据反馈数据地址,unsigned int receiveLen;代表接收反馈数据长度。简单点来说,前两个数据用于I2C协议写数据操作,后两个用于I2C协议读数据操作。
由于后两个数据是对于读操作进行的,所以后面两个操作只要传入相应的变量,就行了,不需要设置啥。关键在于写操作中的unsigned char *sendBuf;从上方对max30102进行复位的代码可以看见,对于这个unsigned char *sendBuf;的使用,是先自己定义一个数组,需要多大空间也是自己设定,数据的第一位存放的是传感器中寄存器的物理地址,也就是下标为0的位置;后续的位置从下标1开始存储要写入的数据,要读数据的话,必须先进行写操作,这是个要注意的地方,想读数据要先写数据,这个时候,你只要设置下标为0寄存器的物理地址即可,不需要传入后续的数据,这样就不会对寄存器进行书写。
为了进行一些读写操作的正确性验证所以需要对寄存器数据进行单个的读写操作:
这里直接调用HI3861中封装的I2C协议的I2cWriteread函数,可以省略书写步骤,不用繁琐的先调用遍写,再去读,这样直接读取单个寄存器中的数据。
/*************************************************************** * 函数名称: ReadMax30102Register * 说 明: ReadMax30102Register读取寄存器中数值函数,想读取寄存器中数值在I2c协议中必须对一个寄存器地址先读后写 * 参 数: uint8_t regAddr, uint8_t *dataBuffer, uint8_t dataLen //传入读取的寄存器地址,接收数据寄存器指针,接收数据长度 * 返 回 值: 返回相应位置寄存器中的值 ***************************************************************/ u32 ReadMax30102Register(uint8_t regAddr, uint8_t *dataBuffer, uint8_t dataLen) { WifiIotI2cData i2cData = { .sendBuf = ®Addr, .sendLen = 1, // 固定为1字节(寄存器地址) .receiveBuf = dataBuffer, //dataBuffer指针指向寄存器中数据地址 .receiveLen = dataLen }; return I2cWriteread(WIFI_IOT_I2C_IDX_1, ( max30102_i2c_addr << 1) | 0x00, &i2cData); }
之前提及了对于max30102的软复位操作,其实相当于整体重置操作,但是我们也可以仅仅对于FIFO进行复位操作清空队列:
如果详细看了上方对代码的讲解这里也就很简单了,就不详细讲解了,都是一些I2C协议的读写,还有验证。
/*************************************************************** * 函数名称: reset_FIFO * 说 明: FIFO寄存器读写指针复位 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 reset_FIFO(void){ u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; //u8 reg_val; //处理FIFO写指针复位,只用处理写指针复位即可,处理完写指针复位,读指针也会自动复位 send_data[0] = max30102_WD_addr; send_data[1] = 0x00; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; //I2C协议传入 ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); osDelay(10); // 等待复位完成 //是用读寄存器函数 u8 wdptr=1,rdptr=1; ReadMax30102Register(max30102_WD_addr,&wdptr,1); ReadMax30102Register(max30102_RD_addr,&rdptr,1); if (wdptr == 0 && rdptr == 0) { printf("FIFO指针复位成功\n"); } else { printf("FIFO指针复位失败:写指针=%u,读指针=%u\n", wdptr, rdptr); } return ret; }
这里是对红外光和红光的驱动设置:
上方根据官方文件对寄存器进行了解析,如果不太清楚可以回去重看官方文件。
/*************************************************************** * 函数名称: max30102_led //开启led灯 * 说 明: led功率设置 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 max30102_led(void){ printf("led启动\n"); u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; //设置红外光 send_data[0] = max30102_led1_addr; //设置红外光功率 send_data[1] = 0x24; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; //I2C通讯写入 ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){ printf("\n红外光启动失败!\n"); } //设置红光 send_data[0] = max30102_led2_addr; //设置红光功率 send_data[1] = 0x24; i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){ printf("\n红外光启动失败!\n"); } return ret; }
这里是想验证是否进行了自动监测数据,并获取监测数据的样本长度:
/*************************************************************** * 函数名称: GetFIFOLength_max30102 * 说 明: 获取可读数据长度 * 参 数: 无 * 返 回 值: 返回可读数据长度 ***************************************************************/ u8 GetFIFOLength_max30102(void) { u8 wdptr, rdptr; ReadMax30102Register(max30102_WD_addr, &wdptr, 1); ReadMax30102Register(max30102_RD_addr, &rdptr, 1); // // 关键:使用无符号减法,避免负数溢出 // u16 diff = (u16)wdptr - (u16)rdptr; // 显式转换为16位无符号数 // printf("差值(十进制):%u\n", diff); // 用%u打印无符号数 // return (diff ); // FIFO深度32,模32 printf("FIFO指针:写指针=%u,读指针=%u\n", wdptr, rdptr); return (wdptr - rdptr + 32) % 32; }
这里是对于监测的数据进行读取驱动操作:
/*************************************************************** * 函数名称: readsample_max30102 * 说 明: 读取数据寄存器中单个样本 * 参 数: u32 *ir, u32 *red,红外光指针,红光指针 * 返 回 值: 返回通讯情况 ***************************************************************/ u32 readsample_max30102(u32 *ir, u32 *red){ //读取单个样本,单个样本数据长度6位,前三个红外光,后三个红光 u32 ret; u8 send_data[2]; u8 thebuf[6]; WifiIotI2cData i2cdata = {0}; //设置写入数据长度,接收数据长度 send_data[0] = max30102_FIFO_data_addr; i2cdata.sendBuf = send_data; i2cdata.sendLen = 1; i2cdata.receiveBuf = thebuf; i2cdata.receiveLen = 6; ret = I2cWriteread(WIFI_IOT_I2C_IDX_1, ( max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret != WIFI_IOT_SUCCESS){printf("\n读取样本失败!!!\n");}else{ // 解析IR数据(3字节,大端格式) *ir = ((uint32_t)thebuf[0] << 16) | ((uint32_t)thebuf[1] << 8) | (uint32_t)thebuf[2]; // 解析Red数据 *red = ((uint32_t)thebuf[3] << 16) | ((uint32_t)thebuf[4] << 8) | (uint32_t)thebuf[5]; //printf("\n样本: IR=0x%06lX, Red=0x%06lX\n", ir, red); } return ret; } //测试读取样本 void test_readsample(void){ u8 datalen = GetFIFOLength_max30102(); //GetFIFOLength_max30102(); printf("\n可读取采集数据长度为%u\n",datalen); osDelay(10); //循环读取样本数据 u32 ir,red; for(int i =0;i<datalen; i++){ printf("\n样本序号%d\n",i); readsample_max30102(&ir,&red); } } //
上方就是一些底层的一些驱动,现在将其集成进行初始化就行:
/*************************************************************** * 函数名称: spo2_init_max30102 //开启心率血氧模式,同时测试心率和红光 * 说 明: heartrate初始化,清空寄存器 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 spo2_init_max30102(void){ printf("测试心率血氧启动\n"); u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; // u8 reg_val; //调用传感器为心率模式,选择设置模式地址,传入0x02数据表示使用测试心率模式 send_data[0] = max30102_mode_addr; send_data[1] = 0x03; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\n心率血氧模式启动失败\n"); }else{ //开启心率检测模式配置采样率和脉宽 //设置数据 send_data[0] = max30102_spo2_addr; send_data[1] = 0x27; //根据官方文档来进行调整要设置的采样率和脉宽 i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\n心率血氧模式启动失败\n"); }else{ // //启动心率,再配置LED红外光 ret = max30102_led(); if (ret != WIFI_IOT_SUCCESS){ printf("\n红外功率设置失败\n"); }else{ //配置FIFO队列的模式 send_data[0] = max30102_FIFO_config; send_data[1] = 0x0F; i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\nFIFO_config配置失败\n"); } } } } osDelay(10); return ret; }
下方就是对读取出来的红外光和红光数据的解析算法,这只是简易的,方便应用,效果上应该不太好,建议自己了解去书写:
/*************************************************************** 心率血氧计算算法部分 ***************************************************************/ // 配置参数 #define SAMPLE_NUM 100 // 缓冲区大小,4秒数据(假设采样率25Hz) #define SAMPLE_INTERVAL_MS 40 // 采样间隔,40ms->25Hz #define MIN_PEAK_DISTANCE 15 // 峰最小间隔,防抖 #define MAX_PEAKS 10 // 平均心率使用最大峰数量 #define SPO2_SAMPLE_SIZE 100 // 血氧样本量 #define MAX_SAMPLES 15 // HRV计算样本量 // 心理状态相关配置 #define HRV_BASELINE_SAMPLES 50 // 计算HRV基线的样本数 #define HRV_STRESS_THRESHOLD 20.0 // HRV压力阈值(ms) #define HR_BASLINE_WINDOW 300 // 心率基线计算窗口(秒) #define HR_STRESS_THRESHOLD 15 // 心率偏离基线阈值(BPM) // 心理状态枚举 typedef enum { MOOD_RELAXED = 0, // 放松状态 MOOD_NORMAL = 1, // 正常状态 MOOD_STRESSED = 2, // 压力/焦虑状态 MOOD_UNKNOWN = 3 // 未知状态 } MoodState; // 全局数据缓冲区 static u32 ir_buffer[SAMPLE_NUM] = {0}; static u32 red_buffer[SPO2_SAMPLE_SIZE] = {0}; static u32 ir_raw_buffer[SPO2_SAMPLE_SIZE] = {0}; static int buffer_index = 0; static int spo2_index = 0; // 心率计算相关 static int last_peak_index = -MIN_PEAK_DISTANCE; static int peak_intervals[MAX_PEAKS] = {0}; static int peak_count = 0; static int intervals[MAX_SAMPLES] = {0}; static int sample_count = 0; // 心理状态相关全局变量 static double hrv_baseline = 0; // HRV基线值 static int hr_baseline = 0; // 心率基线值 static int baseline_sample_count = 0; // 基线采样计数 static MoodState current_mood = MOOD_UNKNOWN; // 当前心理状态 // 结果变量 int g_heart_rate = 0; int g_spo2 = 0; // 计算数组均值 double mean(int *arr, int n) { double s = 0; for (int i = 0; i < n; i++) s += arr[i]; return s / n; } // 计算标准差SDNN (近似HRV) double stddev(int *arr, int n) { double m = mean(arr, n); double sum_sq = 0; for (int i = 0; i < n; i++) { double diff = arr[i] - m; sum_sq += diff * diff; } return sqrt(sum_sq / n); } // 排序比较函数 int compare_int(const void *a, const void *b) { return (*(int*)a - *(int*)b); } // 将间隔(样本数)转换为心率(BPM) int interval_to_hr(int interval_samples) { if(interval_samples == 0) return 0; // 间隔(ms) = 间隔(样本数) * 采样间隔(ms) int interval_ms = interval_samples * SAMPLE_INTERVAL_MS; return 60000 / interval_ms; } // 简单均值滤波 u32 smooth_ir(u32 *buffer, int len) { u32 sum = 0; for (int i = 0; i < len; i++) { sum += buffer[i]; } return sum / len; } // 更新HRV和心率基线 void update_baseline(double hrv, int hr) { static double hrv_sum = 0; static int hr_sum = 0; // 收集足够的样本计算基线 if (baseline_sample_count < HRV_BASELINE_SAMPLES) { hrv_sum += hrv; hr_sum += hr; baseline_sample_count++; if (baseline_sample_count == HRV_BASELINE_SAMPLES) { hrv_baseline = hrv_sum / HRV_BASELINE_SAMPLES; hr_baseline = hr_sum / HRV_BASELINE_SAMPLES; printf("❤️ 心率基线已建立: %d BPM\n", hr_baseline); printf("🩺 HRV基线已建立: %.2f ms\n", hrv_baseline); } } else { // 定期更新基线(滑动窗口) static int update_counter = 0; if (++update_counter >= HR_BASLINE_WINDOW) { update_counter = 0; hrv_baseline = (hrv_baseline * 0.9) + (hrv * 0.1); // 指数平滑 hr_baseline = (hr_baseline * 0.9) + (hr * 0.1); } } } // 基于HRV和心率估算心理状态 MoodState estimate_mood(double hrv, int hr) { if (baseline_sample_count < HRV_BASELINE_SAMPLES) { return MOOD_UNKNOWN; // 基线未建立 } // 计算与基线的偏离度 double hrv_deviation = hrv_baseline - hrv; // HRV降低表示压力 int hr_deviation = hr - hr_baseline; // 心率升高表示压力 // 简单的心理状态判断规则 if (hrv_deviation > HRV_STRESS_THRESHOLD && hr_deviation > HR_STRESS_THRESHOLD) { return MOOD_STRESSED; // 高压力/焦虑状态 } else if (hrv >= hrv_baseline && hr <= hr_baseline) { return MOOD_RELAXED; // 放松状态 } else { return MOOD_NORMAL; // 正常状态 } } // 打印心理状态描述 const char* get_mood_description(MoodState state) { switch(state) { case MOOD_RELAXED: return "放松状态"; case MOOD_NORMAL: return "正常状态"; case MOOD_STRESSED: return "压力/焦虑状态"; default: return "未知状态"; } } // 计算血氧饱和度 void compute_spo2(int *spo2_result) { u32 red_sum = 0, ir_sum = 0; u32 red_min = 0xFFFFFFFF, red_max = 0; u32 ir_min = 0xFFFFFFFF, ir_max = 0; for (int i = 0; i < SPO2_SAMPLE_SIZE; i++) { red_sum += red_buffer[i]; ir_sum += ir_raw_buffer[i]; if (red_buffer[i] < red_min) red_min = red_buffer[i]; if (red_buffer[i] > red_max) red_max = red_buffer[i]; if (ir_raw_buffer[i] < ir_min) ir_min = ir_raw_buffer[i]; if (ir_raw_buffer[i] > ir_max) ir_max = ir_raw_buffer[i]; } float red_dc = red_sum / (float)SPO2_SAMPLE_SIZE; float ir_dc = ir_sum / (float)SPO2_SAMPLE_SIZE; float red_ac = red_max - red_min; float ir_ac = ir_max - ir_min; if (ir_ac == 0 || ir_dc == 0 || red_dc == 0) { *spo2_result = -1; // 表示异常 return; } float r = (red_ac / red_dc) / (ir_ac / ir_dc); float spo2 = 110.0f - 25.0f * r; if (spo2 > 100.0f) spo2 = 100.0f; if (spo2 < 0.0f) spo2 = 0.0f; g_spo2 = (int)(spo2 + 0.5f); *spo2_result = g_spo2; } // 更新心率状态和健康分析(增强版) void update_heart_status(int current_interval) { // 添加样本到HRV计算缓冲区 intervals[sample_count % MAX_SAMPLES] = current_interval; sample_count++; if (sample_count >= MAX_SAMPLES) { // 复制并排序去极端值 int tmp[MAX_SAMPLES]; memcpy(tmp, intervals, sizeof(tmp)); qsort(tmp, MAX_SAMPLES, sizeof(int), compare_int); // 去除最高最低2个 int valid_count = MAX_SAMPLES - 4; int valid_intervals[valid_count]; memcpy(valid_intervals, &tmp[2], valid_count * sizeof(int)); // 计算HRV(标准差) double hrv = stddev(valid_intervals, valid_count); // 当前心率(取平均间隔计算) int avg_interval = (int)mean(valid_intervals, valid_count); int current_hr = interval_to_hr(avg_interval); printf("❤️ 当前心率: %d BPM\n", current_hr); printf("🩺 心率变异性(HRV): %.2f ms\n", hrv); // 更新基线 update_baseline(hrv, current_hr); // 估算心理状态 current_mood = estimate_mood(hrv, current_hr); printf("🧠 心理状态估算: %s\n", get_mood_description(current_mood)); } else { int current_hr = interval_to_hr(current_interval); printf("❤️ 当前心率(采样中): %d BPM,等待更多数据...\n", current_hr); } } // 心率和血氧计算主函数 int calculate_heart_rate_and_spo2(void) { u32 red = 0, ir = 0; u32 ret; // 读取传感器数据 ret = readsample_max30102(&ir, &red); if (ret != 0) { printf("读取传感器数据失败\n"); return -1; } // 保存数据到缓冲区 ir_buffer[buffer_index] = ir; red_buffer[spo2_index] = red; ir_raw_buffer[spo2_index] = ir; // 平滑滤波 u32 ir_avg = smooth_ir(ir_buffer, SAMPLE_NUM); int prev = (buffer_index - 1 + SAMPLE_NUM) % SAMPLE_NUM; int next = (buffer_index + 1) % SAMPLE_NUM; u32 current = ir_buffer[buffer_index]; // 峰值检测 + 动态阈值 if (current > ir_buffer[prev] && current > ir_buffer[next] && current > ir_avg + 1000) { // 检测到心跳峰值 int interval = buffer_index - last_peak_index; if (interval < 0) interval += SAMPLE_NUM; if (interval >= MIN_PEAK_DISTANCE) { peak_intervals[peak_count % MAX_PEAKS] = interval; peak_count++; last_peak_index = buffer_index; // 计算平均心率 int total = 0; int valid = (peak_count < MAX_PEAKS) ? peak_count : MAX_PEAKS; for (int i = 0; i < valid; i++) { total += peak_intervals[i]; } int avg_interval = total / valid; g_heart_rate = interval_to_hr(avg_interval); // 更新心率状态 update_heart_status(avg_interval); } } // 更新缓冲区索引 buffer_index = (buffer_index + 1) % SAMPLE_NUM; spo2_index++; if (spo2_index >= SPO2_SAMPLE_SIZE) { spo2_index = 0; int spo2 = 0; compute_spo2(&spo2); if (spo2 > 0) { printf("🩸 血氧饱和度(SpO2) = %d%%\n", spo2); } } return 0; // 成功返回0 } //测试心率和血氧监测函数 void testcounthrspo2(void){ // 初始化失败计数器 int failure_count = 0; // 持续调用心率和血氧计算 while (1) { // 执行心率和血氧计算 if (calculate_heart_rate_and_spo2() != 0) { failure_count++; printf("警告:心率计算失败!计数: %d\n", failure_count); // 如果连续失败超过10次,尝试重置传感器 if (failure_count >= 10) { printf("错误:失败次数过多,正在重置传感器...\n"); max30102_IO_Init(); osDelay(SAMPLE_INTERVAL_MS); spo2_init_max30102(); failure_count = 0; } } else { failure_count = 0; // 成功执行,重置计数器 } // 按照采样率控制延时 osDelay(SAMPLE_INTERVAL_MS); } } /*************************************************************** 心率血氧计算算法部分 ***************************************************************/
然后就是最后的头文件初始化集成运行函数了:
/*************************************************************** * 函数名称: max30102_init * 说 明: max30102初始化,清空寄存器 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ void max30102_init(void){ max30102_IO_Init(); if(reset_max30102()==WIFI_IOT_SUCCESS){ printf("\nmax30102重置成功!!!\n"); }else{ printf("\nmax30102重置失败!!!\n"); } reset_FIFO(); osDelay(10); spo2_init_max30102(); osDelay(10); // u8 result = GetFIFOLength_max30102(); // //GetFIFOLength_max30102(); // printf("\n可读取采集数据长度为%u\n",result); test_readsample(); osDelay(10); testcounthrspo2(); }
下方放置根据HI3861自行书写驱动max30102的头文件还有源代码:
max30102.h:
#ifndef __max30102_H__ #define __max30102_H__ //只要处理传输的数据就行了 /* max30102传感器数据类型定义 ------------------------------------------------------------*/ typedef struct { int heart_rate; int spo2; } max30102_Data_TypeDef; //只需要心率和血氧的数据就够了 /* 传感器地址、寄存器宏定义 --------------------------------------------------------------------*/ //使用max30102传感器默认设备地址 #define max30102_i2c_addr 0x57 //max30102传感器中寄存器的地址 #define max30102_FIFO_config 0x08 #define max30102_mode_addr 0x09 //模式选择寄存器地址 #define max30102_spo2_addr 0x0A //配置采样率脉宽寄存器的地址 #define max30102_led1_addr 0x0C //红外Led功率 #define max30102_led2_addr 0x0D //红光Led 功率 //MAX30102中存储红外和红光的寄存器地址,总共可以存32个样本,每个样本6个字节,前3个字节红光,后三个字节是红外 #define max30102_WD_addr 0x04 //写指针地址位置 #define max30102_RD_addr 0x06 //读指针地址位置 #define max30102_FIFO_data_addr 0x07 //这个地址是FIFO Data Register,可以存储32个数据样本 //配置不同模式 #define HR_mode 0x02 //心率模式下只启动红外光 #define Spo2_mode 0x03 //血氧模式 有红光有红外光 void max30102_init(void); //初始化传感器 void max30102_read_data(max30102_Data_TypeDef *max30102_Data); //读取max30102传感器数据 #endif
max30102.c:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <math.h> #include <stdlib.h> #include <stdbool.h> #include "cmsis_os2.h" #include "max30102.h" #include "wifiiot_errno.h" #include "wifiiot_gpio.h" #include "wifiiot_gpio_ex.h" #include "wifiiot_i2c.h" #include "wifiiot_i2c_ex.h" //设定数据类型宏定义 #define u8 uint8_t #define u16 uint16_t #define u32 uint32_t /*************************************************************** * 函数名称: ReadMax30102Register * 说 明: ReadMax30102Register读取寄存器中数值函数,想读取寄存器中数值在I2c协议中必须对一个寄存器地址先读后写 * 参 数: uint8_t regAddr, uint8_t *dataBuffer, uint8_t dataLen //传入读取的寄存器地址,接收数据寄存器指针,接收数据长度 * 返 回 值: 返回相应位置寄存器中的值 ***************************************************************/ u32 ReadMax30102Register(uint8_t regAddr, uint8_t *dataBuffer, uint8_t dataLen) { WifiIotI2cData i2cData = { .sendBuf = ®Addr, .sendLen = 1, // 固定为1字节(寄存器地址) .receiveBuf = dataBuffer, //dataBuffer指针指向寄存器中数据地址 .receiveLen = dataLen }; return I2cWriteread(WIFI_IOT_I2C_IDX_1, ( max30102_i2c_addr << 1) | 0x00, &i2cData); } /*************************************************************** * 函数名称: max30102_IO_Init * 说 明: max30102_GPIO初始化 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ static void max30102_IO_Init(void){ //开发板初始化管脚 GpioInit(); //GPIO_0复用为I2C1_SDA IoSetFunc(WIFI_IOT_IO_NAME_GPIO_0, WIFI_IOT_IO_FUNC_GPIO_0_I2C1_SDA); //GPIO_1复用为I2C1_SCL IoSetFunc(WIFI_IOT_IO_NAME_GPIO_1, WIFI_IOT_IO_FUNC_GPIO_1_I2C1_SCL); //baudrate: 400kbps I2cInit(WIFI_IOT_I2C_IDX_1, 400000); I2cSetBaudrate(WIFI_IOT_I2C_IDX_1, 400000); } /*************************************************************** * 函数名称: reset_max30102 * 说 明: 软复位max30102,系统初始化使用,恢复寄存器默认值,全局复位 * 参 数: 无 * 返 回 值: ret 为I2C通讯写入返回值,通讯成功返回 WIFI_IOT_SUCCESS 0 ***************************************************************/ u32 reset_max30102(void){ u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; u8 reg_val; //处理复位 //发送信息[0]要设置为寄存器地址 send_data[0] = max30102_mode_addr; send_data[1] = 0x40; // 0x40为复位命令 i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){//这个单纯验证I2C通讯协议是否发送成功 printf("\nret的值位%u\n",&ret); printf("\n初始复位失败!!!!\n"); }else{ //测试复位成功数据,直接读复位后的数据 do{ send_data[0] = max30102_mode_addr; i2cdata.sendBuf = send_data; i2cdata.sendLen = 1; i2cdata.receiveBuf = ®_val; i2cdata.receiveLen = 1; ret = I2cRead(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x01, &i2cdata); printf("here\n"); if (ret != WIFI_IOT_SUCCESS) { printf("\n寄存器读取失败: %u\n", ret); return ret; //读取失败就证明通讯协议失败直接退出就行 } // 短暂延时,避免频繁读取 osDelay(10); }while (reg_val & 0x40); // 等待Bit7变为0,设置了复位会先将寄存器内部值变成0x80,下一步再运行清零操作,寄存器内部值变为0x00 // 3. 寄存器复位判断 if(reg_val == 0x00 )printf("\n复位成功完成!\n"); else{ printf("\n最终复位失败!\n"); } } return ret;//正常通讯成功ret返回0 } /*************************************************************** * 函数名称: reset_FIFO * 说 明: FIFO寄存器读写指针复位 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 reset_FIFO(void){ u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; //u8 reg_val; //处理FIFO写指针复位,只用处理写指针复位即可,处理完写指针复位,读指针也会自动复位 send_data[0] = max30102_WD_addr; send_data[1] = 0x00; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; //I2C协议传入 ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); osDelay(10); // 等待复位完成 //是用读寄存器函数 u8 wdptr=1,rdptr=1; ReadMax30102Register(max30102_WD_addr,&wdptr,1); ReadMax30102Register(max30102_RD_addr,&rdptr,1); if (wdptr == 0 && rdptr == 0) { printf("FIFO指针复位成功\n"); } else { printf("FIFO指针复位失败:写指针=%u,读指针=%u\n", wdptr, rdptr); } return ret; } /*************************************************************** * 函数名称: max30102_led //开启led灯 * 说 明: led功率设置 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 max30102_led(void){ printf("led启动\n"); u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; //设置红外光 send_data[0] = max30102_led1_addr; //设置红外光功率 send_data[1] = 0x24; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; //I2C通讯写入 ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){ printf("\n红外光启动失败!\n"); } //设置红光 send_data[0] = max30102_led2_addr; //设置红光功率 send_data[1] = 0x24; i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret!= WIFI_IOT_SUCCESS){ printf("\n红外光启动失败!\n"); } return ret; } /*************************************************************** * 函数名称: GetFIFOLength_max30102 * 说 明: 获取可读数据长度 * 参 数: 无 * 返 回 值: 返回可读数据长度 ***************************************************************/ u8 GetFIFOLength_max30102(void) { u8 wdptr, rdptr; ReadMax30102Register(max30102_WD_addr, &wdptr, 1); ReadMax30102Register(max30102_RD_addr, &rdptr, 1); // // 关键:使用无符号减法,避免负数溢出 // u16 diff = (u16)wdptr - (u16)rdptr; // 显式转换为16位无符号数 // printf("差值(十进制):%u\n", diff); // 用%u打印无符号数 // return (diff ); // FIFO深度32,模32 printf("FIFO指针:写指针=%u,读指针=%u\n", wdptr, rdptr); return (wdptr - rdptr + 32) % 32; } /*************************************************************** * 函数名称: readsample_max30102 * 说 明: 读取数据寄存器中单个样本 * 参 数: u32 *ir, u32 *red,红外光指针,红光指针 * 返 回 值: 返回通讯情况 ***************************************************************/ u32 readsample_max30102(u32 *ir, u32 *red){ //读取单个样本,单个样本数据长度6位,前三个红外光,后三个红光 u32 ret; u8 send_data[2]; u8 thebuf[6]; WifiIotI2cData i2cdata = {0}; //设置写入数据长度,接收数据长度 send_data[0] = max30102_FIFO_data_addr; i2cdata.sendBuf = send_data; i2cdata.sendLen = 1; i2cdata.receiveBuf = thebuf; i2cdata.receiveLen = 6; ret = I2cWriteread(WIFI_IOT_I2C_IDX_1, ( max30102_i2c_addr << 1) | 0x00, &i2cdata); if(ret != WIFI_IOT_SUCCESS){printf("\n读取样本失败!!!\n");}else{ // 解析IR数据(3字节,大端格式) *ir = ((uint32_t)thebuf[0] << 16) | ((uint32_t)thebuf[1] << 8) | (uint32_t)thebuf[2]; // 解析Red数据 *red = ((uint32_t)thebuf[3] << 16) | ((uint32_t)thebuf[4] << 8) | (uint32_t)thebuf[5]; //printf("\n样本: IR=0x%06lX, Red=0x%06lX\n", ir, red); } return ret; } //测试读取样本 void test_readsample(void){ u8 datalen = GetFIFOLength_max30102(); //GetFIFOLength_max30102(); printf("\n可读取采集数据长度为%u\n",datalen); osDelay(10); //循环读取样本数据 u32 ir,red; for(int i =0;i<datalen; i++){ printf("\n样本序号%d\n",i); readsample_max30102(&ir,&red); } } // /*************************************************************** * 函数名称: spo2_init_max30102 //开启心率血氧模式,同时测试心率和红光 * 说 明: heartrate初始化,清空寄存器 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ u32 spo2_init_max30102(void){ printf("测试心率血氧启动\n"); u32 ret; WifiIotI2cData i2cdata = {0}; u8 send_data[2]; // u8 reg_val; //调用传感器为心率模式,选择设置模式地址,传入0x02数据表示使用测试心率模式 send_data[0] = max30102_mode_addr; send_data[1] = 0x03; i2cdata.sendBuf = send_data; i2cdata.sendLen = 2; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\n心率血氧模式启动失败\n"); }else{ //开启心率检测模式配置采样率和脉宽 //设置数据 send_data[0] = max30102_spo2_addr; send_data[1] = 0x27; //根据官方文档来进行调整要设置的采样率和脉宽 i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\n心率血氧模式启动失败\n"); }else{ // //启动心率,再配置LED红外光 ret = max30102_led(); if (ret != WIFI_IOT_SUCCESS){ printf("\n红外功率设置失败\n"); }else{ //配置FIFO队列的模式 send_data[0] = max30102_FIFO_config; send_data[1] = 0x0F; i2cdata.sendBuf = send_data; ret = I2cWrite(WIFI_IOT_I2C_IDX_1, (max30102_i2c_addr << 1) | 0x00, &i2cdata); if (ret != WIFI_IOT_SUCCESS){ printf("\nFIFO_config配置失败\n"); } } } } osDelay(10); return ret; } /*************************************************************** 心率血氧计算算法部分 ***************************************************************/ // 配置参数 #define SAMPLE_NUM 100 // 缓冲区大小,4秒数据(假设采样率25Hz) #define SAMPLE_INTERVAL_MS 40 // 采样间隔,40ms->25Hz #define MIN_PEAK_DISTANCE 15 // 峰最小间隔,防抖 #define MAX_PEAKS 10 // 平均心率使用最大峰数量 #define SPO2_SAMPLE_SIZE 100 // 血氧样本量 #define MAX_SAMPLES 15 // HRV计算样本量 // 心理状态相关配置 #define HRV_BASELINE_SAMPLES 50 // 计算HRV基线的样本数 #define HRV_STRESS_THRESHOLD 20.0 // HRV压力阈值(ms) #define HR_BASLINE_WINDOW 300 // 心率基线计算窗口(秒) #define HR_STRESS_THRESHOLD 15 // 心率偏离基线阈值(BPM) // 心理状态枚举 typedef enum { MOOD_RELAXED = 0, // 放松状态 MOOD_NORMAL = 1, // 正常状态 MOOD_STRESSED = 2, // 压力/焦虑状态 MOOD_UNKNOWN = 3 // 未知状态 } MoodState; // 全局数据缓冲区 static u32 ir_buffer[SAMPLE_NUM] = {0}; static u32 red_buffer[SPO2_SAMPLE_SIZE] = {0}; static u32 ir_raw_buffer[SPO2_SAMPLE_SIZE] = {0}; static int buffer_index = 0; static int spo2_index = 0; // 心率计算相关 static int last_peak_index = -MIN_PEAK_DISTANCE; static int peak_intervals[MAX_PEAKS] = {0}; static int peak_count = 0; static int intervals[MAX_SAMPLES] = {0}; static int sample_count = 0; // 心理状态相关全局变量 static double hrv_baseline = 0; // HRV基线值 static int hr_baseline = 0; // 心率基线值 static int baseline_sample_count = 0; // 基线采样计数 static MoodState current_mood = MOOD_UNKNOWN; // 当前心理状态 // 结果变量 int g_heart_rate = 0; int g_spo2 = 0; // 计算数组均值 double mean(int *arr, int n) { double s = 0; for (int i = 0; i < n; i++) s += arr[i]; return s / n; } // 计算标准差SDNN (近似HRV) double stddev(int *arr, int n) { double m = mean(arr, n); double sum_sq = 0; for (int i = 0; i < n; i++) { double diff = arr[i] - m; sum_sq += diff * diff; } return sqrt(sum_sq / n); } // 排序比较函数 int compare_int(const void *a, const void *b) { return (*(int*)a - *(int*)b); } // 将间隔(样本数)转换为心率(BPM) int interval_to_hr(int interval_samples) { if(interval_samples == 0) return 0; // 间隔(ms) = 间隔(样本数) * 采样间隔(ms) int interval_ms = interval_samples * SAMPLE_INTERVAL_MS; return 60000 / interval_ms; } // 简单均值滤波 u32 smooth_ir(u32 *buffer, int len) { u32 sum = 0; for (int i = 0; i < len; i++) { sum += buffer[i]; } return sum / len; } // 更新HRV和心率基线 void update_baseline(double hrv, int hr) { static double hrv_sum = 0; static int hr_sum = 0; // 收集足够的样本计算基线 if (baseline_sample_count < HRV_BASELINE_SAMPLES) { hrv_sum += hrv; hr_sum += hr; baseline_sample_count++; if (baseline_sample_count == HRV_BASELINE_SAMPLES) { hrv_baseline = hrv_sum / HRV_BASELINE_SAMPLES; hr_baseline = hr_sum / HRV_BASELINE_SAMPLES; printf("❤️ 心率基线已建立: %d BPM\n", hr_baseline); printf("🩺 HRV基线已建立: %.2f ms\n", hrv_baseline); } } else { // 定期更新基线(滑动窗口) static int update_counter = 0; if (++update_counter >= HR_BASLINE_WINDOW) { update_counter = 0; hrv_baseline = (hrv_baseline * 0.9) + (hrv * 0.1); // 指数平滑 hr_baseline = (hr_baseline * 0.9) + (hr * 0.1); } } } // 基于HRV和心率估算心理状态 MoodState estimate_mood(double hrv, int hr) { if (baseline_sample_count < HRV_BASELINE_SAMPLES) { return MOOD_UNKNOWN; // 基线未建立 } // 计算与基线的偏离度 double hrv_deviation = hrv_baseline - hrv; // HRV降低表示压力 int hr_deviation = hr - hr_baseline; // 心率升高表示压力 // 简单的心理状态判断规则 if (hrv_deviation > HRV_STRESS_THRESHOLD && hr_deviation > HR_STRESS_THRESHOLD) { return MOOD_STRESSED; // 高压力/焦虑状态 } else if (hrv >= hrv_baseline && hr <= hr_baseline) { return MOOD_RELAXED; // 放松状态 } else { return MOOD_NORMAL; // 正常状态 } } // 打印心理状态描述 const char* get_mood_description(MoodState state) { switch(state) { case MOOD_RELAXED: return "放松状态"; case MOOD_NORMAL: return "正常状态"; case MOOD_STRESSED: return "压力/焦虑状态"; default: return "未知状态"; } } // 计算血氧饱和度 void compute_spo2(int *spo2_result) { u32 red_sum = 0, ir_sum = 0; u32 red_min = 0xFFFFFFFF, red_max = 0; u32 ir_min = 0xFFFFFFFF, ir_max = 0; for (int i = 0; i < SPO2_SAMPLE_SIZE; i++) { red_sum += red_buffer[i]; ir_sum += ir_raw_buffer[i]; if (red_buffer[i] < red_min) red_min = red_buffer[i]; if (red_buffer[i] > red_max) red_max = red_buffer[i]; if (ir_raw_buffer[i] < ir_min) ir_min = ir_raw_buffer[i]; if (ir_raw_buffer[i] > ir_max) ir_max = ir_raw_buffer[i]; } float red_dc = red_sum / (float)SPO2_SAMPLE_SIZE; float ir_dc = ir_sum / (float)SPO2_SAMPLE_SIZE; float red_ac = red_max - red_min; float ir_ac = ir_max - ir_min; if (ir_ac == 0 || ir_dc == 0 || red_dc == 0) { *spo2_result = -1; // 表示异常 return; } float r = (red_ac / red_dc) / (ir_ac / ir_dc); float spo2 = 110.0f - 25.0f * r; if (spo2 > 100.0f) spo2 = 100.0f; if (spo2 < 0.0f) spo2 = 0.0f; g_spo2 = (int)(spo2 + 0.5f); *spo2_result = g_spo2; } // 更新心率状态和健康分析(增强版) void update_heart_status(int current_interval) { // 添加样本到HRV计算缓冲区 intervals[sample_count % MAX_SAMPLES] = current_interval; sample_count++; if (sample_count >= MAX_SAMPLES) { // 复制并排序去极端值 int tmp[MAX_SAMPLES]; memcpy(tmp, intervals, sizeof(tmp)); qsort(tmp, MAX_SAMPLES, sizeof(int), compare_int); // 去除最高最低2个 int valid_count = MAX_SAMPLES - 4; int valid_intervals[valid_count]; memcpy(valid_intervals, &tmp[2], valid_count * sizeof(int)); // 计算HRV(标准差) double hrv = stddev(valid_intervals, valid_count); // 当前心率(取平均间隔计算) int avg_interval = (int)mean(valid_intervals, valid_count); int current_hr = interval_to_hr(avg_interval); printf("❤️ 当前心率: %d BPM\n", current_hr); printf("🩺 心率变异性(HRV): %.2f ms\n", hrv); // 更新基线 update_baseline(hrv, current_hr); // 估算心理状态 current_mood = estimate_mood(hrv, current_hr); printf("🧠 心理状态估算: %s\n", get_mood_description(current_mood)); } else { int current_hr = interval_to_hr(current_interval); printf("❤️ 当前心率(采样中): %d BPM,等待更多数据...\n", current_hr); } } // 心率和血氧计算主函数 int calculate_heart_rate_and_spo2(void) { u32 red = 0, ir = 0; u32 ret; // 读取传感器数据 ret = readsample_max30102(&ir, &red); if (ret != 0) { printf("读取传感器数据失败\n"); return -1; } // 保存数据到缓冲区 ir_buffer[buffer_index] = ir; red_buffer[spo2_index] = red; ir_raw_buffer[spo2_index] = ir; // 平滑滤波 u32 ir_avg = smooth_ir(ir_buffer, SAMPLE_NUM); int prev = (buffer_index - 1 + SAMPLE_NUM) % SAMPLE_NUM; int next = (buffer_index + 1) % SAMPLE_NUM; u32 current = ir_buffer[buffer_index]; // 峰值检测 + 动态阈值 if (current > ir_buffer[prev] && current > ir_buffer[next] && current > ir_avg + 1000) { // 检测到心跳峰值 int interval = buffer_index - last_peak_index; if (interval < 0) interval += SAMPLE_NUM; if (interval >= MIN_PEAK_DISTANCE) { peak_intervals[peak_count % MAX_PEAKS] = interval; peak_count++; last_peak_index = buffer_index; // 计算平均心率 int total = 0; int valid = (peak_count < MAX_PEAKS) ? peak_count : MAX_PEAKS; for (int i = 0; i < valid; i++) { total += peak_intervals[i]; } int avg_interval = total / valid; g_heart_rate = interval_to_hr(avg_interval); // 更新心率状态 update_heart_status(avg_interval); } } // 更新缓冲区索引 buffer_index = (buffer_index + 1) % SAMPLE_NUM; spo2_index++; if (spo2_index >= SPO2_SAMPLE_SIZE) { spo2_index = 0; int spo2 = 0; compute_spo2(&spo2); if (spo2 > 0) { printf("🩸 血氧饱和度(SpO2) = %d%%\n", spo2); } } return 0; // 成功返回0 } //测试心率和血氧监测函数 void testcounthrspo2(void){ // 初始化失败计数器 int failure_count = 0; // 持续调用心率和血氧计算 while (1) { // 执行心率和血氧计算 if (calculate_heart_rate_and_spo2() != 0) { failure_count++; printf("警告:心率计算失败!计数: %d\n", failure_count); // 如果连续失败超过10次,尝试重置传感器 if (failure_count >= 10) { printf("错误:失败次数过多,正在重置传感器...\n"); max30102_IO_Init(); osDelay(SAMPLE_INTERVAL_MS); spo2_init_max30102(); failure_count = 0; } } else { failure_count = 0; // 成功执行,重置计数器 } // 按照采样率控制延时 osDelay(SAMPLE_INTERVAL_MS); } } /*************************************************************** 心率血氧计算算法部分 ***************************************************************/ /*************************************************************** 调用上方底层驱动进行使用函数 ***************************************************************/ /*************************************************************** * 函数名称: max30102_init * 说 明: max30102初始化,清空寄存器 * 参 数: 无 * 返 回 值: 无 ***************************************************************/ void max30102_init(void){ max30102_IO_Init(); if(reset_max30102()==WIFI_IOT_SUCCESS){ printf("\nmax30102重置成功!!!\n"); }else{ printf("\nmax30102重置失败!!!\n"); } reset_FIFO(); osDelay(10); spo2_init_max30102(); osDelay(10); // u8 result = GetFIFOLength_max30102(); // //GetFIFOLength_max30102(); // printf("\n可读取采集数据长度为%u\n",result); test_readsample(); osDelay(10); testcounthrspo2(); }