Gitee:源码点这里 Github: 源码点这里
目录
- 1.功能分析和效果显示
-
- 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.功能需求与分析
- 收集空气中的四种常用数据(温度、湿度、光强、可燃气体含量)
- 控制舵机根据光强拉床帘(模拟卧室根据日出控制窗帘,智能卧室功能)
- 根据温度和湿度判断室内情况,控制电机和舵机(模拟高温自动窗户散热和风扇散热)
- 根据空气中可燃气体的含量,控制蜂鸣器和电机舵机(模拟气体泄漏报警,自动打开窗户和排气扇)
- 制作主控的Ul界面显示上述常用数据并控制家用电器(风扇、照明、门窗)
- 将上述常用数据上传到云数据库并制作前端UI显示数据。
- 通过手机连接云,远程监控家庭情况,控制云
简单分析一下
- 主控使用STM32.这些数据可以通过传统传感器收集数据。在程序中判断收集的值,然后控制舵机和电机。
- UI使用界面STEMWIN这个UI库来做,这个库整体用起来比较简单好用,做出来的UI也OK。
- 使用连网ESP8266模块,只要266模块就可以轻松驾驭。平台基本上是百度、阿里、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物联网智能家居系统
二、下位机-STM32程序
整体框架图 硬件定时器分配:
定时器 | 任务 |
---|---|
TIM1 | 舵机1 |
TIM2 | L298N电机调速 |
TIM3 | 定期发送心跳包 |
TIM4 | 保存云下发数据 |
TIM8 | 舵机2 |
UCOSIII软件定时器:蜂鸣器控制
1.系统任务设计
STM32中,使用UCOS-III该系统将该功能分为8个小任务。每个模块如何使用许多在线教程,这里没有一个接一个地发布,需要自己搜索。详细代码可参考开源连接
1.1 数据采集任务
将每个模块读取数据单独包装一个函数,将函数放入任务中,然后将数据读取到数组中,然后通过消息队列发送到需要数据的任务中。了解每个模块数据的大小,否则数组可能会越界卡住。考虑到家庭情况,您可以对数据一个小范围内。同时,通过全球变量更新数据显示界面的数据可以使数据显示更加流畅 锁定调度器,以防止调度器打乱时间顺序。MQ-2模块是使用ADC采集电压与DMA传输数据不受时间顺序的影响,因此没有锁
pre>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的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的复位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; //报文长度