# 一、项目原地址与创新点
## 项目原地址
[https://oshwhub.com/li-chuang-kai-fa-ban/chuang-lian-kong-zhi-kuo-zhan-ban](https://oshwhub.com/li-chuang-kai-fa-ban/chuang-lian-kong-zhi-kuo-zhan-ban)
## 本项目实物展示视频
[B站视频链接:【TaurusHard】【立创训练营】基于梁山派的智能窗帘](https://www.bilibili.com/video/BV1Mh4y1z7of/?spm_id_from=333.999.list.card_archive.click&vd_source=088ad4dedbbd5d327a72a2c32b269d5e)
## 创新点
本次项目设计在官方开源工程的基础上,增加了以下内容:
### 1.自定义可扩展通用协议
设计初衷是想通过自定义一套通用协议增加设备之间的兼容性,采用帧头+长度+命令域+数据域+校验的基本模型,校验采用CRC-16Modbus校验以保障通信过程中的安全性,通过UART0和串口调试助手实现模拟上位机对窗帘自动/手动模式的切换、开关窗帘的控制以及系统各信息状态(系统的实时模式、窗帘的实时状态、雨滴传感器百分比值、光照传感器百分比值、温湿度传感器的数据等)的获取等(即有线控制);另外,语音模块发送给MCU的部分也采用了该协议,包括后续扩展的模块也可以使用该协议,增强了设备之间的兼容性和扩展性。
### 2.DHT11模块
增加DHT11温湿度传感器模块,实现对环境温度和湿度的检测;
### 3.WiFi模块
增加WiFi模块,通过WiFi模块连接OneNET云平台,实现系统模式、窗帘状态、雨滴传感器百分比值、光照传感器百分比值、温湿度传感器的数据等的实时上传;除此之外,通过OneNET云平台下发指令实现远程控制窗帘自动/手动模式切换及开关窗帘的控制;
### 4.OLED模块
增加OLED模块,实现系统模式、窗帘状态、雨滴传感器百分比值、光照传感器百分比值、温湿度传感器的数据等实时显示,增强人机交互性;
### 5.手势检测功能
增加手势传感器模块,通过识别各种手势实现窗帘自动/手动模式切换及开关窗帘的控制。
# 二、系统框图
整个系统的设计框图如下,除了官方工程中的雨滴传感器、光照强度传感器、红外接收模块、电机驱动、语音模块,新增了DHT11温湿度传感器、WiFi模块、OLED模块、手势识别模块和串口通信部分,同时预留了扩展接口方便日后扩展功能。
![image.png](//image.lceda.cn/pullimage/WKtchzrxBVMiYQTcj8882f9aR0LKk1d9cRxZxXKR.png)
# 三、引脚选择
在官方案例的基础上,根据要新增的功能模块挑选合适的引脚,梁山派主控板的P1和P2引脚选择分别如下。
![image.png](//image.lceda.cn/pullimage/DDY1eICLHSL6mbfD4on5K2bPyqTvdE8fJqTSisT9.png)
![image.png](//image.lceda.cn/pullimage/u627qUCdwzvDUkTZpdWoOOXNEgOYRco2KwD1ohJY.png)
# 四、功能实现详细指标
1.系统上电时,OLED显示"Connect WiFi...",等待WiFi和OneNET云平台连接成功且各传感器初始化完成后系统开始运行;运行期间OLED实时显示当前模式(自动AUTO/手动HAND)、窗帘状态(打开ON/关闭OFF)、雨滴含量百分比、光照强度百分比、DHT11温湿度等信息;
2.系统运行期间,每隔5s上报当前模式、窗帘状态、雨滴含量百分比、光照强度百分比、DHT11温湿度至OneNET云平台;
3.自动模式下,当检测到雨滴含量百分比超过阈值时,打开窗帘;当前检测到光照强度百分比超过阈值时,打开窗帘,否则关闭窗帘;
4.支持通过语音控制自动/手动模式的切换、窗帘打开/关闭:
系统上电时会播报“您可以叫我小派小派唤醒我”,此时说出“小派小派”,语音模块回复“我在”,即可唤醒语音模块
说出“切换为自动模式”,语音模块回复“已为您切换为自动模式”,此时系统切换至自动模式;
说出“切换为手动模式”,语音模块回复“已为您切换为手动模式”,此时系统切换至手动模式;
说出“打开窗帘”,语音模块回复“已为您打开窗帘”,此时系统打开窗帘,系统切换回手动模式;
说出“关闭窗帘”,语音模块回复“已为您关闭窗帘”,此时系统关闭窗帘,系统切换回手动模式;
5.支持通过红外遥控器控制自动/手动模式的切换、窗帘打开/关闭:
按下红外遥控器“*”键,系统切换至自动模式,语音模块播报“当前为自动模式”;
按下红外遥控器“#”键,系统切换至手动模式,语音模块播报“当前为手动模式”;
按下红外遥控器“<”键,系统打开窗帘,系统切换回手动模式,语音模块播报“窗帘已打开”;
按下红外遥控器“>”键,系统关闭窗帘,系统切换回手动模式,语音模块播报“窗帘已关闭”;
6.支持通过串口指令控制自动/手动模式的切换、窗帘打开/关闭及系统状态信息的读取:
通过UART0发送指令“A5 05 00 62 B3”,系统切换至自动模式,语音模块播报“当前为自动模式”;
通过UART0发送指令“A5 05 01 A3 73”,系统切换至手动模式,语音模块播报“当前为手动模式”;
通过UART0发送指令“A5 05 02 E3 72”,系统打开窗帘,系统切换回手动模式,语音模块播报“窗帘已打开”;
通过UART0发送指令“A5 05 03 22 B2”,系统关闭窗帘,系统切换回手动模式,语音模块播报“窗帘已关闭”;
通过UART0发送指令“A5 05 08 63 75”,UART0回复“A5 0B 08 01 00 02 08 17 29 4A 51”,表示当前为手动模式、窗帘为关闭状态、雨滴含量百分比为2%、光照强度百分比为8%、DHT11温度为23度、DHT11湿度为41%;
7.支持通过手势控制自动/手动模式的切换、窗帘打开/关闭:
做出“向上”手势动作,系统切换至自动模式,语音模块播报“当前为自动模式”;
做出“向下”手势动作,系统切换至手动模式,语音模块播报“当前为手动模式”;
做出“向左”手势动作,系统打开窗帘,系统切换回手动模式,语音模块播报“窗帘已打开”;
做出“向右”手势动作,系统关闭窗帘,系统切换回手动模式,语音模块播报“窗帘已关闭”;
8.支持通过OneNET云平台控制自动/手动模式的切换、窗帘打开/关闭:
通过云平台下发指令“CMD0#”,系统切换至自动模式,语音模块播报“当前为自动模式”;
通过云平台下发指令“CMD1#”,系统切换至手动模式,语音模块播报“当前为手动模式”;
通过云平台下发指令“CMD2#”,系统打开窗帘,系统切换回手动模式,语音模块播报“窗帘已打开”;
通过云平台下发指令“CMD3#”,系统关闭窗帘,系统切换回手动模式,语音模块播报“窗帘已关闭”。
# 五、硬件模块
## 1.雨滴传感器模块
雨滴传感器模块的工作原理是通过检测水滴的导电性来判断是否下雨。水滴接触到电极上时会改变电极之间的电阻值。通过测量电阻值的变化可以判断是否有水滴存在。为了防止雨滴传感器短路,增加一个电阻R1作为负载。电路图如下:
![image.png](//image.lceda.cn/pullimage/eZtKFBTqLieOjJwRB8HdXza8QZrK5yPLNV1pA87R.png)
## 2.光照传感器模块
光照传感器模块本质上是通过光敏电阻进行识别。它是一种特殊的电阻器,随着光照强度的升高电阻值会迅速降低,其在无光照时,几乎呈高阻状态,因此无光照时电阻很大。电路图如下:
![image.png](//image.lceda.cn/pullimage/2aghCjwQovYYCzbo2ETeHDITQ9Vz9pBQ2rfIKc5w.png)
## 3.步进电机驱动模块
本工程使用原工程的二相四线式步进电机,采用八拍控制方式(即【A+】->【A+B+】->【B+】->【B+A-】->【A-】->【A-B-】->【B-】->【B-A+】),电机驱动使用两路L9110S,根据其数据手册中的应用电路设计如下电路:
![image.png](//image.lceda.cn/pullimage/NprnRIjx3NxWsQ3TQfcfK1osnWMjQTo20nPIwIxV.png)
## 4.红外接收模块
红外接收模块使用官方案例中的模块,根据数据手册中的应用电路设计如下电路:
![image.png](//image.lceda.cn/pullimage/DP0iEBp2oyiIerYRTgniRYU8H9zqvgRhGhRXyqeL.png)
## 5.语音识别模块
语音识别模块使用海凌科电子的HLK-V20,其支持本地指令离线识别,可自由定制唤醒词、命令词与应答播报词,具有丰富的外围接口等。电路图如下:
![image.png](//image.lceda.cn/pullimage/Sf9v3dLpqw5QJKCnj6luRSCo4K068DvT4jLIaTkE.png)
## 6.DHT11温湿度传感器模块
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。其成本低、长期稳定、可以测量相对湿度和温度测量,且使用单总线进行温湿度采集。
本工程在原理图中预留出DHT11模块的接口,如下图所示。
![image.png](//image.lceda.cn/pullimage/2W1rh2NzOKWABkmBOZb1yzCHFLfUQeE145IN28vZ.png)
## 7.WiFi模块
本工程的WiFi模块使用ESP8266系列无线模块,其是安信可科技自主研发设计的一系列高性价比WiFisOC模组。该系列模块支持标准的IEEE802.11 b/g/n 协议,内置完整的TCP/IP协议栈。用户可以使用该系列模块为现有的设备添加联网功能,也可以构建独立的网络控制器,广泛应用于智能穿戴,智能家居,家庭安防,遥控器,汽车电子,智慧照明,工业物联网等领域。
本工程在原理图中预留出WiFi模块的接口,如下图所示。
![image.png](//image.lceda.cn/pullimage/SykjYAww9oUdHiJXG2mS00iRkCoNE8WZyPbeP2e4.png)
## 8.OLED显示屏模块
本工程选用一款0.96寸IIC通信的OLED屏幕,用来显示窗帘系统的各个参数,包括当前模式(自动AUTO/手动HAND)、窗帘状态(打开ON/关闭OFF)、雨滴含量百分比、光照强度百分比、DHT11温湿度等信息。
本工程在原理图中预留出OLED模块的接口,如下图所示。
![image.png](//image.lceda.cn/pullimage/Rxc2DOCQ5yxXgEikt8wAteGNRi4ZUGMO4CrCqyiJ.png)
## 9.手势识别传感器模块
本工程采用PAJ7620U2作为手势识别传感器,增加手势识别功能,其是原相科技(PixArt)公司推出的一款光学数组式传感器,内置光源和环境光抑制滤波器集成的 LED,镜头和手势感测器在一个小的立方体模组,能在黑暗或低光环境下工作。同时传感器内置手势识别,支持9个手势类型和输出的手势中断和结果。除此之外,模组还提供接近检测功能,可用于感知测量物体的亮度及物体的接近或离开。
详细介绍可见本人的移植文档(在模块移植手册的2.59小节):[https://lceda001.feishu.cn/wiki/JNvYwEU5SiGldFkNcxncYXhZnZc](https://lceda001.feishu.cn/wiki/JNvYwEU5SiGldFkNcxncYXhZnZc)
本工程在原理图中预留出手势识别传感器模块的接口,如下图所示。
![image.png](//image.lceda.cn/pullimage/JCebAgraUkdDfloKhgAhTl4Mot7ZEVMwzBfhs2Jj.png)
## 10.电源指示灯
本工程增加一路LED指示灯用以指示扩展板的上电状态,扩展板通电后LED会亮起,如下图所示。
![image.png](//image.lceda.cn/pullimage/ka1zMJtWhuGgyLHXq3WhnSUChNzwfwHJ1OxwoHOw.png)
## 11.其他预留接口
为了保证扩展板的可扩展性,预留部分GPIO和UART方便后续的扩展,如下图所示。
![image.png](//image.lceda.cn/pullimage/bd22BRCUU8kpTm4rrzKQos4OfiepHtZy4EvZoQlZ.png)
![image.png](//image.lceda.cn/pullimage/Pwm7l7UUvHIF4ebqqfmTclu5PLau3UAGS2JLkMQY.png)
# 六、软件设计
## 1.通用通信协议的拟定
协议采用帧头+长度+命令域+数据域+校验的方式拟定,其中帧头为固定的一个字节0xA5;长度为整帧报文的长度,最大长度为55字节(5字节固定长度+50字节可变数据域长度);命令域为1字节,最多支持255种不同的命令;数据域包含通信中需要传输的数据,为可变长区域,支持0-50字节;校验采用CRC-16校验,占两字节,采用小端存储方式,采用ModbusCRC-16校验方式、多项式为0xA001、初始值为0xFFFF,适用工业数据通信。暂拟定以下命令:
| 帧头 | 长度 | 命令域 | 数据域 | CRC校验 | 描述 |
| :---: | :---: | :---: | :---: | :---: | :---: |
| A5 | 05 | 00 | 无 | 62 B3 | COM命令(串口->MCU)切换为自动模式 |
| A5 | 05 | 01 | 无 | A3 73 | COM命令(串口->MCU)切换为手动模式 |
| A5 | 05 | 02 | 无 | E3 72 | COM命令(串口->MCU)打开窗帘 |
| A5 | 05 | 03 | 无 | 22 B2 | COM命令(串口->MCU)关闭窗帘 |
| A5 | 05 | 04 | 无 | 63 70 | 语音模块命令(语音模块->MCU)切换为自动模式 |
| A5 | 05 | 05 | 无 | A2 B0 | 语音模块命令(语音模块->MCU)切换为手动模式 |
| A5 | 05 | 06 | 无 | E2 B1 | 语音模块命令(语音模块->MCU)打开窗帘 |
| A5 | 05 | 07 | 无 | 23 71 | 语音模块命令(语音模块->MCU)关闭窗帘 |
| A5 | 05 | 08 | 无 | 63 75 | COM命令(串口->MCU)查询当前系统状态 |
| A5 | 0B | 08 | 第一字节表示当前系统模式
第二字节表示窗帘状态
第三字节表示雨滴含量百分比
第四字节表示光照强度百分比
第五字节表示DHT11温度
第六字节表示DHT11湿度百分比 | XX XX | COM命令(MCU->串口)查询当前系统状态回复
例如UART0回复“A5 0B 08 01 00 02 08 17 29 4A 51”,
表示当前为手动模式、窗帘为关闭状态、雨滴含量百分比为2%、
光照强度百分比为8%、DHT11温度为23度、DHT11湿度为41%; |
## 2.雨滴传感器和光照传感器模块
雨滴传感器和光照传感器部分均采用AD采样的方式实现,根据原理图,雨滴传感器AD采样接口为PF8,对应ADC2的通道6;光照传感器AD采样接口为PF6,对应ADC2的通道4。代码与官方例程类似,由于篇幅限制,具体代码可查看工程。
另外,在adc.c的基础上,增加了简单的均值滤波函数。
![image.png](//image.lceda.cn/pullimage/6f7yx2qjTzIzXMnirJN69KryeTtgXWxvV6Y6MHGr.png)
## 3.步进电机模块
步进电机模块代码与官方例程类似,由于篇幅限制,具体代码可查看工程。
## 4.红外接收模块
红外接收模块代码与官方例程类似,由于篇幅限制,具体代码可查看工程。
代码差异点:
![image.png](//image.lceda.cn/pullimage/gOC8yuj0FQkv76Fy01lTBZoaIhUyeKyrAlc4uzdd.png)
## 5.语音模块
语音模块的详细配置见官方的文档和视频,这里不再进行赘述,下面是几张关键配置的截图。
![image.png](//image.lceda.cn/pullimage/6ojP4iAd4EKJbagfAdgHujtNnaDS6pzTT1P4lDln.png)
![image.png](//image.lceda.cn/pullimage/D1dJuij8DEdEsJUmrGfIcCh9IwNCypKAMt0L2KEN.png)
![image.png](//image.lceda.cn/pullimage/1LCXmb9e6iEuN5VpJJd3hAaVheJtpDNn5tiYKBLX.png)
语音模块代码与官方例程类似,由于篇幅限制,具体代码可查看工程。
## 6.DHT11温湿度传感器模块
DHT11温湿度传感器模块代码与官方移植手册类似,由于篇幅限制,具体代码可查看工程。
## 7.WiFi模块
### OneNET云平台
本工程选择中国移动OneNET作为云平台进行数据的交互和远程控制。
进入OneNET官网->开发者中心->多协议接入,新建MQTT测试产品:
![image.png](//image.lceda.cn/pullimage/4EcidofC2ebSPHwOgxDt5xIgUfM0zYN1Tr7bcubu.png)
在产品概况界面,可以查看到自己的产品ID:
![image.png](//image.lceda.cn/pullimage/hcPyYrNMDlpU429UkpD7XluHUq5y5UDseCurMBNy.png)
在设备列表->设备详情界面,查看自己的设备ID和鉴权信息:
![image.png](//image.lceda.cn/pullimage/UOjMIZrK3On1z3a89v99rL9fyCOkNFBOoQfDRPP6.png)
将上述三个信息分别对应修改onenet.c中的红框处,其中PROID为产品ID、AUTH_INFO为鉴权信息、DEVID为设备ID:
![image.png](//image.lceda.cn/pullimage/xb3QiAxCGlkhLbRcjNyS08m8mCmFcznJx6lxgSnF.png)
修改好后即可通过WiFi模块连接上OneNET云平台(前提是WiFi模块能够正常联网)。
### 云平台下发指令
拟定四种指令控制系统模式的切换和窗帘的开关,分别为“CMD0#”、“CMD1#”、“CMD2#”和“CMD3#”,具体交互方式为:
通过云平台下发指令“CMD0#”,系统切换至自动模式,语音模块播报“当前为自动模式”;
通过云平台下发指令“CMD1#”,系统切换至手动模式,语音模块播报“当前为手动模式”;
通过云平台下发指令“CMD2#”,系统打开窗帘,系统切换回手动模式,语音模块播报“窗帘已打开”;
通过云平台下发指令“CMD3#”,系统关闭窗帘,系统切换回手动模式,语音模块播报“窗帘已关闭”。
OneNET云平台下发指令入口见下图,发送时选择字符串发送。
![image.png](//image.lceda.cn/pullimage/2nrDxharrsds7Lb1gPTSjxZd7a44cxNwenMSBDKt.png)
![image.png](//image.lceda.cn/pullimage/dLyOEUewrDnQRWdsfwDA6QULtnHipGdVjzTLcB6a.png)
WiFi模块代码和与OneNET云平台交互代码过多,由于篇幅限制,具体代码可查看工程。
关键代码如下:
esp8266.c中WiFi与OneNET信息:
![image.png](//image.lceda.cn/pullimage/cIqHr0U1NAAnmhShiErPWzUT58KaGtq3koA1b4mM.png)
esp8266.c中WiFi模块初始化:
![image.png](//image.lceda.cn/pullimage/FE5unToVGlrPTYp0rtSrqyAyEyqwhF5leowIaVMu.png)
onenet.c中组包发送至云平台:
![image.png](//image.lceda.cn/pullimage/QPJHz9vaZ6zxmueWU3SjzHvhB8hbsRAkWFZg1Uy5.png)
onenet.c中接收云平台下发指令处理:
![image.png](//image.lceda.cn/pullimage/K2jMP1DKue0DFGHQpZ7Xa0XiOcMgk8q22FC4sBLH.png)
## 8.OLED模块
OLED模块代码与官方移植手册类似,由于篇幅限制,具体代码可查看工程。
## 9.手势识别传感器模块
手势识别传感器模块代码由于篇幅限制,具体代码可查看本人的移植文档(在模块移植手册的2.59小节):[https://lceda001.feishu.cn/wiki/JNvYwEU5SiGldFkNcxncYXhZnZc](https://lceda001.feishu.cn/wiki/JNvYwEU5SiGldFkNcxncYXhZnZc)
## 10.自定义通用通信协议处理设计
### 设计思路
定义一个结构体用以标识接收数据的状态以及存放接收的数据,封装一个接口函数recv\_frame\_process\(\),输入参数为uint8\_t的数据,在该函数中处理接收数据的状态,如果按照自定义协议接收,则存入数据至结构体的数据缓冲区中,接收完一整帧数据后进行CRC校验,若通过则将结构体的数据缓冲区拷贝至数据处理缓冲区中,并置标志位等待系统逻辑处理该报文;若不通过则重新接收,等待下一帧数据到来。
代码如下所示。
### com.h
```
#ifndef _COM_H_
#define _COM_H_
#include <stdio.h>
#include <string.h>
#include "crc.h"
#define U8_FRAME_HAED ((uint8_t)0xA5)
#define U8_FRAME_FIXED_FORWARD_LEN ((uint8_t)3)
#define U8_FRAME_FIXED_BACKWARD_LEN ((uint8_t)2)
#define U8_FRAME_FIXED_LEN ((uint8_t)(U8_FRAME_FIXED_FORWARD_LEN + U8_FRAME_FIXED_BACKWARD_LEN))
// LEN
#define U8_MAX_LEN_PAYLOAD ((uint8_t)50)
#define U8_MAX_LEN_DATA ((uint8_t)(U8_MAX_LEN_PAYLOAD + U8_FRAME_FIXED_LEN))
// CMD
#define U8_CMD_COM_AUTO_MODE ((uint8_t)0x00) // COM命令(串口->MCU)自动模式
#define U8_CMD_COM_HAND_MODE ((uint8_t)0x01) // COM命令(串口->MCU)手动模式
#define U8_CMD_COM_OPEN_CURTAIN ((uint8_t)0x02) // COM命令(串口->MCU)打开窗帘
#define U8_CMD_COM_CLOSE_CURTAIN ((uint8_t)0x03) // COM命令(串口->MCU)关闭窗帘
#define U8_CMD_VOICE_AUTO_MODE ((uint8_t)0x04) // 语音模块命令(语音模块->MCU)自动模式
#define U8_CMD_VOICE_HAND_MODE ((uint8_t)0x05) // 语音模块命令(语音模块->MCU)手动模式
#define U8_CMD_VOICE_OPEN_CURTAIN ((uint8_t)0x06) // 语音模块命令(语音模块->MCU)打开窗帘
#define U8_CMD_VOICE_CLOSE_CURTAIN ((uint8_t)0x07) // 语音模块命令(语音模块->MCU)关闭窗帘
#define U8_CMD_COM_SYS_STATUS ((uint8_t)0x08) // COM命令(串口->MCU)查询当前系统状态
// FRAME
typedef enum
{
pos_frame_head,
pos_frame_len,
pos_frame_cmd,
pos_frame_payload
} pos_frame_em;
typedef enum
{
RECV_STATUS_HEAD,
RECV_STATUS_LEN,
RECV_STATUS_DATA
} Em_Recv_Status;
typedef struct
{
uint8_t recv_data_status;
uint8_t recv_data_len;
uint8_t recv_data_pos;
uint8_t recv_data[U8_MAX_LEN_DATA];
uint8_t recv_finish_flag;
} ST_Uart_Recv_Frame;
void make_and_send_frame(uint8_t u8_command, uint8_t *u8_payload, uint8_t u8_payload_len);
void recv_frame_process(uint8_t u8_data);
void recv_finish_process(void);
#endif
```
### com.c
```
#include "com.h"
#include "mode.h"
#include "voice.h"
#include "motor.h"
#include "uart.h"
ST_Uart_Recv_Frame st_uart_recv_frame;
uint8_t recv_buffer[U8_MAX_LEN_DATA] = { 0 };
uint8_t send_reply_data[U8_MAX_LEN_DATA] = { 0 };
void uart_recv_frame_init(void)
{
memset(&st_uart_recv_frame, 0, sizeof(ST_Uart_Recv_Frame));
}
static void recv_frame_process_reinit(void)
{
ST_Uart_Recv_Frame *recv_frame = &st_uart_recv_frame;
recv_frame->recv_data_pos = 0;
recv_frame->recv_data_len = 0;
recv_frame->recv_data_status = RECV_STATUS_HEAD;
recv_frame->recv_finish_flag = 0;
}
void make_and_send_frame(uint8_t u8_command, uint8_t *u8_payload, uint8_t u8_payload_len)
{
uint8_t send_buffer[U8_MAX_LEN_DATA] = { 0 };
uint16_t crc = 0;
if(u8_payload_len > U8_MAX_LEN_PAYLOAD)
{
return;
}
send_buffer[pos_frame_head] = U8_FRAME_HAED;
send_buffer[pos_frame_len] = u8_payload_len + U8_FRAME_FIXED_LEN;
send_buffer[pos_frame_cmd] = u8_command;
memcpy(send_buffer + pos_frame_payload, u8_payload, u8_payload_len);
crc = Cal_CRC_MultipleData(send_buffer, (u8_payload_len + U8_FRAME_FIXED_FORWARD_LEN));
send_buffer[pos_frame_payload + u8_payload_len] = crc & 0xFF;
send_buffer[pos_frame_payload + u8_payload_len + 1] = (crc >> 8) & 0xFF;
usart_send_string(BSP_USART, send_buffer, send_buffer[pos_frame_len]);
}
void recv_frame_process(uint8_t u8_data)
{
ST_Uart_Recv_Frame *recv_frame = &st_uart_recv_frame;
switch(recv_frame->recv_data_status)
{
case RECV_STATUS_HEAD:
if(U8_FRAME_HAED == u8_data)
{
recv_frame->recv_data[recv_frame->recv_data_pos++] = u8_data;
++recv_frame->recv_data_status;
}
else
{
}
break;
case RECV_STATUS_LEN:
recv_frame->recv_data[recv_frame->recv_data_pos++] = u8_data;
recv_frame->recv_data_len = u8_data;
++recv_frame->recv_data_status;
if((recv_frame->recv_data_len < U8_FRAME_FIXED_LEN) || (recv_frame->recv_data_len > U8_MAX_LEN_DATA))
{
recv_frame_process_reinit();
}
else
{
}
break;
case RECV_STATUS_DATA:
recv_frame->recv_data[recv_frame->recv_data_pos++] = u8_data;
if(recv_frame->recv_data_pos == recv_frame->recv_data_len)
{
if(0x0000 == Cal_CRC_MultipleData(recv_frame->recv_data, recv_frame->recv_data_len))
{
memcpy(recv_buffer, recv_frame->recv_data, recv_frame->recv_data_len);
recv_frame->recv_finish_flag = 1;
}
else
{
recv_frame_process_reinit();
}
}
else
{
}
break;
default:
recv_frame_process_reinit();
break;
}
}
void recv_finish_process(void)
{
ST_Uart_Recv_Frame *recv_frame = &st_uart_recv_frame;
uint8_t payload[20];
if(1 == recv_frame->recv_finish_flag)
{
switch(recv_buffer[pos_frame_cmd])
{
case U8_CMD_COM_AUTO_MODE: // COM命令(串口->MCU)自动模式
set_sys_mode(SYS_MODE_AUTO);
play_voice_auto_mode();
break;
case U8_CMD_COM_HAND_MODE: // COM命令(串口->MCU)手动模式
set_sys_mode(SYS_MODE_HAND);
play_voice_hand_mode();
break;
case U8_CMD_COM_OPEN_CURTAIN: // COM命令(串口->MCU)打开窗帘
set_sys_mode(SYS_MODE_HAND);
open_curtain();
set_sys_status(SYS_STATUS_OPEN);
play_voice_open_curtain();
break;
case U8_CMD_COM_CLOSE_CURTAIN: // COM命令(串口->MCU)关闭窗帘
set_sys_mode(SYS_MODE_HAND);
close_curtain();
set_sys_status(SYS_STATUS_CLOSE);
play_voice_close_curtain();
break;
case U8_CMD_VOICE_AUTO_MODE: // 语音模块命令(语音模块->MCU)自动模式
set_sys_mode(SYS_MODE_AUTO);
break;
case U8_CMD_VOICE_HAND_MODE: // 语音模块命令(语音模块->MCU)手动模式
set_sys_mode(SYS_MODE_HAND);
break;
case U8_CMD_VOICE_OPEN_CURTAIN: // 语音模块命令(语音模块->MCU)打开窗帘
set_sys_mode(SYS_MODE_HAND);
open_curtain();
set_sys_status(SYS_STATUS_OPEN);
break;
case U8_CMD_VOICE_CLOSE_CURTAIN: // 语音模块命令(语音模块->MCU)关闭窗帘
set_sys_mode(SYS_MODE_HAND);
close_curtain();
set_sys_status(SYS_STATUS_CLOSE);
break;
case U8_CMD_COM_SYS_STATUS: // COM命令(串口->MCU)查询当前系统状态
payload[0] = get_sys_mode();
payload[1] = get_sys_status();
payload[2] = get_percent_rain();
payload[3] = get_percent_light();
payload[4] = get_dht11_temp();
payload[5] = get_dht11_humi();
make_and_send_frame(U8_CMD_COM_SYS_STATUS, payload, 6);
break;
default:
break;
}
recv_frame_process_reinit();
}
}
```
## 11.系统逻辑处理设计
系统逻辑处理设计分为系统初始化和系统逻辑处理。系统初始化包括各个硬件模块的初始化、OneNET平台的连接等。除此之外还定义了手势识别结果处理、系统各个参数的设置和获取接口等,方便其他函数调用。
系统逻辑处理部分包括:
1.接收报文处理;
2.红外命令判断;
3.手势检测处理;
4.获取DHT11温湿度传感器值;
5.获取雨滴百分比值和光照百分比值;
6.OLED显示处理;
7.自动模式逻辑处理;
8.步进电机限位判断;
9.每5s进行数据采集并上传至云平台;
10.OneNET下发指令处理。
代码如下所示。
### mode.h
```
#ifndef _MODE_H
#define _MODE_H
#include "gd32f4xx.h"
// 0-自动 1-手动
#define SYS_MODE_AUTO ((uint8_t)0)
#define SYS_MODE_HAND ((uint8_t)1)
// 0-窗帘关 1-窗帘开
#define SYS_STATUS_CLOSE ((uint8_t)0)
#define SYS_STATUS_OPEN ((uint8_t)1)
#define THRESHOLD_PERCENT_RAIN ((uint8_t)40) // 雨滴百分比阈值
#define THRESHOLD_PERCENT_LIGHT ((uint8_t)40) // 光照百分比阈值
void set_sys_mode(uint8_t mode);
uint8_t get_sys_mode(void);
void set_sys_status(uint8_t status);
uint8_t get_sys_status(void);
uint8_t get_percent_rain(void);
uint8_t get_percent_light(void);
uint8_t get_dht11_temp(void);
uint8_t get_dht11_humi(void);
void sys_init(void);
void sys_process(void);
#endif
```
### mode.c
```
#include "mode.h"
#include "motor.h"
#include "adc.h"
#include "voice.h"
#include "com.h"
#include "ir.h"
#include "led.h"
#include "uart.h"
#include "esp8266.h"
#include "onenet.h"
#include "MqttKit.h"
#include "oled.h"
#include "adc.h"
#include "paj7620u2.h"
#include "dht11.h"
uint8_t sys_mode = SYS_MODE_AUTO;
uint8_t sys_status = SYS_STATUS_CLOSE;
uint8_t *dataPtr = NULL;
uint16_t cnt = 0;
uint8_t percent_rain = 0, percent_light = 0;
uint8_t oled_dis_buffer[50];
uint8_t dht11_temp = 0, dht11_humi = 0;
// head len cmd data crc
/*
//COM命令(串口->MCU)
A5 05 00 62 B3 //自动模式
A5 05 01 A3 73 //手动模式
A5 05 02 E3 72 //打开窗帘
A5 05 03 22 B2 //关闭窗帘
//语音模块命令(语音模块->MCU)
A5 05 04 63 70 //自动模式
A5 05 05 A2 B0 //手动模式
A5 05 06 E2 B1 //打开窗帘
A5 05 07 23 71 //关闭窗帘
//COM命令(串口->MCU)查询当前系统状态
发送:A5 05 08 63 75 回复:A5 0B 08 01 00 02 08 17 29 4A 51
*/
/******************************************************************
* 函 数 名 称:set_sys_mode
* 函 数 说 明:设置系统模式
* 函 数 形 参: SYS_MODE_AUTO--自动模式
* SYS_MODE_HAND--手动模式
* 函 数 返 回:无
* 作 者:TaurusHard
* 备 注:无
******************************************************************/
void set_sys_mode(uint8_t mode)
{
sys_mode = mode;
motor_stop(); // 防止电机正在动作时,切换模式,导致电机死机
}
uint8_t get_sys_mode(void)
{
return sys_mode;
}
void set_sys_status(uint8_t status)
{
sys_status = status;
}
uint8_t get_sys_status(void)
{
return sys_status;
}
uint8_t get_percent_rain(void)
{
return percent_rain;
}
uint8_t get_percent_light(void)
{
return percent_light;
}
uint8_t get_dht11_temp(void)
{
return dht11_temp;
}
uint8_t get_dht11_humi(void)
{
return dht11_humi;
}
static void gesture_check_process(void)
{
uint16_t gesture_data;
uint8_t data[2];
if(!GS_Read_nByte(PAJ_GET_INT_FLAG1, 2, &data[0])) // 读取手势状态
{
gesture_data = (uint16_t)data[1] << 8 | data[0];
if(gesture_data)
{
switch(gesture_data)
{
case GES_UP: // 向上 自动模式
printf("[GES]UP\r\n");
set_sys_mode(SYS_MODE_AUTO);
play_voice_auto_mode();
break;
case GES_DOWM: // 向下 手动模式
printf("[GES]DOWN\r\n");
set_sys_mode(SYS_MODE_HAND);
play_voice_hand_mode();
break;
case GES_LEFT: // 向左 打开窗帘
printf("[GES]LEFT\r\n");
set_sys_mode(SYS_MODE_HAND);
open_curtain();
set_sys_status(SYS_STATUS_OPEN);
play_voice_open_curtain();
break;
case GES_RIGHT: // 向右 关闭窗帘
printf("[GES]RIGHT\r\n");
set_sys_mode(SYS_MODE_HAND);
close_curtain();
set_sys_status(SYS_STATUS_CLOSE);
play_voice_close_curtain();
break;
default:
break;
}
}
}
}
static void auto_mode_process(void)
{
if(percent_rain > THRESHOLD_PERCENT_RAIN)
{
open_curtain();
set_sys_status(SYS_STATUS_OPEN);
return;
}
if(percent_light > THRESHOLD_PERCENT_LIGHT)
{
open_curtain();
set_sys_status(SYS_STATUS_OPEN);
return;
}
else
{
close_curtain();
set_sys_status(SYS_STATUS_CLOSE);
}
}
void sys_init(void)
{
led_init(); // LED初始化
usart_gpio_config(115200U); // UART0初始化
hlk_usart_init(9600U); // 语音模块初始化
uart2_init(115200U); // UART2初始化
adc_init(); // ADC初始化
ir_init(); // 红外初始化
motor_init(); // 电机初始化
while(dht11_init()) // DHT11初始化
{
printf("DHT11 Init Error!\r\n");
delay_ms(200);
}
printf("DHT11 Init OK!\r\n");
while(!paj7620u2_init()) // PAJ7620U2传感器初始化
{
printf("PAJ7620U2 Init Error!\r\n");
delay_ms(500);
}
printf("PAJ7620U2 Init OK!\r\n");
OLED_Init(); // OLED初始化
OLED_ShowString(8, 25, "Connect WiFi...", 16);
ESP8266_Init(); // ESP8266初始化
while(OneNet_DevLink()) // 连接OneNET
{
delay_ms(500);
}
OLED_Clear();
}
void sys_process(void)
{
recv_finish_process(); // 接收报文处理
infrared_command_judgment(); // 红外命令判断
gesture_check_process(); // 手势检测处理
DHT11_Read_Data(&dht11_temp, &dht11_humi); // 读取DHT11温湿度值
percent_rain = get_adc_percent(BSP_RAINDROP_ADC_CHANNEL, ADC_SAMPLE_CNT); // 读取雨滴百分比值
percent_light = get_adc_percent(BSP_LIGHT_ADC_CHANNEL, ADC_SAMPLE_CNT); // 读取光照百分比值
sprintf((char *)oled_dis_buffer, "R:%03d%% L:%03d%%", percent_rain, percent_light);
OLED_ShowString(10, 32, (unsigned char *)oled_dis_buffer, 16);
sprintf((char *)oled_dis_buffer, "T:%03dC H:%03d%%", dht11_temp, dht11_humi);
OLED_ShowString(10, 48, (unsigned char *)oled_dis_buffer, 16);
if(SYS_MODE_AUTO == get_sys_mode()) // 若当前为自动模式
{
sprintf((char *)oled_dis_buffer, "MODE:AUTO");
OLED_ShowString(30, 0, (unsigned char *)oled_dis_buffer, 16);
auto_mode_process();
}
else // 若当前为手动模式
{
sprintf((char *)oled_dis_buffer, "MODE:HAND");
OLED_ShowString(30, 0, (unsigned char *)oled_dis_buffer, 16);
}
if(SYS_STATUS_CLOSE == get_sys_status()) // 若窗帘关闭
{
sprintf((char *)oled_dis_buffer, "STATUS:OFF");
OLED_ShowString(25, 16, (unsigned char *)oled_dis_buffer, 16);
}
else // 若窗帘打开
{
sprintf((char *)oled_dis_buffer, "STATUS:ON ");
OLED_ShowString(25, 16, (unsigned char *)oled_dis_buffer, 16);
}
limit_judgment(get_step_count()); // 步进电机限位判断
++cnt;
if(500 == cnt) // 每5s进行数据采集并上传至云平台
{
ESP8266_Clear();
OneNet_SendData();
// LED = !LED;
cnt = 0;
}
dataPtr = ESP8266_GetIPD(0);
if(dataPtr != NULL)
{
OneNet_RevPro(dataPtr);
}
delay_ms(10);
}
```
## 12.主函数设计
main.c设计较简洁,系统上电后首先进行优先级分组和滴答定时器初始化,然后调用mode.c中的系统初始化,最后在while(1)调用mode.c中的系统处理函数,代码如下。
### main.c
```
#include "sys.h"
#include "main.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
#include "mode.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
systick_config(); // 滴答定时器初始化
sys_init(); // 系统初始化
while(1)
{
sys_process(); // 系统处理
}
}
```
# 七、模块调试与整机联调
焊接好硬件和写好代码后,首先对各个模块代码进行逐个调试,均无问题后进行整合调试,最后进行整机调试。整机调试时会遇到各个模块之间数据传输的冲突、配合等问题,最后通过优化代码结构,解决其中的各种小bug等方式来解决。整个过程比较顺利,没有遇到大坑。
# 八、功能展示视频
[B站视频链接:【TaurusHard】【立创训练营】基于梁山派的智能窗帘](https://www.bilibili.com/video/BV1Mh4y1z7of/?spm_id_from=333.999.list.card_archive.click&vd_source=088ad4dedbbd5d327a72a2c32b269d5e)
# 九、收获与后续改进计划
## 收获
1.丰富了画板子的经验;
2.移植了一款手势识别传感器,编写了移植文档并发布在了官方移植手册中,同时将该模块应用到本项目中;
3.OneNET云平台的学习;
4.各个传感器模块的使用;
5.多模块的协同配合调试经验。
## 后续改进计划
1.制作一个上位机代替串口助手,能够根据具体需求更清晰地获取系统的各个状态;
2.丰富自定义协议;
3.制作一个手机APP,实现手机端对系统的控制;
4.将工程修改为FreeRTOS版本,将执行机构分解为多任务处理,并在此版本上继续迭代;
5.增加升级功能;
6.增加USB功能;
7.待定。