这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【STM32WBA55CG开发板】基于MAX30100的血氧心率监测

共3条 1/1 1 跳转至

【STM32WBA55CG开发板】基于MAX30100的血氧心率监测

助工
2024-12-05 15:49:53     打赏

Max30100是一款集成的脉搏血氧和心率检测传感器

【基本原理】 Max30100主要通过两个LED灯发射光线,光线穿透人体组织后,由一个光电探测器接收,再经过低噪声模拟信号处理器对信号进行处理,从而检测出脉搏的血氧和心率信号. 【 主要特点】 低功耗:运行电压在1.8V到3.3V之间,可通过软件控制,待机电流极小,典型值为0.7µA,能延长可穿戴设备的电池寿命,使电源可始终保持连接状态. 高度集成:将LED、光电传感器和高性能模拟前端集成在一个尺寸仅为5.6mm x 2.8mm x 1.2mm的14引脚光学增强型系统级封装内,简化了设计,方便在各种小型可穿戴设备中使用. 可编程:具有可编程的采样率和LED电流,用户可根据具体应用需求灵活调整,在保证测量精度的同时节省电力. 高性能:高信噪比(SNR)提供了强大的运动伪影恢复能力,即使在运动状态下也能准确测量血氧和心率;集成环境光消除功能,可减少外界光线对测量结果的干扰;具备高采样率能力和快速数据输出能力,能够实时、快速地提供测量数据. 【寄存器及数据存储】 中断状态寄存器:位于基地址0x00,共有5个中断状态位。包括FIFO数据填满标志位(A_FULL)、片内温度读标志位(TEMP_RDY)、心率数据完成标志位(HR_RDY)、SpO2数据完成标志位(SPO2_RDY)以及电源准备完成标志位(PWR_RDY). 中断使能寄存器:基地址为0x01,可通过软件寄存器控制除电源准备完成中断外的每一个硬件中断的使能状态. FIFO寄存器:包括FIFO写指针寄存器(基地址0x02)、FIFO溢出计数器寄存器(基地址0x03)、FIFO读指针寄存器(基地址0x04)和FIFO数据寄存器(基地址0x05)。FIFO是一个16位的循环数据存储结构,能够存储16个SPO2通道数据,每个样本由一个红外(IR)字和一个红光(RED)字组成,共4字节数据. 【 应用领域】 可穿戴设备:如智能手环、智能手表等,可实时监测用户的心率和血氧饱和度,为用户提供健康数据参考,帮助用户更好地了解自身身体状况. 健身设备:在运动过程中,准确测量心率和血氧等生理指标,帮助用户合理调整运动强度,避免过度运动造成身体损伤,同时也为运动效果评估提供数据支持. 医疗监控设备:可集成到便携式心率监测仪、睡眠监测器等医疗设备中,为医护人员提供患者的实时生理数据,辅助医疗诊断和病情监测.

【移植】

1、max30100的头文件代码如下:

/**
 * ************************************************************************
 * 
 * @file MAX30100.h
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#ifndef _MAX30100_H
#define _MAX30100_H

#include "main.h"                  // Device header

#include "stdbool.h"

#define MAX30100_Device_address 			0xAE

//register addresses
#define REG_INTR_STATUS_1 	                0x00
#define REG_INTR_STATUS_2 	                0x01
#define REG_INTR_ENABLE_1 	                0x02
#define REG_INTR_ENABLE_2 	                0x03
#define REG_FIFO_WR_PTR 		            0x04
#define REG_OVF_COUNTER 		            0x05
#define REG_FIFO_RD_PTR 		            0x06
#define REG_FIFO_DATA 			            0x07
#define REG_FIFO_CONFIG 		            0x08
#define REG_MODE_CONFIG 		            0x09
#define REG_SPO2_CONFIG 		            0x0A
#define REG_LED1_PA 				        0x0C
#define REG_LED2_PA 				        0x0D
#define REG_PILOT_PA 				        0x10
#define REG_MULTI_LED_CTRL1                 0x11
#define REG_MULTI_LED_CTRL2                 0x12
#define REG_TEMP_INTR 			            0x1F
#define REG_TEMP_FRAC 			            0x20
#define REG_TEMP_CONFIG 		            0x21
#define REG_PROX_INT_THRESH                 0x30
#define REG_REV_ID 					        0xFE
#define REG_PART_ID 				        0xFF

#define SAMPLES_PER_SECOND 					100	//检测频率

uint8_t MAX30100_reset(void);
void MAX30100_Config(void);
void MAX30100_read_fifo(void);

uint8_t MAX30100_write_reg(uint8_t addr, uint8_t data);
uint8_t MAX30100_read_reg(uint8_t addr );

#endif

这个主要是声明一些寄存器的地址,方面代码的可读性。

2、Max30100.c

/**
 * ************************************************************************
 * 
 * @file MAX30100.c
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#include "MAX30100.h"
#include "main.h"
extern I2C_HandleTypeDef hi2c3;

volatile  uint16_t fifo_red;  //定义FIFO中的红光数据
volatile uint16_t fifo_ir;   //定义FIFO中的红外光数据

/**
 * ************************************************************************
 * @brief 向MAX30100寄存器写入一个值
 * 
 * @param[in] addr  寄存器地址
 * @param[in] data  传输数据
 * 
 * @return 
 * ************************************************************************
 */
