写点什么

基于 STM32 设计的健康检测设备 (测温心率计步)

作者:DS小龙哥
  • 2022 年 1 月 07 日
  • 本文字数:9862 字

    阅读完需:约 32 分钟

1. 项目介绍

本文介绍的项目是基于 STM32 设计的健康检测设备,支持体温测量,心率检测,支持运动计步(采用 MPU6050 陀螺仪实现),支持 WIFI 传输数据到手机 APP 打印显示。


硬件环境介绍:


MCU 采用 STM32F103C8T6


心率传感器采用 PulseSensor


体温检测传感器采用红外测温传感器


运动计步功能采用 MPU6050 陀螺仪实现


OLED 显示屏采用 0.96 寸中景园电子的 OLED 显示屏-SPI 接口


编程软件采用 keil5


心率传感器:



MPU6050 传感器:



体温测温模块



OLED 显示屏:



硬件效果图:






工程源码截图:



项目源码下载地址: https://download.csdn.net/download/xiaolong1126626497/63992839


视频演示地址: https://live.csdn.net/v/182607

2. 项目源码介绍

2.1 计步功能实现代码

计步功能是通过 MPU6050 陀螺仪测量计算得到,下面贴出计步算法的核心实现代码.


/*******************************************************************************文件名:         stepAlgorithm.c描述   :         计步算法*******************************************************************************/
#include "stepAlgorithm.h"#include "rtc.h"#include "math.h"#include "mpu6050.h"
#define TRUE 1 #define FALSE 0#define VALUE_NUM 4

