IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32课设-智能物联网家居系统(UCOSIII+STEMWIN) -> 正文阅读

[嵌入式]STM32课设-智能物联网家居系统(UCOSIII+STEMWIN)

工程与源代码下载地址
Gitee:源码点这里
Github: 源码点这里

一、功能分析与效果展示

1.功能需求与分析

  1. 采集四种居家常用数据(温度、湿度、光照强度、空气中的可燃气体含量)
  2. 根据光照强度来控制舵机拉动床帘(模拟卧室根据日出情况来控制窗帘,智能卧室功能)
  3. 根据温度、湿度来判断室内情况,控制电机和舵机(模拟高温自动开窗散热和开风扇散热)
  4. 根据空气中的可燃气体含量判断室内情况,控制蜂鸣器、电机舵机(模拟煤气泄漏报警并自动开窗和开排气扇)
  5. 制作主控的Ul界面,显示上述的常用数据和控制家里常用电器(风扇、灯光、门窗)
  6. 将上述的常用数据上传至云端数据库,并做出前端UI对数据进行显示。
  7. 通过手机连接云端,通过云端远程监控家庭情况并做出控制

简单分析一下

  • 主控使用STM32,采集数据使用常规的传感器就可以采集到这些数据。在程序中对采集回来的值做一个判断,然后控制舵机和电机。
  • UI界面这块,使用STEMWIN这个UI库来做,这个库整体使用起来比较简单好上手,做出来的UI也OK。
  • 连网这方面使用ESP8266模块,只要配置好串口就可以轻松驾驭。平台基本上百度、阿里、OneNet都可以,只要支持MQTT协议接入的平台就行。
  • 为了方便开发和项目管理,使用UCOS-III系统,将每个情况分成小任务来完成,这样做快捷方便拓展性强

2.硬件选型

序号名称数量
1STM32F103ZET6开发板1
24.3寸电容屏1
3DHT11模块1
4MQ-2 气体检测模块1
5BH1750 光照强度检测模块1
6ESP8266 WIFI 模块1
73.3V4路继电器模块1
8L298N 驱动模块1
9SG90舵机2
10电线、杜邦线若干
1112V电机2
1212V灯泡4

3.效果展示

硬件实物图

在这里插入图片描述

UI界面

在这里插入图片描述UI界面使用图标均使用阿里图标库中的图案,具体请看UI界面设计。

前端界面

在这里插入图片描述

展示视频

基于STM32的物联网智能家居系统


b站

二、下位机-STM32程序

整体框架图
在这里插入图片描述
硬件定时器分配:

定时器任务
TIM1舵机1
TIM2L298N电机调速
TIM3心跳包定时发送
TIM4保存云端下发数据
TIM8舵机2

UCOSIII软件定时器:蜂鸣器控制

1.系统任务设计

STM32中,使用UCOS-III系统,将功能分成了8个小任务分别实现。具体每个模块如何使用网上教程很多,这里就不一一贴出,有需要可以自行搜索。详细代码可以参考开源连接

1.1 数据采集任务

将每个模块读取数据单独封装一个函数,将函数放入任务中,再将数据读取至数组中,完毕后通过消息队列发送到需要数据的任务。对每个模块数据的大小要有了解,不然有可能会出现数组越界卡死的情况。考虑到居家情况,可以对数据做一个小范围的限幅。同时在这里通过全局变量更新数据显示界面的数据可以让数据显示更加流畅
这里注意模块读取数据有严格时序,使用delay函数时,会引起任务调度,所以数据读取函数前后需要给调度器上锁,防止调度器打乱时序,这里的MQ-2模块是使用ADC采集电压与DMA传输数据,不受时序影响,所以就没有加锁

void datacollection_task(void *p_arg)
{
	int SensorData[5] = {0};
	char temp_data[7];
	char humi_data[7];
	char lux_data[25];
	char ppm_data[10];
	OS_ERR err;
	DHT11_Init();
	iic_by30_init();
	while(1)
	{
		OSIntEnter(); //调度器加锁	
		DHT11_Read_Data(&SensorData[0],&SensorData[1]);
		get_sunlight_value(&SensorData[2]);
		OSIntExit(); //调度器解锁	
		MQ135_GetValue(&SensorData[3]);	
		// 如果进入了数据显示界面则更新数据
		if(windos_flag){
			sprintf(temp_data,"%d C",SensorData[0]);
			sprintf(humi_data,"%d%%",SensorData[1]);
			sprintf(lux_data,"%d Lux",SensorData[2]);
			sprintf(ppm_data,"%d PPM",SensorData[3]);
			TEXT_SetText(hWin_temp_Edit,temp_data);
			TEXT_SetText(hWin_humi_Edit,humi_data);
			TEXT_SetText(hWin_lux_Edit,lux_data);
			TEXT_SetText(hWin_ppm_Edit,ppm_data);
		}
		
		if(SensorData[2] >= 500)
			SensorData[2] = 500;
		/* 发送消息队列给OneNet上传任务 */
		OSQPost((OS_Q        *)&SensorDataMsg,                             //消息变量指针
					 (void        *)SensorData,                //要发送的数据的指针,将内存块首地址通过队列“发送出去”
					 (OS_MSG_SIZE  )30,     //数据字节大小
					 (OS_OPT       )OS_OPT_POST_FIFO | OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式
					 (OS_ERR      *)&err);	                            //返回错误类型		
		//printf("已经发送信号量给上云任务!\r\n");
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
	}
}