uint8_t MAX30100_write_reg(uint8_t addr, uint8_t data)
{
  HAL_I2C_Mem_Write(&hi2c3, MAX30100_Device_address,	addr,	1,	&data,1,HAL_MAX_DELAY);
  return 1;
}


/**
 * ************************************************************************
 * @brief 读取MAX30100寄存器的一个值
 * 
 * @param[in] addr  寄存器地址
 * 
 * @return 
 * ************************************************************************
 */
uint8_t MAX30100_read_reg(uint8_t addr )
{
  uint8_t data=0;
  HAL_I2C_Mem_Read(&hi2c3, MAX30100_Device_address, addr, 1, &data, 1, HAL_MAX_DELAY);
  return data;
}


/**
 * ************************************************************************
 * @brief MAX30100传感器复位
 * 
 * 
 * @return 
 * ************************************************************************
 */
uint8_t MAX30100_reset(void)
{
	if(MAX30100_write_reg(REG_MODE_CONFIG, 0x40))
        return 1;
    else
        return 0;    
}

/**
 * ************************************************************************
 * @brief MAX30100传感器模式配置
 * 
 * 
 * ************************************************************************
 */
void MAX30100_Config(void)
{
	MAX30100_write_reg(REG_INTR_ENABLE_1,0xc0);//// INTR setting
	MAX30100_write_reg(REG_INTR_ENABLE_2,0x00);//
	MAX30100_write_reg(REG_FIFO_WR_PTR,0x00);//FIFO_WR_PTR[4:0]
	MAX30100_write_reg(REG_OVF_COUNTER,0x00);//OVF_COUNTER[4:0]
	MAX30100_write_reg(REG_FIFO_RD_PTR,0x00);//FIFO_RD_PTR[4:0]
	
	MAX30100_write_reg(REG_FIFO_CONFIG,0x0f);//sample avg = 1, fifo rollover=false, fifo almost full = 17
	MAX30100_write_reg(REG_MODE_CONFIG,0x03);//0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
	MAX30100_write_reg(REG_SPO2_CONFIG,0x27);	// SPO2_ADC range = 4096nA, SPO2 sample rate (50 Hz), LED pulseWidth (400uS)  
	MAX30100_write_reg(REG_LED1_PA,0x32);//Choose value for ~ 10mA for LED1
	MAX30100_write_reg(REG_LED2_PA,0x32);// Choose value for ~ 10mA for LED2
	MAX30100_write_reg(REG_PILOT_PA,0x7f);// Choose value for ~ 25mA for Pilot LED
}

/**
 * ************************************************************************
 * @brief 读取FIFO寄存器的数据
 * 
 * 
 * ************************************************************************
 */
