十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32智能窗户窗帘自动控制系统:下雨自动关窗与本地可靠策略

STM32智能窗户窗帘自动控制系统:下雨自动关窗与本地可靠策略 1. 项目定位把“自动关窗”做成一套能跑的监测控制系统今年初夏一个下雨天我下班回家发现阳台窗户没关雨把客厅木地板泡出一圈鼓包。书架最下层的一本书也湿到没法看。那次之后我认真查了一圈成品智能窗帘机发现大多数只解决“定时拉帘”没人解决“下雨自动关窗”这种最实际的需求。于是我自己用 STM32 搭了一套智能窗户/窗帘监测控制系统把代码、原理图和仿真一起整理好准备开源给同样被天气坑过的人。这套系统并不复杂核心是一块 STM32F103C8T6 主控板配合 DHT11 温湿度传感器、雨滴传感器、光敏电阻、两路 ULN2003 步进电机驱动。一路电机负责推拉窗户另一路负责窗帘开合再加上数码管或 LCD1602 显示状态整机已经能覆盖日常绝大多数使用场景。需要说明的是我这里没有做联网和 App 控制原因很简单窗户这种设备要的是本地可靠而不是远程遥控。你正在上班途中忽然发现窗户没关手机遥控的前提是家里网络没断、网关没挂但窗户进水短路的风险往往来自雷雨天气断网断电的概率恰恰最高所以本地自动判断反而更实用。很多人看到“智能窗户/窗帘”这个名字第一反应是“这不就是个按钮开关加电机吗”。其实真正难的不是转电机而是采集信号之后的判断逻辑。比如雨滴传感器贴在窗台外侧下雨时雨水扫到传感器上要关窗但偶尔一阵风带着水雾飘过来又干了系统不能跟着频繁开关再比如室内温度偏高要不要开窗得先确认外面没有在下雨否则一开窗就是事故。这套代码里最重要的部分是状态机、防抖和条件优先级而不是某个单独的驱动函数。你在后面章节看到的所有设计包括 TIM 定时器分配、ADC 采样通道选择、按键扫描方式几乎都是为了把“什么时候开、什么时候关、什么条件下不允许动作”这几个判断做结实。从成本看整机物料价格不高主控芯片、传感器模块、电机驱动模块和结构件加在一起一套原型不到一百元。你要是手头已经有 STM32 最小系统板和 ULN2003 驱动板那就更省了只补几个传感器模块就能搭起来。这篇文章里我会按照项目开源包的目录顺序来讲先说我怎么定功能和选型再说原理图里的关键设计然后拆代码里的 TIM 定时器、DHT11 驱动和 ADC 采样接着讲自动控制策略怎么定优先级最后分享我在 Proteus 仿真和实板调试中踩过的坑。如果你是刚开始学 STM32 的新手建议先照着仿真步骤把工程跑通再动硬件如果你已经有嵌入式开发基础可以直接跳到第四章和第六章那里集中了这套系统最值得留意的逻辑和工程细节。1.1 功能划分与运行模式我在设计时把整个系统分成了四种模式手动模式、自动模式、雨模式、紧急模式。四个模式之间不是平行关系而是有明确的优先级这是整套逻辑的核心。手动模式下按键可以独立控制窗户电机和窗帘电机自动判断全部暂停。这个模式最初是为调试准备的后来发现日常也很好用比如冬天开了一条小缝透气不希望系统因为温度变化擅自把窗关上手动模式就能锁住动作。自动模式是默认运行模式。系统会周期读取 DHT11 的温湿度、雨滴传感器返回的电压、光敏电阻返回的电压然后按照预先写好的规则表去执行开窗、关窗、开帘、关帘。自动模式里依然允许手动按键临时干预一次比如正在自动关窗时你按了停止键系统会记住当前状态并暂停执行直到条件不再满足或下一次触发。雨模式是我后来加的一层保护。当雨滴传感器持续检测到雨水超过 5 秒系统会无条件进入雨模式无论当前温度是高是低先执行关窗动作。这个设计是基于实际教训得出的——刚开始我只把“下雨关窗”当成普通规则里的一条结果发现室内温度高时逻辑会先开窗通风和关窗规则反复冲突。后来我把雨检测提升为独立状态规则优先级立刻清晰了。紧急模式针对烟雾和高温异常。若接上 MQ-2 烟雾传感器并检测到异常浓度不管外面是否下雨系统都会强制推窗并开启蜂鸣器报警。这看起来有点反常识——下雨时开窗会让雨水进来但真有火灾隐患时通风排烟比防雨更重要。系统在接传感器时把这个优先级放到了比雨模式更高的位置避免了两条规则互相打架。1.2 硬件选型思路主控选择 STM32F103C8T6理由很朴素我这几年手头存得最多的就是它网上关于它的资料也最全连寄存器级的教程都能找到。C8T6 有 64KB Flash、20KB SRAM跑这套逻辑绰绰有余。有人说用 Arduino 或者 ESP32 不是更简单吗Arduino 确实开发快但遮阳板联动、电机行程累计、多路 ADC 预取这些逻辑写下来C8T6 的定时器资源更宽裕代码也不容易被开发板的封装绑架方便以后改到其他 STM32 型号上。传感器方面DHT11 负责检测环境温湿度它属于单总线器件驱动时序对新手来说是个绕不开的练习点。雨滴传感器模块和我选的光敏电阻模块本质上都是 ADC 采样后比较电压变化的器件输出引脚接 STM32 的 ADC 通道即可。电机部分我选 28BYJ-48 步进电机配合 ULN2003 驱动板而不是普通直流减速电机原因是窗户开合需要精确控制行程步进电机给固定脉冲数就能对应固定转动圈数不需要额外编码器。直流电机虽然转速高但要加限位开关、还要处理惯性系统复杂度明显上升。当时我也想过用舵机直接拉窗帘舵机控制信号简单但转角只有 180 度左右窗帘轨道行程长了根本不够用。步进电机加齿轮或直接拧绕线轮才能带动长度较大的窗帘。如果你只做平开小窗用一颗大扭矩舵机倒是替代方案源码里电机控制接口已经和器件解耦更换驱动只需改底层函数不影响自动控制逻辑。2. 原理图里的几个关键节点最小系统、传感器上拉和电机隔离开源包里放的是我用嘉立创 EDA 画的 STM32F103C8T6 最小系统原理图和 Proteus 仿真工程。原理图没有画得太花哨每一部分电路都有明确用途。这里我挑几个最容易出问题、也最值得讲解的节点展开。2.1 最小系统除了晶振和复位还要留意 Boot 引脚STM32F103C8T6 的最小系统包括 3.3V 电源、8MHz 外部晶振、复位电路、BOOT0/BOOT1 引脚配置和 SWD 下载接口。原理图上 8MHz 晶振的两个引脚分别通过 20pF 负载电容接地晶振输入输出端还并了一个 1MΩ 反馈电阻用来保证起振稳定。很多人焊板子时为了省事直接不焊晶振靠 STM32 内部 HSI 也能跑但 USB 通信和需要精确波特率的场合还是会受影响我建议保留外部晶振毕竟仿真和实板都围绕 8MHz 展开。BOOT0 通过 10kΩ 电阻下拉到地BOOT1 同样下拉。上电后从主 Flash 启动这是常规做法。有一个新手常掉的坑BOOT0 悬空时如果环境中干扰较大偶尔会进到系统存储器模式程序不跑串口还莫名出现 ISP 握手字符。原理图上多拉两个电阻成本很低却能省掉非常多调试时间。电源引脚 VDD 旁我放了 100nF 去耦电容并在主供电处加了 10μF 钽电容。去耦电容的作用是给高频开关噪声一个低阻抗通路它不是玄学。电机启动瞬间电流会拉掉板载电压如果去耦不足STM32 会直接复位这个问题在第六章实板调试里我会再提。硬件设计时电源布局尽量让 5V 动力电和 3.3V 逻辑电分开走最后在电源入口处单点共地。2.2 DHT11 数据线的上拉电阻与单总线连接DHT11 数据线在原理图里最好画得清楚一点。它是一个单总线器件只有一个数据引脚既要接收主机的起始信号又要向主机返回 40 位数据。因为 DHT11 的引脚是开漏输出所以数据线上必须接上拉电阻到 3.3V否则高电平拉不起来通信必然失败。我习惯用 4.7kΩ 上拉到 3.3V实际 DHT11 模块上通常已经板载了上拉电阻自行画 PCB 时一定要补上这一颗。数据引脚接到 STM32 的 PA3。需要注意的是STM32 的 GPIO 口可以配置成推挽输出也可以配置成浮空输入而 DHT11 通信过程中主机需要频繁切换方向。初始化时先输出模式拉低数据线 18ms 以上然后释放并切回输入模式等待 DHT11 的响应信号。有些代码为了省事一直用开漏输出模式这样也能完成方向切换因为开漏输出置 1 时等效于释放总线配合外部上拉就成了输入状态。但如果你用的是库里自带的 GPIO 模式切换注意别把引脚配成模拟输入模拟输入读不到数字逻辑电平DHT11 永远等不到响应。原理图里传感器模块的 VCC 我直接接到了 3.3V。DHT11 本身工作电压是 3.3V 到 5.5V接 3.3V 更稳不需要电平转换。雨滴传感器和光敏电阻模块的 AO 输出是模拟电压VCC 接 3.3V 也可以但如果这些模块上的比较器芯片需要 5V 供电才能正常工作就必须确认模块的 VCC 范围和输出逻辑电平否则读到的信号可能全部偏高。对于这类不带电平转换的模块我给 AO 输出和 STM32 引脚之间留了可选的 1kΩ 串联电阻和 100nF 滤波电容。2.3 步进电机驱动ULN2003 的公共端怎么接才正确电机控制部分是整个原理图里最容易被接错的地方。ULN2003 驱动板上的接口看似简单但 28BYJ-48 电机的红线和四根相线接法有讲究。红线是电机线圈的公共端需要接外部的驱动电压正极通常是 5V。四根相线分别接到 ULN2003 的 IN1 到 IN4 对应的输出端。很多初学者把红线当成电源接到 STM32 的 3.3V结果电机转动无力甚至把板载稳压芯片拖垮。正确做法是给我画的两个电机驱动模块分别接 5V 电源但 5V 电源的地和 STM32 的 GND 必须共地否则控制信号没有参考电平电机完全不会动。原理图上我用 PB0-PB3 控制窗户电机PB8-PB11 控制窗帘电机。四相八拍控制信号是普通 GPIO 输出高电平脉冲不需要 PWM因为步进电机不像直流电机要靠占空比调速。ULN2003 内部已经集成了续流二极管所以不需要额外给电机线圈加反峰吸收二极管。需要留意的是 ULN2003 驱动板本身有一个小电源指示灯很多模块会配备一颗稳压二极管如果模块质量一般最好在电机供电入口加 100μF 电解电容用来吸收电机启停瞬间的电流冲击。蜂鸣器我接在 PA8 上采用 NPN 三极管驱动原理图里用一个 1kΩ 基极电阻连接到 STM32 引脚。如果直接把蜂鸣器接到 GPIO 上灌电流能力不足以驱动大电流蜂鸣器还会让 IO 口电压跌落。按键部分采用矩阵式接法太浪费引脚我这里只用了三个独立按键模式切换、手动关窗/停止、手动开窗/开帘全部接在 PB5-PB7 上并配置内部上拉按下时引脚读到低电平这样可以省掉外部上拉电阻。原理图开源包里还画了 OLED 屏接口作为可选扩展。我测试时用 LCD1602 看状态确实够用但后来觉得做成品界面还是 0.96 寸 OLED 更直观于是原理图上保留了 I2C 接口引脚接 PB6SCL和 PB7SDA。如果你也要加 OLED注意和按键引脚错开不要复用同一组 GPIO。3. 工程框架与驱动拆解TIM 定时器、DHT11 单总线和 ADC 采集很多 STM32 项目能跑起来靠的是把所有事情塞进一个大循环但这套窗控系统不适合这么做因为电机转动期间不能长时间阻塞传感器采集又需要时间片轮询。我最终采用单主循环加定时器节拍的状态机结构代码工程里总共只有几个核心文件结构比很多人想象的简单。3.1 开发环境与固件库的选择我用的 Keil MDK 5 工程基于 STM32 标准外设库编写原因是我被 STM32CubeIDE 生成的 HAL 代码层搞过一次项目明明不大代码量却膨胀得厉害。标准库直接操作外设结构体逻辑一目了然。需要明确的是现在新买的 STM32F1 芯片仍然兼容这家库只是官方已经不更新了用起来没有任何影响。安装 Keil 时要注意MDK 只能装一个版本目录如果电脑上同时有 C51 和 MDK会经常出现打开 C51 工程需要换编译器版本的问题。我自己的方式是在同一套 MDK 里通过 Pack Installer 安装对应的器件支持包C51 工程也一起放到同一个 IDE 里安装时不要覆盖版本MDK 会把两个编译器兼容到一起。实际装好后新建工程时在 Device 下拉框里能找到 STM32F103C8 即可。有人问 VSCode 能不能开发 STM32。能通过 EIDE 插件或者 PlatformIO 都可以。但我建议模板阶段不要同时折腾编辑器Keil 里的调试信息、寄存器查看窗口对新手更友好。我后来把代码放到 VSCode 看结构和 Git 提交编译还是回到 Keil这样效率最高。开源工程里我同时也放了 CMake 列表文件给想迁移到命令行编译的人留了入口。3.2 用 TIM 定时器生成 1ms 系统节拍而不是空跑 Delay这套系统里有好几处需要计时的地方DHT11 超时等待、按键消抖延时、雨滴防抖计时、步进电机步间延时。如果每个需要延时的函数都自己写空循环 Delay代码会非常难维护且容易造成 CPU 空转时间太长ADC 采样不及时。我选择用一个 TIM 定时器产生 1ms 中断在中断里累加系统节拍标记。这里以 TIM2 初始化为例假设系统时钟已经配成 72MHz配置过程大致如下void TIM2_Init(void) { TIM_TimeBaseInitTypeDef tb; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); tb.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz计数周期 1us tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_Period 1000 - 1; // 计到 1000即 1ms tb.TIM_ClockDivision TIM_CKD_DIV1; tb.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM2, tb); TIM_ClearFlag(TIM2, TIM_FLAG_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); }PSC 设成 71 等价于 72 分频计数频率变成 1MHz计数器每 1us 加一加到 999 产生一次中断所以中断周期是 1ms。中断服务函数里放一个volatile uint16_t systick_ms自加供主函数查询。注意这个变量要声明成 volatile否则编译器优化后主循环可能读不到最新值。按键消抖和防抖计时都基于这个时间戳进行差量判断比如按键按下后 20ms 再读一次电平就不需要 Delay 卡住总线上其他模块。3.3 DHT11 驱动时序起始信号、应答和 40 位数据解析DHT11 的例程网上很多但很多代码直接照抄跑不通才知道时序有问题。完整的读取流程是主机先把数据线拉低持续至少 18ms作为起始信号然后拉高 20 到 40μs释放总线。DHT11 收到起始信号后先拉低 80μs 回应再拉高 80μs随后开始连续输出 40 位数据。数据位判断的关键在于高电平持续时间。DHT11 输出一位数据时先把总线拉低 50μs再拉高。如果高电平持续约 26 到 28μs代表数据位为 0如果高电平持续约 70μs代表数据位为 1。所以读取函数不能简单读电平高低而要测量高电平的宽度uint8_t DHT11_ReadBits(void) { uint8_t val 0; for (uint8_t i 0; i 8; i) { while (DHT11_IN() RESET); // 等待低电平结束 delay_us(40); // 延时 40us 后判断电平 if (DHT11_IN() SET) { val | 0x80 i; } while (DHT11_IN() SET); // 等待高电平结束 } return val; }这段代码里的核心技巧是读取高电平宽度。等待低电平变高然后延时 40μs 再看电平。如果读到的电平还是高说明是 1如果已经变低说明是 0。这样做比反复测量微秒时间更简单前提是“从低电平开始到延时 40us 后”的准确度高。如果你的按键消抖和电机控制都用 TIM 节拍不会干扰这里的高精度延时才能保证 DHT11 时序稳定。40 位数据分为湿度整数、湿度小数、温度整数、温度小数、校验和。校验和等于前四个字节之和的低 8 位读取后必须做一次校验校验不通过就丢弃本次数据。DHT11 的更新周期是 1s连续读取间隔如果小于 1s容易读到旧值或者错误值。我的代码里加了上电后 1s 等待并在循环中每 2s 读取一次 DHT11避免传感器还没稳定就发起通信。3.4 雨滴和光敏模块的 ADC 采样配置雨滴传感器模块和光敏电阻模块输出的都是模拟电压STM32 需要把电压转换成数字值再判断。C8T6 的 ADC1 有多个外部通道我把雨滴 sensor 接到 PA1对应 ADC1_IN1光敏电阻接到 PA2对应 ADC1_IN2。初始化流程比较简单先开启 GPIOA 时钟和 ADC1 时钟把引脚配置成模拟输入然后配置 ADC1 为独立模式单次转换采样时间为 55.5 周期转换完成读ADC_GetConversionValue即可。这里有一个必须注意的问题雨滴传感器的 AO 输出电压和雨水量的关系可能和你想的相反。多数模块干躁时输出高电压湿润时输出低电压因为板载比较器会把传感器电导变化转换成数字量。如果你的模块恰好相反不要改硬件在代码里把阈值判断取反就行。开源包里我在 main 中定义了RAIN_THRESHOLD_HIGH和RAIN_THRESHOLD_LOW两个上限和下限方便直接适配不同模块。ADC 采集值一定要做均值滤波。单纯读一次 ADC 就作为触发条件在小雨、水雾、传感器表面有积水残留时非常不稳定。我的代码里对每个通道连续采 8 次去掉最大和最小值后取平均再和阈值比较。这套做法虽然增加了一点采样时间但换来的稳定性非常可观后续控制逻辑的误触发概率能降一半以上。4. 智能控制的关键策略防抖、规则优先级和故障保护代码和硬件都准备好之后真正让窗子“变聪明”的就是控制主循环里的策略层。很多人误以为策略层就是几行 if else其实如果条件之间没有优先级会出现夏天一热就想开窗、外面又开始下雨两条规则反复打架电机不停换向最后烧掉驱动管。我最终落地的策略用一个有限状态机来约束动作在状态机里只有“空闲、正在开窗、正在关窗、手动暂停、紧急模式”这几种状态传感器只决定是否请求状态切换不直接驱动电机。4.1 条件判断和动作映射表项目代码里的核心规则表如下表所示。需要说明的是这个表的优先级从上到下依次降低每个周期按顺序判断一旦命中高优先级规则后面的规则不再执行。场景条件执行的电机动作触发后的状态烟雾浓度过高窗户电机强制全开蜂鸣器鸣叫紧急模式手动可解除雨滴持续 5 秒以上窗户电机关闭雨模式雨停延时后退出室内温度高于 30℃ 且无雨窗户电机打开通风自动模式室内湿度高于 80% 且无雨窗户电机打开通风自动模式光线强度高于上限窗帘电机关闭遮阳自动模式光线强度低于下限且温度低于 22℃窗帘电机打开采光自动模式手动按键按下立即执行对应动作手动暂停/手动运行这个规则表看起来松散但已经能覆盖大多数家庭场景。有人问为什么不根据光照判断白天晚上、再到点自动开关窗帘那会引入实时时钟模块问题复杂度会跳档。先把模拟量阈值判断做好如果要加 RTC规则表里增加一个时段条件逻辑框架不用改。4.2 多级防抖为什么不能用“一次检测”触发动作雨滴传感器的误触发是整套系统里最烦人的问题。传感器放在室外一阵风吹来带着水雾扑到板子上AO 电压瞬间变低如果代码立刻执行关窗风停以后传感器变干又执行开窗这个来回动作会让窗户不停抖动。解决思路是引入两级防抖第一级是在传感器模块电压连续五次扫描中都低于阈值时才认为下雨第二级是触发关窗后至少持续两分钟不执行反向动作除非更高级的紧急条件出现。代码里我写了两个计数变量一根rain_continuous_count记录连续有效值次数另一个rain_hold_ms记录进入雨模式后的保持时间。雨停后不会马上恢复通风而是让系统保持关窗 30 分钟确认窗台传感器彻底变干再退出雨模式。这个“保持时间”对新风量影响不大但能避免间歇小雨反复折腾机械机构。按键防抖也类似扫描到电平变化后等 20ms 再判断一次。如果两次结果一致才认为是一次有效按键。由于 STM32F103 的 GPIO 内部上拉阻值较大按键线较长时容易受干扰20ms 只是底线如果现场存在电机启停干扰可以把消抖时间加到 50ms这样动作感觉上还是流畅的。4.3 手动模式永远高于自动模式手动模式优先级问题很多人的实现方式是按键能覆盖自动逻辑但覆盖后不保存状态于是下一次循环来了自动逻辑又把电机拉回去。比如你手动把窗户关上结果温度还是 30℃自动逻辑下一秒钟就把窗户又推开了你反复按键也关不掉系统陷入“抢方向盘”状态。我的方案是引入一个override_flag当手动按键生效后该标志位置 1自动控制的常规规则暂时整个休眠。只有按下模式切换键回到自动模式或者出现烟雾紧急条件时自动控制才重新接管。这个设计模拟的是“主人优先异常提前介入”的原则操作逻辑简单且不容易误触。电机动作还有一个细节开窗方向和关窗方向不能直接靠 IO 正反转判断因为如果窗户已经关到位继续给电机发送关闭方向脉冲步进电机会堵转时间长了驱动板和电机都会发热。代码里用了一个shutter_position变量做行程累计每次执行开/关动作前先判断当前位置是否到达限位。如果你装了行程开关可以接两路 GPIO 读取没有安装的话就用脉冲步数标定一个最大行程当累计步数达到最大值后自动停止。4.4 电机动作异常保护步进电机控制里我设置了两个保护条件。第一是“单次动作最长运行时间”如果程序 20 秒内都没到达设定的行程终点大概率是机械卡死或线序接错此时强制停止电机并进入错误状态通过蜂鸣器提示。这个异常保护在窗户被异物卡住时非常关键没有它电机一直耗力会烧毁驱动芯片。第二是“连续动作保护”电机每次换向都设置至少 500ms 的停顿因为 ULN2003 驱动板虽然是达林顿管但瞬间换向可能会因为感性负载产生较大冲击电流。500ms 停顿还是为了让机械齿轮传动方向间隙稳定避免突然反向造成窗户抖动。这个保护在实际运行中非常有效震动噪音明显下降。5. 仿真先行用 Proteus 把逻辑跑通再碰硬件做硬件项目最怕的就是一边焊板子一边调逻辑出了问题很难确定是传感器坏了还是代码错了。我的顺序是先画好原理图再用 Proteus 仿真把系统逻辑和传感器模型跑通确认无误后再焊实体板。Proteus 虽然无法完全替代真实电路但对这套系统来说传感器响应、电机换向、按键逻辑这些核心内容都能得到有效验证。5.1 Proteus 工程搭建与器件连接首先在 Proteus 8 中新建原理图从库中加入 STM32F103C8T6、DHT11、ULN2003A、28BYJ-48 步进电机、电阻、LED 等模型。STM32 元件在库里的名称通常包含STM32F103C8或STM32F103R6选 C8 即可。放置芯片后晶振引脚也要接上 8MHz 晶振模型和电容否则仿真跑不起来。DHT11 是 Proteus 自带的传感器模型位于传感器库放置后把数据引脚对应到 STM32 的 PA3VCC 和 GND 分别接好。ULN2003A 和步进电机模型也有要严格按照驱动的引脚顺序连接。Proteus 中如果发现步进电机转速和实际不一致不要着急改参数先把程序编译成 hex再把 hex 加载到 STM32 元件属性里运行后观察电机转动方向。雨滴传感器模型在 Proteus 里不是现成组件我直接用滑动变阻器模拟 AO 电压。电阻输出端接 PA1通过调整阻值改变 ADC 输入电压等效于雨滴传感器阻值变化。光敏电阻也相似用一个电位器分压模拟光照强度变化。这么做看似粗糙但恰好能测试代码里 ADC 均值滤波和阈值判断分支是否正确执行因为滑动变阻器可以稳定输出某个电压比真实传感器还容易判断。5.2 从 Keil 编译到加载 hex 的完整流程工程在 Keil 里编译前要把 Output 选项卡里的 Create HEX File 勾上否则 Proteus 中找不到可执行文件。同时检查工程配置中 Flash 大小是否正确C8T6 的 Flash 是 64KB如果你在 Device 里误选了 C6链接时可能因为地址越界报错导致 hex 无法生成。编译成功后会生成 hex 文件默认路径在工程目录的 Objects 文件夹里文件名通常是Project.hex。在 Proteus 中双击 STM32 芯片点击 Program File 旁边的文件夹图标选择生成的 hex 文件然后设置晶振频率为 8MHz。点击左下角运行按钮后如果程序和原理图连接正确LED 和电机模型会开始动作。仿真中我用按键模型做手动触发时发现一个现象Proteus 的按键不像实物那样有抖动点击一次可能被程序判成多次触发。这是因为模型切换电平的时间太短程序在多次扫描中可能读到了同一个逻辑变化。实际按键有机械抖动反而给了软件消抖一个稳定过程。为了解决仿真误触发我在代码里把消抖阈值从 20ms 提高到 50ms对仿真和实物影响都很小你可以根据你的按键模型微调。5.3 仿真能验证什么不能验证什么仿真环境最大的价值是让代码逻辑可以被观察和中断。我可以把蜂鸣器引脚接上逻辑分析仪看触发烟雾条件时是否按预定节奏输出脉冲可以修改电位器模拟下雨观察电机是否在延时 5 秒后才开始关窗验证防抖代码是否生效。这些都是写代码阶段最难把握的部分。但仿真也有明显盲区。Proteus 里的 STM32 外设模型不会和你真实芯片完全一致尤其 ADC 阻抗、DHT11 的电气时序、ULN2003 驱动电流特性仿真和实物有一定差距。我在仿真阶段能确认的是状态机转移正确却不能确认实际电机电流会不会拉低电源电压也不能确认 DHT11 接线过长时是否通信失败这些必须在实体板上测试。建议把仿真当成“逻辑验证器”而不是“硬件验证器”。如果你刚接触仿真建议先把最简功能跑通按下按键灯亮再通过电位器改变 ADC 电压触发自动模式看电机模型是否切换到正确动作。整个流程熟悉以后再逐步加入 DHT11、蜂鸣器和 OLED这样排查会容易很多。6. 实体板调试记录DHT11 读失败、电机复位和 ADC 波动仿真通过后我在面包板上搭建了实体原型调试过程中踩了几个坑这里按排查顺序记录下来。这些问题都不是代码逻辑导致的而是硬件细节和模块差异造成的如果你照做也会遇到类似的坑。6.1 上电后 DHT11 第一次读取必定失败刚上电时我给 DHT11 发送起始信号读回来的校验和总是错误重复试几次又恢复正常。当时怀疑接线有问题用示波器量波形发现数据线上只有一簇脉冲之后完全没有数据。后来翻 DHT11 数据手册才发现DHT11 上电后有 1 秒以上的稳定时间如果在这期间发起读取传感器内部状态机还没准备好不会正确应答。解决办法是在主函数初始化后延时 1.5 秒再进行第一次温湿度采样。另外每次读取失败后不要立刻重试最好等 2 秒以上。DHT11 的采样周期就是 1 秒频繁读取本身就不符合传感器工作特性。经过调整后程序运行好几天再也没出现过校验和错误。这个坑在所有 DHT11 项目里都会遇到提前在代码里加个启动延时能省很多事。6.2 步进电机一启动STM32 立刻复位第一次接上步进电机后按下开窗按钮电机刚转半圈开发板上的电源指示灯闪了一下系统就复位了。检查发现 5V 电源由电脑 USB 口提供电机启动瞬间电流可能高达几百毫安而 USB 口过流保护会直接把电压拉低。STM32 的复位门槛很灵敏只要 3.3V 电压跌落超过一定幅度芯片就复位。解决方法是把电机驱动单独接一个 5V 适配器供电然后把电源地和 STM32 系统板的地连在一起。如果只有一个电源也要在电机供电入口并联一个 470μF 电解电容和 0.1μF 陶瓷电容用大电容吸收瞬时电流小电容滤除高频噪声。这个操作比任何代码优化都有效供电纹波直接决定整个系统是否稳定。另外我最初用同一个 5V 给两路 ULN2003 供电当两路电机同时动作时电源仍然会跌落。后来我把两路电机的动作改为串行执行即同一时间只让一个电机转动。窗控系统里很少需要窗户和窗帘同时动作串行不会影响体验电流压力却小了一半。如果一定要同时动作建议用 12V 电源加 DC-DC 降压模块而不是用 5V 稳压器硬扛。6.3 雨滴传感器阈值抖动导致误动作实体测试时我把雨滴传感器放在窗台上用滴管滴水模拟下雨一开始很灵敏。可当水滴在传感器表面均匀铺开时AO 电压缓慢变化偶尔会越过阈值又落回来程序在短时间内反复触发。我意识到单阈值判断太脆弱。最终代码里采用了滞回比较方式雨水检测阈值判断有上限和下限只有 ADC 采样值低于“进入雨模式阈值”时才触发关窗触发后ADC 值必须高于“退出雨模式阈值”才会解除雨模式。两个阈值之间设置了明显空档类似空调温控的滞回区间防止信号在临界点来回抖动。还有一个来自实物的坑雨滴传感器使用时间长了板子表面会出现水垢或盐分残留导致干燥后阻抗比新板低。于是程序里的阈值不能只设一个固定值我在校准模式里让用户读一下干燥状态的 ADC 值并把阈值设为“干燥采样值乘以 0.7”这样即使传感器老化阈值也会自动跟着漂移。这些调参经验在仿真里完全看不出来只能靠实际测试积累。6.4 按键引脚在电机运转时出现误触发当电机运转时ULN2003 驱动的几个 IO 口高速翻转会产生较强的电磁干扰如果按键线束离电机信号线太近PB5 上就可能感应出低电平尖峰。程序里按键消抖函数会先看到一个下降沿然后等待 20ms 再读一次如果干扰尖峰在 20ms 内消失这次误触发就被过滤掉了。但电机持续运转时干扰是周期性的不排除恰好某个干扰信号被当成有效按键。我做了两层处理第一按键读取只在状态机空闲时执行电机动作过程中直接跳过按键扫描避免在电机换向点上处理按键第二按键线走线尽量远离电机驱动线和电源线如果做 PCB按键走线周围铺地隔离。按键问题解决后整个系统的手动控制才真正稳定。调试最后我还发现串口打印对排查问题帮助极大。我通过 USART1 以 115200 波特率输出 ADC 原始值、DHT11 解析结果、状态机当前状态和动作标记。现场调试时把这行日志通过蓝牙模块发到手机比看现象猜逻辑快得多。开源代码里保留了DEBUG_PRINT宏改一个参数就能打开或关闭串口输出建议你调试时务必打开跑稳定后再关掉省电。这套项目从原理图到仿真到实体板最难的部分不是让某个传感器工作了而是让所有条件在正确的时间不发生冲突。如果你也打算复刻先跑仿真里的演示工程把规则表改成适合自己家的状态再动手接线。传感器可以少接一路但动作用正转和反转分开测试。等每个模块都单独验证过了再接上状态机你会发现整个系统的逻辑突然就顺了。
返回列表