资讯动态

基于STM32的除湿器源码:状态机与滞回控制的闭环设计

发布时间:2026/10/14 18:39:36 来源:尧图企业网站定制
简介面向嵌入式初学者和电子竞赛学生的 STM32 除湿器完整工程源码以家居除湿场景为切入点串联微控制器开发中的关键环节。压缩包共 212 个文件整体约 10.15MB其中 C 源文件与 h 头文件构成主体代码uvprojx、sct、ioc 等工程文件用于 Keil 与 CubeMX 环境配置另有 hex、axf、map 等编译产物便于核对构建结果。源码覆盖硬件驱动层、控制算法层与用户界面层GPIO、PWM、定时器、ADC 等外设初始化配合湿度采集与 PID 闭环调节流程实现除湿启停控制和风扇、压缩机转速调整LCD 显示、按键输入则完成状态展示与参数设置。同时保留日志记录和通信扩展点的代码骨架可帮助理解感知、决策、执行、反馈的完整闭环。适合对照真实工程熟悉 STM32G0 系列 HAL 库的开发方式或作为课程设计、毕业设计的基础框架。目前已有 489 人学习。1. 基于STM32的除湿器源码不是点灯工程是能闭环的完整状态机南风天回潮的时候衣柜和地下室的湿度能冲到85%RH以上墙角渗水、皮鞋发霉。用一块STM32加上湿度传感器、继电器和风扇就能把除湿做成自动闭环——但网上能直接跑的东西很少多数源码要么只教按键点灯要么湿度采集和控制逻辑各写各的根本联动不起来。这套基于STM32设计的一款除湿器源码核心是把湿度采集、滞回阈值、定时除湿和保护逻辑串成一个完整状态机拿到手改一改就能控制12V半导体制冷片或PTC加热器。适合正在做环境控制类课设或者想给衣柜、储藏间做自动化除湿的开发者既能学状态机设计也能直接改参数上板。2. 硬件选型与电路设计传感器、驱动负载和供电三层要分开想除湿器不是把传感器和继电器接上就能跑的东西硬件上最怕三件事传感器读数漂、继电器拉低MCU电压、冷凝水倒灌进主板。下面按信号链路从传感器到负载到电源说清楚每层的选型和接线要点。2.1 湿度传感器DHT11能入门上量就换I2C数字传感器这套源码在驱动层做了一层抽象对外只暴露一个读取温湿度的接口底层传感器可以换。默认支持最常见的一线式传感器比如DHT11——便宜、资料多但精度只有±5%RH而且单总线时序非常敏感主频跑在72MHz时延时不对读到的就是固定0xFF。源码里对这类传感器做了超时保护和重试机制连续读失败3次就上报故障不会让系统卡死在读数据上。如果你不是交课设而是想真正用在衣柜里我建议换成I2C接口的数字传感器比如0x40地址的HDC1080或0x44地址的SHT30精度能到±2%RH不需要自己掐时序代码里把驱动文件换一个接口实现即可。传感器对比参数如下传感器接口精度湿度范围采样间隔建议适合场景DHT11单总线±5%RH20%~90%RH大于2秒课设、原型验证DHT22单总线±2%RH0~100%RH大于2秒中等精度需求HDC1080I2C±2%RH0~100%RH1秒长时间运行、衣柜除湿SHT30I2C±2%RH0~100%RH1秒批量产品、低功耗关于采样间隔有个容易踩的坑DHT11这类传感器内部靠湿敏电容充电测湿度如果每200ms读一次电容还没恢复读数会越来越干最终系统误判「已经很干了」然后停止除湿实际房间还是湿的。源码里面默认采样周期是10秒这个值在多数小型除湿场景下都够用。2.2 负载驱动制冷片用继电器风扇用NMOS别混着接除湿器负载通常是三个半导体制冷片或PTC加热器主除湿执行器、风扇加速空气循环、可选排水泵。半导体制冷片冷端温度能到5℃以下空气中的水汽会在冷端凝结成水珠滴到集水盒里PTC方案则是把空气加热再吹出去两种负载需要的驱动方式不太一样。制冷片和加热器功率都在30W到100W这个量级建议用继电器通断控制MCU的IO不直接推继电器线圈而是通过ULN2003或S8050三极管做一级驱动线圈两端反向并联续流二极管。源码里负载使能函数带一个保护位只有状态机处于运行态且传感器数据有效时才允许拉高继电器控制脚防止上电瞬间误动作void load_set_enabled(bool enable) { if (system_state ! STATE_RUNNING system_state ! STATE_TIMER_RUN) { load_relay_off(); return; } if (sensor_data_valid false) { load_relay_off(); return; } if (enable) { load_relay_on(); } else { load_relay_off(); } }逻辑说明函数先检查系统状态只有运行态才允许继电器吸合再检查传感器有效标志避免在初始化阶段数据全0时直接开机。这种「双重保险」是源码里很关键的设计直接避免了断电恢复时乱动作。参数说明load_relay_on()和load_relay_off()是板级驱动函数内部操作一个GPIO引脚如果你控制的是MOS管而不是继电器只需替换这两个函数体。风扇建议用独立NMOS管做PWM调速25kHz频率听不到电流声不要和制冷片共用一个继电器否则风扇启停时的反电动势会干扰传感器供电。2.3 供电分配12V、5V、3.3V三路电源的地线顺序决定稳定性整套系统建议用12V适配器供电半导体制冷片直接吃12V再通过降压芯片出一路5V给传感器和继电器线圈5V经LDO降到3.3V给STM32。降压模块选普通的DC-DC就能用关键在走线继电器地、传感器地、MCU地必须单点汇合顺序是「大电流地最靠近电源输入端然后5V地最后3.3V地」。如果不这样处理继电器吸合的瞬间会有几安培的电流突变地线上产生几百毫伏的跳动传感器是模拟输出的话读数就会跟着跳。实测用示波器看3.3V纹波单点接地能做到50mV以内继电器和MCU共地时纹波能到300mV以上。供电分配建议如下电压去向电流估算注意点12V半导体制冷片/加热器3A~8A线径不小于0.5mm²5V传感器、继电器线圈、逻辑电平500mA靠近负载端加100uF电解电容3.3VSTM32、LED指示100mALDO输入输出各加0.1uF陶瓷电容这套供电方案跑下来整机功耗能控制在70W以内对家用环境来说不算高。需要提醒的是制冷片冷端温度很低如果机身平放冷凝水会沿着制冷片表面往下流板子必须竖装或者冷端下方设计接水槽这个问题后面避坑章节单独说。3. 控制逻辑与状态机滞回阈值、四态流转和断电恢复除湿器的灵魂不是采集湿度而是「什么时候开、什么时候停、停了之后多久再开」。这些决策全部在状态机里完成下面把源码里的控制逻辑拆开讲。3.1 滞回控制为什么单阈值控制一定会让继电器打到打火如果写成「湿度大于60就开小于60就停」那临界点附近传感器只要抖动0.5%RH继电器就会在十几秒内反复吸合断开触点打火、线圈发热一个夏天继电器就能报废。工程上必须用滞回比较设定目标湿度为55%RH回差为8%RH那么实际逻辑是「大于55开小于47停中间保持原状态」。中间那8%RH就是死区它天然覆盖掉传感器误差和环境的微小波动。源码里用两个变量控制这个行为#define HUMIDITY_SETPOINT 55 /* 目标湿度单位%RH */ #define HYSTERESIS_VALUE 8 /* 滞回死区单位%RH */ bool dehumidifier_need_run(int16_t humidity) { static bool last_run_state false; if (humidity HUMIDITY_SETPOINT) { last_run_state true; } else if (humidity (HUMIDITY_SETPOINT - HYSTERESIS_VALUE)) { last_run_state false; } /* 处于死区中间时last_run_state 保持原样 */ return last_run_state; }逻辑说明这段代码是滞回控制的核心。last_run_state是静态变量保存上次的运行状态当湿度在47到55之间时函数什么也不改直接返回旧状态这样就避免了临界抖动。参数说明HUMIDITY_SETPOINT和HYSTERESIS_VALUE都在配置文件头部改这两个值就能调整除湿深度。我一般会把死区设在8到10太小了继电器频繁动作太大了房间湿度波动明显。如果你用的是±5%RH精度的DHT11建议直接设10。3.2 状态机流转空闲、运行、定时运行、故障四个状态怎么跳源码控制逻辑不是一个if大杂烩而是标准状态机系统初始化后进入空闲态湿度超标跳转到运行态运行态湿度降到死区以下回到空闲态定时模式在指定时段内强制进入运行态任何关键故障统一进故障态。状态跳转条件如下当前状态触发条件下一状态空闲湿度 目标值运行空闲定时时段开始定时运行运行湿度 目标值-死区空闲定时运行定时时段结束空闲任意传感器连续3次读取失败/继电器驱动异常故障故障按复位键且检测正常空闲状态机在主循环里以一个10ms为周期的调度器驱动每次只处理一个事件不会出现「一边在开继电器一边在刷传感器」的竞态while (1) { scheduler_tick(); /* 10ms时基 */ if (event_humidity_updated) { /* 传感器完成一次采集 */ event_humidity_updated false; state_machine_handle(HUMIDITY_EVENT, humidity); } if (event_timer_tick) { /* 定时器1秒心跳 */ event_timer_tick false; state_machine_handle(TIMER_EVENT, 0); } }逻辑说明主循环里不直接写业务而是先收事件再统一交给状态机处理。HUMIDITY_EVENT携带最新的湿度值TIMER_EVENT用来驱动定时除湿时段的判断。参数说明10ms时基由定时器中断置位state_machine_handle内部是一个switch-case分发不同事件进入不同状态分支。这套结构的可扩展性很好以后想加「滤网更换提醒」或「水箱满停机关机」加一个事件类型和状态分支就行不用把主循环推倒重来。3.3 定时除湿和断电恢复设置参数要存得住重启不能乱跳很多场景不想全天除湿比如人离开房间就关掉回家前1小时再开。源码里支持定时模式把一天划分成几个时段每个时段有开始时间和持续时长全部存在EEPROM里。这里有个常见误区不要每次状态变化都写EEPROMFlash的擦写寿命只有一万次左右。源码的做法是「只在人工修改设置时写一次EEPROM」运行时的状态变化一律只保持SRAM。断电恢复是容易被忽略的功能。实际使用中如果突然断电直接断电再上电此时传感器需要几百毫秒才能稳定如果上电瞬间读取湿度失败系统可能误判为0%RH然后一直不动作。源码的处理是上电后先进初始化状态等传感器出第一个有效数据后再进入空闲态并且用外部看门狗兜底初始化卡死3秒就自动复位一次。参数存储的结构体建议这样组织typedef struct { uint16_t humidity_setpoint; /* 目标湿度 */ uint16_t hysteresis; /* 滞回死区 */ uint8_t timer_enable; /* 定时功能使能 */ uint16_t timer_start_min; /* 定时开始单位分钟按0点算 */ uint16_t timer_duration_min; /* 持续时长 */ uint16_t crc16; /* 校验值 */ } dehumidifier_params_t;逻辑说明crc16字段用来校验EEPROM里的数据是否完整上电读取时先算CRC不匹配就恢复默认值防止断电瞬间写入了一半的数据导致参数错乱。参数说明时间用「从0点算起的分钟数」存储范围0到143923:59避免去解析BCD时间码。这套参数表是整个源码配置的入口编译前直接改结构体默认值就能适配不同场景。4. 移植与编译从源码到板子跑起来要过的几道关拿到源码工程后先别急着编译照下面几步把工程结构和初始化顺序理一遍能省出至少半天排错时间。4.1 源码包的分层结构驱动、中间层、应用分开移植只动一层源码包按三层组织最底层是板级驱动负责操作GPIO、I2C、定时器等外设中间层是传感器抽象和负载抽象对外提供统一的读取温湿度和开关负载接口最上层是状态机和业务逻辑完全不碰寄存器。文件清单大致如下目录/文件作用移植时需要动吗bsp_gpio.c引脚初始化、继电器和风扇IO需要按板子改引脚号bsp_i2c.cI2C时序读传感器如果用硬件I2C需改引脚bsp_timer.c10ms时基和PWM输出无需改只调参数sensor_dht11.c单总线温湿度读取换传感器时整体替换sensor_hdc1080.cI2C温湿度读取换传感器时整体替换app_state_machine.c状态机核心逻辑基本不用改app_params.c参数管理、EEPORM读写按存储介质改底层函数这个结构的好处是你要换板子只改bsp_开头的文件要换传感器只改sensor_开头的文件状态机永远不用动。很多初学朋友移植失败就是因为把GPIO初始化和业务逻辑写在同一个文件里换一块板子就要全局搜索引脚号。4.2 初始化顺序先时钟、再GPIO、再传感器方向不能反源码里的初始化函数按严格顺序执行这个顺序不是随便定的先配时钟树再配GPIO和定时器然后初始化传感器并等数据全部成功后才进入状态机主循环。如果把传感器初始化放在时钟之前I2C发出去的时序全是乱的读回来的湿度值就会在20和80之间乱跳。void system_init(void) { system_clock_config(); /* 1. 时钟树PLL锁定后再往下走 */ bsp_gpio_init(); /* 2. 继电器/风扇/按键引脚 */ bsp_timer_init(); /* 3. 10ms与1s时基 */ sensor_init(); /* 4. 传感器上电等待100ms */ params_load(); /* 5. 从EEPROM读配置 */ state_machine_init(); /* 6. 进入空闲态 */ }逻辑说明system_clock_config内部会等待PLL稳定标志之后再操作外设总线。sensor_init里有一个100ms的延时让传感器完成内部上电校准跳过这个延时会出现第一次读数恒定不变的情况。参数说明因为传感器初始化要延时这段逻辑放在看门狗喂狗之前初始化耗时约150ms远低于3秒的超时阈值不会误触发复位。如果你在这个函数里加了自己的初始化逻辑注意别让单步时间超过看门狗阈值。4.3 把阈值、采样和滤波参数集中成一张配置表源码把影响行为的所有可调参数收敛到一个配置区域而不是散落在各个.c文件里。这样拿到源码后你只需要打开一处就能完成所有调参参数默认值说明调节建议采样周期10秒湿度读取间隔不要小于5秒滤波窗口5次滑动平均滤波越大越平滑响应越慢目标湿度55%RH启动除湿阈值衣柜50地下室60滞回死区8%RH防抖区间建议8~10继电器最短间隔300秒两次吸合之间至少要等的时间保护继电器和制冷片看门狗超时3秒故障复位阈值不要小于2秒其中「继电器最短间隔」这个参数很多人会忽视。半导体制冷片频繁启停会导致冷端温度还没降下来又被断电效率极低还容易损坏。源码在继电器控制函数里记录上次切换时刻如果距离300秒以内直接忽略新的切换请求——这相当于给滞回控制又加了一道时间锁实际用下来除湿效率明显更稳。5. 避坑与排查除湿器开发最常见的六个翻车现场这一章值得你收藏。下面每条都是实际跑板子时遇到过的问题按「现象、原因、解决」写清楚你照着对比就能定位。5.1 I2C总线周期性卡死湿度读数突然变成0xFF现象系统运行几小时后湿度读数突然变成固定值重启恢复正常过一阵又复发。原因I2C通信过程中SCL或SDA被从设备拉低总线进入死锁状态。大多是传感器线缆过长加上没有上拉电阻或上拉电阻值过大导致信号上升沿太缓。解决在SCL和SDA各加一个4.7kΩ上拉电阻到3.3V线缆长度超过30cm时改用屏蔽线代码里加总线恢复逻辑——检测到总线忙时连续翻转9次SCL让从设备释放SDA如果3次恢复失败就上报故障而不是无限等待。5.2 继电器频繁吸合、触点打火继电器盒发烫现象除湿器运行一晚上继电器位置发烫伴随连续「嗒嗒嗒」的吸合声。原因滞回死区设得太小或者传感器滤波不够湿度值在阈值附近来回跳动导致继电器快速切换。另一个原因是继电器驱动三极管的基极电阻偏大线圈电压不足触点吸合力不够造成打火。解决把滞回死区从原来的3改到8以上给湿度值加滑动平均滤波窗口设5次同时用万用表测继电器线圈两端电压吸合时应不低于额定电压的90%。5.3 湿度读数周期性偏低除湿器感觉「用力过猛」现象房间湿度明明还有60%显示却只有50%系统一直不启动。原因传感器被放置在出风口附近或制冷片旁边吹出来的干冷空气直接吹到传感器上测到的局部湿度比环境平均湿度低10%RH以上。解决传感器加一个挡风罩或者把传感器移到进风口一侧、距离制冷片至少15cm。源码里也增加了「首次读数过滤」系统启动后前两次采样只用于初始化滑动平均不参与阈值判断。5.4 断电重启后继电器不受控制地吸合一下又断开现象上电瞬间能听到继电器「咔」一声吸合然后立刻断开伴随MCU复位。原因上电时3.3V还没稳定到工作电压GPIO输出状态不确定刚好某个引脚输出高电平驱动了三极管继电器短暂吸合。解决硬件上在继电器驱动三极管基极加一个10kΩ下拉电阻保证MCU未初始化时基极是低电平软件上在system_init的最开始把所有负载引脚设为低电平再配置GPIO的模式。这两个措施一起用彻底消除上电乱跳。5.5 冷凝除湿机型第二天发现主板上有水渍现象运行一段时间后打开外壳发现PCB表面有水珠严重时MCU复位。原因制冷片冷端温度5℃左右空气中的水汽会直接在冷端凝结冷凝水沿制冷片流到集水槽时水珠飞溅到旁边的电路板上。另外停机瞬间冷端温度快速回升表面的霜融化后滴落。解决物理设计上把集水槽放在制冷片正下方并加一块挡水板隔开PCB控制逻辑上增加停机延时——关机指令发出后风扇继续转60秒把冷端残留的凝水吹干再彻底断电。这个「停机吹干」逻辑在源码里是单独一个状态实测能减少90%的倒灌问题。5.6 EEPROM参数丢失每次上电都恢复默认值现象设置好目标湿度55%后断电再上电发现又变回60%。原因参数写入时没有做完整性校验或者写入时机不对。运行中如果刚好在写EEPROM时断电数据就写了一半上电读到无意义数值后走了默认值分支。解决写参数前先擦除、再写入、最后读回校验CRC三个步骤不完整就不标记写入成功上电读取时CRC校验失败就保留默认值同时通过串口打印提示「参数校验失败已加载默认配置」。源码参数结构体里的crc16字段就是干这个用的。6. 上手验证与调试技巧不依赖高端仪器把控制逻辑调稳源码拿到手、板子焊好之后第一件事不是直接挂在高湿环境里而是先用一个串口日志框架把系统内部的状态全部暴露出来再配合手边的简单道具做闭环验证。下面这套验证方法不需要逻辑分析仪逻辑分析仪不是必须的。6.1 用结构化的串口日志做全链路观测在状态机的每次跳转、每次继电器动作、每次传感器采样处各加一行格式化输出样例格式如下printf([%lu] state%d hum%d temp%d relay%d fan%d\r\n, HAL_GetTick(), system_state, humidity, temperature, relay_status, fan_duty);逻辑说明HAL_GetTick()是系统上电以来的毫秒数用来打时间戳system_state是当前状态枚举humidity是滤波后的湿度值relay_status和fan_duty分别反映继电器通断和风扇PWM占空比。参数说明串口波特率建议115200每10秒打印一行。这些数据拿到电脑上用串口终端记录就能画出湿度变化曲线和状态跳转的时间线。判断依据很简单状态跳转必须和继电器动作在同一个日志周期内如果状态机说运行了继电器却没吸合说明驱动层有问题不用拿万用表也能定位到是哪一层丢的。6.2 没有环境试验箱用湿毛巾和收纳箱造一个「高湿房间」用一个60升左右的塑料收纳箱里面放一盆温水盖上盖子等10分钟箱内湿度就能稳定到80%RH以上。把除湿器放进箱子接上串口日志连续记录60分钟正常情况下的数据曲线应该是湿度从80下降到55%RH时继电器吸合开始除湿湿度继续降到47%RH左右继电器断开箱内湿度在47到55之间缓慢回升。判断系统是否有效的量化标准30分钟内箱内湿度至少下降8%RH以上继电器开关次数不超过4次。低于这个标准优先检查传感器位置和制冷片散热条件不要先怀疑控制逻辑——多数时候是冷端散热没做好制冷片冷端温度降不下来冷凝效率就被散热条件卡住了。最后分享一个习惯自从第一次做环境控制项目在状态机和日志上吃过亏从那以后我每次写这种带传感器闭环的代码都强制自己先把状态日志框架写出来再写业务逻辑。哪一行代码出问题日志一拉就现原形省下的排错时间比写日志的时间多得多。这套基于STM32的除湿器源码本身也带了这套日志框架你拿到手后只需要把配置表里的参数按自己场景改一遍就能跑起来希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