void MAX30100_read_fifo(void)
{
  uint16_t un_temp;
  fifo_red=0;
  fifo_ir=0;
  uint8_t ach_i2c_data[6];
  
  //read and clear status register
  MAX30100_read_reg(REG_INTR_STATUS_1);
  MAX30100_read_reg(REG_INTR_STATUS_2);
  
  ach_i2c_data[0]=REG_FIFO_DATA;
	
	HAL_I2C_Mem_Read(&hi2c3,MAX30100_Device_address,REG_FIFO_DATA,1,ach_i2c_data,6,HAL_MAX_DELAY);
	
  un_temp=ach_i2c_data[0];
  un_temp<<=16;
  fifo_red+=un_temp;
  un_temp=ach_i2c_data[1];
  un_temp<<=8;
  fifo_red+=un_temp;
  un_temp=ach_i2c_data[2];
  fifo_red+=un_temp;
  
  un_temp=ach_i2c_data[3];
  un_temp<<=16;
  fifo_ir+=un_temp;
  un_temp=ach_i2c_data[4];
  un_temp<<=8;
  fifo_ir+=un_temp;
  un_temp=ach_i2c_data[5];
  fifo_ir+=un_temp;
	
	if(fifo_ir<=10000)
	{
		fifo_ir=0;
	}
	if(fifo_red<=10000)
	{
		fifo_red=0;
	}
}

max30100.c中主要定义实现对max30100寄存器的读写,器件的复位、配置,以及读取。

3、为了实现通过血氧值来实现对心率的计算,这里添加了一个FFT变换函数algorithm.c

/**
 * ************************************************************************
 * 
 * @file algorithm.c
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#include "algorithm.h"
#include "main.h" 


#define XPI         (3.1415926535897932384626433832795)				//定义圆周率值,保留31位
#define XENTRY      (100)
#define XINCL       (XPI/2/XENTRY)									//用于正弦函数的精度控制
#define PI 			3.1415926535897932384626433832795028841971    	//定义圆周率值,保留40位


/**
 * ************************************************************************
 * @brief 静态正弦值对应表
 * 
 * 	使用预先计算好的数值可以节省计算时间
 * ************************************************************************
 */
static const double XSinTbl[] = 
{
	0.00000000000000000  , 0.015707317311820675 , 
	0.031410759078128292 , 0.047106450709642665 , 
	0.062790519529313374 , 0.078459095727844944 , 
	0.094108313318514325 , 0.10973431109104528  , 
	0.12533323356430426  , 0.14090123193758267  ,
	0.15643446504023087  , 0.17192910027940955  , 
	0.18738131458572463  , 0.20278729535651249  , 
	0.21814324139654256  , 0.23344536385590542  , 
	0.24868988716485479  , 0.26387304996537292  , 
	0.27899110603922928  , 0.29404032523230400  ,
	0.30901699437494740  , 0.32391741819814940  , 
	0.33873792024529142  , 0.35347484377925714  , 
	0.36812455268467797  , 0.38268343236508978  , 
	0.39714789063478062  , 0.41151435860510882  , 
	0.42577929156507272  , 0.43993916985591514  ,
	0.45399049973954680  , 0.46792981426057340  , 
	0.48175367410171532  , 0.49545866843240760  , 
	0.50904141575037132  , 0.52249856471594880  , 
	0.53582679497899666  , 0.54902281799813180  , 
	0.56208337785213058  , 0.57500525204327857  ,
	0.58778525229247314  , 0.60042022532588402  , 
	0.61290705365297649  , 0.62524265633570519  , 
	0.63742398974868975  , 0.64944804833018377  , 
	0.66131186532365183  , 0.67301251350977331  , 
	0.68454710592868873  , 0.69591279659231442  ,
	0.70710678118654757  , 0.71812629776318881  , 
	0.72896862742141155  , 0.73963109497860968  , 
	0.75011106963045959  , 0.76040596560003104  , 
	0.77051324277578925  , 0.78043040733832969  , 
	0.79015501237569041  , 0.79968465848709058  ,
	0.80901699437494745  , 0.81814971742502351  , 
	0.82708057427456183  , 0.83580736136827027  , 
	0.84432792550201508  , 0.85264016435409218  , 
	0.86074202700394364  , 0.86863151443819120  , 
	0.87630668004386369  , 0.88376563008869347  ,
	0.89100652418836779  , 0.89802757576061565  , 
	0.90482705246601958  , 0.91140327663544529  , 
	0.91775462568398114  , 0.92387953251128674  , 
	0.92977648588825146  , 0.93544403082986738  , 
	0.94088076895422557  , 0.94608535882754530  ,
	0.95105651629515353  , 0.95579301479833012  , 
	0.96029368567694307  , 0.96455741845779808  , 
	0.96858316112863108  , 0.97236992039767667  , 
	0.97591676193874743  , 0.97922281062176575  , 
	0.98228725072868872  , 0.98510932615477398  ,
	0.98768834059513777  , 0.99002365771655754  , 
	0.99211470131447788  , 0.99396095545517971  , 
	0.99556196460308000  , 0.99691733373312796  , 
	0.99802672842827156  , 0.99888987496197001  , 
	0.99950656036573160  , 0.99987663248166059  ,
	1.00000000000000000  
};


