☰
基于STM32的智能除湿衣柜控制系统:从硬件选型到状态机实现
2026/9/25 4:35:17 网站建设 项目流程

1. 项目缘起与整体设计思路

南方回南天有多离谱,住过一楼或者地下室的朋友应该都懂。墙面冒水珠、衣柜里的衣服摸起来潮乎乎的,放半个月就能闻到一股霉味,皮衣和真丝衬衫更是重灾区。市面上带除湿功能的衣柜动辄两三千,本质上就是加了个半导体除湿模块加个湿度开关,溢价高得离谱。我去年帮朋友改造老房子的时候顺手做了这个STM32智能除湿衣柜控制系统,整套硬件成本压到一百出头,代码、原理图、仿真全部开源,前后迭代了三个版本,现在稳定跑了小半年。

这个项目解决的核心问题很明确:在封闭衣柜空间内,根据实时温湿度自动启停除湿执行机构,同时兼顾防凝露、防过热和低功耗待机。它适合有STM32基础、想找一个完整闭环项目练手的嵌入式学习者,也适合想低成本改造家里衣柜的动手派。整套方案基于STM32F103C8T6最小系统板,外挂DHT11温湿度传感器、继电器驱动半导体制冷片、OLED本地显示,预留了ESP8266接口做数据上报。代码层面用标准库开发,Keil5工程直接编译,Proteus仿真文件同步提供,没有硬件也能先把逻辑跑通。

为什么选STM32F103C8T6而不是更便宜的STC89C52?这里有个很实际的考量。除湿控制不是简单的“湿度高于阈值就开继电器”,它涉及滞回比较、延时保护、传感器滤波、状态机切换这几件事。51单片机跑这些逻辑虽然也能跑,但RAM和Flash余量太小,后期想加个OLED菜单或者串口协议就很吃力。F103C8T6有64KB Flash、20KB RAM,资源充裕,而且ADC、定时器、USART外设齐全,价格也就十块钱左右,性价比在这个场景下是最优解。

整体方案的设计思路可以概括为“感知—决策—执行—反馈”四层。感知层用DHT11采集温湿度,单总线协议,虽然精度一般(湿度±5%RH,温度±2℃),但对于衣柜除湿这种场景完全够用,毕竟我们关心的是“潮不潮”而不是“精确到小数点后两位”。决策层在STM32内部实现,核心是一个带滞回区间的状态机,避免湿度在阈值附近反复跳动导致继电器频繁吸合。执行层通过继电器控制半导体制冷片的通断,制冷片冷面凝露、热面散热,配合小风扇把干燥空气循环起来。反馈层用0.96寸OLED显示当前温湿度和系统状态,同时预留串口输出调试信息。

注意:半导体制冷片工作时热面温度很高,必须配散热片和风扇,否则几分钟就能把自己烧了。这一点在原理图里体现为风扇和制冷片并联供电,但实际布线时建议风扇单独走一路,避免制冷片启动瞬间把风扇电压拉低。

方案选型上还有一个容易被忽略的点:为什么用继电器而不是MOS管直接驱动制冷片。制冷片额定电流普遍在3A到6A之间,MOS管方案需要选低导通电阻的型号并加散热,而继电器模块现成、隔离性好、驱动简单,虽然寿命有次数限制,但配合滞回控制后每天吸合次数不超过二十次,用个三五年没问题。成本上继电器模块也就两三块钱,比搭MOS驱动电路省事得多。

2. 硬件核心细节与原理图拆解

2.1 主控最小系统与供电设计

原理图的主控部分就是标准的STM32F103C8T6最小系统:8MHz晶振提供HSE时钟,经过PLL倍频到72MHz作为系统主频;32.768kHz晶振留给RTC,虽然这个项目暂时没用到,但预留出来方便后期加定时记录功能。复位电路用10k上拉加100nF电容,BOOT0和BOOT1各接10k下拉电阻,保证从Flash启动。SWD接口引出四根线(3.3V、GND、SWDIO、SWCLK),用ST-Link下载调试,比串口下载稳定得多。

供电部分是整个硬件里最需要仔细处理的地方。系统有三路电压需求:12V给制冷片和风扇,5V给继电器模块和OLED(部分OLED支持5V),3.3V给STM32和DHT11。我的做法是12V适配器输入,经过LM2596降压到5V,再经过AMS1117-3.3降到3.3V。这里有个坑:LM2596的输出电容不能省,我第一版为了省空间只放了100uF,结果继电器吸合瞬间5V轨跌到4.2V,STM32直接复位。后来换成470uF电解并联100nF陶瓷,问题消失。