sportsInfo_t userSportsInfo;//存放三轴数据 float oriValues[3] = {0}; //用于存放计算阈值的波峰波谷差值 float tempValue[VALUE_NUM] ={0}; int tempCount = 0; //是否上升的标志位 u8 isDirectionUp = FALSE; //持续上升次数 int continueUpCount = 0; //上一点的持续上升的次数,为了记录波峰的上升次数 int continueUpFormerCount = 0; //上一点的状态,上升还是下降 u8 lastStatus = FALSE; //波峰值 float peakOfWave = 0; //波谷值 float valleyOfWave = 0; //此次波峰的时间 long timeOfThisPeak = 0; //上次波峰的时间 long timeOfLastPeak = 0; //当前的时间 long timeOfNow = 0; //当前传感器的值 float gravityNew = 0; //上次传感器的值 float gravityOld = 0; //动态阈值需要动态的数据,这个值用于这些动态数据的阈值 float initialValue = (float) 1.3; //初始阈值 float ThreadValue = (float) 2.0;//三轴轴值accValue_t accValue;//行走信息:卡路里、里程、步数static sportsInfo_t sportsInfo;//计步缓存static u8 stepTempCount =0;
/******************************************************************************** 函数名:onSensorChanged* 功能描述: G-Sensor工作后会一直调用这个函数对三轴数据进行平方和开根号的处理 * 调用DetectorNewStep检测步子 * * 参数说明: * 输入:* pAccValue:G-sensor的原始数据* timeStamp_p:动态时间戳* 返回值说明:* 修改记录:*******************************************************************************/sportsInfo_t *onSensorChanged(accValue_t *pAccValue,timeStamp_t *timeStamp_p,personInfo_t * personInfo) { accValue_t *p = pAccValue; personInfo_t *userInfo = personInfo; timeStamp_t *time_p = timeStamp_p; oriValues[0] = p->accX; oriValues[1] = p->accY; oriValues[2] = p->accZ; //对三轴数据进行平方和开根号的处理 gravityNew = (float) sqrt(oriValues[0] * oriValues[0]+ oriValues[1] * oriValues[1] + oriValues[2] * oriValues[2]); //检测步子 return DetectorNewStep(gravityNew,time_p,userInfo); }

/******************************************************************************** 函数名:DetectorNewStep* 功能描述: * 步伐更新:如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步 * 阀值更新:符合时间差条件,波峰波谷差值大于initialValue,则将该差值纳入阈值的计算中 * 参数说明: 输入:values:经过处理的G-sensor数据timeStamp_p:时间戳* 返回值说明:* 修改记录:sportsInfo_t *onSensorChanged(accValue_t *pAccValue,timeStamp_t *timeStamp_p,personInfo_t * personInfo)*******************************************************************************/sportsInfo_t *DetectorNewStep(float values,timeStamp_t *timeStamp_p,personInfo_t * personInfo) { static u32 time_old; personInfo_t *userInfo = personInfo; static u32 step_per_2_second; //每两秒所走的步数 float step_lenth,walk_speed,walk_distance,Calories;//步长 u32 time_now; timeStamp_t *time_p = timeStamp_p; if (gravityOld == 0) { gravityOld = values; } else { if (DetectorPeak(values, gravityOld))//检测到波峰 { timeOfLastPeak = timeOfThisPeak;//更新上次波峰的时间 //将时间戳转换为以毫秒ms为单位 time_now = timeOfNow = ((time_p->hour*60+time_p->minute)*60+time_p->second)*1000+time_p->twentyMsCount*20; //获取时间 ,并转化为毫秒 //如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步 if ( (timeOfNow - timeOfLastPeak >= 250 )//Jahol Fan 修改为300,防止轻微动都也会检测步子 //&& (timeOfNow - timeOfLastPeak <= 2000) &&(peakOfWave - valleyOfWave >= ThreadValue) ) { timeOfThisPeak = timeOfNow; //更新此次波峰时间 stepTempCount++;//Jahol:加1为两步 step_per_2_second ++; //Jahol:这样计算卡路里,不能滤除人为的误操作,导致的结果是:里程和卡路里偏大 if((time_now - time_old) >= 2000 ) //如果时间过了2秒 {
if( 1 == step_per_2_second ) { step_lenth = userInfo->height/5; } else if( 2 == step_per_2_second ) { step_lenth = userInfo->height/4; } else if( 3 == step_per_2_second ) { step_lenth = userInfo->height/3; } else if( 4 == step_per_2_second ) { step_lenth = userInfo->height/2; } else if(5 == step_per_2_second) //Jahol:为了使计步准确,设置上限值为5步,牺牲卡路里准确性 { step_lenth = userInfo->height/1.2f; } else if( 7 == step_per_2_second ) { step_lenth = userInfo->height; } else if(step_per_2_second >= 8) // step_diff>8 { step_lenth = userInfo->height*1.2f; } else { step_lenth = 0; } walk_speed = step_per_2_second*step_lenth/2; //速度 ,单位:米/秒 walk_distance = step_per_2_second*step_lenth; //行走距离,单位:米 Calories = 4.5f*walk_speed*(userInfo->weight/2)/1800; //Jahol:weight是以kg为单位 sportsInfo.calories += Calories; sportsInfo.distance += walk_distance; time_old = time_now; //更新时间 step_per_2_second = 0; } else { //do nothing } /* * 处理无效运动: * 1.连续记录5才开始计步 * 2.例如记录的步用户停住超过3秒,则前面的记录失效,下次从头开始 * 3.连续4记录了步用户还在运动,之前的数据才有效 * */ if ((stepTempCount< 5 )&&(timeOfNow - timeOfLastPeak >= 3000)) { stepTempCount = 0; } else if((stepTempCount>= 5)&&(timeOfNow - timeOfLastPeak <= 3000)) { sportsInfo.stepCount += stepTempCount; stepTempCount = 0; } else { //do nothing } } //Jahol:更新阀值,问题:阀值不会一直变大,不能变小? if (timeOfNow - timeOfLastPeak >= 250 && (peakOfWave - valleyOfWave >= initialValue)) { timeOfThisPeak = timeOfNow; ThreadValue = Peak_Valley_Thread(peakOfWave - valleyOfWave);//更新阀值 } } } gravityOld = values; return &sportsInfo;}
/******************************************************************************** 函数名:DetectorPeak* 功能描述: *检测波峰 。以下四个条件判断为波峰: *(1)目前点为下降的趋势:isDirectionUp为FALSE *(2)之前的点为上升的趋势:lastStatus为TRUE *(3)到波峰为止,持续上升大于等于2次*(4)波峰值大于20 //Jahol:把这个值修改为15*记录波谷值 :*(1)观察波形图,可以发现在出现步子的地方,波谷的下一个就是波峰,有比较明显的特征以及差值 *(2)所以要记录每次的波谷值,为了和下次的波峰做对比 * 参数说明: * 输入:* newValue:最新的经过处理的G-sensor数据* oldValue:前一个处理的G-sensor数据* 返回值说明:* 修改记录:*******************************************************************************/u8 DetectorPeak(float newValue, float oldValue) { lastStatus = isDirectionUp; if (newValue >= oldValue) //采样数据呈上升趋势 { isDirectionUp = TRUE; continueUpCount++; } else //数据呈下降趋势 { continueUpFormerCount = continueUpCount; continueUpCount = 0; isDirectionUp = FALSE; } if ((!isDirectionUp) && lastStatus && (continueUpFormerCount >= 2 || oldValue >= 20)) { peakOfWave = oldValue; return TRUE; } else if ((!lastStatus) && isDirectionUp) { valleyOfWave = oldValue; return FALSE; } else { return FALSE; } }/******************************************************************************** 函数名:Peak_Valley_Thread* 功能描述: * 阈值的计算 * 1.通过波峰波谷的差值计算阈值 * 2.记录4个值,存入tempValue[]数组中 * 3.在将数组传入函数averageValue中计算阈值 * * 参数说明: * 返回值说明:* 修改记录:*******************************************************************************/float Peak_Valley_Thread(float value) { float tempThread = ThreadValue; u8 i = 0; if (tempCount < VALUE_NUM) { tempValue[tempCount] = value; tempCount++; } else { tempThread = averageValue(tempValue, VALUE_NUM);//计算阀值 for ( i = 1;i < VALUE_NUM;i++)//线性移位更新 { tempValue[i - 1] = tempValue[i]; } tempValue[VALUE_NUM - 1] = value; } return tempThread; }
/******************************************************************************** 函数名:averageValue* 功能描述: * 梯度化阈值 * 1.计算数组的均值 * 2.通过均值将阈值梯度化在一个范围里 * * 参数说明: * 返回值说明:* 修改记录:*******************************************************************************/float averageValue(float value[], int n){ float ave = 0; u8 i =0; for ( i = 0; i < n; i++) { ave += value[i];//求和 } ave = ave / VALUE_NUM;//求平均值 if (ave >= 8) ave = (float) 4.3; //???? else if (ave >= 7 && ave < 8) ave = (float) 3.3; else if (ave >= 4 && ave < 7) ave = (float) 2.3; else if (ave >= 3 && ave < 4) ave = (float) 2.0; else { ave = (float) 1.3; } return ave; }

personInfo_t user_info;u8 WatchInfo_init(void){ WatchInfo_setUserInfo(170,134); //设置身高、体重用于计算卡路里消耗 return 0; //初始化成功返回0}/********************************************************************************************************** * 函数名: WatchInfo_setUserInfo * 功能描述: 设置手表使用者的个人信息 * 参数说明: * 返回值说明: * 修改记录:**********************************************************************************************************/u8 WatchInfo_setUserInfo(u8 height,u8 weight){ user_info.height = ((float)height)/100; user_info.weight = ((float)weight)/2; return 0;//成功 0}

personInfo_t * WatchInfo_getUserInfo(u8 *error){ u8 err; err = 0;//0表示获取成功 error = &err; return &user_info;}
复制代码

2.2 ESP8266 WIFI 模块

设备测量的数据最终通过 WIFI 传递给手机 APP 显示,下面列出 ESP8266 的核心代码。


#include "esp8266.h"extern u8  USART3_RX_BUF[USART3_MAX_RECV_LEN];     //接收缓冲,最大USART3_MAX_RECV_LEN字节extern u8  USART3_TX_BUF[USART3_MAX_SEND_LEN];     //发送缓冲,最大USART3_MAX_SEND_LEN字节extern vu16 USART3_RX_STA;                         //接收数据状态
/////////////////////////////////////////////////////////////////////////////////////////////////////////// //用户配置区
//连接端口号:8086,可自行修改为其他端口.const u8 portnum[]="8089";
//WIFI STA模式,设置要去连接的路由器无线参数,请根据你自己的路由器设置,自行修改.const u8 wifista_ssid[]="wbyq1"; //路由器SSID号const u8 wifista_encryption[]="wpa2_aes"; //wpa/wpa2 aes加密方式const u8 wifista_password[]="123456789"; //连接密码
//WIFI AP模式,模块对外的无线参数,可自行修改.const u8 wifiap_ssid[]="Cortex_M3"; //对外SSID号const u8 wifiap_encryption[]="wpawpa2_aes"; //wpa/wpa2 aes加密方式const u8 wifiap_password[]="12345678"; //连接密码

/*函数功能:向ESP82668266发送命令函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms)返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败*/u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime){ u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA&0X8000)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack)) { res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 } USART3_RX_STA=0; } } if(waittime==0)res=1; } return res;}

