# 项目说明
基于梁山派(GD32F470ZGT6)开发板做的智能窗户控制系统,用户可以通过自身的按键、语音识别或手机客户端(WiFi,蓝牙)、智能音箱(小爱同学)(使用WiFi和智能音箱可以远程控制)控制窗户状态和窗户工作模式。屏幕显示模块会实时显示窗户的连接状态(WiFi、蓝牙)、传感器数值、窗户模式、窗户状态,控制板可以根据当前设定模式和场景环境自动控制窗户开关。也会把当前环境的信息会实时发送到用户手机上方便用户对窗户远程控制。
# 功能介绍
## 设计框图
![设计框图](//image.lceda.cn/pullimage/zy1EzyMc8AQGo5lva3M7UbZWyJEKv8fXF52oQm4T.png)
## 模块介绍
![模块介绍](//image.lceda.cn/pullimage/kH1hCVcOju1B42YoDV9YqeZg7GtgOqssOB7X4x8Z.jpeg)
## 屏幕显示
**状态栏**:
- 显示时间、WiFi和蓝牙的连接状态。
**主界面**:
- 显示烟雾、光照、雨滴、人体传感器当前信息。
- 显示窗户当前模式和窗户当前进度位置。
## 语音控制
- 支持语音控制设置窗户工作模式,控制窗户开关。
- 支持小爱同学语音控制设置窗户工作模式,控制窗户开关。
## 远程控制
- 可以使用手机App,远程控制设置窗户工作模式,控制窗户开关。
- 传感器数据实时同步至手机App。
## 模式介绍
**正常模式:**
- 在任何时候都可以通过自身按键、语音识别、手机客户端、智能音箱,手动控制窗户开关。
- 可以通过上一条的控制方式对窗户的位置进行控制(如:全开,开1/3,开一半,开2/3,全关)。
- 若当前模式不是正常模式(即手动模式)会在控制的时候自动切换为正常模式。
**智能模式:**
- 通过烟雾、光照、雨滴模块,检测当前场景环境自动控制窗户开关。
- 当烟雾大于设定阈值时、光照大于设定上限时、雨滴小于设定阈值时,会自动打开窗户。
- 当雨滴大于设定阈值时、光照小于设定下线时、烟雾小于设定阈值时,会自动关闭窗户。
- 优先级:烟雾开 > 雨滴关 > 光照关 > 光照开。
**睡眠模式:**
- 通过烟雾、光照、雨滴,人体感应模块,检测当前场景环境自动控制窗户开关。
- 当烟雾大于设定阈值时、光照大于设定上限时、雨滴小于设定阈值时、没有人经过时,会自动打开窗户。
- 当雨滴大于设定阈值时、光照小于设定下限时、烟雾小于设定阈值时,有人经过时,会自动关闭窗户。
- 优先级:烟雾开 > 人体关 > 雨滴关 > 光照关 > 光照开。
**阳光模式:**
- 通过烟雾、光照模块,检测当前场景环境自动控制窗户开关。
- 当烟雾大于设定阈值时、光照大于设定上限时,会自动打开窗户。
- 当光照小于设定下限时、烟雾小于设定阈值时,会自动关闭窗户。
- 优先级:烟雾开 > 光照关 > 光照开。
**雨滴模式:**
- 通过烟雾、雨滴模块,检测当前场景环境自动控制窗户开关。
- 当烟雾大于设定阈值时、雨滴小于设定阈值时,会自动打开窗户。
- 当雨滴大于设定阈值时、烟雾小于设定阈值时,会自动关闭窗户。
- 优先级:烟雾开 > 雨滴关 > 雨滴开。
# 项目进度
**2023.8.25** 确定项目方案
**2023.8.26** 绘制原理图
**2023.9.05** 绘制PCB
**2023.9.12** 打样PCB
**2023.9.16** 焊接PCB
**2023.9.17** 设计程序
**2023.9.30** 调试项目
**2023.10.10** 智能窗户1.0版完成
# 一、器件选型
## 主控芯片
梁山派(GD32F470ZGT6)
![梁山派](//image.lceda.cn/pullimage/UXTiZ5tbqcUPjqyxiDt0Lfu33fQCQ6CDKKwu54dw.png)
## 电源部分
使用TYPE-C接口供电,采用 AMS1117-3.3 线性稳压器为电路提供纹波小的3.3V电源,主要给WiFi模块和OLED屏幕供电。
![TYPE-C](//image.lceda.cn/pullimage/FTag1B4ffaLgr150s42o2lT1RvGwPxPPg2AOe3Io.png)
![AMS1117-3.3](//image.lceda.cn/pullimage/w0b3fYsk3m520ZQergXQ18aiA9w1i9SnpY0WryYp.png)
## 传感器部分
### 雨滴传感器
>购买连接:[雨滴感应模块 雨水传感器下雨感知模块天气模块 水位显示模块水滴](https://m.tb.cn/h.5gbBAes?tk=JX8CW0h3O6Y)
雨滴具有一定的导电性,当雨滴滴在上面时会形成电流回路,通过检测两个电极之间的阻值就可以判断是否下雨。
![雨滴传感器](//image.lceda.cn/pullimage/t2ltv8fvLuKzvWtdXHfVSX7fLIiEstt1n1Rh6MDM.png)
### 光照传感器
光照传感器模块是检测光敏电阻的阻值通过电压比较器输出开关信号和通过电阻分压电路来输出模拟量。由于梁山派有A/D转化功能所以只需要光敏电阻加一个分压电路就可以实现测量光照度。
![光照传感器](//image.lceda.cn/pullimage/XMW3La37x4knxTfTGsP89eAOtVJKcAoL0kCZ0raf.png)
![光敏电阻](//image.lceda.cn/pullimage/0LUqfysgiYBEWooO0DJ2IWVI6q95ymrIhJKZ17nH.png)
### 烟雾传感器
MQ -2 烟雾传感器模块是由二氧化锡半导体气敏材料制成,通过加热丝给气敏材料加热提供必要的工作条件当烟雾和气敏材料接触时就会引起表面导电率的变化。和光照传感器一样只需要给MQ -2传感器加一个分压电路就可以实现测量烟雾浓度。
![烟雾传感器](//image.lceda.cn/pullimage/fKyjB4RCxy3i2xGYPqhuHKd3UzWguvTWhFV2cItZ.png)
![MQ-2传感器](//image.lceda.cn/pullimage/ySRqBsldQJDb3hrMFz9DXRKqyQC6OWXJeLXUSj9W.png)
### 人体传感器
>购买链接:[微型SR602人体感应模块 热释电人体红外传感器探头开关 灵敏度高](https://m.tb.cn/h.5gqBE8S?tk=PTYmW0hXmn7)
人体传感器模块是利用温度变化的特征来探测红外线的辐射来判断是否有人靠近。灵敏度高,响应快,可重复触发,供电范围广(3.3-15v)当有人靠近时输出2.5s(默认)高电平(3.3v)。可以改变延时调节电阻来实现不同的延时时间最长3715s。
![人体传感器](//image.lceda.cn/pullimage/6sgK5gN0jVvxTT0dYVf20nrb8oKGb7TzQ9YMTIyB.png)
## 控制模块
### ESP-01sWiFi模块
>购买链接:[ESP01S/ESP07S/ESP12F/ESP32S 2.4Gwifi模块 ESP8266智能物联](https://m.tb.cn/h.5gJYpjK?tk=HyMzW0hV4Yr)
ESP—01S模块是基于ESP8266系列的WIFI模块,具有低功耗、高性能、可扩展性能强的特点。支持STA、AP、STA+AP三种工作模式,常用于远程控制、物联网设备。
![WiFi模块](//image.lceda.cn/pullimage/zSs5cTMtCNapnDf9IUR1F3mHHZvHT3uoSHsN0gQo.png)
### HC-05蓝牙模块
>购买链接:[HC-05/HC-06主从机一体蓝牙模块板电子无线蓝牙DIY无线串口透传](https://m.tb.cn/h.5TheMcs?tk=NHgDW0hfvU3)
HC-05模块是基于CSR BC417的蓝牙模块,采用蓝牙2.0+4.0BLE双模协议的无线通讯设备。支持主机、从机、主从三种工作模式,通过串口和MCU通信,常用于无线控制、物联网设备。
![蓝牙模块](//image.lceda.cn/pullimage/dBS0s1MEQYbZNiScgl8pMqLXuMiR3MHPoCfMWL28.png)
### HLK-V20语音模块
>购买链接:[AI智能语音模块V20 海凌科离线语音开关控制 语音识别控制开发板](https://detail.tmall.com/item.htm?_u=62t4uge5fe3d&id=624187674417&spm=a1z09.2.0.0.65622e8dcBIkxT)
HLK-V20是一款离线语音识别模块,支持150条本地指令离线识别,可自由定制唤醒词、命令词与应答播报词,具有丰富的外围接口。离线语音识别指的是只能识别固定命令词条,不需要连接网络。
![语音模块](//image.lceda.cn/pullimage/vbzNAIz4l6LgbMm7LkpFryumqOKXUNv9hxQuj5QE.png)
## 其他器件
### L110S电机驱动芯片
L9110S是一块直流电机驱动电路,电路内部集成了采用MOS管设计的H桥驱动电路,待机电流小驱动能力强,用于驱动控制窗户的步进电机。
![电机驱动芯片](//image.lceda.cn/pullimage/9CamxJM61XhxlXApP0DLSRatuf3NomtkpGffhxlc.png)
### 四线两项步进电机
>购买链接:[原装进口Y15-56 二相四线 微型 步进丝杆电机 带滑块 激光雕刻机](https://m.tb.cn/h.5gYb3Nl?tk=b2uqW0hfcDV)
步进电机是将电脉冲信号,转变为角位移或线位移的开环控制电机,又称为脉冲电机。步进电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数。当步进驱动器接收到一个脉冲信号时,它就可以驱动步进电机按设定的方向转动一个固定的角度。
![步进电机](//image.lceda.cn/pullimage/mol0eiYOt9FReaLH3cNln7yugww6VdJAa7OeL0L7.png)
### 0.96寸OLED显示屏
0.96寸四针OLED屏幕是一种I2C协议通信的屏幕,OLED的自发光技术不需要背光源、对比度高、功耗低等特点。主要用于显示设备参数等内容。
![OLED屏幕](//image.lceda.cn/pullimage/lQIOMuPSMoM84qxcaNVlEzfZxiKrZOLKqcI8HEFp.png)
# 二、原理图设计
## 电源
使用TYPE-C接口供电通过下拉CC引脚支持CtoC线供电,预留一个接线口方便后续更改供电方式,WiFi模块对电源的供电要求比较高所以使用AMS1117-3.3LDO线性稳压芯片,可持续输出1A大电流供电路运行。
![数据手册](//image.lceda.cn/pullimage/VYCmR3I1IotrAdDPnRcQDcNjXAbsZCaYt7yiVVSX.png)
![电源](//image.lceda.cn/pullimage/Bqqb0tN9cQF1MLjMCj3Xf0EWLk9q3cwx7jFg404x.png)
## 传感器
烟雾、光照、雨滴这三个传感器各使用一个电阻进行分压来获得模拟量。
查看数据手册,PC1、PC2和PC3都支持ADC功能,所以通过这三个引脚输入传感器的模拟量。
人体传感器输出的是高低电平,使用任意一个引脚即可。
![数据手册](//image.lceda.cn/pullimage/7gbmu5hGKYFCwdVLDR4Mfdwmu35AiKOKzJebOTDL.png)
![传感器](//image.lceda.cn/pullimage/bCIHjbNg0zj03YTBQBXNfEgbApnAGRUrp7uB5zNi.png)
## 控制模块
这三个模块都采用UART与单片机通信,查看数据手册找到合适的引脚。语音模块使用的是PA2和PA3的复用7功能 为USART1,WIFI模块使用的是PB10和PB11的复用7功能 为USART2,蓝牙模块使用的是PA0和PA1的复用8 为UART3。
WiFi和蓝牙模块除过UART通信引脚还有状态指示引脚,这些引脚都输出高低电平没有通信协议,所以这些引脚根据模块和开发板的位置就近选择。
![数据手册](//image.lceda.cn/pullimage/QSq8h3Cr6wju8t2bP5jy658b6UzLrZEHzx5FbAGR.png)
![数据手册](//image.lceda.cn/pullimage/sfzhndFQLkIrONizGRNHGWKbYXkbV0WVc8FGr9iv.png)
参考WIFI模块数据手册,通过RC延时电路把EN引脚拉高。
![数据手册](//image.lceda.cn/pullimage/9pmB2UMn1nvgUjRlfOGT6y8SjqXHnGD8IMWiH5Fw.png)
![控制模块](//image.lceda.cn/pullimage/X2lOpHXr1dW6sBccGl5qsLgieRv7Tnizny1t2G0s.png)
## 电机驱动
BAK、FOR 为电机转向控制脚,X3、X4 为电机输出驱动脚,通过 BAK、FOR 输入脚的电平控制 实现转向控制功能。
| BAK | FOR | X3 | X4 | 状态 |
| :--: | :--: | :--: | :--: | :--: |
| 0 | 0 | Z | Z | 待机 |
| 0 | 1 | 0 | 1 | 正转 |
| 1 | 0 | 1 | 0 | 反转 |
| 1 | 0 | 0 | 0 | 刹车 |
这个芯片只需要高低电平信号就可以控制电机,所以使用的引脚就近选择,参考L9110S数据手册案例的设计电路设计。
![参考手册](//image.lceda.cn/pullimage/kUByOmzNGNcgMKmVAh1XkKO8f7iNJOkYFlgvSYMA.png)
![电机驱动](//image.lceda.cn/pullimage/PtjnLeIU2ZJO5SSOJVK1Pv1IOKpNbcwe9FBMOo12.png)
## 其他电路
**按键:** 使用芯片内部上拉,软件消抖,所以就不需要过多的外围电路。
**LED灯:** 使用低电平点亮的方式,提高电流驱动能力,避免输出高电平时对单片机内核电压的干扰,保证稳定性。
**屏幕:** 选用的屏幕通信协议是IIC通信,芯片支持硬件IIC通信,通过查看数据手册PB6和PB7的复用4是IIC0所以选择这两个引脚。
![数据手册](//image.lceda.cn/pullimage/Kp6MzddKDLpttiAlIzCm7nv4uLxaBW9FgcxMpgFu.png)
![其他电路](//image.lceda.cn/pullimage/AhRbwjHUuhX9JbqcOr4MZU1ZmKB2UoT3YorbnWfP.png)
# 三、电路板设计
PCB大小为10x10cm,因为我使用的是比较薄的扬声器大概5mm厚,梁山派的排针母座是8mm左右,所以就把扬声器和比较小的元件放在了梁山派的下面,节省空间。
![电路板实物](//image.lceda.cn/pullimage/HSYqJJ1zguK3LNSiDCsbJqwoqe8URkveoG12izIF.jpeg)
# 四、主要程序设计
## 传感器测量值
雨滴、光照、烟雾均返回了扩大百倍的百分比值,人体传感器返回的是布尔量。
```c
// 获取adc的值,参数:adc通道号,返回:adc的值
uint16_t get_adc_value(uint8_t adc_channel_x)
{
uint16_t adc_value = 0;
//设置采集通道
adc_regular_channel_config(BSP_ADC, 0, adc_channel_x, ADC_SAMPLETIME_15);
//开始软件转换
adc_software_trigger_enable(BSP_ADC, ADC_REGULAR_CHANNEL);
//等待 ADC 采样完成
while ( adc_flag_get(BSP_ADC, ADC_FLAG_EOC) == RESET );
//读取采样值
adc_value = adc_regular_data_read(BSP_ADC);
//返回采样值
return adc_value;
}
// 获取雨滴传感器的值。
uint8_t Sensor_GetRaindrop(void)
{
uint16_t value = 0;
value = get_adc_value(ADC_CHANNEL_11);
return (1-((float)value/4095)) * 100;
}
// 获取光照传感器的值
uint8_t Sensor_GetLight(void)
{
uint16_t value = 0;
value = get_adc_value(ADC_CHANNEL_12);
return (1-((float)value/4095)) * 100;
}
// 获取烟雾传感器的值
uint8_t Sensor_GetMQ_2(void)
{
uint16_t value = 0;
value = get_adc_value(ADC_CHANNEL_13);
return (1-((float)value/4095)) * 100;
}
// 获取人体传感器的状态
uint8_t Sensor_GetSR_602(void)
{
return gpio_input_bit_get(GPIOC,GPIO_PIN_5); //返回人体传感器的值
}
```
## 语音模块
>语音模块配置平台:[智能公元/AI产品零代码平台 (smartpi.cn)](http://smartpi.cn/#/)
>语音模块配置参考:[15.软件设计-语音识别命令配置-平台2【毕设-智能窗帘项目】](https://www.bilibili.com/video/BV1cH4y1Q7Dn/?spm_id_from=444.41.list.card_archive.click&vd_source=6d3714ced8c62356bf40446a098605b6)
>海凌科下载工具:[海凌科下载工具](http://h.hlktech.com/Mobile/download/fdetail/93.html)
先按照下面配置设置串口通讯引脚、命令词、串口输入配置,再根据个人喜好设置唤醒词、退出回复语等。大概半小时左右生成好了,使用[海凌科下载工具](http://h.hlktech.com/Mobile/download/fdetail/93.html)里的HLK-M-Update-Tool.zip(.zip)软件进行烧录,如果识别不到端口先安装CH340的驱动。
### Pin引脚设置
按照图片设置串口的参数,配置帧头为40帧尾为23。
![引脚配置](//image.lceda.cn/pullimage/Vq8UNMNJjyLlbbgIuhKGQnt0ddS1YJsmOOZ0EBHe.png)
### 命令词配置
>注意:
>1.推荐在串口发送数据时加一个100ms延时。
>2.行为名字的字母大小写不能改变。
| 行为名字 | 命令词 | 回复语 | 串口发送参数 |
| :-----------: | :----------------------------------------------------------: | :----------------------------------: | :---------------: |
| ON | 打开窗户\|开窗 | 已打开\|打开啦 | 40 4f 4e |
| OFF | 关闭窗户\|关窗 | 已关闭\|关闭啦 | 40 4f 46 46 |
| Window1 | 把窗户开到三分之一\|把窗户关到三分之二 | 好的\|收到\|知道啦 | 23 31 |
| Window2 | 把窗户开到二分之一\|把窗户关到二分之一\|把窗户开一半\|把窗户关一半 | 好的\|收到\|知道啦 | 23 32 |
| Window3 | 把窗户开到三分之二\|把窗户关到三分之一 | 好的\|收到\|知道啦 | 23 33 |
| Mode0 | 正常模式\|把窗户设置为正常模式 | 已设置为正常模式\|知道啦\|好的\|收到 | 40 6d 6f 64 65 30 |
| Mode1 | 智能模式\|把窗户设置为智能模式 | 已设置为智能模式\|知道啦\|好的\|收到 | 40 6d 6f 64 65 31 |
| Mode2 | 睡眠模式\|把窗户设置为睡眠模式 | 已设置为睡眠模式\|知道啦\|好的\|收到 | 40 6d 6f 64 65 32 |
| Mode3 | 阳光模式\|把窗户设置为阳光模式 | 已设置为阳光模式\|知道啦\|好的\|收到 | 40 6d 6f 64 65 33 |
| Mode4 | 雨滴模式\|把窗户设置为雨滴模式 | 已设置为雨滴模式\|知道啦\|好的\|收到 | 40 6d 6f 64 65 34 |
| volumeUpUni | 增大音量\|大点声\|声音大一点 | 知道啦\|好的\|我会大点声的 | 56 2b |
| volumeDownUni | 减小音量\|小点声\|声音小一点 | 知道啦\|好的\|我会小点声的 | 56 2d |
| volumeMinUni | 最小音量\|声音放到最小 | 知道啦\|好的\|这已经是最小声音了 | 56 4e |
| volumeMaxUni | 最大音量\|声音放到最大 | 知道啦\|好的\|这已经是最大声音了 | 56 56 |
### 串口输入配置
>**注意:**
>1.推荐在语音播报时加一个100ms延时。
>2.行为名字和串口输入参数的字母大小写不能改变。
| 行为名字 | 串口输入参数 | 测速(发送)消息值 | 播放语音 |
| :------: | :----------: | :----------------: | :------------------------: |
| NO1 | no | 40 01 01 23 | 已打开窗户 |
| OFF1 | off | 40 02 02 23 | 已关闭窗户 |
| MODE00 | mode0 | 40 03 03 23 | 已设置为正常模式 |
| MODE11 | mode1 | 40 04 04 23 | 已设置为智能模式 |
| MODE22 | moed2 | 40 05 05 23 | 已设置为睡眠模式 |
| MODE33 | mode3 | 40 06 06 23 | 已设置为阳光模式 |
| MODE44 | mode4 | 40 07 07 23 | 已设置为雨滴模式 |
| OK | ok | 40 08 08 23 | 知道啦 |
| SMOKE | smoke | 40 09 09 23 | 检测到大量烟雾,已打开窗户 |
| LIGHT | light | 40 0A 0A 23 | 天黑啦,已关闭窗户 |
| RAIN | rain | 40 0B 0B 23 | 下雨啦,已关闭窗户 |
| BODY | body | 40 0C 0C 23 | 检测到有人经过,已关闭窗户 |
| LIGHT1 | light1 | 40 0D 0D 23 | 天亮了,已打开窗户 |
| RAIN1 | rain1 | 40 0E 0E 23 | 雨已经停了,已打开窗户 |
| WINDOW1 | window1 | 40 0F 0F 23 | 窗户已打开至三分之一 |
| WINDOW2 | window2 | 40 11 11 23 | 窗户已打开至二分之一 |
| WINDOW3 | window3 | 40 12 12 23 | 窗户已打开至三分之二 |
### 固件烧录
>| 语音模块 | GND | TX | RX |
>| --- | --- | --- | --- |
>| 下载器 | GND | RX | TX |
断掉设备所有电源,连接设备和下载器,选择固件烧录(文件路径一定要全英文路径,中文会报错),等进度条变黄时插上电源等待烧录完成重启。
![接线图](//image.lceda.cn/pullimage/5rkJKipIJN95aAsC9Bsxbo4udpUGinmQrRsV822e.png)
![下载](//image.lceda.cn/pullimage/iUEeIgE1IqSNUzcJFmmjVQkJSzqN68FEFkQkbk6m.png)
## 语音播报
把语音播报指令列上一个二维数组`HLK_Actions`,通过`Voice_Actions`函数选择要播报语音的消息编号,发送给语音模块进行播报。
语音播报指令根据《语音串口输入配置》的顺序排序,使与配置的消息编号相同。
```c
// 语音播报指令
const char HLK_Actions[][4]= //18条
{
{0x00,0x00,0x00,0x00}, //补空
{0x40,0x01,0x01,0x23}, //开窗
{0x40,0x02,0x02,0x23}, //关窗
{0x40,0x03,0x03,0x23}, //正常模式0
{0x40,0x04,0x04,0x23}, //智能模式1
{0x40,0x05,0x05,0x23}, //睡眠模式2
{0x40,0x06,0x06,0x23}, //阳光模式3
{0x40,0x07,0x07,0x23}, //雨滴模式4
{0x40,0x08,0x08,0x23}, //设备响应
{0x40,0x09,0x09,0x23}, //烟雾开
{0x40,0x0A,0x0A,0x23}, //光照关
{0x40,0x0B,0x0B,0x23}, //雨滴关
{0x40,0x0C,0x0C,0x23}, //人体关
{0x40,0x0D,0x0D,0x23}, //光照关
{0x40,0x0E,0x0E,0x23}, //雨滴开
{0x40,0x0F,0x0F,0x23}, //窗户开1/3
{0x40,0x10,0x10,0x23}, //窗户开1/2
{0x40,0x11,0x12,0x23}, //窗户开2/3
};
// 语音播报 参数:消息编号
void Voice_Actions(uint8_t ActionsNum)
{
uint8_t i;
for(i = 0; i < 4; i++)
{
HLK_SendData(HLK_Actions[ActionsNum][i]);
}
}
```
## WIFi模块
> WiFi模块配置软件:[Arduino IDE](https://www.arduino.cc/en/software)
> 点灯Blinker资源下载:[APP和SDK下载](https://diandeng.tech/dev)
> EspTouch下载:[EspTouch资源下载](https://www.espressif.com.cn/zh-hans/products/software/esp-touch/resources)
### 点灯Blinker界面配置
下载点灯BlinkerAPP按图进行配置。
![配置流程](//image.lceda.cn/pullimage/G4q79JPUpAY0SbIaI0zF3JQS7KXzW3hgKHUq77KA.png)
界面配置参数
```
{¨config¨{¨headerColor¨¨transparent¨¨headerStyle¨¨light¨¨background¨{¨img¨´´}}¨dashboard¨|{¨type¨¨btn¨¨ico¨¨fad fa-blinds¨¨mode¨Ê¨t0¨¨窗户¨¨t1¨¨文本2¨¨bg¨É¨cols¨Í¨rows¨Í¨key¨¨btn_mado¨´x´É´y´Ê¨speech¨|÷¨lstyle¨Ë¨clr¨¨#076EEF¨}{ß8¨deb¨ßCÉßHÉßIÑßJÌßK¨debug¨´x´É´y´¤EßNÉ}{ß8ß9ßA¨fad fa-atom¨ßCÉßD¨正常模式¨ßFßGßHÉßIÍßJÍßK¨btn_mode0¨´x´Í´y´ÊßNËßOßP}{ß8¨num¨ßD¨光照传感器¨ßA¨fal fa-question¨ßO¨#389BEE¨¨min¨É¨max¨¢1c¨uni¨´%´ßHÉßIÍßJÍßK¨num_light¨´x´É´y´ÑßNͨrt¨«}{ß8ßVßD¨烟雾传感器¨ßA¨fad fa-chart-network¨ßOßYßZÉßaº0ßb´%´ßHÉßIÍßJÍßK¨num_smoke¨´x´Í´y´ÑßNÍßd«}{ß8¨tex¨ßD¨雨滴传感器¨ßF´´¨size¨¨18¨ßHÉßA¨fad fa-humidity¨ßIÍßJËßK¨tex_rain¨´x´É´y´¤CßNÌ}{ß8ßhßD¨人体传感器¨ßF´´ßjßkßHÉßA¨fad fa-house-return¨ßIÍßJËßK¨tex_body¨´x´Í´y´¤CßNÌ}{ß8¨ran¨ßD¨窗户位置¨ßOßPßaÍßZÉßHÉßIÑßJÊßK¨ran_num¨´x´É´y´ÐßNÌßd«}{ß8ß9ßA¨fad fa-server¨ßCÉßD¨智能模式¨ßFßGßHÉßIËßJËßK¨btn_mode1¨´x´É´y´ÎßNÉßOßP}{ß8ß9ßA¨fad fa-bed¨ßCÉßD¨睡眠模式¨ßFßGßHÉßIËßJËßK¨btn_mode2¨´x´Ë´y´ÎßNÉßOßP}{ß8ß9ßA¨fad fa-sun¨ßCÉßD¨阳光模式¨ßFßGßHÉßIËßJËßK¨btn_mode3¨´x´Í´y´ÎßOßP}{ß8ß9ßAßlßCÉßD¨雨滴模式¨ßFßGßHÉßIËßJËßK¨btn_mode4¨´x´Ï´y´ÎßOßPßNÉ}{ß8ßhßD¨----------------智能窗户控制-----------------¨ßFßGßj´24´ßHËßA´´ßIÑßJÊßK´0´´x´É´y´ÉßNÎßOßP}÷¨actions¨|¦¨cmd¨¦ßL‡¨text¨‡´on´¨打开窗户¨¨off¨¨关闭窗户¨—{ß16{¨btn_mode¨´on´}ß17¨自动模式¨}{ß16{ß1Bß19}ß17¨手动模式¨}÷¨triggers¨|{¨source¨¨switch¨¨source_zh¨¨开关状态¨¨state¨|´on´ß19÷¨state_zh¨|´打开´´关闭´÷}÷ßd|÷}
```
### 小爱同学配置
由于电灯科技里的小爱没有对智能窗户适配,这里用的是智能灯泡的代码,需要先对小爱同学进行训练后就可以完美控制窗户了。
先下载米家APP绑定小爱同学后,再在小爱同学APP进行个人训练。
![米家配置](//image.lceda.cn/pullimage/47WmmBgvPo8urlzWsXW2ikLHkNWYnkaoOxT82Bpu.png)
![小爱配置](//image.lceda.cn/pullimage/biELODmFcolmvJ68bZa09V6GPRQVmOoZysLNGYvn.png)
### 模块烧录
| WIFI模块 | GND | TX | RX | 3.3 | IO0 |
| --- | --- | --- | --- | --- | --- |
| 下载器 | GND | RX | TX | 3.3 | GND |
代码文件在附件WiFi模块里有两个版本,一个支持ESPTouch配网,一个固定网络。使用时需要更改以下信息,通过Arduino IDE编译按上表接线后再把下载器连接到电脑下载。
```c
char auth[] = "1122334455"; //点灯Key
char ssid[] = "NiseKana"; //wifi名称
char pswd[] = "12345678"; //wifi密码
```
### 模块配网
EspTouch:是乐心科技开发的一种智能配网技术,通过携带Wi-Fi网络信息的数据包广播,待连接设备通过监听和解析接收到的广播数据包,获取到网络的SSID和密码,并自动进行连接。
![配网流程](//image.lceda.cn/pullimage/EhRCf2mWPuYPbA5tx8iG3BgZfRbyknIfbboPjcAi.png)
## 蓝牙模块
>使用蓝牙调试器控制(下载连接在文章末的工程附件里)按照表格配置App,模块使用的波特率为9600,八个数据位,一个停止位,无校验位。
| 功能模式 | 正常模式 | 智能模式 | 睡眠模式 | 阳光模式 | 雨滴模式 | 打开窗户 | 开窗1/3 | 开窗1/2 | 开窗2/3 | 关闭窗户 |
| :------: | :------: | :------: | :------: | :------: | :------: | :------: | :-----: | :-----: | :-----: | :------: |
| 发送数据 | @mode0 | @mode1 | @mode2 | @mode3 | @mode4 | @ON | #1 | #2 | #3 | @OFF |
## WiFi、蓝牙、语音接指令收程序
- 这三个模块都采用串口与梁山派通信解码函数基本上都一样,这里只列举WiFi运行的解码程序,使用```strcmp```函数来解码串口消息。
- ```strcmp```:是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。基本形式为```strcmp(str1,str2)```,若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。使用时记得加头文件```<string.h>```。
```c
// WIFI运行函数
void WIFI_Run(void)
{
//判断串口接收标志位
if(WIFI_RX_FLAG)
{
if(strcmp(WIFI_RX_BUFF,"@ON") == 0)
{
Window_Open(); //打开窗户
Con_modeset(0); //模式设置为手动模式
}else if(strcmp(WIFI_RX_BUFF,"@OFF") == 0)
{
Window_Close(); //关闭窗户
Con_modeset(0);
}else if(strcmp(WIFI_RX_BUFF,"@mode0") == 0)
{
Con_modeset(0); //设置正常模式
}else if(strcmp(WIFI_RX_BUFF,"@mode1") == 0)
{
Con_modeset(1); //设置智能模式
}else if(strcmp(WIFI_RX_BUFF,"@mode2") == 0)
{
Con_modeset(2); //设置睡眠模式
}else if(strcmp(WIFI_RX_BUFF,"@mode3") == 0)
{
Con_modeset(3); //设置阳光模式
}else if(strcmp(WIFI_RX_BUFF,"@mode4") == 0)
{
Con_modeset(4); //设置雨滴模式
}else if(strcmp(WIFI_RX_BUFF,"#0") == 0)
{
Window_Close(); //关闭窗户
Con_modeset(0);
}else if(strcmp(WIFI_RX_BUFF,"#1") == 0)
{
Window_2_3(); //打开窗户至 2/3
Con_modeset(0);
}else if(strcmp(WIFI_RX_BUFF,"#2") == 0)
{
Window_1_2(); //打开窗户至 1/2
Con_modeset(0);
}else if(strcmp(WIFI_RX_BUFF,"#3") == 0)
{
Window_1_3(); //打开窗户至 1/3
Con_modeset(0);
}else if(strcmp(WIFI_RX_BUFF,"#4") == 0)
{
Window_Open(); //打开窗户
Con_modeset(0);
}
// 清除缓冲区数据
Clear_WIFI_RX_BUFF();
}
}
```
## OLED屏幕
### 屏幕驱动
屏幕驱动代码使用了[模块移植手册](https://lceda001.feishu.cn/wiki/JNvYwEU5SiGldFkNcxncYXhZnZc)中第一章节1.9的 0.96寸IIC单色屏案例。只需在`OLED.h`文件中更改相应的端口号即可使用。
```c
//-----------------OLED端口移植----------------
//SCL - PB6 I2C0_SCL
//SDA - PB7 I2C0_SDA
#define RCU_LCD_SCL RCU_GPIOB //SCL
#define PORT_LCD_SCL GPIOB
#define GPIO_LCD_SCL GPIO_PIN_6
#define RCU_LCD_SDA RCU_GPIOB //SDA
#define PORT_LCD_SDA GPIOB
#define GPIO_LCD_SDA GPIO_PIN_7
#define I2C_TIME_OUT 50000
#define I2C_SPEED 400000
#define I2CX_SLAVE_ADDRESS7 0x78
#define I2CX I2C0
#define RCU_I2CX RCU_I2C0
```
### 字体图像取模
使用[PCtoLCD2002](https://iqn.lanzoub.com/iRKhM1atuuyf)软件进行图像字体取模。
请先按下图设置软件后在进行取模。
![软件设置](//image.lceda.cn/pullimage/EBs9XPvU0KLBNjg7wM2ksi4xp4fSrIz9u7QULjTd.png)
![图像取模](//image.lceda.cn/pullimage/Za5ui9lQY7fmk3XguCZhMnTvKAlt7U1xi4ZZEMTG.png)
## 步进电机
### 电机驱动
- 采用八拍方式驱动步进电机,转动顺序为:【A+】->【A+B+】->【B+】->【B+A-】->【A-】->【A-B-】->【B-】->【B-A+】。
- 使用定时器中断的方法让电机根据标志位定时转动,避免使用延时函数阻塞其他程序运行。
![转动图解](//image.lceda.cn/pullimage/lfBdRZdI5Tls9YKO1Hu7hsOXlLLwcdBKa6eraZoV.png)
```c
//电机顺时针转动顺序
uint8_t phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09};
//电机顺时逆转动顺序
uint8_t phaseccw[8] = {0x09, 0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08};
//电机顺时针转动标志位
uint8_t motor_cw_flag = 0;
//电机逆时针转动标志位
uint8_t motor_ccw_flag = 0;
//当前步数
static int step_count = 0;
//步进电机关上窗户的最大步数
#define MAX_STEPS 600
//电机顺时针转动
void motor_cw(void)
{
static uint8_t i=0;
//开启了顺时针动作
if( motor_cw_flag == 1 )
{
AP ( ( phasecw[i] >> 3 ) & 0x01 );
BP ( ( phasecw[i] >> 2 ) & 0x01 );
AM ( ( phasecw[i] >> 1 ) & 0x01 );
BM ( ( phasecw[i] >> 0 ) & 0x01 );
//拍数增加
i = ( i + 1 ) % 8;
//记录当前步数
step_count++;
}
}
//电机逆时针转动
void motor_ccw( void )
{
static uint8_t i=0;
//如果开启了逆时针动作
if( motor_ccw_flag == 1 )
{
AP ( ( phaseccw[i] >> 3 ) & 0x01 );
BP ( ( phaseccw[i] >> 2 ) & 0x01 );
AM ( ( phaseccw[i] >> 1 ) & 0x01 );
BM ( ( phaseccw[i] >> 0 ) & 0x01 );
i = ( i + 1 ) % 8;
//记录当前步数
if( step_count <= 1 ) step_count = 1;
step_count--;
}
}
//电机停止
void motor_stop( void )
{
AP ( 0 );
BP ( 0 );
AM ( 0 );
BM ( 0 );
}
//定时器中断方式转动电机
void TIMER5_DAC_IRQHandler(void)
{
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(TIMER5,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP); // 清除中断标志位
//顺时针旋转
motor_cw();
//逆时针旋转
motor_ccw();
}
}
```
### 窗户控制函数
- 使用```Window_Open```和```Window_Close```函数打开和关闭窗户。
- 使用```Window_2_3,Window_1_2,Window_1_3```函数可以对窗户实现更细致的控制。
- 通过设置电机正反转标志位```motor_cw_flag```和```motor_ccw_flag```并在定时中断里执行电机正反转函数```motor_cw```和```motor_ccw```来对电机控制。
- 因为窗户控制函数```Window_Open```和```Window_Close```和```Window_2_3,Window_1_2,Window_1_3```只在中断程序中运行一次无法自行清除电机正反转标志位```motor_cw_flag```和```motor_ccw_flag```所以使用下面的```Window_Limit```窗户限位函数来清除电机正反转标志位。
```c
//打开窗户
void Window_Open(void)
{
//设置窗户限制位
Window_LimitFlag = 0;
//如果步数没有到达0步
if( get_step_count() > 0 )
{
//步进电机逆时针旋转,步数累减
motor_ccw_flag = 1;
motor_cw_flag = 0;
}else{
//停止步进电机动作
motor_ccw_flag = 0;
motor_cw_flag = 0;
motor_stop();
}
}
//关闭窗户
void Window_Close(void)
{
//设置窗户限制位
Window_LimitFlag = 0;
//如果步数没有到达最大步数
if( get_step_count() < MAX_STEPS )
{
//步进电机顺时针旋转,步数累加
motor_cw_flag = 1;
motor_ccw_flag = 0;
}else{
//停止步进电机动作
motor_cw_flag = 0;
motor_ccw_flag = 0;
motor_stop();
}
}
// 打开窗户至 2/3
void Window_2_3(void)
{
//设置窗户限制位
Window_LimitFlag = 1;
if(get_step_count()>150){
//打开
motor_cw_flag = 0;
motor_ccw_flag = 1;
}else if(get_step_count()<150){
//关闭
motor_cw_flag = 1;
motor_ccw_flag = 0;
}
}
// 打开窗户至 1/2
void Window_1_2(void)
{
//设置窗户限制位
Window_LimitFlag = 2;
if(get_step_count()>300){
//打开
motor_cw_flag = 0;
motor_ccw_flag = 1;
}else if(get_step_count()<300){
//关闭
motor_cw_flag = 1;
motor_ccw_flag = 0;
}
}
// 打开窗户至 1/3
void Window_1_3(void)
{
//设置窗户限制位
Window_LimitFlag = 3;
if(get_step_count()>450){
//打开
motor_cw_flag = 0;
motor_ccw_flag = 1;
}else if(get_step_count()<450){
//关闭
motor_cw_flag = 1;
motor_ccw_flag = 0;
}
}
```
### 窗户限位函数
- 函数参数: 0:无限制 1:1/3限制 2:1/2限制 3:2/3限制
- 通过上面的窗户控制函数设置窗户限位标志位```Window_LimitFlag```通过mian.c程序对```Window_Limit(Window_LimitFlag);```函数不断地设置限制位来实现限制窗户开关或打开至1/3,1/2,2/3。
```c
void Window_Limit(uint8_t value)
{
if(value==0){
//如果当前是顺时针旋转
if( motor_cw_flag == 1 )
{
//如果顺时针旋转的步数已经累加超过最大步数
if( get_step_count() >= MAX_STEPS )
{
//停止旋转
motor_cw_flag = 0;
motor_stop();
}
}
//如果当前是逆时针旋转
if( motor_ccw_flag == 1 )
{
//如果逆时针旋转的步数已经累减到0步
if( get_step_count() <= 0 )
{
//停止旋转
motor_ccw_flag = 0;
motor_stop();
}
}
}
if(value==3){
//如果当前是顺时针旋转
if( motor_cw_flag == 1 )
{
//如果顺时针旋转的步数已经累加超过最大步数
if( get_step_count() > 150 )
{
//停止旋转
motor_cw_flag = 0;
motor_stop();
}
}
//如果当前是逆时针旋转
if( motor_ccw_flag == 1 )
{
//如果逆时针旋转的步数已经累减到0步
if( get_step_count() < 150 )
{
//停止旋转
motor_ccw_flag = 0;
motor_stop();
}
}
}
if(value==2){
//如果当前是顺时针旋转
if( motor_cw_flag == 1 )
{
//如果顺时针旋转的步数已经累加超过最大步数
if( get_step_count() > 300 )
{
//停止旋转
motor_cw_flag = 0;
motor_stop();
}
}
//如果当前是逆时针旋转
if( motor_ccw_flag == 1 )
{
//如果逆时针旋转的步数已经累减到0步
if( get_step_count() < 300 )
{
//停止旋转
motor_ccw_flag = 0;
motor_stop();
}
}
}
if(value==1){
//如果当前是顺时针旋转
if( motor_cw_flag == 1 )
{
//如果顺时针旋转的步数已经累加超过最大步数
if( get_step_count() > 450)
{
//停止旋转
motor_cw_flag = 0;
motor_stop();
}
}
//如果当前是逆时针旋转
if( motor_ccw_flag == 1 )
{
//如果逆时针旋转的步数已经累减到0步
if( get_step_count() < 450 )
{
//停止旋转
motor_ccw_flag = 0;
motor_stop();
}
}
}
}
```
## 控制程序
通过`Con_modecon`函数选择当前的运行模式,`UI_ModeSet`函数更新屏幕显示内容。
```C
//模式选择
void Con_modecon(void)
{
switch(MODE)
{
case 0: UI_ModeSet(MODE);
break;
case 1: Con_AoutMode();UI_ModeSet(MODE);
break;
case 2: Con_SleepMode();UI_ModeSet(MODE);
break;
case 3: Con_SunMode();UI_ModeSet(MODE);
break;
case 4: Con_RainMode();UI_ModeSet(MODE);
break;
default: ;
break;
}
}
```
通过判断传感器当前值与设定的阈值大小实现自动开关窗户,通过`return ;`跳出函数实现优先级的判断。
```C
//模式选择
void Con_modecon(void)
{
switch(MODE)
{
case 0: UI_ModeSet(MODE);
break;
case 1: Con_AoutMode();UI_ModeSet(MODE); //设置窗户模式,显示模式名字
break;
case 2: Con_SleepMode();UI_ModeSet(MODE);
break;
case 3: Con_SunMode();UI_ModeSet(MODE);
break;
case 4: Con_RainMode();UI_ModeSet(MODE);
break;
default: ;
break;
}
}
//自动模式
void Con_AoutMode(void)
{
uint8_t Mq2_value;
uint8_t Raind_value;
uint8_t Light_value;
Mq2_value = Sensor_GetMQ_2();
Raind_value = Sensor_GetRaindrop_State();
Light_value = Sensor_GetLight();
// 烟雾开窗
if(Mq2_value >= MQ2_THRESHOLD)
{
LED_GlintFlag = 1; //打开警报灯
Window_Open(); //打开窗户
Voice_ActionsOnLim(9); //播报语音
return ; //跳出当前函数,实现优先级
}else{LED_GlintFlag = 0;} //如果烟雾值小于设定阈值就关闭警报灯
// 雨滴关窗
if(Raind_value)
{
Window_Close();
return ;
}
// 光照关窗
if(Light_value < Light_MINTHRESHOLD)
{
Window_Close();
return ;
}
// 光照开窗
if(Light_value >= Light_MAXTHRESHOLD)
{
Window_Open();
}
}
//睡眠模式
void Con_SleepMode(void)
{
uint8_t Mq2_value;
uint8_t Raind_value;
uint8_t Light_value;
Mq2_value = Sensor_GetMQ_2();
Raind_value = Sensor_GetRaindrop_State();
Light_value = Sensor_GetLight();
// 烟雾开窗
if(Mq2_value >= MQ2_THRESHOLD)
{
LED_GlintFlag = 1;
Window_Open();
Voice_ActionsOnLim(9);
return ;
}else{LED_GlintFlag = 0;}
// 人体关窗
if(Sensor_GetSR_602())
{
Window_Close();
return ;
}
// 雨滴关窗
if(Raind_value)
{
Window_Close();
return ;
}
// 光照关窗
if(Light_value < Light_MINTHRESHOLD)
{
Window_Close();
return ;
}
// 光照开窗
if(Light_value >= Light_MAXTHRESHOLD)
{
Window_Open();
}
}
//阳光模式
void Con_SunMode(void)
{
uint8_t Mq2_value;
uint8_t Light_value;
Mq2_value = Sensor_GetMQ_2();
Light_value = Sensor_GetLight();
// 烟雾开窗
if(Mq2_value >= MQ2_THRESHOLD)
{
LED_GlintFlag = 1;
Window_Open();
Voice_ActionsOnLim(9);
return ;
}else{LED_GlintFlag = 0;}
// 光照关窗
if(Light_value < Light_MINTHRESHOLD)
{
Window_Close();
return ;
}
// 光照开窗
if(Light_value >= Light_MAXTHRESHOLD)
{
Window_Open();
}
}
//雨滴模式
void Con_RainMode(void)
{
uint8_t Mq2_value;
uint8_t Raind_value;
Mq2_value = Sensor_GetMQ_2();
Raind_value = Sensor_GetRaindrop_State();
// 烟雾开窗
if(Mq2_value >= MQ2_THRESHOLD)
{
LED_GlintFlag = 1;
Window_Open();
Voice_ActionsOnLim(9);
return ;
}else{LED_GlintFlag = 0;}
// 雨滴关窗
if(Raind_value)
{
Window_Close();
return ;
}
// 雨滴开窗
if(Raind_value ==0)
{
Window_Open();
}
}
```
## 项目主程序
```c
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // NVIC 优先级分组
systick_config(); // 滴答定时器初始化
//模块初始化
OLED_Init();
UI_Boot(); //显示开机动画
delay_1ms(20);
RTC_Init();
USART0_Init();
Sensor_Init();
KEY_Init();
LED_Init();
WIFI_Init();
BT_Init(9600);
HLK_Init();
Motor_Init();
Window_Reset(); //窗户复位
OLED_Clear();
UI_ModeSet(0);
UI_Window(0);
while(1)
{
//步进电机限位判断
Window_Limit(Window_LimitFlag);
KEY_Run(); //按键运行
WIFI_Run(); //WIFI运行
BT_Run(); //蓝牙运行
HLK_Run(); //语音运行
Con_modecon(); //模式选择
delay_1ms(20);
WiFi_RunData(); //WiFi发送数据
UI_Titile(WIFI_ConnectGet(),BT_ConnectGet(),0); //标题显示
UI_Data(); //内容显示
OLED_Refresh(); //屏幕刷新
}
}
```
# 五、项目调试
## 遇到的问题
在调试ESP-01sWIFI模块时,写的程序运行一段时间会自动重启,特别在手机连接以后重启的概率就会增大,并打印```ets Jan 8 2013,rst cause:2, boot mode:(3,6)```,由代码知复位原因为:2 (外部复位),启动模式为:3,6 (flash启动,高速v1启动)。在网上查了一下有的说电源不稳在模块电源前加一个100uf的电容,或者是程序里出现了大量循环没有喂狗引起的看门狗复位,或是内存溢出程序中出现了野指针等。把这些方法都试了一遍后问题没有解决,最后用了点灯科技的官方示例后也仍未解决。
![自动重启报错](//image.lceda.cn/pullimage/35All5OaOlwNtJOIic3Frm1dHvHZBEYBjV9Yg8fM.jpeg)
![加电容](//image.lceda.cn/pullimage/nXFLOlC2cTXooDhxiiD5PLq3r0nC0WjTWbEMm4lX.jpeg)
## 解决方法
直到把手机客户端界面重写了就不会重启,经过一段时间的折腾发现只要不启用实时数据就不会重启了。估计是程序引起的重启吧。
![解决方法](//image.lceda.cn/pullimage/vHpADDIGGg0PJm5IxkZ120PMAIKhErSnSIyYam39.png)
# 六、作品展示
>B站演示视频:[https://www.bilibili.com/video/av534626790/](https://www.bilibili.com/video/av534626790/)
![智能窗户1](//image.lceda.cn/pullimage/b273xmy9ern4ZtTcpDv2SNYh2tO5INuoxoXWFOtY.jpeg)
![智能窗户2](//image.lceda.cn/pullimage/xpPnqxJc6gTN9cQcNOZvVNJWFcAzFqlI2oaNDdV6.jpeg)
![智能窗户3](//image.lceda.cn/pullimage/a1Jmfaa9VXX6M2cx0LCYSvgNRxC8LF7XquGtKkfa.jpeg)
![智能窗户4](//image.lceda.cn/pullimage/tKOn8smSToL1NpYFqsEBjtC5RyQBY3WRyfSA37lU.png)