DHT11的供电也要注意。它虽然标称3.3V到5.5V都能工作,但3.3V供电时数据线高电平只有3.3V,如果STM32也是3.3V供电就没问题。数据线需要接一个4.7k到10k的上拉电阻到3.3V,我用的4.7k,实测通信稳定。DHT11的采样周期不能低于1秒, datasheet写的是1Hz,实际使用中我设成2秒读一次,给传感器足够的恢复时间。

2.2 传感器与执行机构的接口电路

DHT11的接口很简单,VCC接3.3V,GND接地,DATA接PA0并配上拉电阻。但布线时有个细节:DATA线尽量远离继电器和制冷片的电源线,否则继电器吸合瞬间的电磁干扰可能导致DHT11读取出错。我在PCB上把DHT11的走线放在板子另一侧,中间用地线隔离,误码率从原来的百分之几降到几乎为零。

继电器模块我用的是现成的5V低电平触发模块,IN脚接PB0,VCC接5V,GND共地。模块内部自带光耦隔离和续流二极管,所以STM32的IO口直接驱动没问题。但要注意:有些继电器模块是高电平触发,买的时候看清楚,否则上电瞬间继电器就吸合了。如果不确定,可以在代码里把初始化电平设成高电平(对应低电平触发模块的断开状态),上电后再根据逻辑控制。

OLED用的是0.96寸I2C接口的SSD1306,SCL接PB6,SDA接PB7,走的是STM32的硬件I2C1。这里我踩过一个坑:STM32F103的硬件I2C有已知的锁死问题,在某些干扰下会卡在等待ACK的状态。解决办法有两个,一是用软件模拟I2C,二是加超时检测并在超时后重新初始化I2C外设。我选了后者,在代码里加了一个I2C超时复位函数,跑了半年没再出现过死机。

风扇和制冷片的驱动电路在原理图上就是继电器常开触点串联12V电源。但实际接线时,制冷片和风扇建议并联但分别走线,因为制冷片启动电流大,如果和风扇共用细线,风扇转速会明显下降。我用的是0.5平方的线单独给制冷片,风扇用0.2平方的线,两者在电源端汇合。

2.3 原理图绘制与PCB布局要点

原理图我用的是立创EDA,免费且元件库全,DHT11、SSD1306、STM32F103C8T6都有现成符号。绘制时注意几个点:电源网络要标注清楚,12V、5V、3.3V用不同颜色区分;网络标签要统一命名,比如DHT11_DATA、OLED_SCL,避免自动生成的N$1这种名字,后期查线很痛苦;每个元件的封装要提前确认,特别是DHT11有两种封装,一种是四针直插,一种是三针模块,买之前看清楚。

PCB布局我遵循“强弱电分离、模拟数字分离”的原则。12V和继电器部分放在板子左侧,STM32和传感器放在右侧,中间用地线隔离带隔开。DHT11如果外接的话,接口放在板子边缘,远离发热元件。晶振尽量靠近STM32的OSC引脚,走线短而粗,下面不要走其他信号线。SWD接口放在板子边缘方便插拔。

实操心得:第一版PCB我把继电器放在了DHT11旁边,结果每次继电器吸合DHT11就报一次校验错误。后来把继电器挪到板子另一头,中间加了地线隔离,问题解决。如果你也遇到类似情况,先检查传感器和干扰源的物理距离。

3. 软件架构与核心代码实现

3.1 系统状态机与滞回控制逻辑

软件的核心是一个四状态的状态机:待机态、除湿态、延时保护态、故障态。待机态下系统每2秒读一次DHT11,湿度低于55%RH就保持待机,高于60%RH进入除湿态。除湿态下继电器吸合,制冷片和风扇工作,同时启动一个定时器记录除湿时长。如果湿度降到50%RH以下,退出除湿态回到待机;如果连续除湿超过30分钟湿度还没降下来,进入故障态,关闭继电器并在OLED上显示错误码。

这里的关键是滞回区间的设计。如果只用单一阈值,比如湿度高于60%开、低于60%关,那么湿度在60%附近波动时继电器会疯狂吸合断开,一天下来几百次,继电器寿命急剧缩短。我设的滞回区间是10%RH:高于60%才开,低于50%才关。这样即使湿度在55%到65%之间波动,继电器也不会频繁动作。实测在回南天环境下,继电器每天吸合次数在15到20次之间,完全在安全范围内。

延时保护态是干什么的?制冷片停机后不能立刻重启,因为热面温度还没降下来,立刻重启会导致制冷效率极低甚至损坏。我在代码里设了3分钟的最小停机时间,继电器断开后启动一个软件定时器,3分钟内即使湿度超标也不允许再次吸合。这个时间是根据制冷片的熱惯性估算的,实际用红外测温枪测过,停机3分钟后热面温度从60度降到35度左右,可以安全重启。