1.2 场景处理任务

这里主要是接收采集任务发来的数据,然后根据数据的不同来控制不同的外设。简单判断一下即可,这里每个判断加一个标志位与手动控制区分开,这样就可以通过按钮来切换自动或者手动控制外设。

void Autocontrol_task(void *p_arg)
{
	OS_ERR err;
	OS_MSG_SIZE MsgSize;
	while(1)
	{
		int *TEXT_Buffer;	
		TEXT_Buffer = OSQPend ((OS_Q         *)&SensorDataMsg,               
									(OS_TICK       )0,                   
									(OS_OPT        )OS_OPT_PEND_BLOCKING,  //如果没有获取到信号量就等待
									(OS_MSG_SIZE  *)&MsgSize,          
									(CPU_TS       *)0,                     
									(OS_ERR       *)&err);
		
		/* 
			 TEXT_Buffer[0] = 温度 
			 TEXT_Buffer[1] = 湿度
			 TEXT_Buffer[2] = 光强
		   TEXT_Buffer[3] = 煤气含量检测
		*/
		
		/**********************************
		
			场景1:出现煤气泄漏的情况 
		
		**********************************/
		
		/* 可能出现煤气泄漏 */
		if(TEXT_Buffer[3] >= 300 && control_flag){
			printf("场景1\r\n");
			/* 蜂鸣器报警 */
			OSTmrStart(&BeepTmr,&err);
			/* 开窗通风 */
			TIM_SetCompare1(TIM8,175);
			/* 开排气扇 */
			MotorPWM = 1;
		}else if(TEXT_Buffer[3] <= 300 && control_flag){
			/* 关闭蜂鸣器报警 */
			BEEP = 0;
			OSTmrStop(&BeepTmr,OS_OPT_TMR_NONE,0,&err);
			/* 关闭窗口 */
			TIM_SetCompare1(TIM8,195);
			/* 关闭排气扇 */
			MotorPWM = 0;			
		}
		
		/**********************************
		
			场景2:根据光照强度调节窗帘的开关 
		
		**********************************/
		
			if(TEXT_Buffer[2] <= 10 && control_flag){
				/* 到了深夜 */
				TIM_SetCompare1(TIM1,195);
			}else if(TEXT_Buffer[2] <= 30 && TEXT_Buffer[2] >= 10 && control_flag){
				/* 有一点光 */
				TIM_SetCompare1(TIM1,190); // 45度
		}else if(TEXT_Buffer[2] <= 120 && TEXT_Buffer[2] >= 30 && control_flag){
			 /* 有光照  */
			TIM_SetCompare1(TIM1,185); // 90度
		}else if (TEXT_Buffer[2] <= 400 && TEXT_Buffer[2] >= 120 && control_flag){
			/* 日出 */
			TIM_SetCompare1(TIM1,175); // 180度
		}
		
		/**********************************
		
			场景3:根据温度开关风扇
		
		**********************************/
		
		if(TEXT_Buffer[1] >= 30 && TEXT_Buffer[1] <= 35 && control_flag ){
			TIM_SetCompare2(TIM2,50);
		}
		else if(TEXT_Buffer[1] >= 35 && control_flag){
			TIM_SetCompare2(TIM2,100);
		}else if (TEXT_Buffer[1] <= 30 && control_flag) 
		{
			TIM_SetCompare2(TIM2,0);
		}
	}
}

1.3 OneNet连接任务

接下来就是要将数据发送到云端和接受云端数据了,这个可以选择的平台和教程也很多,这里贴几个给大家参考,我是选择使用电信部的OneNet平台。
连接OneNet平台
STM32连接OneNet
大致步骤就是上网站注册,拿到IP、端口号、产品ID、设备号、密码。通过TCP连接到服务器之后使用MQTT协议发送报文就可以建立连接了。具体参考开源代码
注意OneNet是选择多协议接入,不是选MQTT套件

这里OneNet的IP和端口号是固定的,直接复制即可

IP端口号
183.230.40.396002

产品ID:
在这里插入图片描述设备号和密码
在这里插入图片描述
在MQTT.h中填入

#define  PRODUCTKEY           "183.230.40.39"                               //OneNetIP   
#define  PRODUCTKEY_LEN       strlen(PRODUCTKEY)                            
#define  DEVICENAME           "927136809"                                   //设备号  
#define  DEVICENAME_LEN       strlen(DEVICENAME)                            //设备名长度
#define  P_TOPIC_NAME         "$dp"                                         //需要发布的主题     
#define  PRODUCTID 			  "503697"                                      //产品ID
#define  AUTHENTICATION       "123456"										//密码

