一、前言
这是本科时的毕业设计,想着之后读研了,研究方向是机器学习了,可能不会这么再碰32或者51之类的板子了,就想趁着还没有忘记就来梳理一下,纪念陪伴了我两年的硬件朋友们,作为的一个足迹。
二、项目背景及资源分享
这次毕业设计的灵感来源于20年的电赛,当时因为考研时间紧张的原因,在做一个《无线传感器结点》题目的时候,当时是使用的无线传感器模块讲数据传到电脑作为一个上位机的展示。但当时想做的是使用wifi模块来实现无线传输功能,传到一个自己写的web服务器,在页面上进行展示的,由于时间原因最终还是选择了前者。因此毕业设计就选择这个相近的题目,也算是弥补当时的遗憾。
本次的课题是数据传送至onenet云平台上的,之前看到一个up主讲的stm32连接onenet的详细步骤,这里给大家分享一下:
链接:https://www.bilibili.com/video/BV1y54y1q7uT?from=search&seid=4104843828856938758
这是onenet平台的官方MQTT协议连接手册: 链接:https://open.iot.10086.cn/doc/mqtt/
相关资源的百度云链接: 链接:https://pan.baidu.com/s/1sqiSFrOp_KPExF8dQxs43A 提取码:5613
三、项目介
1、项目名称
基于STM32的家庭健康监测系统
2、系统框架
根据需求,该系统可以实时监测被测者的心率、体温以及周遭环境的温度,也同时可以通过姿态解算来判断被测者是否摔倒。该系统可以将被测者的心率、体温等数据既在本地显示,也可以通过WI-FI传输至云平台以实现远程显示。当被测者摔倒时会发出蜂鸣声,以便引起周围人向被测者施以援手;当被测者吸烟时则会发出警报直至香烟熄灭,可以让被测者远离不健康的生活习惯。
3、功能简介
该设计是主要功能如下: (1) 实时的采集心率、温度、烟雾浓度等信息; (2) 实时的显示心电图以及温度数值信息; (3) 实现跌倒的判断,并且在跌倒时发出报警; (4) 实现吸烟警告,在吸烟时发出报警; (5) 实现将温度、心率、姿态解算数据、烟雾浓度等发送至云平台; (6) 通过登录云平台查看心率、温度、烟雾浓度的折线图。
4、控制核心
STM32C8T6(最小核心板),当然,用其他型号的32,如STM32ZET6也是可以的。
5、外围模块
心率模块:ADS1292R 温度模块:LMT70 姿态解算模块:MPU6050 WIFi模块:ATK-ESP8266 液晶显示模块:OLED12864
6、上位机
OneNET云平台
四、相关设计及框图
1、系统总统设计
基于本系统的需求,本设计提出了分层的设计思想,将系统分为:硬件采集层、网络传输层、数据展示层,提高了软硬件之间的耦合性,便于分工与维护。其中,硬件采集层负责收集心率、温度、烟雾浓度、姿态解算数据,网络传输层负责将前一层采集到的数据通过WI-FI传输到数据展示层,而数据展示层分为本地数据展示和云端数据展示。
2、数据采集层设计框图
3、数据传输层设计框图
4、数据展示层设计框图
五、各功能模块详细设计
1、心率监测功能
在检测的时候,会有许噪音对检测的结果带来干扰,比如人体自身以及电路的干扰都会对检测结果产生影响,当许多干扰信号和心电信号混在一起的时候就可能会使有用信号发生变形甚至被淹没,因此滤波是非常必要的,而滤波可以分为代码实现的软件滤波和芯片自带的硬件滤波,ADS1292R是一款医用级的前端芯片。该模块按照以下的工作时序与STM32进行全双工的SPI通讯。 该模块在系统中的工作流程图: 当ADS1292R上电后至少需要等待1s,当寄存器稳定后再进入ADS1292R的引脚初始化,其中包含了CS、START、RESET、DRYDY控制引脚,当引脚初始化超时则会重新初始化,其次是SPI通讯端口的初始化,然后继续对ADS1292R进行采集操作的一系列配置就可以获得心率的原始数据。
该段是对ADS1292R初始化的配置函数
uint8_t ads1292r_init( uint8_t timeout)
{
uint8_t id ;
port_gpio_init() ;
port_spi_init() ;
ADS1292_CS_SET() ;
ADS1292_REST_RESET() ;
ADS1292_START_RESET() ;
port_delay_ms(1000) ;
ADS1292_REST_SET() ;
port_delay_ms(100) ;
ads1292r_send_cmd(ADS1292R_COMMAND_SDATAC) ;
port_delay_ms(100) ;
ads1292r_send_cmd(ADS1292R_COMMAND_RESET) ;
port_delay_ms(1000) ;
ads1292r_send_cmd(ADS1292R_COMMAND_SDATAC) ;
port_delay_ms(100) ;
while( ( id != ads1292r_reg.id) && ( timeout > 0))
{
id = ads1292r_rw_reg(ADS1292R_COMMAND_RREG|ADS1292R_REG_ID, 0) ;
timeout-- ;
port_delay_ms(100) ;
}
ads1292r_reg.cfg1 = ADS1292R_SET_BITS(ads1292r_reg.cfg1, ADS1292R_DR, ADS1292R_OVERSAMPLING_500SPS) ;
ads1292r_reg.cfg2 = ADS1292R_SET_BITS(ads1292r_reg.cfg2, ADS1292R_PDB_LOFF_COMP, ADS1292R_PDB_LOFF_COMP_ON) ;
ads1292r_reg.cfg2 = ADS1292R_SET_BITS(ads1292r_reg.cfg2, ADS1292R_PDB_REFBUF, ADS1292R_PDB_REFBUF_ON) ;
ads1292r_reg.cfg2 = ADS1292R_SET_BITS(ads1292r_reg.cfg2, ADS1292R_VREF_4V, ADS1292R_VREF_2420MV) ;
ads1292r_reg.loff_sens = ADS1292R_SET_BITS(ads1292r_reg.loff_sens, ADS1292R_LOFF2N, ADS1292R_LOFF2N_ON) ;
ads1292r_reg.loff_sens = ADS1292R_SET_BITS(ads1292r_reg.loff_sens, ADS1292R_LOFF2P, ADS1292R_LOFF2P_ON) ;
ads1292r_reg.rld_sens = ADS1292R_SET_BITS(ads1292r_reg.rld_sens, ADS1292R_CHOP, ADS1292R_CHOP_FREQ_DIV4) ;
ads1292r_reg.rld_sens = ADS1292R_SET_BITS(ads1292r_reg.rld_sens, ADS1292R_PDB_RLD, ADS1292R_PDB_RLD_ON) ;
ads1292r_reg.rld_sens = ADS1292R_SET_BITS(ads1292r_reg.rld_sens, ADS1292R_RLD2N, ADS1292R_RLD2N_ON) ;
ads1292r_reg.rld_sens = ADS1292R_SET_BITS(ads1292r_reg.rld_sens, ADS1292R_RLD2P, ADS1292R_RLD2P_ON) ;
ads1292r_reg.resp2 = ADS1292R_SET_BITS(ads1292r_reg.resp2, ADS1292R_RLDREF_INT, ADS1292R_RLDREF_INT) ;
return timeout ;
}
其他的关于芯片识别和采样频率设置、开启导联脱落比较器、开启二联导脱落检测功能、开启通道二右腿驱动输出并选择内部信号的代码这里就不放了,这些可以通过官方给的芯片资料是可以移植的。
然后来说一下软件滤波的操作: 初始化函数返回的timeout的是初始化失败次数,如果超过则会一直卡在这里不能继续往下。流程图的原始数据是仅仅只是硬件滤波后的ADC原始数据,为了能够得到符合正常范围的心电信号,还需要对数据进行软件滤波,处于过于偏大或者偏小的波形,最后才能将滤波后的心率值存到数组中。以下为软件滤波的部分代码:
ads1292r_get_value(ad_b);
ecg_data = i24toi32(ad_b+6);
ecg_sum = 0;
for(i = 0; i < 8; i++)
{
ecg_data_s[i] = ecg_data_s[i+1];
ecg_sum += ecg_data_s[i];
}
ecg_data_s[8] = ecg_data;
ecg = (ecg_sum + ecg_data) / 9;
ecg_min = 5000000;
ecg_max = -5000000;
for(i=0; i < 29; i++)
{
ecg_buf[i]=ecg_buf[i+1];
ecg_max = ecg_max < ecg_buf[i] ? ecg_buf[i] : ecg_max;
ecg_min = ecg_min > ecg_buf[i] ? ecg_buf[i] : ecg_min;
}
ecg_buf[29]=ecg;
ecg_max = ecg_max < ecg ? ecg : ecg_max;
ecg_min = ecg_min > ecg ? ecg : ecg_min;
if((ecg_max - ecg_min > 30000)&&(tim3_tick > 200))
{
if((60000/tim3_tick < 200) && (60000/tim3_tick > 40))
{
Heart_buff[0] = Heart_buff[1];
Heart_buff[1] = Heart_buff[2];
Heart_buff[2] = Heart_buff[3];
Heart_buff[3] = Heart_buff[4];
Heart_buff[4] = Heart_buff[5];
Heart_buff[5] = Heart_buff[6];
Heart_buff[6] = Heart_buff[7];
Heart_buff[7] = Heart_buff[8];
Heart_buff[8] = Heart_buff[9];
Heart_buff[9] = 60000/tim3_tick;
Heart_rate=(Heart_buff[0]+Heart_buff[1]+Heart_buff[2]+Heart_buff[3]+Heart_buff[4]
+Heart_buff[5]+Heart_buff[6]+Heart_buff[7]+Heart_buff[8]+Heart_buff[9])/10;
}
tim3_tick = 0;
}
从流程图中可以看到在定时器中断的作用下,每隔100ms就会将获得滤波后的心电信号以串口的方式传输到主控的STM32芯片再对心率数据进行下一步的处理。
2、温度采集功能
LMT70是一款高精度的医用级温度传感器,其仅需3.3V的电压就可以驱动,且功耗极低,可以检测的温度范围是-55摄氏度到150摄氏度。温度模块与主控STM32采用的是IIC通信,其具体的引脚连接图如下: 以下为该模块在系统中的流程图: 由于在硬件电路中,ADS115和LMT70是合在一起的,共同算作温度模块,因此,在流程图中的ADS115初始化是指整个温度模块的初始化,以下为ADS115初始化函数:
void ads1115_Init(void)
{
ads1115_I2C_INIT();
m_gain = GAIN_ONE;
if (ConfigeRegister(m_channel))
{
printf("init configreg error\r\n");
}
}
该初始化函数完成了IIC引脚的初始化以及寄存器的配置。通过对convert寄存器的处理,可以获得原始的ADC数据,最后再通过对该数据进行才得到最终温度数值,再把该数据通过IIC传给STM32进行进一步的处理。
具体的式子如下:
adcx=GetAds1115Values();
temp=(float)adcx*0.125f;
tem = (-0.0000084515f)*temp*temp+(-0.176928f)*temp+203.393f;
3、跌倒监测功能(姿态解算)
MPU6050是整合性六轴运动处理组件,其中组合了三轴陀螺仪和三轴加速器。加速度传感器是用来检查空集中的6个面(前后左右上下)中的哪些面受到了力的作用,陀螺仪是检测3个方向的欧拉角,以水平摊开的手掌为例,判断手掌上下摆动幅度的角度叫俯仰角用pitch表示,判断手掌左右水平移动的角度叫偏航角用yaw表示,判断手掌左右翻滚的角度叫滚转角用roll表示。本模块所需要实现简单的跌倒检测的功能,首先得知道什么算跌倒或者如何表示跌倒的状态。跌倒是指突发、不自主的、非故意的体位改变,倒在地上或更低的平面上[4]。人体在跌倒瞬间,身体的加速度和角速度在水平和垂直方向都会发生巨大的变化[5]。因此,通过判断三个方向的欧拉角以及三个方向的综合加速度综合考虑来判断是否摔倒。 MPU6050的初始化代码是可以根据官方资料移植的,所以这里就不贴初始化了,这里介绍一下跌倒监测的实现。 首先知道DMP功能移植DMP是MPU6050内部的运动引擎,由Inven Sence公司自主提供,用于从内部传感器中直接解算出四元数,大幅降低运算复杂度。由于DMP可直接输出四元数,从而可以减轻外围微处理器的工作负担,且能避免繁琐的滤波和数据融合处理,能降低系统运算的复杂度。以为为四元组转换公式的转换公式,其中四元单位数的平方和为1。
然后通过四元组转化得到三个方向的欧拉角,代码实现如下:
if(sensors&INV_WXYZ_QUAT)
{
q0 = quat[0] / q30;
q1 = quat[1] / q30;
q2 = quat[2] / q30;
q3 = quat[3] / q30;
*pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;
*roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3;
*yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;
得到三个方向的欧拉角之后,再结合人体加速度向量幅值SVM来综合判断是否跌倒。 其中SVM是三个方向的综合加速度值,式子如下:
S
V
M
=
a
=
a
x
2
+
a
y
2
+
a
z
2
\mathbf{SVM}=a=\sqrt{a_x^2+a_y^2+a_z^2}
SVM=a=ax2?+ay2?+az2?
?
以下为跌倒监测的代码实现:
void fall(){
MPU_Get_Accelerometer(&aacx,&aacy,&aacz);
SVM = sqrt(pow(aacx,2)+ pow(aacy,2) + pow(aacz,2));
if( fabs(pitch)>40 || fabs(roll)>40 || fabs(yaw)>40 )
mpu_1_flag = 1;
else
mpu_1_flag = 0;
if( SVM>22000 || SVM<12000 ){i = 0;}
i++;
if( i<=3 ){mpu_2_flag = 1;}
else
{
i = 3;
mpu_2_flag = 0;
}
if( mpu_2_flag || mpu_1_flag){mpu_flag = 1;}
else {mpu_flag = 0;}
BEEP=0;
delay_ms(300);
if(mpu_flag==1)
{
BEEP=1;
LED2=0;
LED1=1;
delay_ms(300);
} BEEP=0;
delay_ms(300);
LED1=0;
LED2=1;
}
4、WI-FI模块
该模块的工作流程如下: 系统上电后对首先是对WI-FI模块引脚进行初始化包括WI-FI模块的RESET引脚,在本设计中WI-FI模块通过USART3串口与主控STM32进行通信,因此还需要对USART3(串口3)进行初始化,串口初始化代码是基本功这里就也不贴代码啦。
紧接着是 然后是对WI-FI模块的配置,首先是主控STM32通过串口3向ESP8266发送RST复位指令,然后清除ESP8266中的缓存,根据本设计的需求是要把硬件采集层所获取的所有数据传输到云平台,因此配置其工作模式为“STA”模式(WI-FI模式)。配置为STA模式后需要连接手机热点,通过串口3发送AT指令到ESP8266配置WIFI的名称和密码,命令格式为: AT+CWJAP=“名称”,“密码”,为了方便串口3发送命令,本设计将热点名称和密码做了宏定义,宏定义如下:
#define ESP8266_WIFI_INFO "AT+CWJAP=\"ONTNET\",\"lyycz1314\"\r\n"
能上网之后接下来访问云平台公网IP,因为ESP8266模块内部自带了TCP/IP协议栈,因此通过AT命令连接到云平台,命令格式为:AT+CIPSTART= “TCP”,“IP”,“端口”,为了方便串口3发送命令,本设计将云平台的IP地址、端口做了宏定义,宏定义如下:
#define ESP8266_ONENET_INFO "AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n"
以上这两个每个人的都不一样,根据自己的具体情况配置,如果直接复制就会访问到别人的云平台创建的项目上,这样就尴尬啦。
配置完成后就可以向云平台发送MQTT协议包,等待平台回应后就可以将各种数据打包发送至云平台了,以下为ESP8266初始化的具体代码:
void ESP8266_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Initure.GPIO_Pin = GPIO_Pin_1;
GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Initure);
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_RESET);
delay_ms(250);
GPIO_WriteBit(GPIOB, GPIO_Pin_1, Bit_SET);
delay_ms(500);
ESP8266_Clear();
printf("AT\r\n");
while(ESP8266_SendCmd("AT\r\n\r", "OK", 100))
delay_ms(500);
printf("CWMODE\r\n");
while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK", 100))
delay_ms(500);
printf("AT+CWDHCP\r\n");
while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK", 200))
delay_ms(500);
printf("CWJAP\r\n");
while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP", 200))
delay_ms(500);
printf("CIPSTART\r\n");
while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT", 200))
delay_ms(500);
printf("ESP8266 Init OK\r\n");
}
上段代码也是可以移植的,以下是ESP8266向云平台发送数据的具体代码:
void OneNet_SendData(void)
{
MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};
char buf[128];
short body_len = 0, i = 0;
memset(buf, 0, sizeof(buf));
body_len = OneNet_FillBuf(buf);
if(body_len)
{
if(MQTT_PacketSaveData(DEVID, body_len, NULL, 5, &mqttPacket) == 0)
{
for(; i < body_len; i++)
mqttPacket._data[mqttPacket._len++] = buf[i];
ESP8266_SendData(mqttPacket._data, mqttPacket._len);
MQTT_DeleteBuffer(&mqttPacket);
}
else
printf("WARN: EDP_NewBuffer Failed\r\n");
}
}
这里是定义ONENET平台收数据的数据流的名称,上面的视频链接中如何配置云平台也会说到,并且更加详细。
unsigned char OneNet_FillBuf(char *buf)
{
char text[32];
memset(text, 0, sizeof(text));
strcpy(buf, ",;");
memset(text, 0, sizeof(text));
sprintf(text, "Temperature,%.3lf;", tem+0.05f);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "Heart rate,%d;", Heart_rate);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "The Falls,%d;",mpu_flag);
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "The Smoke,%lf;",temp2);
strcat(buf, text);
return strlen(buf);
}
5、显示模块、烟雾报警模块
由于这两个模块比较简单,尤其是OLED12864是最常见的模块,移植也直接用IIC协议,包括配置文件都是一起移植的,所以这里这两个模块间便不在赘述。
六、实物测试及效果展示
1、总体效果
2、网络测试
通过串口调试助手,将WIFI模块与云平台连接情况打印出来,就可以清楚的知道问题出在哪一步。
3、开始界面:
4、心率、温度、烟雾浓度测试:
其中第一个数字表示的是心率值,第二个是当前室温,第三个是烟雾浓度值,第四个只有0\1,0表示没有跌倒,1表示跌倒。
后是对跌倒检查功能的测试,首先默认水平是正常的状态,当把设计快速立放则会被视作摔了,液晶显示器的最后一位数字也会从0变成1,蜂鸣器会发出报警,以及下面的指示灯会变成红色,以下为测试结果:
5、oneNET平台测试
以下为平台接收到单片机传上来的各数据: 折线图显示:
七、总结
终于写完啦,这个应该是我目前最长的一个帖子了哈,以上就是我的毕业设计了。当时本来还想加语音识别以及播报和北斗GPS定位的,测试了一下,加语音识别播报最小板引脚的勉强刚刚够,但如果加GPS的话由于硬件限制,stm32c8t6只有128k,不够存GPS数据,换zet6测试就可以,由于时间原因没有换核心板子(换了代码很多地方都要改,工作量有点大)后来还是没有用c8t6调出来,这也是遗憾的一点。有问题或者有兴趣进一步了解的同学,可以私聊,在能力范围内的会尽力帮助大家~~~
|