故障态的处理逻辑是:连续除湿超过30分钟湿度下降不足5%RH,判定为“除湿无效”,可能是制冷片坏了、风扇停了或者衣柜门没关。这时候关闭继电器,OLED显示“ERR”,同时串口输出错误信息。用户看到后可以检查硬件。这个逻辑帮我朋友发现过一次风扇被衣服挡住的问题。

3.2 DHT11驱动与数据滤波

DHT11的驱动代码网上很多,但质量参差不齐。我参考了正点原子的例程后自己重写了一版,核心是微秒级延时和超时检测。DHT11的单总线协议是:主机拉低至少18ms,然后拉高20到40us,释放总线;DHT11响应时拉低80us再拉高80us,然后开始传输40位数据。每一位以50us低电平开始,高电平持续26到28us表示0,持续70us表示1。

代码里我用TIM4做微秒延时,因为SysTick在中断里调用有风险。具体实现是:TIM4预分频到1MHz,即每计数一次是1us,然后写一个delay_us函数。读取一位数据的逻辑是:等待低电平结束,然后延时40us,再读引脚电平,如果是高就是1,低就是0。这个40us的采样点很关键,太早可能读到上升沿,太晚可能读到下一位的开始。

uint8_t DHT11_ReadBit(void) { uint8_t retry = 0; while(DHT11_PIN_READ() && retry < 100) { retry++; delay_us(1); } retry = 0; while(!DHT11_PIN_READ() && retry < 100) { retry++; delay_us(1); } delay_us(40); if(DHT11_PIN_READ()) return 1; else return 0; }

数据滤波方面,我用了滑动平均加中值滤波的组合。每次读取DHT11得到温湿度后,存入一个长度为5的数组,然后去掉最大值和最小值,剩下三个取平均。这样既能滤掉偶发的野值,又不会引入太大延迟。实测下来,原始数据偶尔会跳变1到2%RH,滤波后曲线平滑很多。

注意:DHT11上电后需要1秒的稳定时间,代码里在初始化后加了1.5秒延时。如果跳过这个延时,第一次读取大概率失败。

3.3 OLED显示与串口调试信息输出

OLED显示我写了一个简单的多级菜单,主界面显示当前温湿度、系统状态和除湿累计时长。状态用图标表示:待机是一个月亮,除湿是一个水滴,故障是一个感叹号。图标用取模软件生成,存在代码的常量数组里。显示刷新率设成2Hz,和DHT11采样率同步,避免频繁刷屏导致OLED闪烁。

串口部分用USART1,波特率115200,每2秒输出一行调试信息,格式是“Humi:xx.x Temp:xx.x State:x Relay:x”。这个输出在调试时非常有用,可以直观看到状态机的切换过程。比如你发现继电器频繁吸合,看串口输出就能知道是湿度波动导致的还是逻辑写错了。

代码结构上我分了五个文件:main.c、dht11.c、oled.c、relay.c、uart.c。main.c里是主循环和状态机,dht11.c负责传感器读取和滤波,oled.c负责显示,relay.c负责继电器控制和延时保护,uart.c负责串口输出。这种模块化写法方便后期移植,比如你想把DHT11换成SHT30,只需要改dht11.c里的读取函数,其他文件不用动。

4. 仿真验证与实物调试对比

4.1 Proteus仿真环境搭建

没有硬件的朋友可以用Proteus先把逻辑跑通。仿真工程里我放了STM32F103C8T6、DHT11、SSD1306、继电器和LED(模拟制冷片)。DHT11在Proteus里没有现成模型,我用一个电位器模拟湿度输出,通过ADC读取,然后在代码里做映射。具体做法是:电位器接PA1,ADC采样值0到4095映射到湿度0到100%RH。这样转动电位器就能模拟湿度变化,观察继电器和OLED的响应。

仿真里要注意时钟配置。Proteus的STM32模型默认用的是内部8MHz RC振荡器,如果你代码里配置了外部晶振,仿真会跑不起来。解决办法是在Proteus的STM32属性里把Crystal Frequency设成8MHz,然后在代码里用HSE。我试过用内部RC,延时函数会偏慢,DHT11通信会失败。

OLED在Proteus里用I2C调试器代替,可以实时看到写入的显存数据。虽然不是图形化显示,但能验证I2C通信是否正常。继电器用LED代替,吸合时LED亮。整个仿真跑下来,状态机切换、滞回控制、延时保护这些逻辑都能验证,唯一不能验证的是DHT11的实际时序,因为Proteus的DHT11模型是理想化的。

