STM32课设-智能物联网家居系统(UCOSIII+STEMWIN)

news/2024/10/28 18:25:57/

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

目录

  • 一、功能分析与效果展示
    • 1.功能需求与分析
    • 2.硬件选型
    • 3.效果展示
      • 硬件实物图
      • UI界面
      • 前端界面
      • 展示视频
  • 二、下位机-STM32程序
    • 1.系统任务设计
      • 1.1 数据采集任务
      • 1.2 场景处理任务
      • 1.3 OneNet连接任务
      • 1.3 OneNet上传数据任务
      • 1.4 OneNet下发命令处理任务
      • 1.5 UI显示任务、触摸检测任务
      • 1.6 系统指示灯与堆栈检测任务
    • 2.UI界面设计
      • 2.1主界面
      • 2.1灯光界面
      • 2.2 数据显示界面
      • 2.3 电器控制界面
      • 2.4系统设置界面
      • 2.5 关于界面
  • 三、上位机-OneNet云端前端界面
  • 四、总结

一、功能分析与效果展示

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的复位IOMQTT_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 0x03case 0x04 : printf("连接已拒绝,无效的用户名或密码,准备重启\r\n");   //串口输出信息Connect_flag = 0;                                            //Connect_flag置零,重启连接						break;                                                       //跳出分支case 0x04case 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,表示正确,进入ifprintf("建立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);				    //清空缓冲区1memset(tempAll, 0, 100);				//清空缓冲区2memset(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 ENDbreak;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_atitem.y = item.valueitem.y1 = 250item.y2 = 200
});return rootData.meiqi_lfWa
}

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

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

四、总结

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

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

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


http://www.ppmy.cn/news/147551.html

相关文章

7-6 设计一个风扇Fan类

设计一个名为Fan的类表示一个风扇。这个类包括&#xff1a; 1.三个名为SlOW、MEDIUM和FAST&#xff0c;其值为1、2和3常量表示风扇的速度。 2.一个名为speed的int类型私有数据域表示风扇的速度&#xff08;默认值为SLOW&#xff09;。 3.一个名为on的boolean类型私有数据域表…

轴风扇和离心风扇--有什么区别?

任何产生运行热量可能对其性能产生负面影响的组件或系统都必须冷却。这是您在学校学到的简单工程规则之一&#xff0c;在您承担实际热管理项目任务之前可能会忽略它。 除了节流系统中的电源、应用一些散热器或使用管道或冷板之外&#xff0c;您将需要一个风扇来移动一些空气并…

arduino智能风扇

Arduino 智能风扇 文件 ArduinoIDE下载 https://www.arduino.cc 所需工具 ● Arduino Uno开发板 https://detail.tmall.com/item.htm?spm1139j.1.0.0.2fa62cadLV9YyO&id22375863328&ns1&abbucket1 DHT11 温湿度模块 DHT11DHT22温湿度传感器模块SHT30/SHT3031 AM23…

基于STM32水温炉温温度控制系统电路设计-分享

(1)、主控制器采用STM32单片机 (2)、必须要有温度检测装置&#xff0c;采用DS18B20 (3)、要有加热装置&#xff0c;采用220V交流电供电 (4)、要能够设置报警的温度上限值以及温度加热的下限值 (5)、要有声光报警装置&#xff0c;用于报警温度上限 (6)、要能够自动加热使温…

家庭装修工作分解结构

1000家庭装修1100各功能区装修确定1110门厅1120客厅1121客厅的环境1122客厅的色彩1123客厅的自然采光1124客厅的空间分隔1300卧室1310卧室的功能1320卧室的色彩1400书房 1500儿童房1600餐厅 1610餐厅 的环境1620餐厅的色彩1700厨房餐厅 的环境厨房的色彩厨房的通风1800卫生间卫…

EasyExcel实现excel区域三级联动(模版下载)

序号 前言需求不通过excel,实现省市区级联实战pom.xml配置controller配置service类业务处理类测试 前言 首先&#xff0c;我们先来了解一下java实现模板下载的几种方式 1、使用poi实现2、使用阿里的easyexcel实现 今天社长就给大家说一下easyexcel的实现模板下载的之旅。在这里…

【数据结构】——树习题

目录 题1题2题3题4题5题6题7题8题9 题1 1、设高度为h的二叉树上只有度为0和度为2的结点&#xff0c;则该二叉树中所包含的结点数至少为&#xff08;&#xff09;&#xff0c;最多为&#xff08;&#xff09;。 A、h &#xff1b;2h-1 B、2h-1 &#xff1b; 2h-1 C、2h1&#xf…

什么是代购系统

随着国内外市场的不断开放和消费升级&#xff0c;越来越多的消费者开始选择海外代购&#xff0c;寻求更具性价比和个性化的商品选购方式。因此&#xff0c;代购业务也如雨后春笋般发展起来&#xff0c;成为了一个广受欢迎的行业。 代购系统是为了满足消费者的需要而诞生的&…