在任务中,首先初始化WIFI模块、MQTT堆栈、OneNet连接信息等,然后与OneNet发起TCP连接,连接成功后。向OneNet发送订阅报文,订阅成功后,启动定时器3,定时30S,每30S向OneNet发送一次心跳,保证我们时刻在线,同时启动上传数据任务和下发命令处理来上传我们采集的数据和处理云端对下位机的控制。连接任务具体见下代码,定时器中断、WIFI连接代码和TCP代码参考开源代码,这里就不一一贴出

//连接OneNet任务
void connectOneNet_task(void *p_arg)
{
	OS_ERR err;
	WiFi_ResetIO_Init();            //初始化WiFi的复位IO
    MQTT_Buff_Init();               //初始化接收,发送,命令数据的 缓冲区 以及各状态参数
	OneNetIoT_Parameter_Init();	    //初始化连接OneNet平台MQTT服务器的参数	
	while(1)                        //主循环
	{		
		/*--------------------------------------------------------------------*/
		/*   Connect_flag=1同服务器建立了连接,我们可以发布数据和接收推送了    */
		/*--------------------------------------------------------------------*/
		if(Connect_flag==1)
		{     
			/*-------------------------------------------------------------*/
			/*                     处理发送缓冲区数据                      */
			/*-------------------------------------------------------------*/
				if(MQTT_TxDataOutPtr != MQTT_TxDataInPtr)
			{                //if成立的话,说明发送缓冲区有数据了
				//3种情况可进入if
				//第1种:0x10 连接报文
				//第2种:0x82 订阅报文,且ConnectPack_flag置位,表示连接报文成功
				//第3种:SubcribePack_flag置位,说明连接和订阅均成功,其他报文可发
				if((MQTT_TxDataOutPtr[2]==0x10)||((MQTT_TxDataOutPtr[2]==0x82)&&(ConnectPack_flag==1))||(SubcribePack_flag==1))
				{    
					printf("发送数据:0x%x\r\n",MQTT_TxDataOutPtr[2]);  //串口提示信息
					MQTT_TxData(MQTT_TxDataOutPtr);                       //发送数据
					MQTT_TxDataOutPtr += BUFF_UNIT;                       //指针下移
					if(MQTT_TxDataOutPtr==MQTT_TxDataEndPtr)              //如果指针到缓冲区尾部了
						MQTT_TxDataOutPtr = MQTT_TxDataBuf[0];            //指针归位到缓冲区开头
				} 				
			}//处理发送缓冲区数据的else if分支结尾
			
			/*-------------------------------------------------------------*/
			/*                     处理接收缓冲区数据                      */
			/*-------------------------------------------------------------*/
			if(MQTT_RxDataOutPtr != MQTT_RxDataInPtr){  //if成立的话,说明接收缓冲区有数据了														
				printf("接收到数据:");
				/*-----------------------------------------------------*/
				/*                    处理CONNACK报文                  */
				/*-----------------------------------------------------*/				
				//if判断,如果第一个字节是0x20,表示收到的是CONNACK报文
				//接着我们要判断第4个字节,看看CONNECT报文是否成功
				if(MQTT_RxDataOutPtr[2]==0x20){             			
				    switch(MQTT_RxDataOutPtr[5]){					
						case 0x00 : printf("CONNECT报文成功\r\n");                            //串口输出信息	
								    ConnectPack_flag = 1;                                        //CONNECT报文成功,订阅报文可发
									break;                                                       //跳出分支case 0x00                                              
						case 0x01 : printf("连接已拒绝,不支持的协议版本,准备重启\r\n");     //串口输出信息
									Connect_flag = 0;                                            //Connect_flag置零,重启连接
									break;                                                       //跳出分支case 0x01   
						case 0x02 : printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n"); //串口输出信息
									Connect_flag = 0;                                            //Connect_flag置零,重启连接
									break;                                                       //跳出分支case 0x02 
						case 0x03 : printf("连接已拒绝,服务端不可用,准备重启\r\n");         //串口输出信息
									Connect_flag = 0;                                            //Connect_flag置零,重启连接
									break;                                                       //跳出分支case 0x03
						case 0x04 : printf("连接已拒绝,无效的用户名或密码,准备重启\r\n");   //串口输出信息
									Connect_flag = 0;                                            //Connect_flag置零,重启连接						
									break;                                                       //跳出分支case 0x04
						case 0x05 : printf("连接已拒绝,未授权,准备重启\r\n");               //串口输出信息
									Connect_flag = 0;                                            //Connect_flag置零,重启连接						
									break;                                                       //跳出分支case 0x05 		
						default   : printf("连接已拒绝,未知状态,准备重启\r\n");             //串口输出信息 
									Connect_flag = 0;                                            //Connect_flag置零,重启连接					
									break;                                                       //跳出分支case default 								
					}
				}			
				//if判断,第一个字节是0x90,表示收到的是SUBACK报文
				//接着我们要判断订阅回复,看看是不是成功
				else if(MQTT_RxDataOutPtr[2]==0x90){ 
						switch(MQTT_RxDataOutPtr[6]){					
						case 0x00 :
						case 0x01 : printf("订阅成功\r\n");            //串口输出信息
												SubcribePack_flag = 1;                //SubcribePack_flag置1,表示订阅报文成功,其他报文可发送
												Ping_flag = 0;                        //Ping_flag清零
												LED1 = 0;	                           //连接指示灯
												TIM3_ENABLE_30S();                    //启动30s的PING定时器
												OS_TaskResume(&TASK9_TCB,&err);       //启动数据上传任务
												OS_TaskResume(&TASK10_TCB,&err);      //启动下发命令处理任务
												break;                                //跳出分支                                             
						default   : printf("订阅失败,准备重启\r\n");  //串口输出信息 
												Connect_flag = 0;                     //Connect_flag置零,重启连接
												break;                                //跳出分支 								
					}					
				}
				//if判断,第一个字节是0xD0,表示收到的是PINGRESP报文
				else if(MQTT_RxDataOutPtr[2]==0xD0)
			{ 
					printf("PING报文回复\r\n"); 		  //串口输出信息 
					if(Ping_flag==1){                     //如果Ping_flag=1,表示第一次发送
						 Ping_flag = 0;    				  //要清除Ping_flag标志
					}else if(Ping_flag>1){ 				  //如果Ping_flag>1,表示是多次发送了,而且是2s间隔的快速发送
						Ping_flag = 0;     				  //要清除Ping_flag标志
						TIM3_ENABLE_30S(); 				  //PING定时器重回30s的时间
					}				
				}	
				//if判断,如果第一个字节是0x30,表示收到的是服务器发来的推送数据
				//我们要提取控制命令
				else if((MQTT_RxDataOutPtr[2]==0x30))
				{ 
					printf("服务器等级0推送\r\n"); 		   //串口输出信息 
					MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr);  //处理等级0推送数据
				}				
								
				MQTT_RxDataOutPtr += BUFF_UNIT;                     //指针下移
				if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr)            //如果指针到缓冲区尾部了
					MQTT_RxDataOutPtr = MQTT_RxDataBuf[0];          //指针归位到缓冲区开头                        
			}//处理接收缓冲区数据的else if分支结尾		
		}//Connect_flag=1的if分支的结尾
		
		/*--------------------------------------------------------------------*/
		/*      Connect_flag=0同服务器断开了连接,我们要重启连接服务器         */
		/*--------------------------------------------------------------------*/
		else
		{ 
			printf("需要连接服务器\r\n");                 //串口输出信息
			TIM_Cmd(TIM4,DISABLE);                           //关闭TIM4 
			TIM_Cmd(TIM3,DISABLE);                           //关闭TIM3  
			WiFi_RxCounter=0;                                //WiFi接收数据量变量清零                        
			memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);          //清空WiFi接收缓冲区 
			if(WiFi_Connect_IoTServer()==0)
			{   			     //如果WiFi连接云服务器函数返回0,表示正确,进入if
				printf("建立TCP连接成功\r\n");            //串口输出信息
				Connect_flag = 1;                            //Connect_flag置1,表示连接成功	
				WiFi_RxCounter=0;                            //WiFi接收数据量变量清零                        
				memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);      //清空WiFi接收缓冲区 
				MQTT_Buff_ReInit();                          //重新初始化发送缓冲区                    
			}				
		}
		
		delay_ms(100);
	}
}

