# 卷帘大将
注:
该工程描述文件同说明文档,可下载附件“卷帘大将\_电动窗帘\_说明文档\.pdf”查看。
工程代码可下载附件 “Code_RollerBlindGeneral.zip” 查看。
配网、配网过程意外断电重启、联网控制演示视频已上传,可下拉至文档下方查看。
PDF文件内置超链接,可直接点击跳转Datasheet等文件。
作者:OpticalMoe
日期:2020/08/23
**高清靓照:**
![高清靓照](//image.lceda.cn/pullimage/tt9Gt019af0zH57yrO0ZjlQayn9U7HHcOQHQd1Fs.jpeg)
![高清靓照](//image.lceda.cn/pullimage/tB0X6Mf8gPZb8J2RTdaCO1SiJmoS9jE9GKuq7UKr.jpeg)
![高清靓照](//image.lceda.cn/pullimage/36tDIgylKyeyQr9IdaXQwdQnunZAsLQJmvqVpqEm.jpeg)
- - -
## 前 言
该项目旨在开发一款电动窗帘设备。
设备使用STM32单片机做主控,通过控制编码器电机正反转实现窗帘的电动开合,并通过EMW3080接入阿里云物联网平台,实现本地/云端控制。
* 电源:采用LP6498芯片,设计输出5V@1A。LDO采用HX9193,设计输出3.3V。
* WIFI:采用EMW3080(阿里飞燕固件),可接入阿里云生活物联网平台。
* 电机驱动:配有AB相编码器接口,可实现电机的位置环、速度环PID控制;驱动芯片采用RZ7899,支持25V@3A(最大5A);另配置了一限位开关接口,防止窗帘超限位运行损坏。
* 外设:配有无源蜂鸣器,通过TIM定时器控制,可奏乐;两个LED指示灯用于指示WIFI联网状态和设备运行状态;光敏电阻和NTC热敏电阻,可反馈光照和温度;两枚按键,单键可手动控制窗帘的上下行,双键可进入配网模式。
- - -
## 使用方法
**一、配网**
1. 上电后任意时刻,同时按动两按键两秒(先后按动两按键也可识别)。
2. 设备奏乐“ Bi~~~ Pu ”,开始配网流程。蓝色、绿色指示灯快闪。
3. 手机打开“云智慧”,扫码后,按提示开始配网。(扫码可下载APP)。
4. 等待5s后设备奏乐“嘀嘀嘀”三声,设备开启AP热点。
5. 手机端输入设备将要连接的家庭路由器名称和密码,点击“开始连接”,开始配网。(低版本安卓系统可直接接入adh_xxx热点,高版本安卓需手动连接该热点,热点连接成功后会断开,返回APP界面即可)。
6. 耐心等待设备上云。若过程中设备奏乐“ 滴~~~~ ”表示出错,可以断电重启后从第2步重试。
7. 当设备配网成功后,奏乐“ 1234567 ”,蓝色、绿色指示灯慢闪。
注:在配网过程中任意时刻意外断电导致设备配网失败,下次上电自动进入配网模式。从第2步开始依次执行配网操作直至配网完成。
**二、联网控制**
* 设备配网后可实现联网控制。
* 设备联网成功后奏乐“123”,蓝色指示灯开始慢闪。
* 按动上键或下键,窗帘匀速上行或下行,松手后,APP端自动刷新窗帘位置。
* APP端滑动窗帘位置滑块,窗帘运行到指定位置停止并锁定。
* 点击APP端“快捷操作”,可直接控制窗帘到达预设位置。
* 点击APP端“状态信息”,可查看设备温度、电压、光强等数据。
* 配网二维码:(未安装APP可扫码下载)
![配网二维码](//image.lceda.cn/pullimage/O4aYkeqtUe24zm9r3ofC6xUinMX86mKcFOnT0dBV.png)
- - -
**目 录**
**一 器件选型**
* 电源
* MCU
* WIFI
* 电机驱动
* 外设
**二 原理图设计**
* 电源
* MCU
* EMW3080
* 电机驱动
* 外设
**三 PCB设计**
* 电源&电机驱动
* MCU
* EMW3080
* 外设
**四 焊接**
**五 APP设计**
**六 程序调试**
* 代码移植&上传数据
* ADC&DMA采样
* 蜂鸣器驱动
* PID及参数整定
* 按键控制
* 任务/消息调度器改写
* 配网模式
**结论**
- - -
## 一 器件选型
本次活动要求设计一款物联网设备。为了控制成本,器件选型尽可能地选择性价比高的器件。
### 1.电源
* **电源输入插座** 采用DC005插座,设计可承受30V@3A。
Datasheet:[DC005-30A](https://atta.szlcsc.com/upload/public/pdf/source/20181207/C111573_8ADF7A9E2DA38C4FFF507DB908EB4EB9.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=fzDLrz83vRIAgK64gTsoNgSTFnE%3D&response-content-disposition=attachment%3Bfilename%3DC111573_DC%25E8%25BF%259E%25E6%258E%25A5%25E5%2599%25A8%252FDC005-2.5MMA%25E7%25BA%25A7_2018-12-07.PDF)
商城编号:C111573
封装:DC005-T25
输入:30V(最大)
电流:3A(最大)
注意选用A级插座,并注意可承受的电压电流是否满足。
* **DCDC** 采用LP6498AB6F芯片,设计输出5.12V@1A。
Datasheet:[LP6498](https://atta.szlcsc.com/upload/public/pdf/source/20190410/C387722_5E8EF9F8DFA2C79D78906FD8B2280BF7.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=MEOCe68loWFcaP42nXQBgiJbcFQ%3D&response-content-disposition=attachment%3Bfilename%3DC387722_LP6498AB6F_2019-04-10.PDF)
商城编号:C387722
封装:SOT23-6
输入:4.5 ~ 30V
输出:4.8 ~ 12V
电流:1200mA(最大)
该芯片耐压高,输入、输出电压范围宽,电流大。体积小,外围电路简单,输出电压可调。便宜皮实,性价比高。
* **LDO** 采用HX9193-33GB,设计输出3.3V@600mA。
Datasheet:[HX9193-33GB](https://atta.szlcsc.com/upload/public/pdf/source/20181113/C296123_247B444685EF4D8999014683EA1AF591.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=XVOQdquv%2FDEuNQwCCuEmU%2F3mrlE%3D&response-content-disposition=attachment%3Bfilename%3DC296123_%25E4%25B8%2589%25E7%25AB%25AF%25E7%25A8%25B3%25E5%258E%258BICHX9193-33GB_2018-11-13.PDF)
商城编号:C296123
封装:SOT-23-5
输入:6V(最大)
输出:3.3V(固定)
电流:600mA(最大)
压降:480mV(最大)
该芯片电流大,压降小。体积小,外围电路简单。便宜皮实,性价比高。
- - -
### 2\. MCU
MCU采用 **STM32F030K6** 单片机。
Datasheet:[STM32F030K6T6](https://atta.szlcsc.com/upload/public/pdf/source/20190902/C106925_4C89CDEE905DCB71B11CF5798678D860.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=xy8f9QNRqyJ5D0XPnevyawZD%2BDA%3D&response-content-disposition=attachment%3Bfilename%3DC88446_STM32F030K6T6TR_2019-09-02.PDF)
商城编号:C88446
封装:LQFP32
选用这款单片机的主要原因是 ~~便宜~~ 性价比高。同时STM32芯片可以使用 ST-Link 连接 Keil 在线DEBUG,也可以使用 **STM32CubeMonitor** 软件打印内部变量变化曲线,方便PID调试。
这款单片拥有32KB FLASH,4KB RAM,48MHz的主频,LQFP-32的封装,一个串口,5个定时器,一个10通道12bit AD,26个IO。可谓是小巧精悍,实力不凡。
> 原本计划上RTOS的,但是4KB的RAM跑OS有点勉强,稍稍加点东西就超,最后没跑上。
> 自己做的ST-Link V2-1,成本低,性能强,比某宝盗版J-Link采样频率高。
- - -
### 3\. WIFI
**WIFI** 选用的是EMW3080V2(阿里云飞燕固件)。WIFI选型没有经验,全跟课程走。
- - -
### 4.电机驱动
* **电机驱动** 部分采用RZ7899驱动芯片。
Datasheet:[RZ7899](https://atta.szlcsc.com/upload/public/pdf/source/20170213/1486965283321.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=h0oQds3tRKJPlhUe0RfGmyCNW3s%3D&response-content-disposition=attachment%3Bfilename%3DC92373_RZ7899_2017-02-13.PDF)
商城编号:C92373
封装:SOP-8_150mil
输入:3 ~ 25V
电流:3A
峰值电流:5A
内建刹车功能、内置过温保护、内置短路保护、内置过流保护。
* **电流传感器** 采用CC6900SO-5A芯片。
Datasheet:[CC6900SO-5A](https://atta.szlcsc.com/upload/public/pdf/source/20181213/C350864_3935909D6FCF7E5436F1F062F69C3E0E.pdf?Expires=4070880000&OSSAccessKeyId=LTAIJDIkh7KmGS1H&Signature=YiwemN7QFvwhXSYo58SSxDWYTZg%3D&response-content-disposition=attachment%3Bfilename%3DC350864_CC6900SO-5A_2018-12-13.PDF)
商城编号:C350864
封装:SOP-8
增益:400mV/A
电流:5A
- - -
### 5.外设
外设部分设计有:一个无源蜂鸣器,两个按键,两个LED指示灯。编码器接口,限位开关接口。
- - -
## 二 原理图设计
根据个人的设计习惯,原理图按功能划分,设计在5张A4图纸上。下面依次介绍。
### 1.电源
电源部分主要分为四块。分别是:**电源输入插座、DCDC降压、LDO降压、测试点**。
**电源输入插座** 正极先通过SS54二极管,再接入设备。
**DCDC** 和 **LDO** 部分按照官方手册绘制就可。
![原理图-电源](//image.lceda.cn/pullimage/TfS9fWFkqElvlpGMhXy1OhqRPhmQCRo8WUjirExt.png)
```
原理图-电源
```
- - -
### 2\. MCU
MCU部分主要设计 **晶振电路,复位电路,SWD下载接口**。
**晶振** 采用SMD-3225封装的8MHz无源晶振,该封装对烙铁焊接不友好。晶振电路主要由晶振和两个22pF无极性陶瓷电容构成。
**复位电路** 由10k上拉电阻和0.1uF电容构成,主要完成上电复位功能。
**SWD接口** 用于调试和下载程序,引出了SWCLK、SWDIO、NRST,采用XH2.5-4P端子接口。引出NRST引脚,即使程序中未使能SWD调试接口仍能下载、调试程序。
![原理图-MCU](//image.lceda.cn/pullimage/hg2rVWtJXxy3BMnuJYrH8e8REWiBzdpXBCSY49Qh.png)
```
原理图-MCU
```
- - -
### 3\. EMW3080
EMW3080电路主要包括 **电源滤波、串口、BOOT、测试点**。
**电源滤波** 采用0.1uF和10uF组合的形式;根据手册要求,电源采用3.3V。
**串口** 通过0R电阻交叉连接到MCU串口;GPIO23根据手册要求通过10k上拉;
**BOOT** 引脚预留0R电阻接地,但不焊接;EN引脚通过0R电阻连接到MCU和按键,主要完成WIFI的复位工作。
**测试点** 包括串口的TXD和RXD接口。调试时连接串口,可监视MCU和WIFI间交换的所有数据。
> 连接外部串口监视数据时,MCU串口需设置为开漏+上拉模式,否则会导致MCU与WIFI间数据乱码。
![原理图-EMW3080](//image.lceda.cn/pullimage/lhAjgH3Gm265FYJvEWrJLNM1Yp15QaOe0paqfH6A.png)
```
原理图-EMW3080
```
- - -
### 4\. 电机驱动
电机驱动部分主要完成 **电流传感器电流采样、电源电压采样、电机驱动、测试点**。
**电源电压采样** 采用分压电阻结构,通过100k和10k电阻获得低的采样电压送入MCU-ADC接口。
**电流采样** 按照CC6900SO-5A官方手册绘制即可。
**电机驱动芯片** 按照官方手册绘制,注意输入和输出接口走线宽度。同时在电机接口上设计四个二极管钳位。电机采用5.0-2P接口,方便拆装。
**测试点** 主要有电流采样点、电压采样点、电机驱动正反转信号点,便于调试时确定状态。
注意功率地与信号地分开,并连接
![原理图-电机驱动](//image.lceda.cn/pullimage/Tt0260ZeDmawoDoUiOKyoBqxZ2NyzoGOoJie5f4X.png)
```
原理图-电机驱动
```
- - -
### 5\. 外设
外设主要设计 **无源蜂鸣器、光敏电阻、热敏电阻、编码器接口、按键、LED指示灯、测试点、机械孔**。
**无源蜂鸣器** 需连接到TIM-PWM输出引脚,可以通过调整TIM装载值和比较值控制蜂鸣器音调和音量。
**热敏电阻** 和 **光敏电阻** 需要串联一个已知阻值的电阻接入电路,通过MCU-ADC测量中间点电压反向推算出外部温度和光强。
> 热敏电阻和光敏电阻测量精度非常有限,即使程序中加入修正,采样值仍可能和实际值偏差较大。对温度和光强精度要求高的场所慎用。
**编码器接口** 是为了电机的位置环、速度环PID设计,可连接AB相编码器。接口内已设计上拉电阻和硬件消抖电路,编码器电源通过两个0R电阻在5V和3.3V间选择,注意不可同时连接5V和3.3V电阻。
**按键** 用于控制窗帘的上拉、下拉动作,同时在必要时刻充当配网开关。
**蓝色LED指示灯** 用于指示WIFI连接状态,**绿色LED** 用于指示设备运行状态。
**测试点** 可测量光敏电阻和热敏电阻输出电压。
**机械孔** 是四个M3螺丝孔,方便设备通过螺丝安装在需要的地方。
![原理图-外设](//image.lceda.cn/pullimage/nv3PnkDgL1C5qx8eAa93J5oJDLq2WQ0ISTHGDN2U.png)
```
原理图-外设
```
- - -
## 三 PCB设计
PCB设计经验不足,在此抛砖引玉。如有错误之处,还望大佬不惜赐教。
![PCB效果图](//image.lceda.cn/pullimage/1mXDSHXZxVGB70g22QDLD4krlMLdb7WxIjbA0uFU.png)
```
PCB效果图
```
- - -
### 1\. 电源&电机驱动
**电源** 和 **电机驱动** 主要注意走线宽度、功率地和信号地分开、端子下面挖空防止接地短路等。
![PCB-电源&电机驱动](//image.lceda.cn/pullimage/i5HsjL5xzMk9M1YQQkGj7kFMmvywm0bXYFHKldQg.png)
```
PCB-电源&电机驱动
```
左侧为 **LDO**,右侧为 **DCDC**。注意电感离DCDC芯片近一些,电感下面不要走信号线。(此图为错误示范)
![PCB-DCDC&LDO](//image.lceda.cn/pullimage/wFLNddjtHocm0miJWRFr75xTnIVztfvMB2XNmxgp.png)
```
PCB-DCDC&LDO
```
- - -
### 2\. MCU
MCU主要注意晶振连线短一些,滤波电容靠近MCU电源引脚。
![PCB-MCU](//image.lceda.cn/pullimage/KZ7yxzw3HWy2zTB1yw2AGIYLiyQbY17t6xAlTv3e.png)
```
PCB-MCU
```
- - -
### 3\. EMW3080
EMW3080按照官方手册要求,1、2、24、25脚不接,天线前方、左右留16mm净空区。搜索EDA中所有的封装都不完全满足官方手册要求,我自己画了一个。
![PCB-EMW3080](//image.lceda.cn/pullimage/qr46IvUmW0ePuPrHv7rodVwsbzogx7fcucldQrpu.png)
```
PCB-EMW3080
```
- - -
### 4\. 外设
**热敏** 避开发热区域;**光敏** 避开LED区域;**编码器接口** 放在电机端子旁边,方便连接;**按键** 和 **LED指示灯** 放在板子下方,方便操作。
![PCB-外设](//image.lceda.cn/pullimage/USJQhyT4ZAM4QuUusiuqtmQQ1LFPPATdZiAugjQU.png)
```
PCB-外设
```
- - -
## 四 焊接
拿到PCB,准备焊接工具,开始焊接。
![PCB](//image.lceda.cn/pullimage/2aQLCMKoi6bwNuJz1BouIrdH6GU0pnpuATcyA0Dr.jpeg)
```
PCB
```
因为部分PCB中有一些封装对烙铁十分不友好。所以,上风枪。
![工具](//image.lceda.cn/pullimage/5xdQvoVY128rhaH1TMxB917hdYvBIjiEnsmjiBld.jpeg)
```
工具
```
下面简述下焊接步骤和注意事项。
* 首先,准备0.5mm左右的焊锡丝、焊锡膏、助焊剂。清理烙铁头,烙铁温度350℃。尖嘴镊子。提前释放身上静电。
* 第一步,焊接DCDC芯片及外围电路。电感封装问题,只能用风枪和焊锡膏焊。焊接完成后,焊接DC005接口。接入12V电源,使用万用表电压档测量5V测试点电压是否在5.12V左右。若电压不正确,需核对反馈电阻阻值是否正确。
* 第二步,焊接LDO电路。焊完后上电测试输出电压是否在3.3V左右。
* 第三步,电源没有问题后,焊接其他元件。顺序没有要求,一般由高度低的元件开始焊接。
单片机可以使用针管挤焊锡膏在焊盘上,摆好单片机,烙铁走一遍就能焊好,不连锡,贼好用。
电容焊盘也较短,需要焊锡膏和烙铁配合焊接。
PCB注意有几个元件不能焊接。分别是:编码器电源5V处0R电阻,EMW3080的BOOT接地电阻。
焊接完成后,效果如下。
![实物图](//image.lceda.cn/pullimage/B5OSsPPBub4zktbGxyPzaDh9r43sl0OD4t872rkS.jpeg)
```
实物图
```
- - -
## 五 APP设计
APP设计采用阿里云物联网平台。具体过程可参考[B站课程回放](https://www.bilibili.com/video/BV1uC4y1t7KH)
一些属性参数如下:(部分功能未使用)
![属性参数](//image.lceda.cn/pullimage/HsWQ53XBjWaXkvRBPkdqTSpYXMl11yWYCegdqAu5.png)
```
属性参数
```
![三元组信息](//image.lceda.cn/pullimage/LqkYJQjeB8uQ1TrQPcrVh11uVQeKLIEkxRAEBPew.png)
```
三元组信息
```
![APP主界面](//image.lceda.cn/pullimage/PlCI7GGAFrB0f094ssI0SdaWU9XRtFGl2mOLmvpx.png)
```
APP主界面
```
- - -
## 六 程序调试
程序调试主要按功能块调试。调试日志按以下顺序依次进行:**代码移植、上传数据、蜂鸣器驱动、ADC&DMA采样、PID&参数整定、按键控制、任务调度器改写、配网模式**。
### 1\. 代码移植&上传数据
零妖大佬给的例程是基于51单片机。51程序和STM32不兼容,需要移植一些底层代码。代码平台 **CubeMX&HAL** 库,**MDK-ARM 5.27** 。
> 移植需要一定的软件操作基础,回忆的过程,不完整。
> 工程代码可移步附件下载查看。
移植前可通过串口先让WIFI上云,减小移植难度。
首先打开 **CubeMX** 软件,选择 **STM32F030K6T6** ,使能外部晶振,使能SWD接口。勾选必要的外设。设置时钟48MHz。填写工程名称,保存位置。选择使用的IDE为MDK-ARM 5.27,勾选“为每一个外设生成.c/.h文件”。点击“生成代码”。
![引脚配置](//image.lceda.cn/pullimage/kjWOid8ZWm2g39pPMhvdJnmLvz2mZI7TcwZ3YqzI.png)
```
引脚配置
```
![时钟树](//image.lceda.cn/pullimage/51rKAZh4WLQOONtZsItjFbR3TaWWuVq5QG4V0gmW.png)
```
时钟树
```
代码生成后,点击“打开工程”,自动调用Keil软件。
首先,在keil左侧Project中添加一个文件夹,用来存放我们的.c文件。双击该文件夹,加入Code_User文件夹下所有文件。
然后,点击 **魔术棒** ,点击“C/C++”选项卡,点击“Include Paths”后面三点,添加“Code_User”文件夹路径。
编译文件,不出意外,您会收获 **error(s)** 。
下面开始解决这些错误:
1. 大多数错误是由于 **#include \** 造成的,这是51的头文件,32不使用,**删除** 所有该语句。
2. **Button.c** 文件实现按键的短按、长按识别。该设备只需识别单按键和双按键,**修改** 其中的**Button_Loop** 函数。
3. **DeviceName.c** 文件操作三元组数据。我们把三元组数据移植到 **usart.c** 文件下,**删除** 该文件。
4. **DS18B20.c** 文件用于读取温度传感器数据。我们温度采用热敏电阻配合ADC,不涉及该文件,**删除**。
5. **IAP_EEPROM.c** 文件操作 **EEPROM**。32没有,**删除** 文件。
6. **main.c** 文件是设备主逻辑实现。复制内容到CubeMX创建的 **main.c** 文件中,**删除** 该文件。
7. **Mode.c** 文件操作LED和继电器。我们没有继电器,LED灯移植到 **WIFI.c** 文件下,**删除** 该文件。
8. **Relay.c** 文件操作继电器,**删除** 。
9. **Timer0.c** 文件实现任务调度器,需要修改定时器底层。**删除** 定时器初始化结尾前代码,并添加“**HAL\_TIM\_Base\_Start\_IT\(&htim17\);**”启动定时器。
10. **Uart_1.c** 文件用于转发串口2数据到电脑。**STM32F030K6** 只有一个串口,**删除** 该文件。
11. **Uart2.c** 文件主要和WIFI交换数据。**修改** 底层代码,使用STM32的 **DMA+空闲中断** 接收不定长数据。
12. **WDT.c** 文件实现看门狗。未使用,**删除**。
其他错误可双击编译结果跳转至指定位置。具体问题自行查阅资料修改,不赘述。
首次移植可只删除和移植串口代码,其他无关紧要的稍后移植。
如果一切顺利,编译没有错误,可下载到MCU。WIFI能够上云。
``` c
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC_Init();
MX_TIM3_Init();
MX_TIM14_Init();
MX_USART1_UART_Init();
MX_TIM17_Init();
MX_TIM1_Init();
MX_TIM16_Init();
/* USER CODE BEGIN 2 */
//******** Init ***************//
AdcInit();
MotorSpeedInit();
MotorPositionInit();
//******** OS Init ***************//
Init_Uart2();
Timer0_Init();
Button_Init();
WIFI_Init();
//******** PID ***************//
Timer_0_Add_Fun(10, MotorPositionLoop); //位置环
// Timer_0_Pas_Fun(MotorPositionLoop); //位置环 暂停
Timer_0_Add_Fun(10, MotorSpeedLoop); //速度环
Timer_0_Pause_Fun(MotorSpeedLoop); //速度环 暂停
//******** OS ***************//
Timer_0_Add_Fun(50, UserButton); //按键检测底层业务
Timer_0_Add_Fun(5, Uart2_CheckMessageLoop); //帧处理函数
Timer_0_Add_Fun(500, WIFI_LED_Loop); //网络状态指示灯 1Hz
Timer_0_Add_Fun(500, Mode_LED_Loop); //设备状态指示灯 1Hz
Timer_0_Add_Fun(60 * 1000, WIFI_SubTemp); //上报一次 温度 信息
Timer_0_Add_Fun(61 * 1000, WIFI_SubLlluminance);//上报一次 光强 信息
Timer_0_Add_Fun(62 * 1000, WIFI_SubVoltage); //上报一次 电压 信息
// Timer_0_Add_Fun(32 * 1000, WIFI_SubMotorMode); //上报一次 电机运行模式 信息
// Timer_0_Add_Fun(33 * 1000, WIFI_SubLimitStatus);//上报一次 限位状态 信息
// Timer_0_Add_Fun(34 * 1000, WIFI_SubAction); //上报一次 电机动作 信息
// Timer_0_Add_Fun(35 * 1000, WIFI_SubMode); //上报一次 窗帘模式 信息
// Timer_0_Add_Fun(36 * 1000, WIFI_SubPosition); //上报一次 窗帘位置 信息
//******** Message ***************//
Timer0_Add_MessageFun('A', DistributionNetwork); //上次AP配网不成功,开机会自动进 “配网模式”
Timer0_Add_MessageFun('F', DistributionNetwork); //上下按键同时长按2S 配网
Timer0_Add_MessageFun('U', MotorUp); //上键 上行
Timer0_Add_MessageFun('D', MotorDown); //下键 下行
Timer0_Add_MessageFun('S', LetGo); //松手检测
Timer0_Add_MessageFun('S', WIFI_SubPosition); //上传位置
//******** Buzzer ***************//
Timer0_Add_MessageFun('C', Buzzer_DJI); //连接网络
Timer0_Add_MessageFun('U', Buzzer_Di); //上键 上行
Timer0_Add_MessageFun('D', Buzzer_Di); //下键 下行
//******** Pause ***************//
PauseUpload(); //未联网时,所有上传动作暂停
//******** ReStart ***************//
Timer0_Add_MessageFun('C', ReStartUpload); //开机 连接网络成功
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
Timer0_SYS_APP_LOOP();
Timer0_SYS_APP_LOOP_Message();
Timer0_SYS_APP_LOOP_Once();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
```
上传数据基于零妖代码结构。把所有上传项目独立,可配置不同项目不同上传频率。
``` C
//******************* 上传数据 **************************//
void WIFI_SubTemp(void)
{
WIFI_SubStation(0);
}
void WIFI_SubLlluminance(void)
{
WIFI_SubStation(1);
}
void WIFI_SubMotorMode(void)
{
WIFI_SubStation(2);
}
void WIFI_SubLimitStatus(void)
{
WIFI_SubStation(3);
}
void WIFI_SubMode(void)
{
WIFI_SubStation(5);
}
void WIFI_SubPosition(void)
{
WIFI_SubStation(6);
}
void WIFI_SubVoltage(void)
{
WIFI_SubStation(7);
}
```
- - -
### 2\. ADC&DMA采样
ADC采用DMA多通道不连续采集。使用二维数组缓存数据,每次获取ADC测量值时均采样10次求平均后上传。
``` C
//0:Current; 1:Voltage; 2:Temp; 3:Photo; 4:Vref
uint16_t AdcValue[10][5];
float AdcActualValue[10][5];
uint8_t AdcValuePosition = 0;
```
``` C
void AdcInit(void)
{
//校准ADC
HAL_ADCEx_Calibration_Start(&hadc);
//开DMA
AdcValuePosition = 0;
// HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
HAL_ADC_Stop_DMA(&hadc);
if(++AdcValuePosition >= 10)
AdcValuePosition = 0;
HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
}
```
温度和光强公式根据元件手册给出的温度-阻值、光强-阻值曲线拟合而成,辅以修正因子修正。
``` C
float AdcGetOneChannel(uint8_t channel)
{
uint8_t i;
float AdcReturn;
float PowerVoltage;
//校准ADC
HAL_ADCEx_Calibration_Start(&hadc);
//开DMA
AdcValuePosition = 0;
HAL_ADC_Start_DMA(&hadc, (uint32_t *)AdcValue[AdcValuePosition], 5);
HAL_Delay(1);
HAL_ADC_Stop_DMA(&hadc);
for(i = 0; i < 10; i++)
{
PowerVoltage = 1.2 * 4096 / AdcValue[i][4]; //电源电压,内部参考电压1.2V
switch(channel)
{
case 0: //电流,中点2.5v,增益100mV/A
AdcActualValue[i][0] = ( 2.5 - ( AdcValue[i][0] * PowerVoltage / 4096 )) / 0.1;
break;
case 1: //电压,1/11
AdcActualValue[i][1] = ( AdcValue[i][1] * PowerVoltage / 4096 ) * 11;
break;
case 2: //温度
//-10~50℃: y = -33.186 * x + 80.268 (R^2 = 0.998) //修正:-5
AdcActualValue[i][2] = -33.186 * ((float)AdcValue[i][2] * PowerVoltage / 4096) + 80.268 - 5;
break;
case 3: //光强: [400lx] y = -16691 * x + 8262.9 (R^2 = 0.9764);
if(AdcValue[i][3] > (0.47 * 4096 / PowerVoltage))
AdcActualValue[i][3] = 30.24 * pow(( (float)AdcValue[i][3] * PowerVoltage / 4096 ), -3.54);
else
AdcActualValue[i][3] = -16691 * ((float)AdcValue[i][3] * PowerVoltage / 4096 ) + 8262.9;
break;
default:
break;
}
if(i == 0)
AdcReturn = AdcActualValue[i][channel];
AdcReturn += AdcActualValue[i][channel];
AdcReturn /= 2;
}
return AdcReturn;
}
```
- - -
### 3\. 蜂鸣器驱动
蜂鸣器通过TIM14-1通道驱动。内置25个环形缓存区数组实现蜂鸣器音调、音量、延时功能。
``` C
#define BuzzerParameterMax 25
//蜂鸣器参数:频率(0-65535Hz),音量(0-100),时长(0-65535ms)
uint16_t BuzzerParameter[BuzzerParameterMax][3] = {{0xFFFF, 0, 0}};
uint8_t BuzzerPositionOut = 0, BuzzerPositionIn = 0, BuzzerCount = 0;
float BuzzerReload = 0.0, BuzzerTime = -1.0, BuzzerBeat = 0.0;
uint8_t BuzzerWorking = 0;
```
``` C
void BuzzerInterrupt(void) //蜂鸣器中断
{
if(BuzzerTime > 0) //延时中...
{
BuzzerTime -= BuzzerBeat;
}
else //切换
{
if(BuzzerCount == 0) //OVER
{
BuzzerWorking = 0;
HAL_TIM_PWM_Stop(&htim14, TIM_CHANNEL_1);
HAL_TIM_Base_Stop_IT(&htim14);
HAL_GPIO_WritePin(Buzzer_GPIO_Port, Buzzer_Pin, GPIO_PIN_RESET);
}
else //NEXT
{
BuzzerReload = 1000000.0 / BuzzerParameter[BuzzerPositionOut][0];
__HAL_TIM_SET_AUTORELOAD(&htim14, (uint16_t)BuzzerReload - 1);
__HAL_TIM_SET_COMPARE(&htim14, TIM_CHANNEL_1, (uint16_t)(BuzzerReload * BuzzerParameter[BuzzerPositionOut][1] * 0.01 * 0.9));
BuzzerBeat = BuzzerReload / 1000; //ms
BuzzerTime = BuzzerParameter[BuzzerPositionOut][2];
HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim14);
BuzzerPositionOut = (BuzzerPositionOut + 1) % BuzzerParameterMax;
BuzzerWorking = 1;
BuzzerCount--;
}
}
}
```
``` C
uint8_t BuzzerSetParameter(uint16_t frequncy, uint8_t volume, uint16_t time) //设置蜂鸣器参数
{
BuzzerParameter[BuzzerPositionIn][0] = frequncy;
BuzzerParameter[BuzzerPositionIn][1] = volume;
BuzzerParameter[BuzzerPositionIn][2] = time;
BuzzerPositionIn = (BuzzerPositionIn + 1) % BuzzerParameterMax;
if((++BuzzerCount == 1) && (!BuzzerWorking)) //从停止状态启动
{
BuzzerTime = -1.0;
BuzzerInterrupt();
}
if(BuzzerCount == BuzzerParameterMax)
return 0;
else
return 1;
}
```
- - -
### 4\. PID及参数整定
PID使用增量式和位置式,分别用于速度环和位置环。
PID参数因电机而异,需要自行耐心调整。
``` C
void MotorIncrementPID(struct PID *pid, int16_t pidInput)
{
pid->PidInput = pidInput;
//Pid
pid->PidEt = pid->PidSetPoint - pid->PidInput;
pid->PidOutput += pid->PidKp * (pid->PidEt - pid->PidLastErr) \
+ pid->PidKi * pid->PidEt \
+ pid->PidKd * (pid->PidEt - 2 * pid->PidLastErr + pid->PidLastTwoErr);
//Pid限幅
pid->PidOutput = pid->PidOutput > pid->PidLimitUp ? pid->PidLimitUp : pid->PidOutput;
pid->PidOutput = pid->PidOutput < pid->PidLimitDown ? pid->PidLimitDown : pid->PidOutput;
//覆写
pid->PidLastTwoErr = pid->PidLastErr;
pid->PidLastErr = pid->PidEt;
}
```
``` C
void MotorPositionPID(struct PID *pid, int32_t pidInput)
{
pid->PidInput = pidInput;
//Pid
pid->PidEt = pid->PidSetPoint - pid->PidInput;
pid->PidEtSum += pid->PidEt;
pid->PidOutput = pid->PidKp * pid->PidEt + pid->PidKi * pid->PidEtSum
+ pid->PidKd * (pid->PidEt - pid->PidLastErr);
//Pid限幅
pid->PidOutput = pid->PidOutput > pid->PidLimitUp ? pid->PidLimitUp : pid->PidOutput;
pid->PidOutput = pid->PidOutput < pid->PidLimitDown ? pid->PidLimitDown : pid->PidOutput;
//覆写
pid->PidLastErr = pid->PidEt;
}
```
因为使用的编码器电机阻尼大,大约5V电压才能启动,为了避免电机从停止状态退出过程时间过长,在PID输出和电机间添加MotorCurve函数。保证低速时呈对数变化,高速时线性变化。该函数已在我的遥控车项目验证,效果非常棒。
``` C
float MotorCurve(float inPut)
{
float outPut = 0;
outPut = (inPut < 0 ? -1 : 1);
inPut = inPut > 265 ? 265 : inPut;
inPut = inPut < -265 ? -265 : inPut;
//输出曲线。类似对数曲线。
//分界点 15:266.2; 0.002425; -0.155; use
//可快速从驻车状态退出,且低速时刹车距离变短。
//f(x) = 256.7[227.6, 285.8] * exp(0.002453[0.001927, 0.002979] * x)
// + (-256.7[-297.8, -215.6]) * exp(-0.09653[-0.1635, -0.02957] * x);
/*下面两行可合并为一行。此处改写是因为MarkDown不识别*/
outPut *= 266.2 * (exp(0.002425 * (inPut < 0 ? - inPut : inPut));
output -= exp(-0.155 * (inPut < 0 ? - inPut : inPut))) + 0.5;
return outPut;
}
```
**速度环:**
``` C
void MotorSpeedInit(void)
{
//PID: 5, 0.6, 1
MotorInit(&PidSpeed);
MotorSetPidParameter(&PidSpeed, 5, 0.6, 1);
PidSpeed.PidLimitUp = 150;
PidSpeed.PidLimitDown = -150;
}
```
从速度环曲线可以看出,调整时间大约0.3s。过程存在一点超调,正常现象,可以保证更快速的调整。
![速度环调试曲线](//image.lceda.cn/pullimage/dBaBRAFTr6VKVf6XPBawHqPaBz1T7yz9QWWycoRf.jpeg)
```
速度环调试曲线
```
**位置环:**
``` C
void MotorPositionInit(void)
{
//0.6, 0, 13
MotorInit(&PidPosition);
MotorSetPidParameter(&PidPosition, 0.6, 0, 13); //微调该参数
PidPosition.PidLimitUp = 200;
PidPosition.PidLimitDown = -200;
}
```
位置环中间线性段受限于电机输出限幅。后段波动是施加扰动和释放扰动造成。
![位置环曲线](//image.lceda.cn/pullimage/R9XiPLjXu4uRWKRLRwPluRuuCALuSNsQASQQDMQL.jpeg)
```
位置环曲线
```
- - -
### 5\. 按键控制
按键部分删除了原来的Button\_Loop实现函数。添加了User\_Button函数,用于识别单按键按下,双按键按下,按键松开等动作。并向系统发布消息。
``` C
void UserButton(void)
{
if( ((Button_ReadIO(0)) && (Button_ReadIO(1))) \
&& (ButtonTwoTime || Button_Timer[0] || Button_Timer[1]) ) //松手检测
{
Timer0_SendMessage('S'); //松手
}
if((!Button_ReadIO(0)) && (!Button_ReadIO(1))) //同时按下
{
ButtonTwoTime++;
Button_Timer[0] = 0;
Button_Timer[1] = 0;
}
else
{
ButtonTwoTime = 0;
if(Button_ReadIO(0) == 0)
Button_Timer[0]++;
else
Button_Timer[0] = 0;
if(Button_ReadIO(1) == 0)
Button_Timer[1]++;
else
Button_Timer[1] = 0;
}
if(ButtonTwoTime > 65500)
ButtonTwoTime = 65500;
if(Button_Timer[0] > 65500)
Button_Timer[0] = 65500;
if(Button_Timer[1] > 65500)
Button_Timer[1] = 65500;
if(ButtonTwoTime > Button_L_Time)
{
Timer0_SendMessage('F'); //恢复出场设置
return;
}
if(ButtonTwoTime == 0) //按键未同时按下
{
if((Button_Timer[0] > Button_G_Time) && (Button_Timer[0] < Button_L_Time))
{
Timer0_SendMessage('U'); //上
Button_Timer[0] = 65500;
return;
}
if((Button_Timer[1] > Button_G_Time) && (Button_Timer[1] < Button_L_Time))
{
Timer0_SendMessage('D'); //下
Button_Timer[1] = 65500;
return;
}
}
}
```
为了实现一个消息可以对应多个功能函数,我们修改了Message循环部分代码。修改后的代码,可以实现:按下上键,电机速度环模式上行,同时蜂鸣器“滴”提示音。下键同理。
``` C
//******** Message ***************//
Timer0_Add_MessageFun('A', DistributionNetwork); //上次AP配网不成功,开机会自动进 “配网模式”
Timer0_Add_MessageFun('F', DistributionNetwork); //上下按键同时长按2S 配网
Timer0_Add_MessageFun('U', MotorUp); //上键 上行
Timer0_Add_MessageFun('D', MotorDown); //下键 下行
Timer0_Add_MessageFun('S', LetGo); //松手检测
Timer0_Add_MessageFun('S', WIFI_SubPosition); //上传位置
//******** Buzzer ***************//
Timer0_Add_MessageFun('C', Buzzer_DJI); //连接网络
Timer0_Add_MessageFun('U', Buzzer_Di); //上键 上行
Timer0_Add_MessageFun('D', Buzzer_Di); //下键 下行
```
``` C
//系统循环执行-邮箱处理
void Timer0_SYS_APP_LOOP_Message(void)
{
signed char i = 0, j = 0;
if(Timer0_Handler_Flag_Message == 0)
return;
Timer0_Handler_Flag_Message = 0;
for(i = 0; i < Timer_0_List_Count; i++) //调用消息队列中的函数
{
if(Timer0_Message_Struct.MessageQueue[i])
{
for(j = 0; j < Timer_0_List_Count; j++)
{
if(Timer0_Message_Struct.Flag[j] == 1)
{
if(Timer0_Message_Struct.MessageQueue[i] == Timer0_Message_Struct.MessageList[j])
{
Timer0_Message_Struct.MessageFun_Point_List[j]();
// j = Timer_0_List_Count + 10; //注释则可以一个消息对应多个函数
}
}
}
Timer0_Message_Struct.MessageQueue[i] = 0;
}
}
}
```
- - -
### 6\. 任务/消息调度器改写
为了实现一些功能,我们在零妖的任务调度器基础上进行了修改。
对Flag部分重新规划。
``` C
unsigned char Flag[Timer_0_List_Count]; //0:空;1:运行;2:暂停;10:暂停所有
```
修改后的任务调度器添加了:删除、暂停、恢复、暂停所有、恢复所有功能。
``` C
//************** 任务 *****************//
//添加
unsigned char Timer_0_Add_Fun(unsigned long Time,void (*Fun)(void))
{
signed char i = 0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 0) //空的
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
Timer0_Struct.Fun_Point_List[i] = Fun;
Timer0_Struct.Timer[i] = Time-1;
return 1;
}
}
return 0;
}
//删除
unsigned char Timer_0_Del_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Struct.Fun_Point_List[i])
{
Timer0_Struct.Flag[i] = 0;
Timer0_Struct.Counter[i] = 0;
return 1;
}
}
return 0;
}
//暂停
unsigned char Timer_0_Pause_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if((Fun == Timer0_Struct.Fun_Point_List[i])
&& (Timer0_Struct.Flag[i] == 1))
{
Timer0_Struct.Flag[i] = 2;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//恢复
unsigned char Timer_0_ReStart_Fun(void (*Fun)(void))
{
signed char i=0;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Struct.Fun_Point_List[i])
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//暂停所有
unsigned char Timer_0_Pause_All(void)
{
signed char i=0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 1)
{
Timer0_Struct.Flag[i] = 10;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
//恢复 通过“暂停所有”暂停的任务
unsigned char Timer_0_ReStart_All(void)
{
signed char i=0;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Struct.Flag[i] == 10)
{
Timer0_Struct.Flag[i] = 1;
Timer0_Struct.Counter[i] = 0;
}
}
return 0;
}
```
为了实现一些功能,我们添加了Flag,并对其重新规划。
``` C
unsigned char Flag[Timer_0_List_Count]; //0:空;1:运行;2:暂停;10:暂停所有
```
修改后的消息调度器添加了:删除、暂停、恢复、暂停所有、恢复所有功能。
``` C
//************** 消息 *****************//
//添加
unsigned char Timer0_Add_MessageFun(unsigned char Message,void (*Fun)(void))
{
signed char i;
for(i = 0; i < Timer_0_List_Count; i++)
{
if(Timer0_Message_Struct.Flag[i] == 0)
{
Timer0_Message_Struct.Flag[i] = 1;
Timer0_Message_Struct.MessageList[i] = Message;
Timer0_Message_Struct.MessageFun_Point_List[i] = Fun;
return 1;
}
}
return 0;
}
//删除
unsigned char Timer0_Del_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if(Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
{
Timer0_Message_Struct.Flag[i] = 0;
Timer0_Message_Struct.MessageList[i] = 0x00;
return 1;
}
}
return 0;
}
//暂停
unsigned char Timer0_Pause_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if((Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
&& (Timer0_Message_Struct.Flag[i] == 1))
{
Timer0_Message_Struct.Flag[i] = 2;
return 1;
}
}
return 0;
}
//恢复
unsigned char Timer0_ReStart_MessageFun(void (*Fun)(void))
{
signed char i;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前删
{
if(Fun == Timer0_Message_Struct.MessageFun_Point_List[i])
{
Timer0_Message_Struct.Flag[i] = 1;
return 1;
}
}
return 0;
}
//暂停 所有
unsigned char Timer0_Pause_MessageAll(void)
{
signed char i;
for(i = (Timer_0_List_Count - 1); i >= 0; i--) //从后往前
{
if(Timer0_Message_Struct.Flag[i] == 1)
{
Timer0_Message_Struct.Flag[i] = 10;
}
}
return 0;
}
//恢复 所有
unsigned char Timer0_ReStart_MessageAll(void)
{
signed char i;
for(i = (Timer_0_List_Count- 1); i >= 0; i--) //从后往前删
{
if(Timer0_Message_Struct.Flag[i] == 10)
{
Timer0_Message_Struct.Flag[i] = 1;
}
}
return 0;
}
```
- - -
### 7\. 配网模式
同时长按上下两按键2S,进入AP热点配网模式。
``` C
//配网模式
void DistributionNetwork(void)
{
MotorPause();
Timer_0_Pause_All(); //暂停所有任务
Timer0_Pause_MessageAll(); //暂停所有消息
Buzzer_BiPu(); //奏乐
//开始配网流程
Timer_0_Add_Fun_Once(1000, WIFI_CloseRTE); //关闭回显
Timer_0_Add_Fun_Once(1500, WIFI_ResetAuthor);//解除绑定关系,
Timer_0_Add_Fun_Once(2000, WIFI_SendAT); //AT
Timer_0_Add_Fun_Once(2500, WIFI_SetILOP); //设置三元组
Timer_0_Add_Fun_Once(3000, WIFI_StartILOP); //开启ILOP
// Timer_0_Add_Fun_Once(5000, WIFI_StartAWS); //路由器配网
Timer_0_Add_Fun_Once(5000, WIFI_StartAP); //热点配网
//其他操作
Timer_0_Add_Fun(100, WIFI_LED_Loop); //WIFI指示灯快闪 5Hz
Timer_0_Add_Fun(100, Mode_LED_Loop); //快闪 5Hz
Timer_0_ReStart_Fun(Uart2_CheckMessageLoop); //帧处理函数
// Timer0_Add_MessageFun('C', WIFI_SubPosition); //连接成功 主动上报一次位置
Timer0_Add_MessageFun('C', NetworkConnected); //连接成功
Timer0_Add_MessageFun('E', Buzzer_DiLong); //Error
Timer0_Add_MessageFun('A', Buzzer_DiDiDi); //AP已开启
}
```
配网成功,则执行NetworkConnected函数。删除配网过程创建的任务和消息,启用电机,恢复配网前所有任务和消息。最后奏乐,返回原来任务。无需重启设备,节省了重启&联网时间。
这点和**零妖**代码有较大区别。宋工实现过程是:进入配网过程,重新初始化所有任务、消息。创建配网需要的任务和消息,配网成功后调用软件重启指令,重新进入正常工作模式。这样会多一次重启WIFI上云过程。
``` C
//配网成功 已连接
void NetworkConnected(void)
{
//删除 配网 过程创建的所有任务
Timer_0_Del_Fun(WIFI_LED_Loop); //WIFI指示灯
Timer_0_Del_Fun(Mode_LED_Loop); //
Timer0_Del_MessageFun(NetworkConnected); //连接成功
Timer0_Del_MessageFun(Buzzer_DiLong); //Error
Timer0_Del_MessageFun(Buzzer_DiDiDi); //AP已开启
MotorReStart();
Timer_0_ReStart_All(); //恢复 配网前所有任务
Timer0_ReStart_MessageAll(); //恢复 配网前所有消息
Buzzer_DoToXi(); //奏乐
}
```
- - -
## 结论
该项目参考**零妖**代码框架,删除了一些未使用的功能,修改了一些底层功能,增加了一些定制化功能。
设备基本实现了电动窗帘的本地/云端控制功能,能够按一定频率上传设备运行状态。当设备发生故障时,具有一定的处理能力。有着较为友好的交互方式。成本控制得当。
程序设计采用任务/消息调度器模式,可方便地添加、删减功能而不影响其他功能运行。
由于时间、精力、能力有限,设备还存在着诸多问题待修复,已知问题罗列如下,如您有时间、有精力、有能力,可尝试修复。
1. 软硬限位未实现,窗帘存在超限位运行损坏风险。窗帘超限位后APP无法显示实际位置;
2. PID参数设定需要手动完成。
3. 当外接电源为开关电源时,电机反转会触发开关电源过压保护。加入SS54二极管防倒灌可解决。但电机反转时设备电压会被拉高,当使用24V电源时可能会导致电机驱动芯片过压保护.
4. 光敏电阻和热敏电阻测量数值不准确。
- - -
## 致 谢
感谢立创EDA开设活动,提供一个 ~~白嫖~~ 学习的机会。
感谢**零妖**源码,学习了任务/消息调度器、环形缓存区、JSON字符串比较等知识,受益匪浅。
感谢客编:481978A大佬上传的文件,解决了令人头秃的配网流程。