4.2 实物调试中的典型问题与解决

实物调试遇到的问题比仿真多得多。第一个问题是DHT11读取失败率高,十次里有三次返回错误。排查后发现是延时函数不准,TIM4的预分频值算错了。重新计算后,延时误差从±5us降到±1us,读取成功率提到99%以上。这里提醒一句:微秒延时函数一定要用示波器或者逻辑分析仪校准,光靠眼睛看代码是看不出来的。

第二个问题是继电器吸合导致OLED花屏。原因是继电器线圈断电时产生反向电动势,通过电源线耦合到了OLED。解决办法是在继电器线圈两端并联一个续流二极管(1N4148),同时在OLED的VCC和GND之间加一个100uF电解电容。改完之后花屏再没出现过。

第三个问题是制冷片效率低。实测除湿半小时,衣柜湿度只降了3%RH。排查发现是风扇方向装反了,把冷面的冷气吹到了外面,热面的热气留在了衣柜里。把风扇反过来装,让风先经过冷面再吹向衣柜内部,效率立刻提升,半小时降了12%RH。这个坑很隐蔽,因为风扇转起来看起来都差不多,但方向错了效果天差地别。

问题现象可能原因排查方法解决方案
DHT11读取失败延时不准、上拉电阻缺失示波器看时序校准延时、加4.7k上拉
OLED花屏继电器干扰、电源纹波示波器看5V轨加续流二极管、加滤波电容
除湿效率低风扇方向反、衣柜不密封手感测风温调整风扇方向、密封衣柜
继电器频繁吸合滞回区间太小看串口日志加大滞回区间到10%RH
STM32复位电源跌落示波器看3.3V轨加大滤波电容、单独供电

4.3 代码诊断与OTA升级的预留设计

虽然这个项目目前没上OTA,但我在代码里预留了Bootloader接口。具体做法是把Flash分成两部分:0x08000000到0x08003FFF给Bootloader,0x08004000往后给应用程序。Bootloader里实现一个简单的串口协议,收到特定命令后跳转到应用程序区。这样后期想加OTA功能,只需要写一个上位机通过串口发送bin文件,Bootloader负责写入Flash并跳转。

代码诊断方面,我在每个模块里加了错误计数和状态标志。比如DHT11连续读取失败5次,置位一个错误标志,OLED显示“SENSOR ERR”。继电器驱动电路如果检测到反馈信号异常(预留了一个IO口做继电器状态回读),也会置位错误标志。这些标志可以通过串口查询,方便远程诊断。虽然现在没做远程,但接口留好了,后期加个ESP8266就能上报。

实操心得:Bootloader的跳转地址一定要和应用程序的链接地址一致。我第一版Bootloader跳转到0x08004000,但应用程序的Keil工程里IROM1起始地址还是0x08000000,结果跳过去就HardFault。改完链接地址后正常。这个坑很典型,做OTA的时候一定要注意。

5. 常见问题排查与避坑经验实录

5.1 传感器与执行机构的典型故障

DHT11最常见的问题是上电后第一次读取失败。这不是代码bug,而是传感器内部需要稳定时间。我的做法是在初始化后延时1.5秒再读,并且第一次读取的结果直接丢弃,从第二次开始才纳入滤波。另外DHT11的响应速度慢,如果衣柜门频繁开关,湿度变化快,DHT11可能跟不上。这种场景建议换SHT30,I2C接口,响应快很多,价格也就贵几块钱。

继电器的问题是触点粘连。虽然继电器标称寿命10万次,但那是理想条件下的。如果控制的是制冷片这种感性负载,触点断开时会产生电弧,加速氧化。我的做法是在继电器触点两端并联一个RC吸收电路(100欧姆串联0.1uF),实测触点温度明显降低。另外继电器的驱动电流也要注意,有些模块标称5V驱动,实际吸合电流要70mA以上,如果STM32的IO口直接驱动(最大20mA),根本带不动。必须用三极管或者现成的驱动模块。

制冷片的问题是冷面结冰。如果湿度很高且温度很低,冷面可能结冰,冰层反而阻碍热交换。我的代码里加了温度判断:如果温度低于5℃,即使湿度超标也不开除湿,避免结冰。这个逻辑在北方冬天特别有用,南方回南天一般不会触发。

5.2 电源与干扰问题的排查思路

电源问题占了我调试时间的一半以上。最典型的是继电器吸合导致STM32复位。排查方法是:用示波器看3.3V轨,在继电器吸合瞬间有没有跌落。如果有,说明电源功率不够或者滤波不足。解决办法是加大5V和3.3V的滤波电容,或者给继电器单独供电。我最后用的是12V转5V的LM2596给继电器,12V转3.3V的AMS1117给STM32,两路分开,问题彻底解决。

