工程与源代码下载地址
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.硬件选型

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

3.效果展示

硬件实物图

UI界面

UI界面使用图标均使用阿里图标库中的图案,具体请看UI界面设计。

前端界面

展示视频

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

b站

二、下位机-STM32程序

整体框架图

硬件定时器分配:

定时器 任务
TIM1 舵机1
TIM2 L298N电机调速
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.39 6002

产品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 0x00case 0x01 : printf("连接已拒绝,不支持的协议版本,准备重启\r\n");     //串口输出信息Connect_flag = 0;                                            //Connect_flag置零,重启连接break;                                                       //跳出分支case 0x01case 0x02 : printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n"); //串口输出信息Connect_flag = 0;                                            //Connect_flag置零,重启连接break;                                                       //跳出分支case 0x02case 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 0x05default   : 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);                           //关闭TIM4TIM_Cmd(TIM3,DISABLE);                           //关闭TIM3WiFi_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界面可以进行二次美化

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

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

  1. 【C++期末大作业】数据结构课设 | 智能公交车查询系统

    补发一下老东西(未优化) 目录 设计具体内容: 功能模块图: 各模块流程图: 运行效果图: 源码: 设计具体内容: 应用相关数据结构及算法,设计并实现一个具有查询功能的城市公交查询系统.设计其信息数据 ...

  2. stm32/esp32/cam智能安防系统远程视频监控物联网远程传输视频图像GSM短信报警

    系统实现了智能安防报警系统设计,人体感应报警,烟雾报警,GSM短信报警,同时支持远程视频监控功能,安卓端app端远程查看视频流.实物展示视频 ​​​​​​基于stm32单片机ESP32智能安防系统远程 ...

  3. 双非普通一本大一学生学期末课设——运动会比赛计分系统

    双非普通一本大一学生学期末课设--运动会比赛计分系统 并没有做文件系统-只能将就的看一下,也没有结构体 做的很粗糙,很烂,就是发出来纪念一下. 感觉有点像堆出来的一坨屎,但是好歹是自己儿子,为我的代码 ...

  4. 【从零开始JavaEE课设】《影院系统》(一) 需求分析 数据库设计 后端model类

    前言 吼吼吼,紧张的JavaEE阶段学习结束了,刚学完SSM框架,转眼这就到期末课设了.时间过得可真快啊.(然而我的框架使用和前端技术可能还是老八心爱的奥利给) 但是不论如何,课设就这么来了,这倒是个 ...

  5. 【从零开始JavaEE课设】《影院系统》成品展示——主页面

    距离上一次更新做课设的博文已经过去了半个月了.在上一篇博文中,课设还只停留在持久层.现在它已经完工了!先来给大家展示下成果.课设的制作过程后面还会持续更新的,毕竟总还有各种各样的技术等待总结(捂脸笑) ...

  6. 【从零开始JavaEE课设】《影院系统》成品展示——后台管理系统

    后台管理系统是整个课设最磨人的地方写起来的感觉简直就是: while(头发总数 != 0){头发总数--; } 员工登录 员工登录按钮位于前台页面的正上方,点击那个图标即可显示员工登录窗口. 输入员工 ...

  7. 数据结构课设航班订票系统(C语言版)

    数据结构课设航班订票系统(C语言版) 课设要求 (1) 航班管理.每条航线设计出合理的信息,包括:起点和终点站名,航班号,成员额定,飞行周期.飞机型号.余票量.航班票价等 (2) 客户管理.订票的客户 ...

  8. 基于STM32+UCOS的智能车载终端系统总结

    计算机能力挑战赛总结 一.赛题 1. 设计目标 2. 基本要求 3. 扩展要求 4. 发挥要求 二.分析 1. 基本要求-车载娱乐播放 2. 基本要求-车载仪表显示 3. 扩展要求-音频录制 4. 扩 ...

  9. 课设:指纹签到系统-支持PC网页端查看

    设计内容 设计基于ESP8266指纹签到系统,完成指纹签到和签退功能,以ESP8266 NodeMCU为核心,与TFT液晶屏.指纹模块相结合,通过无线节点,实现成员签到和签退操作监测,并通过网络以TC ...

  10. 东北大学20级计算机C语言课设-航空订票系统

    航空订票系统 开发人员:东北大学20级计算机系学生 开发团队:三人小组 开发语言:C语言 开发工具:vs2015 有疑问欢迎进行讨论 总代码数量大概在三千五百行左右 耗时近一个月的时间完成了该次课设, ...

最新文章

  1. 20145202、20145225、20145234 《信息安全系统设计基础》实验五 简单嵌入式WEB 服务器实验...
  2. 【渝粤教育】电大中专测量学 (5)作业 题库
  3. Linux dstat监控工具简讲
  4. msp430中如何连续对位进行取反_【万泉河】论PLC编程中的高内聚与低耦合
  5. 高并发大流量,大麦抢票的技术涅槃之路
  6. android 清理系统垃圾,安卓手机清理系统垃圾方法汇总
  7. JAVA学习day30--方法的重写
  8. Linux的memory日志,Linux:日志,cpu,memory,mount,load等系统信息查看
  9. 在计算机中安装Manjaro
  10. 简单几步让WinUSB设备变为多端点设备
  11. php 半角全角转换,php 把全角字符转换成半角
  12. 2020年河南省计算机对口升学分数线,2020年河南省单招分数线出来了吗?
  13. 移动机顶盒搭建网页服务器,超级简单搭建自己的私人影视库
  14. 在阿里云服务器发邮件
  15. weblogic部署常见问题
  16. socket 通信 error:88
  17. OpenCV-Python学习 <三> 颜色空间及其转换
  18. 2022年危险化学品生产单位安全生产管理人员考试题及答案
  19. 【docker】Dockerfile
  20. 8、数码相框之libjpeg的使用

热门文章

  1. 键盘无法输入字符和数字,但是功能键可以用
  2. OpenWrt开发必备软件模块——进程管理服务procd
  3. 增量式PID是什么?不知道你就落伍了
  4. devicemapper介绍
  5. PDF文件实现在线盖章
  6. python在线编辑器可视化_海龟编辑器(Python编辑器)
  7. 招商银行笔试题之修塔游戏
  8. 基于android点餐系统需求分析,基于Android智能终端的点餐系统设计研究
  9. DotSoft.C3DTools.v7.0.0.3民用勘测地图和GIS工具集合
  10. 我在名牌大学毕业后的经历——曾经努力过,就不会后悔