/**
 * ************************************************************************
 * @brief 向下取整函数
 * 
 * @param[in] x  需要取整的浮点数参数
 * 
 * @return 
 * ************************************************************************
 */
double my_floor(double x)
{
	double y=x;
    if( (*( ( (int *) &y)+1) & 0x80000000)  != 0) //或者if(x<0)
        return (float)((int)x)-1;
    else
        return (float)((int)x);
}


/**
 * ************************************************************************
 * @brief 取余函数
 * 
 * @param[in] x  参数1
 * @param[in] y  参数2
 * @note	避免了对浮点数的直接除法运算,从而提高了效率
 * @return 
 * ************************************************************************
 */
double my_fmod(double x, double y)
{
	double temp, ret;

	if (y == 0.0)
		return 0.0;
	temp = my_floor(x/y);
   ret = x - temp * y;
	if ((x < 0.0) != (y < 0.0))
		ret = ret - y;
	return ret;
}


/**
 * ************************************************************************
 * @brief 正弦函数
 * 
 * @param[in] x  角度值
 * 
 * @return 
 * 
 * @note 通过查表和泰勒展开式计算正弦值,相对于直接调用标准库函数,
 * 		 可能会牺牲一些精度,但在某些嵌入式系统中可能更加高效
 * ************************************************************************
 */
double XSin( double x )
{
    int s = 0 , n;
    double dx , sx , cx;
    if( x < 0 )
        s = 1 , x = -x;
    x = my_fmod( x , 2 * XPI );
    if( x > XPI )
        s = !s , x -= XPI;
    if( x > XPI / 2 )
        x = XPI - x;
    n = (int)( x / XINCL );
    dx = x - n * XINCL;
    if( dx > XINCL / 2 )
        ++n , dx -= XINCL;
    sx = XSinTbl[n];
    cx = XSinTbl[XENTRY-n];
    x = sx + dx*cx - (dx*dx)*sx/2
        - (dx*dx*dx)*cx/6 
        + (dx*dx*dx*dx)*sx/24;

    return s ? -x : x;
}


/**
 * ************************************************************************
 * @brief 余弦函数
 * 
 * @param[in] x  角度值
 * 
 * @return 
 * ************************************************************************
 */
double XCos( double x )
{
    return XSin( x + XPI/2 );
}


/**
 * ************************************************************************
 * @brief 开平方
 * 
 * @param[in] a  参数
 * 
 * @return 
 * ************************************************************************
 */
int qsqrt(int a)
{
	uint32_t rem = 0, root = 0, divisor = 0;
	uint16_t i;
	for(i=0; i<16; i++)
	{
		root <<= 1;
		rem = ((rem << 2) + (a>>30));
		a <<= 2;
		divisor = (root << 1) + 1;
		if(divisor <= rem)
		{
			rem -= divisor;
			root++;
		}
	}
	return root;
}


/**
 * ************************************************************************
 * @brief 两个复数相乘
 * 
 * @param[in] a  复数1
 * @param[in] b  复数2
 * @note 乘积的实部为两个复数实部的乘积减去虚部的乘积
 * 		 乘积的虚部为两个复数实部的乘积加上虚部的乘积
 * @return 
 * ************************************************************************
 */
struct compx EE(struct compx a,struct compx b)
{
	struct compx c;
	c.real=a.real*b.real-a.imag*b.imag;
	c.imag=a.real*b.imag+a.imag*b.real;
	return(c);
}


/**
 * ************************************************************************
 * @brief 对输入的复数组进行快速傅里叶变换(FFT)
 * 
 * @param[in] xin  复数组
 * 
 * ************************************************************************
 */