1.3 OneNet上传数据任务

成功连接上OneNet后,启动数据上传任务,接收传感器的数据,并将数据拼接成MQTT发布报文,发送到云端上,这里每5秒上传一次数据,因为前端显示那边是5s更新一次显示,这样刚好可以同步更新。
这里注意报文每个数据的拼接方式,平台不同,拼接方式会有不同,但基本上大同小异,按使用网站的手册修改即可

void dataupload_task(void *p_arg)
{
	OS_ERR err;
	OS_MSG_SIZE MsgSize;
	while(1)
	{		
		char head1[3];
		char temp[50];				//定义一个临时缓冲区1,不包括报头
		char tempAll[150];			//定义一个临时缓冲区2,包括所有数据
		int	dataLen = 0;			//报文长度
		int *TEXT_Buffer;	
		
		TEXT_Buffer = OSQPend ((OS_Q         *)&SensorDataMsg,               
									(OS_TICK       )0,                   
									(OS_OPT        )OS_OPT_PEND_BLOCKING,  //如果没有获取到信号量就等待
									(OS_MSG_SIZE  *)&MsgSize,          
									(CPU_TS       *)0,                     
									(OS_ERR       *)&err); 
		//printf("已经接受到信号量!\r\n");
		//printf("接受到的温:%d,湿度:%d,光:%d,空:%d\r\n",TEXT_Buffer[0],TEXT_Buffer[1],TEXT_Buffer[2],TEXT_Buffer[3]);
		memset(temp,    0, 50);				    //清空缓冲区1
		memset(tempAll, 0, 100);				//清空缓冲区2
		memset(head1,   0, 3);					//清空MQTT头
		sprintf(temp,"{\"temperature\":\"%d\",\"humidity\":\"%d\",\"Lux\":\"%d\",\"ppm\":\"%d\"}",
		TEXT_Buffer[0], TEXT_Buffer[1],TEXT_Buffer[2],TEXT_Buffer[3]);//构建报文
		head1[0] = 0x03; 						//固定报头
		head1[1] = 0x00; 						//固定报头
		head1[2] = strlen(temp);  				//剩余长度	
		sprintf(tempAll, "%c%c%c%s", head1[0], head1[1], head1[2], temp);
		dataLen = strlen(temp) + 3;
		//printf("%s",tempAll);
		MQTT_PublishQs0(P_TOPIC_NAME,tempAll, dataLen);//添加数据,发布给服务器
		//printf("Seed Data OK!\r\n ");
		OSTimeDlyHMSM(0,0,5,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
	}

1.4 OneNet下发命令处理任务

这里检测接收的数据是否与我们预先设定的命令相符,如果相符,则执行我们设定的动作。具体见下代码

/* 云端命令处理任务 */
void cmd_task(void *p_arg)
{
	OS_ERR err;
	
	char online_cmd[20];
	char *cmd;
	unsigned char OnlinePwmControl;
	while(1)
	{
		
		/* 云端指令处理 */
		if(MQTT_CMDOutPtr != MQTT_CMDInPtr)							  //if成立的话,说明命令缓冲区有数据了	
			{                             		       
					printf("命令:%s\r\n",&MQTT_CMDOutPtr[2]);              //串口输出信息
				
				/* 接收到云控制信号之后关闭自动控制模式 */
				control_flag = 0;
				
				
				/**************************  
				
					接受到控制灯的指令
				
				**************************/
				if(!memcmp(&MQTT_CMDOutPtr[2],LED1_ON,strlen(LED1_ON)))//判断指令,如果是LED1_ON
				{                                            
					Light1 =  1;
					printf("已打开LED1!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],LED1_OFF,strlen(LED1_OFF)))//判断指令,如果是LED1_ON
				{                                            
					Light1 =  0;
					printf("已关闭LED1!\r\n");
				}
				
					if(!memcmp(&MQTT_CMDOutPtr[2],LED2_ON,strlen(LED2_ON)))//判断指令,如果是LED1_ON
				{                                            
					Light2 =  1;
					printf("已打开LED2!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],LED2_OFF,strlen(LED2_OFF)))//判断指令,如果是LED1_ON
				{                                            
					Light2 =  0;
					printf("已关闭LED2!\r\n");
				}
				
					if(!memcmp(&MQTT_CMDOutPtr[2],LED3_ON,strlen(LED3_ON)))//判断指令,如果是LED1_ON
				{                                            
					Light3 =  1;
					printf("已打开LED3!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],LED3_OFF,strlen(LED3_OFF)))//判断指令,如果是LED1_ON
				{                                            
					Light3 =  0;
					printf("已关闭LED3!\r\n");
				}
				
					if(!memcmp(&MQTT_CMDOutPtr[2],LED4_ON,strlen(LED4_ON)))//判断指令,如果是LED1_ON
				{                                            
					Light4 =  1;
					printf("已打开LED4!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],LED4_OFF,strlen(LED4_OFF)))//判断指令,如果是LED1_ON
				{                                            
					Light4 =  0;
					printf("已关闭LED4!\r\n");
				}
				
				
				/************************** 
				
				接受到窗户和排气扇的指令 
				
				**************************/
				
				if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_ON,strlen(Paiqishan_ON)))//判断指令,如果是LED1_ON
				{                                            
					MotorPWM = 1;
					printf("已打开排气扇!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_OFF,strlen(Paiqishan_OFF)))//判断指令,如果是LED1_ON
				{                                            
					MotorPWM = 0;
					printf("已关闭排气扇!\r\n");
				}
				
				if(!memcmp(&MQTT_CMDOutPtr[2],Windos_ON,strlen(Windos_ON)))//判断指令,如果是LED1_ON
				{                                            
					TIM_SetCompare1(TIM8,175); // 180度;
					printf("已打开窗户!\r\n");
				}
				if(!memcmp(&MQTT_CMDOutPtr[2],Windos_OFF,strlen(Windos_OFF)))//判断指令,如果是LED1_ON
				{                                            
					TIM_SetCompare1(TIM8,195);
					printf("已关闭窗户!\r\n");
				}
				
				
				/**************************  
				
					接受到控制电机的指令
				
				**************************/
				if(!memcmp(&MQTT_CMDOutPtr[2],dianji,strlen(dianji)))
				{
					/* 
						OneNet控制命令为:fangshan:控制数值 
						控制数值范围为:0 - 100
						取出指令中的控制数值字符串并转化为u8类型
					*/
					strcpy(online_cmd,(char*)&MQTT_CMDOutPtr[2]);
					cmd = &online_cmd[9];
					OnlinePwmControl = atoi(cmd);
					printf("%d",OnlinePwmControl);
					TIM_SetCompare2(TIM2,OnlinePwmControl);
					
				}					
				//不做处理,后面会发送状态
				else printf("未知指令\r\n");				//串口输出信息	
				MQTT_CMDOutPtr += BUFF_UNIT;                             	 //指针下移
				if(MQTT_CMDOutPtr==MQTT_CMDEndPtr)           	             //如果指针到缓冲区尾部了
					MQTT_CMDOutPtr = MQTT_CMDBuf[0];          	             //指针归位到缓冲区开头		
			}
		OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
	}
}

1.5 UI显示任务、触摸检测任务

直接在循环中调用UI显示和触摸检测即可

//UI显示任务
void emwindemo_task(void *p_arg)
{
	while(1)
	{
		Main_Ui();
	}
	
}
//触摸检测任务
void touch_task(void *p_arg)
{
	OS_ERR err;
	while(1)
	{
		GUI_TOUCH_Exec();	
		OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_PERIODIC,&err);//延时5ms
	}
}

1.6 系统指示灯与堆栈检测任务

用指示灯闪烁来证明系统正在工作,堆栈检测是前期做UI时,监控UI任务是否有出现爆栈的情况

//LED心跳灯
void SystemLED_task(void *p_arg)
{
	OS_ERR err;
	CPU_STK_SIZE free,used;  
	while(1)
	{
		LED0 = !LED0;
		OSTaskStkChk (&EmwindemoTaskTCB,&free,&used,&err);  
    //printf("EmwindemoTaskTCB      used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));
		OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err);//延时500ms
	}
}

2.UI界面设计

UI界面这边是使用STEMWIN来制作,因为F1的RAM比较小,在显示大量控件和图片时会出现卡顿的情况,所以UI整体只是将基本控件放置在屏幕中,没有做换肤和美化。

STEMWIN这个库,使用起来非常方便,在界面构建时,可以使用自带的GUIBuilder来构建,在完成基本布局之后可以自动生成C代码,在具体功能调试时,可以使用仿真文件进行仿真,功能完成之后,再移植到板子上。

UI界面代码中,样式占了大多数,具体的样式代码就不一一贴出,这里就只贴出关键功能代码,具体可以参考开源代码

2.1主界面

主界面的布局如下图所示,使用Dialog为主窗口,中间使用IconView控件,在IconView中添加5个小图标作为5个二级界面的入口,右上角使用系统RTC来记录时间,使用STEMWIN的软件定时器更新到屏幕上显示。
在这里插入图片描述
这里偷了一个小赖,可以直接将图标转化成.c文件保存在ROM中,直接读取显示,中文也只是按照界面做了部分字库。
图片显示具体方式参考:STEMWIN显示BMP
中文显示具体方式参考:STEMWINC字库制作方式
向IconView添加图标

		ICONVIEW_hItem = WM_GetDialogItem(pMsg->hWin, ID_ICONVIEW_0);
		ICONVIEW_SetIconAlign(ICONVIEW_hItem, ICONVIEW_IA_TOP);
		ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdengguang,"灯光控制");
		ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdata_icon,"家庭数据");
		ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdianqi_icon,"电器控制");
		ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmshezhi,"系统设置");
		ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmguanyu_icon,"关于");
		ICONVIEW_SetFont(ICONVIEW_hItem,&GUI_Fontsongit24);

图标被按下

case WM_NOTIFY_PARENT:
    Id    = WM_GetId(pMsg->hWinSrc);
    NCode = pMsg->Data.v;
    switch(Id) {
    case ID_ICONVIEW_0: // Notifications sent by 'Iconview'
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        // USER START (Optionally insert code for reacting on notification message)
        // USER END
        break;
      case WM_NOTIFICATION_RELEASED:
					/* 当小图标按下且松开时 检测是哪个小图标被按下 */
					indx = ICONVIEW_GetSel(WM_GetDialogItem(pMsg->hWin, ID_ICONVIEW_0));
					/* 根据按下的小图标来切换界面 */
					switch(indx){
						/* 如果是灯光界面被按下 */
						case Light:
						GUI_EndDialog(pMsg->hWin,0);
						CreateLightFramewin();	
						break;
						/* 如果是关于界面被按下 */
						case info:
						GUI_EndDialog(pMsg->hWin,0);
						CreatehuanyinFramewin();	
						break;
						/* 如果是居家数据界面被按下 */
						case data:
						GUI_EndDialog(pMsg->hWin,0);
						CreateDataUi();
						break;
						/* 如果是电气控制界面被按下 */
						case control:
						GUI_EndDialog(pMsg->hWin,0);
						ElectricalControl();
						break;
						/* 如果是系统配置界面被按下 */
						case config:
						GUI_EndDialog(pMsg->hWin,0);
						CreateConfig();
						break;
					}
        break;

在创建界面时创建一个1s周期的软件定时器

WM_HWIN CreateFramewin(void) {
    WM_HWIN hWin;
    /* 定时器定义 */
	WM_HTIMER hTimer;
	
 
	WM_SetCreateFlags(WM_CF_MEMDEV_ON_REDRAW);
	hWin = GUI_CreateDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), _cbDialog, WM_HBKWIN, 0, 0);
	 /* 设定定时器周期和应用窗体 */
	hTimer = WM_CreateTimer(WM_GetClientWindow(hWin), 0, 1000, 0);
  
	return hWin;
}

然后界面的定时器触发中,更新RTC时间,这里的RTC库是使用的正点原子的RTC历程

	case WM_TIMER:
		sprintf(date,"%0.2d/%0.2d/%0.2d %0.2d:%0.2d:%0.2d",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
		TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_1),date);
		WM_RestartTimer(pMsg->Data.v, 1000);
		break;

2.1灯光界面

使用Dialog为主窗体,控制使用Button控制来控制GPIO的高低电平,通过IO电平来控制继电器的吸合从而控制12V的点灯。
在这里插入图片描述
4个Button的写法都是一样,这里只贴出一个,其余只需要改IO口和控件号就可以

    case ID_BUTTON_0: // Notifications sent by 'Button'
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        break;
      case WM_NOTIFICATION_RELEASED:
                /* 灯光控制按键松开 就打开或者关闭灯 
			     * 先读取IO的电平信息来判断现在灯的状态
				 */
				LedFlag[0] = ReadLight1Status();
				if(!LedFlag[0]){
					Light1 = 1;
					BUTTON_SetFont(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), &GUI_Fonticon24);
					BUTTON_SetText(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), "关");
					
				}
				else{
					Light1 = 0;
					BUTTON_SetFont(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), &GUI_Fonticon24);
					BUTTON_SetText(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), "开");
				}
        break;
      }
      break;