/*函数功能:ESP8266发送命令后,检测接收到的应答函数参数:str:期待的应答结果返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置)*/u8* ESP8266_CheckCmd(u8 *str){ char *strx=0; if(USART3_RX_STA&0X8000) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 printf("RX=%s",USART3_RX_BUF); } return (u8*)strx;}
/*函数功能:向ESP8266发送指定数据函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms)返 回 值:0,发送成功(得到了期待的应答结果)luojian*/u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime){ u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA&0X8000)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; } } if(waittime==0)res=1; } return res;}
/*函数功能:ESP8266退出透传模式返 回 值:0,退出成功; 1,退出失败*/u8 ESP8266_QuitTrans(void){ while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT","OK",20);//退出透传判断.}

/*函数功能:获取ESP82668266模块的AP+STA连接状态返 回 值:0,未连接;1,连接成功*/u8 ESP8266_ApStaCheck(void){ if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态 if(ESP8266_CheckCmd("+CIPSTATUS:0")&& ESP8266_CheckCmd("+CIPSTATUS:1")&& ESP8266_CheckCmd("+CIPSTATUS:2")&& ESP8266_CheckCmd("+CIPSTATUS:4")) return 0; else return 1;}

/*函数功能:获取ESP8266模块的连接状态返 回 值:0,未连接;1,连接成功.*/u8 ESP8266_ConstaCheck(void){ u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS:"); res=*p; //得到连接状态 return res;}
/*函数功能:获取ip地址函数参数:ipbuf:ip地址输出缓存区*/void ESP8266_GetWanip(u8* ipbuf){ u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 { ipbuf[0]=0; return; } p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); }
/*函数功能:将收到的AT指令应答数据返回给电脑串口参 数:mode:0,不清零USART3_RX_STA; 1,清零USART3_RX_STA;*/void ESP8266_AtResponse(u8 mode){ if(USART3_RX_STA&0X8000) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符 printf("%s",USART3_RX_BUF); //发送到串口 if(mode)USART3_RX_STA=0; } }

/*函数功能:ESP8266 AP模式+TCP服务器模式测试*/void ESP8266_APorServer(void){ u8 p[100],key; u8 ipbuf[20];// u32 rlen=0; //接收长度// u32 constate,t=0; while(ESP8266_SendCmd("AT\r\n","OK",20))//检查WIFI模块是否在线 { ESP8266_QuitTrans();//退出透传 ESP8266_SendCmd("AT+CIPMODE=0\r\n","OK",200); //关闭透传模式 printf("未检测到模块,正在尝试连接模块...\r\n"); DelayMs(800); } printf("ESP8266模块检测OK!\r\n"); while(ESP8266_SendCmd("ATE0\r\n","OK",20)); //关闭回显 printf("请用设备连接WIFI热点:%s,%s,%ss\r\n",(u8*)wifiap_ssid,(u8*)wifiap_encryption,(u8*)wifiap_password); /*1. 设置WIFI AP模式 */ ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50); /*2. 重启模块 */ ESP8266_SendCmd("AT+RST\r\n","OK",20); /*3. 延时3S等待重启成功*/ DelayMs(1000); DelayMs(1000); DelayMs(1000); /*5. 配置模块AP模式无线参数*/ sprintf((char*)p,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",wifiap_ssid,wifiap_password); ESP8266_SendCmd(p,"OK",1000); /*4. 设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/ ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20); /*5. 开启Server模式(0,关闭;1,打开),端口号为portnum */ sprintf((char*)p,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum); ESP8266_SendCmd(p,"OK",50); /*6. 获取当前模块的IP*/ ESP8266_GetWanip(ipbuf);// printf("IP地址:%s 端口:%s",ipbuf,(u8*)portnum); USART3_RX_STA=0; //清空串口的接收标志位// while(1)// {// key=GetKeyVal(1);//退出测试// if(key==1)// { // printf("退出测试!\r\n"); // ESP8266_QuitTrans(); //退出透传// ESP8266_SendCmd("AT+CIPMODE=0","OK",20); //关闭透传模式// break; // }// else if(key==2) //发送数据 // {// ESP8266_SendCmd("AT+CIPSEND=0,12\r\n","OK",200); //设置发送数据长度为12个// ESP8266_SendData("ESP8266测试!","OK",100); //发送指定长度的数据// DelayMs(200);// }// t++;// DelayMs(10);// if(USART3_RX_STA&0X8000) //接收到一次数据了// { // rlen=USART3_RX_STA&0X7FFF; //得到本次接收到的数据长度// USART3_RX_BUF[rlen]=0; //添加结束符 // printf("接收的数据: rlen=%d,%s",rlen,USART3_RX_BUF); //发送到串口 // USART3_RX_STA=0;// if(constate!=3)t=1000; //状态为还未连接,立即更新连接状态// else t=0; //状态为已经连接了,10秒后再检查// }// if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在.// {//// constate=ESP8266_ConstaCheck();//得到连接状态//// if(!constate)printf("连接失败!\r\n");// t=0;// }// if((t%20)==0)LED2=!LED2;// ESP8266_AtResponse(1);// }}
复制代码

2.3 体温检测模块

体温检测模块是串口接口,发送指令返回数据,代码如下:


#include "TEMPERATURE.H"/*发送一个字节*/void UsartSendByte(uint8_t data){    USART2->DR=data;    while(!(USART2->SR&(1<<7))){}}
/*温度模块检测初始化*/void TemPeratureInit(void){ UsartInit(USART2,36,9600); //串口初始化 DelayMs(2); //延时启动 UsartSendByte(0xA5); UsartSendByte(0x45); //发送读方位角指令 UsartSendByte(0xEA);}u8 TEMP_data[20]={0},Receive_ok=0;u8 tem_flag=0;//读取温度信息void GetTemInfo(float *buff){ u8 sum=0,i; if(Receive_ok)//串口接收完毕 { for(sum=0,i=0;i<(TEMP_data[3]+4);i++)//TEMP_data[3]=4 sum+=TEMP_data[i]; if(sum==TEMP_data[i])//校验和判断 { buff[0]=(float)((TEMP_data[4]<<8)|TEMP_data[5])/100; //得到真实温度 buff[1]=(float)((TEMP_data[6]<<8)|TEMP_data[7])/100; //得到真实温度 tem_flag=1; } Receive_ok=0;//处理数据完毕标志 }}
复制代码


用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
基于STM32设计的健康检测设备(测温心率计步)