void FFT(struct compx *xin)
{
	int f,m,nv2,nm1,i,k,l,j=0;
	struct compx u,w,t;

	nv2=FFT_N/2;                  //变址运算,即把自然顺序变成倒位序,采用雷德算法
	nm1=FFT_N-1;  
	for(i=0;i<nm1;i++)        
	{
		if(i<j)                    //如果i<j,即进行变址
		{
			t=xin[j];           
			xin[j]=xin[i];
			xin[i]=t;
		}
		k=nv2;                    //求j的下一个倒位序
		
		while(k<=j)               //如果k<=j,表示j的最高位为1   
		{           
			j=j-k;                 //把最高位变成0
			k=k/2;                 //k/2,比较次高位,依次类推,逐个比较,直到某个位为0
		}
		
		j=j+k;                   //把0改为1
	}

	{  //FFT运算核,使用蝶形运算完成FFT运算
		int le,lei,ip;                           
		f=FFT_N;
		for(l=1;(f=f/2)!=1;l++)                  //计算l的值,即计算蝶形级数
			;
		for(m=1;m<=l;m++)                           // 控制蝶形结级数
		{                                           //m表示第m级蝶形,l为蝶形级总数l=log(2)N
			le=2<<(m-1);                            //le蝶形结距离,即第m级蝶形的蝶形结相距le点
			lei=le/2;                               //同一蝶形结中参加运算的两点的距离
			u.real=1.0;                             //u为蝶形结运算系数,初始值为1
			u.imag=0.0;
			w.real=XCos(PI/lei);                     //w为系数商,即当前系数与前一个系数的商
			w.imag=-XSin(PI/lei);
			for(j=0;j<=lei-1;j++)                   //控制计算不同种蝶形结,即计算系数不同的蝶形结
			{
				for(i=j;i<=FFT_N-1;i=i+le)            //控制同一蝶形结运算,即计算系数相同蝶形结
				{
					ip=i+lei;                           //i,ip分别表示参加蝶形运算的两个节点
					t=EE(xin[ip],u);                    //蝶形运算,详见公式
					xin[ip].real=xin[i].real-t.real;
					xin[ip].imag=xin[i].imag-t.imag;
					xin[i].real=xin[i].real+t.real;
					xin[i].imag=xin[i].imag+t.imag;
				}
				u=EE(u,w);                           //改变系数,进行下一个蝶形运算
			}
		}
	}
}


/**
 * ************************************************************************
 * @brief 找到具有最大实部的元素的索引
 * 
 * @param[in] data  Comment
 * @param[in] count  Comment
 * 
 * @return 
 * ************************************************************************
 */
int find_max_num_index(struct compx *data,int count)
{
	int i=START_INDEX;
	int max_num_index = i;
	float temp = data[i].real;
	for(i=START_INDEX;i<count;i++)
	{
		if(temp < data[i].real)
		{
			temp = data[i].real;
			max_num_index = i;
		}
	}
	return max_num_index; 
}


/**
 * ************************************************************************
 * @brief 直流滤波器,去除信号中的直流成分,并输出滤波后的结果
 * 
 * @param[in] input  输入信号
 * @param[in] df  Comment
 * 
 * @return 
 * ************************************************************************
 */
int dc_filter(int input,DC_FilterData * df) 
{

	float new_w  = input + df->w * df->a;
	int16_t result = 5*(new_w - df->w);
	df->w = new_w;
	
	return result;
}


/**
 * ************************************************************************
 * @brief 对输入信号进行带宽限制滤波,限制输入信号的频率范围,并输出滤波后的结果
 * 
 * @param[in] input  输入信号
 * @param[in] bw  Comment
 * 
 * @return 
 * ************************************************************************
 */
int bw_filter(int input,BW_FilterData * bw) 
{
    bw->v0 = bw->v1;
    
    bw->v1 = (1.241106190967544882e-2*input)+(0.97517787618064910582 * bw->v0);
    return bw->v0 + bw->v1;
}

algorithm.h,为变换函数的头文件:

/**
 * ************************************************************************
 * 
 * @file algorithm.h
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#ifndef __ALGORITHM_H
#define __ALGORITHM_H

#define FFT_N 			1024    //定义傅里叶变换的点数
#define START_INDEX 	8  		//低频过滤阈值

struct compx     	//定义一个复数结构
{
	float real;
	float imag;
};  

typedef struct		//定义一个直流滤波器结构体
{
	float w;
	int init;
	float a;
}DC_FilterData;		//用于存储直流滤波器的参数


typedef struct		//定义一个带宽滤波器结构体
{
	float v0;
	float v1;
}BW_FilterData;		//用于存储带宽滤波器的参数



double my_floor(double x);

double my_fmod(double x, double y);

double XSin( double x );

double XCos( double x );

int qsqrt(int a);


struct compx EE(struct compx a,struct compx b);

void FFT(struct compx *xin);

int find_max_num_index(struct compx *data,int count);
int dc_filter(int input,DC_FilterData * df);
int bw_filter(int input,BW_FilterData * bw);


#endif

4、心率与血氧的具体实现函数:blood.c

/**
 * ************************************************************************
 * 
 * @file blood.c
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#include "blood.h"
#include "main.h"
#include "log_module.h"
extern TIM_HandleTypeDef htim3;
int heart;		//定义心率
float SpO2;		//定义血氧饱和度

//调用外部变量
extern volatile uint16_t fifo_red;		//定义FIFO中的红光数据
extern volatile uint16_t fifo_ir;		//定义FIFO中的红外光数据

volatile uint16_t g_fft_index = 0;         	 	//fft输入输出下标
volatile struct compx s1[FFT_N+16];           	//FFT输入和输出:从S[1]开始存放,根据大小自己定义
volatile struct compx s2[FFT_N+16];           	//FFT输入和输出:从S[1]开始存放,根据大小自己定义



#define CORRECTED_VALUE			47   			//标定血液氧气含量

/**
 * ************************************************************************
 * @brief 更新血氧数据
 * @note 从 MAX30102 的 FIFO 中读取红光和红外数据,并将它们存储到两个复数数组s1和s2中,
 * 		 这些数据随后可以用于进行傅里叶变换等后续处理
 * 
 * ************************************************************************
 */
void blood_data_update(void)
{
	//标志位被使能时 读取FIFO

    //改写为非阻塞方式读取数据

	if(g_fft_index < FFT_N)
	{
		if(MAX30100_read_reg(REG_INTR_STATUS_1)&0x40 )
		{
			//读取FIFO
			MAX30100_read_fifo();  //read from MAX30102 FIFO2
			//将数据写入fft输入并清除输出
			if(g_fft_index < FFT_N)
			{
				//将数据写入fft输入并清除输出
				s1[g_fft_index].real = fifo_red;
				s1[g_fft_index].imag= 0;
				s2[g_fft_index].real = fifo_ir;
				s2[g_fft_index].imag= 0;
				
			}
			g_fft_index++;
		}
        
	}

}


/**
 * ************************************************************************
 * @brief 血液信息转换
 * 
 * 
 * ************************************************************************
 */