2.2 数据显示界面

使用Dialog为主窗体,显示4个图标来表示数据。
这里的数据更新并不是在UI里面完成,而是在外面的数据采集任务中完成,进入此界面置位标志位,让任务对这里的数据进行更新。具体可以看数据采集任务
在这里插入图片描述

2.3 电器控制界面

使用Dialog为主窗体,2Slider滑条分别控制风扇的PWM和舵机的旋转角度,两个Button分别控制另一个风扇和舵机旋转角度。一个Checkbox来切换手动和自动模式的标志位,上电默认是自动模式。

这样做主要是考虑到在实际中,厨房排气扇一般开就满速,不需要做调节,同理窗户也是只有全开和全关。
在这里插入图片描述
Slider滑条控制舵机转动

      case ID_SLIDER_1: // Notifications sent by 'Slider'
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        break;
      case WM_NOTIFICATION_RELEASED:
        break;
      case WM_NOTIFICATION_VALUE_CHANGED:
					WindosValue = 25 * SLIDER_GetValue(WM_GetDialogItem(pMsg->hWin, ID_SLIDER_1));
					if(WindosValue >= 0){
						sprintf(dis_data2,"窗帘状态:全关");
						TIM_SetCompare1(TIM1,195); // 0度
					}
					if(WindosValue >= 25){
						sprintf(dis_data2,"窗帘状态:1/4开");
						TIM_SetCompare1(TIM1,190); // 45度
					}
					if(WindosValue >= 50){
							sprintf(dis_data2,"窗帘状态:半开");
						TIM_SetCompare1(TIM1,185); // 90度
					}
					if(WindosValue >= 75){
						sprintf(dis_data2,"窗帘状态:3/4开");
						TIM_SetCompare1(TIM1,180); // 135度
					}
					if(WindosValue == 100){
						sprintf(dis_data2,"窗帘状态:全开");
						TIM_SetCompare1(TIM1,175); // 180度
					}
					TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_1), dis_data2);
				break;
      }
      break;