干扰问题的排查比较麻烦。我的经验是先怀疑电源,再怀疑地线,最后怀疑信号线。电源问题看纹波,地线问题看地环路,信号线问题看走线。DHT11的DATA线如果和继电器控制线平行走线超过5厘米,误码率会明显上升。解决办法是垂直交叉走线,或者中间加地线隔离。PCB布局时把传感器接口放在板子边缘,远离继电器和电源部分。

还有一个隐蔽的问题是晶振不起振。STM32的HSE晶振如果负载电容不匹配,可能起振很慢甚至不起振。我的做法是负载电容用20pF(晶振规格书推荐值),并在PCB上把晶振尽量靠近芯片,走线短而粗。如果还是不起振,可以尝试降低驱动能力或者换晶振。实测某宝上买的便宜晶振起振时间要几秒,换品牌晶振后瞬间起振。

5.3 代码调试与工具链的踩坑记录

Keil5的安装有个坑:Keil5和Keil4的器件包不兼容。如果你之前装过Keil4,再装Keil5,器件包可能会冲突。解决办法是卸载Keil4,清理注册表,再装Keil5。另外STM32F1的器件包要单独下载,装完Keil5后默认只有ARM的包,需要去官网下载STM32F1xx_DFP。

ST-Link的驱动问题也很常见。STM32无法识别USB设备,多半是ST-Link的驱动没装好。解决办法是去官网下载最新的ST-Link Utility,安装时会自动装驱动。如果还是不行,换一根USB线试试,有些线只有充电功能没有数据功能。另外ST-Link的固件也要更新,老固件可能不支持新的芯片。

代码提示方面,VSCode写C没有代码提示,需要装C/C++插件并配置includePath。我的做法是用Keil写代码,用VSCode看代码和搜索。Keil的代码提示虽然弱,但编译调试方便;VSCode的代码提示强,但配置编译环境麻烦。两者结合用效率最高。

工具用途常见问题解决
Keil5编译调试器件包缺失下载STM32F1xx_DFP
ST-Link Utility下载程序驱动未装官网下载最新版
VSCode代码阅读无提示装C/C++插件配includePath
立创EDA原理图PCB封装错误买元件前确认封装
Proteus仿真时钟配置错误设Crystal为8MHz

6. 项目扩展与个人实操体会

这个项目的基础功能已经稳定,但可扩展的方向很多。最直接的是加ESP8266做数据上报,把温湿度数据传到手机或者云端,实现远程监控。我在代码里预留了USART2给ESP8266,用AT指令连接WiFi,每5分钟上报一次数据。这样即使不在家,也能知道衣柜有没有受潮。另一个方向是加人体感应,用HC-SR501检测衣柜门开关,开门时暂停除湿,关门后恢复,避免冷气外泄。

还有一个有意思的扩展是用A*算法做除湿策略优化。听起来有点杀鸡用牛刀,但如果你有多个衣柜或者多个除湿点,可以用A*算法规划最优的除湿顺序,比如先除湿湿度最高的柜子,再除湿次高的,整体能耗最低。这个思路来自我做智能家居项目时的经验,单点控制用阈值就够了,多点控制就需要算法了。

我个人在实际操作中的体会是:硬件项目最花时间的不是写代码,而是调试硬件。代码逻辑半天就能写完,但硬件问题可能调一个星期。所以建议新手先用Proteus仿真把逻辑跑通,再打板做实物。打板的时候一定要留测试点,比如3.3V、5V、GND、DHT11_DATA、RELAY_IN,方便用示波器和万用表测量。另外元件不要一次性全焊上去,先焊电源部分,测好电压再焊主控,最后焊外设,这样出问题容易定位。

最后分享一个小技巧:DHT11的读取间隔不要低于2秒。我试过1秒读一次,连续跑一天后传感器发热,湿度读数偏高3%到5%。改成2秒后,传感器温度降下来,读数恢复正常。如果你需要更快的响应,建议换SHT30或者HTU21D,这些数字传感器自带加热补偿,可以高频读取。

这个项目后续还可以这样扩展:把除湿逻辑做成可配置的,通过OLED菜单调整阈值和延时时间,不用重新烧录程序。或者加一个SD卡模块,把温湿度数据记录到CSV文件,方便后期分析衣柜的湿度变化规律。再或者用蓝牙模块做近场配置,手机APP调参数。这些扩展都不难,核心的状态机不用动,只需要加外设驱动和菜单逻辑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询