void blood_data_translate(void)
{	
	float n_denom;
	uint16_t i;

	//直流滤波
	float dc_red =0; 
	float dc_ir =0;
	float ac_red =0; 
	float ac_ir =0;
	
	for (i=0 ; i<FFT_N ; i++ ) 
	{
		dc_red += s1[i].real ;
		dc_ir +=  s2[i].real ;
	}
		dc_red =dc_red/FFT_N ;
		dc_ir =dc_ir/FFT_N ;
	for (i=0 ; i<FFT_N ; i++ )  
	{
		s1[i].real =  s1[i].real - dc_red ; 
		s2[i].real =  s2[i].real - dc_ir ; 
	}

	//移动平均滤波
	for(i = 1;i < FFT_N-1;i++) 
	{
		n_denom= ( s1[i-1].real + 2*s1[i].real + s1[i+1].real);
		s1[i].real=  n_denom/4.00; 
		
		n_denom= ( s2[i-1].real + 2*s2[i].real + s2[i+1].real);
		s2[i].real=  n_denom/4.00; 			
	}

	//八点平均滤波
	for(i = 0;i < FFT_N-8;i++) 
	{
		n_denom= ( s1[i].real+s1[i+1].real+ s1[i+2].real+ s1[i+3].real+ s1[i+4].real+ s1[i+5].real+ s1[i+6].real+ s1[i+7].real);
		s1[i].real=  n_denom/8.00; 
		
		n_denom= ( s2[i].real+s2[i+1].real+ s2[i+2].real+ s2[i+3].real+ s2[i+4].real+ s2[i+5].real+ s2[i+6].real+ s2[i+7].real);
		s2[i].real=  n_denom/8.00; 
		
	}

	//开始变换显示	
	g_fft_index = 0;	
	//快速傅里叶变换
	FFT(s1);
	FFT(s2);
	
	for(i = 0;i < FFT_N;i++) 
	{
		s1[i].real=sqrtf(s1[i].real*s1[i].real+s1[i].imag*s1[i].imag);
		s1[i].real=sqrtf(s2[i].real*s2[i].real+s2[i].imag*s2[i].imag);
	}
	//计算交流分量
	for (i=1 ; i<FFT_N ; i++ ) 
	{
		ac_red += s1[i].real ;
		ac_ir +=  s2[i].real ;
	}
	
	for(i = 0;i < 50;i++) 
	{
		if(s1[i].real<=10)
			break;
	}
	
	//读取峰值点的横坐标  结果的物理意义为 
	int s1_max_index = find_max_num_index(s1, 60);
	int s2_max_index = find_max_num_index(s2, 60);

	//检查HbO2和Hb的变化频率是否一致
	if(i>=45)
	{
		//心率计算
		uint16_t Heart_Rate = 60.00 * SAMPLES_PER_SECOND * s1_max_index / FFT_N;
		heart = Heart_Rate;
		
		//血氧含量计算
		float R = (ac_ir*dc_red)/(ac_red*dc_ir);
		float sp02_num = -45.060*R*R+ 30.354 *R + 94.845;
		SpO2 = sp02_num;
		
		//状态正常
	}
	else //数据发生异常
	{
		heart = 0;
		SpO2 = 0;
	}
	//结束变换显示
}

/**
 * ************************************************************************
 * @brief 心率血氧循环函数
 * 
 * 
 * ************************************************************************
 */
void blood_Loop(void)
{
	//血液信息获取
	blood_data_update();
	//血液信息转换
    if(g_fft_index >= FFT_N)
    {
		HAL_TIM_Base_Stop_IT(&htim3);
        blood_data_translate();
        SpO2 = (SpO2 > 99.99) ? 99.99:SpO2;  
	    LOG_INFO_APP("心率%3d/min; 血氧%2d%%\n", heart, (int)SpO2);
		 g_fft_index = 0;
		 memset(s1,0,sizeof(s1));
		 memset(s2,0,sizeof(s2));
		HAL_TIM_Base_Start_IT(&htim3);
    }
	


}

由于是开发板需要运行蓝牙进程,所以在blood_loop中实现非阻塞式的代码,使g_fft_index来做计算数,当采集完指定的数据时,做一个转换,并进行打印展示。

blood.h:

/**
 * ************************************************************************
 * 
 * @file blood.h
 * @author zxr
 * @brief 
 * 
 * ************************************************************************
 * @copyright Copyright (c) 2024 zxr 
 * ************************************************************************
 */
#ifndef _BLOOD_H
#define _BLOOD_H

#include "main.h"
#include "MAX30100.h"
#include "algorithm.h"
#include "math.h"



void blood_data_translate(void);
void blood_data_update(void);
void blood_Loop(void);

#endif

5、到此,驱动代码编写完成,我们打开工程,配置好i2c3为血氧传感器的接口,SDA为PB1,SCL为PB2,配置总线速度为400K

image.png

6、开启一个定时器Tim3用来产生中断,来实现周期读取max30100的数据

image.png

在他的回调函数中循环调用blood_loop

/**
  * @brief  Period elapsed callback in non blocking mode
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  blood_Loop();
}

6、在man.c中添加初始始、配置max30100

/* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim3);
  MAX30100_reset();
  MAX30100_Config();
  /* USER CODE END 2 */

到此,我们的程序就编写完成,下载到开发板后,用手放到max30100上,就可以检测出心率与血氧指数了:

image.png

附程序源码:

BLE_p2pServer.zip




关键词: STM32WBA55CG     MAX30100     心率         

专家
2024-12-05 16:04:30     打赏
2楼

这么快


专家
2024-12-05 18:58:37     打赏
3楼

学习


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]