Slider控制风扇转速

case ID_SLIDER_0: // Notifications sent by 'Slider'
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
        break;
      case WM_NOTIFICATION_RELEASED:
        break;
      case WM_NOTIFICATION_VALUE_CHANGED:	
				dis_Value = SLIDER_GetValue(WM_GetDialogItem(pMsg->hWin, ID_SLIDER_0));
				TEXT_SetFont(hItem, &GUI_Fontkongzhi_font24);
				sprintf(fanshan_data,"风扇转速:%d%%",dis_Value);
				TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_0), fanshan_data);
				TIM_SetCompare2(TIM2,dis_Value);
				sprintf(dis_data,"风扇转速:%d%%",dis_Value);
				break;
      }
      break;

2.4系统设置界面

使用Dialog为主窗体,中间使用Multiedit来做多个子页面切换的效果。这里只做了系统时间设置,可以在后续的拓展中,做WIFI信号选择等。

Multiedit效果就是在本身这个界面中,再添加一个其他子界面到这个页面中,然后通过上面的横条进行切换,但本身这个界面不能关闭或者隐藏挂起。具体使用方法可以参考官方的手册

时间设置主要是使用了Listwheel控制来做一个类似滑轮的效果来选择时间。
滑轮控制的写法是参考了野火的教程:野火列表轮教程
在这里插入图片描述

滑轮选择核心代码,原理参考野火的教程

    case ID_LISTWHEEL_0: // Notifications sent by 'Listwheel'
      switch(NCode) {
      case WM_NOTIFICATION_CLICKED:
         	hItem = WM_GetDialogItem(pMsg->hWin, ID_LISTWHEEL_0);
         	LISTWHEEL_SetTextColor(hItem, LISTWHEEL_CI_SEL, 0x191919);
        break;
      case WM_NOTIFICATION_RELEASED:
        break;
      case WM_NOTIFICATION_SEL_CHANGED:
           hItem = WM_GetDialogItem(pMsg->hWin, ID_LISTWHEEL_0);
            LISTWHEEL_SetTextColor(hItem, LISTWHEEL_CI_SEL, 0x007dfe);
            index1 = LISTWHEEL_GetPos(hItem);
            LISTWHEEL_SetSel(hItem,index1);
		    LISTWHEEL_GetItemText(hItem,index1,Year,10);
        break;
      }
      break;

2.5 关于界面

显示信息,没什么好说的
在这里插入图片描述

三、上位机-OneNet云端前端界面

这里的前端是使用了OneNet自带的大数据显示制作

首先在产品界面右侧找到应用管理
在这里插入图片描述

然后点这个
在这里插入图片描述点新建项目
在这里插入图片描述进去之后就可以在这里找到我们常用的控件了。有一些控件需要会员才能解锁。
在这里插入图片描述当我们获取到数据之后,就可以在前端直接拿后台的数据来进行显示
具体前端操作可以参考官方手册或者
View 2.0控件使用
View 2.0教程

这里列出我的数据源和过滤器
在这里插入图片描述在新建数据源这里设备选择你上传数据的设备。在下面的数据流就可以选择你上传时候的文本了。
在这里插入图片描述比如我上传数据时,报文是这样

		sprintf(temp,"{\"temperature\":\"%d\",\"humidity\":\"%d\",\"Lux\":\"%d\",\"ppm\":\"%d\"}",

那么就会有4个数据流,分别是temperature、humidity、Lux、ppm。在这里进行选择就可以拿到单个数据了。

温度计过滤器

function filter(data, rootData, variables) { 
 data.forEach((item, index) => {
     x = 2 * (item.value / 100)
     y = item.value
 });
 
return [{
    graphic: [{
        text: y +"°C"
    }, 
        ],
    wave: [x]
}]
}

仪表盘过滤器

function filter(data, rootData, variables) { 
function last(arr) {
    var len = arr ? arr.length : 0;
    if (len) return arr[len - 1];
}
return [{
    value: last(data).value,
}]
}

折线图过滤器

function filter(data, rootData, variables){
rootData.meiqi_lfWa.forEach((item, index) => {
    item.x = item.update_at
    item.y = item.value
    item.y1 = 250
    item.y2 = 200
});

return rootData.meiqi_lfWa
}

按钮和旋钮使用就比较简单了,只需要选择一个数据源,然后在样式里面就可以修改下发的命令了。贪方便可以全部控制控件都使用一个数据源来下发命令。这里的按钮1和按钮2就是按下和松开的状态,命令内容里面改成和下位机匹配的命令即可。

按钮
在这里插入图片描述旋钮
注意旋扭要发送当前的数值,只能写成{V}这个格式
在这里插入图片描述

四、总结

首先感谢大家看到这里,简单总结一下
这次课设的作品,总体来说做的完成度一般,没有特别深入,做到了基本的控制、数据显示、物联网,整体实现了一个简单的家居系统,系统可能还有许多BUG没有解决,大家参考的时候酌情参考。当然这个项目后续可以拓展的点很多

  • 增加OpenMv模块来实现人脸检测,这部分做到门锁中
  • 增加语音识别模块来提供语音控制
  • 增加外红遥控模块来控制空调、或者其他红外设备
  • 增加火焰传感器来检测火情
  • UI界面可以进行二次美化

这是我第一次写博客,写的不好的地方请见谅,来都来了点个赞再走吧!

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 19:03:54  更:2022-07-20 19:05:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:47:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码