基于STM32的衣柜半导体除湿系统设计与实现
1. 项目缘起与整体设计思路南方回南天那段时间我家衣柜里的衣服长了霉点皮衣表面起了一层白毛翻箱倒柜找出来的衬衫一股潮味。市面上带除湿功能的衣柜动辄三四千而一个半导体除湿模块加控制板成本不到一百块。这个项目就是在这种背景下动手做的——用STM32F103C8T6做主控搭配DHT11温湿度传感器、半导体制冷片除湿模块、OLED显示和按键交互做一套能自动把衣柜湿度压在设定阈值以下的闭环控制系统。代码、原理图、仿真三件套全部开源适合正在找STM32毕业设计题目的同学也适合想给家里衣柜做智能化改造的DIY玩家。整套系统的核心逻辑不复杂DHT11周期性采集衣柜内部温湿度STM32读取数据后与预设阈值比较湿度超过上限就启动除湿模块半导体制冷片风扇低于下限就停机中间留一个回差区间防止频繁启停。OLED实时显示当前温湿度和工作状态三个按键分别负责阈值加、阈值减和手动强制开关。原理图用嘉立创EDA绘制仿真部分在Proteus里跑通逻辑代码基于Keil MDK开发标准库和HAL库两个版本都有。为什么选STM32F103C8T6而不是更便宜的STC89C52三个原因。第一F103C8T6内置12位ADC虽然这个项目用DHT11是数字传感器不需要ADC但后续如果要加土壤湿度或者模拟式湿度传感器ADC就是现成的。第二F103的定时器资源丰富后面扩展PWM调速风扇或者加编码器调节阈值都方便。第三这款芯片的生态太成熟了网上随便搜一个外设配置都有现成代码调试遇到问题容易找到参考。价格方面F103C8T6最小系统板现在也就十块钱出头比STC89C52贵不了多少但性能和扩展性完全不是一个级别。除湿方案的选择上我对比过三种压缩机制冷除湿、半导体除湿、干燥剂除湿。压缩机方案除湿量大但体积大、噪音大、功耗高放衣柜里不现实。干燥剂方案成本最低但需要定期更换或再生维护麻烦。半导体除湿模块体积小、无噪音、功耗可控12V/3A左右除湿量虽然不大但衣柜这种密闭小空间完全够用。最终选了半导体方案具体型号是TES1-12706制冷片配12V涡轮风扇冷端凝水通过导流槽排出。注意半导体制冷片工作时热端温度很高必须配散热片和风扇否则几分钟内就会烧毁。我第一版没加散热片通电三十秒制冷片就烫得没法摸后来加了铝散热片和4010风扇才稳定下来。2. 硬件原理图设计与关键器件选型2.1 主控最小系统与电源树原理图的主控部分就是STM32F103C8T6的最小系统8MHz晶振配22pF负载电容复位电路用10k上拉电阻加100nF电容BOOT0通过10k电阻下拉到地BOOT1直接接地。SWD调试接口引出四根线3.3V、GND、SWDIO、SWCLK用ST-Link下载程序。这些是标准配置没什么好说的但有一个细节值得提晶振的负载电容要根据晶振规格书来选我用的8MHz晶振标称负载电容是20pF但PCB上走线有寄生电容实际焊了22pF反而起振更稳定。如果你遇到晶振不起振的情况先检查负载电容再检查晶振本身是不是坏的。电源部分需要三路电压12V给半导体制冷片和风扇5V给DHT11和OLED部分OLED模块支持5V3.3V给STM32和OLED逻辑供电。12V从DC电源适配器直接输入经过LM2596降压到5V再经过AMS1117-3.3降到3.3V。LM2596是开关电源芯片效率高但输出纹波比线性稳压器大所以5V后面又加了一级LC滤波10uH电感加100uF电容。AMS1117-3.3是线性稳压器纹波小但压差要求至少1V5V降到3.3V刚好满足。这里有个坑要提醒半导体制冷片启动瞬间电流很大12V/6A的电源适配器在制冷片启动时电压会被拉低到10V左右导致5V输出也跟着波动。我在LM2596输入端并了一个1000uF/25V的电解电容情况明显改善。如果你用12V/3A的适配器制冷片启动时电源直接保护关机根本带不动。2.2 DHT11温湿度传感器接口设计DHT11是单总线数字传感器四个引脚VCC、DATA、NC、GND。DATA引脚需要接一个4.7k到10k的上拉电阻到3.3V我用的4.7k。DHT11的供电电压范围是3.3V到5.5V我直接接3.3V这样DATA引脚的电平就和STM32的IO口匹配不需要电平转换。如果你接5V供电DATA引脚输出高电平时是5V虽然STM32的IO口有5V容忍大部分引脚但为了保险还是加个电平转换或者直接接3.3V。DHT11的采样周期不能太快规格书要求两次采样间隔至少1秒我实际用2秒一次。上电后DHT11需要1秒左右的稳定时间所以初始化后先延时1.5秒再开始读取。读取时序是主机拉低DATA至少18ms然后释放DHT11响应拉低80us再拉高80us之后开始传输40位数据8位湿度整数8位湿度小数8位温度整数8位温度小数8位校验和。校验和等于前四个字节之和的低8位。实操心得DHT11的时序对延时精度要求很高用HAL库的HAL_Delay()函数在微秒级延时上不准我改用定时器做微秒延时。具体做法是配置一个1MHz的定时器每计数一次是1us需要延时多少微秒就设置比较值。这样读DHT11一次成功率从60%提升到99%以上。2.3 除湿模块驱动电路半导体制冷片和风扇都是12V供电STM32的IO口输出3.3V/20mA带不动需要加驱动电路。我用的是IRF540N N沟道MOS管做低边开关栅极通过10k电阻接STM32的IO口源极接地漏极接制冷片负极制冷片正极接12V。栅极和源极之间并一个10k下拉电阻防止IO口悬空时MOS管误导通。MOS管旁边反并一个1N5819肖特基二极管用来泄放制冷片断电时产生的反向电动势。为什么用MOS管而不是继电器继电器有机械触点寿命有限而且吸合时有咔嗒声衣柜里半夜启动会吵到人。MOS管是固态开关无噪音、寿命长、响应快。IRF540N的导通电阻是44mΩ通过3A电流时压降只有0.13V功耗0.4W不加散热片也能扛住。如果你用IRF3205导通电阻更低8mΩ但栅极电荷大需要更大的驱动电流STM32的IO口直接驱动可能不够快建议加一个三极管做栅极驱动。风扇和制冷片并联但风扇电流小0.2A左右可以直接用同一个MOS管驱动。不过要注意制冷片断电后风扇也应该立即停否则热端散热片的热量会倒灌到冷端。我在代码里让风扇和制冷片同步开关没有单独控制。2.4 OLED显示与按键电路OLED用的是0.96寸I2C接口的SSD1306模块四根线VCC、GND、SCL、SDA。SCL和SDA各接一个4.7k上拉电阻到3.3V。I2C地址是0x78写地址有些模块是0x7A具体看模块背面的标注。如果OLED不亮先检查地址对不对再用逻辑分析仪抓I2C波形看有没有ACK。按键用了三个6x6mm轻触开关一端接地另一端接STM32的IO口并配上拉电阻可以用内部上拉省两个电阻。按键按下时IO口读到低电平。我在代码里加了20ms的软件消抖实际测试下来比硬件RC消抖更灵活因为消抖时间可以随时改。注意OLED的I2C通信速率不要设太高SSD1306最高支持400kHz但有些模块在400kHz下不稳定我降到100kHz后就没出现过花屏。另外OLED初始化后需要延时100ms再发显示数据否则第一帧可能显示不全。3. 软件架构与核心代码实现3.1 系统主循环与状态机设计整个软件架构围绕一个状态机展开状态定义如下typedef enum { STATE_IDLE, // 空闲湿度低于下限 STATE_DEHUMID, // 除湿中湿度高于上限 STATE_MANUAL, // 手动强制除湿 STATE_ERROR // 传感器故障 } SystemState_t;主循环每2秒执行一次读DHT11 → 更新OLED显示 → 状态机判断 → 控制MOS管开关。状态切换逻辑是IDLE状态下湿度超过上限进入DEHUMIDDEHUMID状态下湿度低于下限回到IDLE。手动模式下无视湿度直接除湿直到用户再次按键取消。如果DHT11连续3次读取失败进入ERROR状态OLED显示“SENSOR ERROR”除湿模块强制关闭。回差区间的设置很关键。假设上限设60%下限设55%湿度从60%降到55%需要一段时间如果上下限设得太近比如60%和59%除湿模块会频繁启停既费电又伤制冷片。我实测下来5%的回差比较合适衣柜空间小湿度变化快5%的回差大概对应3到5分钟的除湿时间。#define HUMI_UPPER_LIMIT 60 #define HUMI_LOWER_LIMIT 55 #define SAMPLE_INTERVAL 2000 // 2秒 #define ERROR_THRESHOLD 3 // 连续3次失败进入错误状态3.2 DHT11驱动代码详解DHT11的驱动核心是微秒级延时和GPIO方向切换。我用TIM2做微秒延时配置如下void Delay_us(uint16_t us) { __HAL_TIM_SET_COUNTER(htim2, 0); while (__HAL_TIM_GET_COUNTER(htim2) us); }TIM2的时钟源是72MHz预分频器设为71计数频率就是1MHz每计数一次1us。读DHT11的完整流程uint8_t DHT11_Read(float *temp, float *humi) { uint8_t buf[5] {0}; uint8_t i, j; // 主机发送起始信号 DHT11_SetPinOutput(); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); HAL_Delay(20); // 拉低至少18ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); Delay_us(30); // 拉高20-40us DHT11_SetPinInput(); // 等待DHT11响应 if (DHT11_WaitLevel(0, 100) ! 0) return 1; // 等待低电平 if (DHT11_WaitLevel(1, 100) ! 0) return 1; // 等待高电平 if (DHT11_WaitLevel(0, 100) ! 0) return 1; // 等待低电平准备传输数据 // 读取40位数据 for (i 0; i 5; i) { for (j 0; j 8; j) { if (DHT11_WaitLevel(1, 100) ! 0) return 1; // 等待高电平 Delay_us(40); // 延时40us判断高电平持续时间 buf[i] 1; if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { buf[i] | 1; } if (DHT11_WaitLevel(0, 100) ! 0) return 1; // 等待低电平 } } // 校验 if (buf[4] ! (buf[0] buf[1] buf[2] buf[3])) return 1; *humi buf[0] buf[1] * 0.1f; *temp buf[2] buf[3] * 0.1f; return 0; }DHT11_WaitLevel函数的作用是在超时时间内等待引脚达到指定电平超时返回1表示失败。这个函数是DHT11驱动稳定性的关键超时时间设100us因为DHT11的响应脉冲最长80us留一点余量。实操心得DHT11对电源噪声很敏感如果读出来的数据经常校验失败先检查电源有没有加滤波电容。我在DHT11的VCC和GND之间并了一个100nF陶瓷电容读取成功率明显提升。另外DHT11的DATA线不要和电机、继电器等大电流线路走在一起否则干扰会导致时序错乱。3.3 OLED显示与菜单交互OLED显示分两行第一行显示当前温湿度第二行显示阈值和状态。刷新率不用太高2秒一次和传感器采样同步就行。SSD1306的驱动网上很多我用的是一款轻量级的I2C驱动只保留了必要的初始化、清屏、显示字符串和显示数字的函数。按键交互用状态机实现三个按键KEY_UP阈值加、KEY_DOWN阈值减、KEY_MODE手动/自动切换。短按调整阈值长按2秒进入设置模式设置模式下KEY_UP和KEY_DOWN调整上下限KEY_MODE保存退出。长按检测用定时器计数实现每10ms扫描一次按键连续200次检测到按下就判定为长按。void Key_Scan(void) { static uint8_t key_up_cnt 0, key_down_cnt 0, key_mode_cnt 0; if (HAL_GPIO_ReadPin(KEY_UP_PORT, KEY_UP_PIN) GPIO_PIN_RESET) { key_up_cnt; if (key_up_cnt 20) { // 200ms消抖 // 短按处理 } if (key_up_cnt 200) { // 2秒长按 // 长按处理 } } else { key_up_cnt 0; } // KEY_DOWN和KEY_MODE同理 }3.4 除湿控制逻辑与安全保护除湿控制的代码很简单就是根据状态机控制MOS管对应的GPIOvoid Dehumid_Control(SystemState_t state) { if (state STATE_DEHUMID || state STATE_MANUAL) { HAL_GPIO_WritePin(MOS_PORT, MOS_PIN, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(MOS_PORT, MOS_PIN, GPIO_PIN_RESET); } }但安全保护不能少。我加了三重保护第一除湿模块连续工作超过30分钟强制停机5分钟防止制冷片过热第二如果温度传感器读数超过40度衣柜内异常高温立即停机并报警第三看门狗定时器开启主循环喂狗程序跑飞时自动复位。// 除湿超时保护 if (state STATE_DEHUMID) { dehumid_runtime SAMPLE_INTERVAL; if (dehumid_runtime 30 * 60 * 1000) { // 30分钟 Dehumid_Control(STATE_IDLE); HAL_Delay(5 * 60 * 1000); // 停机5分钟 dehumid_runtime 0; } } else { dehumid_runtime 0; }注意HAL_Delay在中断里不能用因为它是基于SysTick的阻塞延时。上面的代码在主循环里所以没问题。如果你要在中断里做延时用定时器或者空循环。4. Proteus仿真与实物调试对比4.1 Proteus仿真环境搭建Proteus仿真STM32需要安装对应的元件库我用的Proteus 8.13自带STM32F103C8T6模型。仿真电路和实物原理图基本一致但有几个地方需要调整DHT11在Proteus里没有现成模型我用一个自定义的传感器模型代替输出固定的温湿度值OLED用I2C调试器代替通过串口输出显示内容MOS管和制冷片用LED加电阻模拟观察开关状态。仿真最大的价值是验证逻辑不是验证硬件。比如状态机切换、按键消抖、超时保护这些纯逻辑的东西在仿真里跑一遍能发现很多bug。我第一版代码在仿真里发现按键长按检测有误原因是消抖计数没有在按键释放时清零导致长按和短按冲突。这种逻辑bug在实物上调试很费时间但在仿真里改代码重新编译只要几秒钟。仿真里跑DHT11的时序比较麻烦因为Proteus的仿真速度受电脑性能影响微秒级延时可能不准。我的做法是在仿真里把DHT11的读取函数替换成一个模拟函数直接返回预设的温湿度值只验证主循环和状态机。等逻辑验证通过后再把真实的DHT11驱动烧到实物上调试。4.2 实物调试步骤与工具实物调试分四步走第一步单独测试最小系统。焊好STM32最小系统板烧一个LED闪烁程序确认晶振起振、复位正常、SWD下载正常。这一步不过后面都是白搭。第二步单独测试DHT11。把DHT11接到最小系统上烧DHT11读取程序通过串口打印温湿度值。如果读不出来用逻辑分析仪抓DATA线的波形对照DHT11时序图看哪一步不对。我遇到过DHT11上电后第一次读取总是失败后来加了1.5秒上电延时就好了。第三步单独测试OLED和按键。OLED用I2C扫描程序确认地址然后显示测试字符。按键用轮询方式读IO口电平串口打印按键状态。这一步确认人机交互正常。第四步联调除湿模块。先用LED代替制冷片确认MOS管开关正常。然后接上制冷片和风扇用手摸冷端感受降温效果。注意制冷片冷端会凝水最好在冷端加一个导流槽或者吸水棉否则水滴到电路板上会短路。调试工具方面ST-Link Utility用来烧录和查看内存逻辑分析仪我用的是Saleae克隆版抓时序串口助手看打印信息。万用表测电压和通断示波器看电源纹波。这些工具加起来不到两百块但能省下大量调试时间。4.3 仿真与实物的差异及应对仿真和实物最大的差异在电源和时序。仿真里电源是理想的实物上电源有内阻、有纹波、有压降。我在仿真里跑得好好的电路实物上电后OLED不亮查了半天发现是AMS1117-3.3的输入电容太小导致上电瞬间电压跌落STM32复位不成功。后来在输入端加了10uF钽电容就好了。时序方面仿真里微秒延时是准确的实物上受中断影响会有抖动。DHT11的读取对时序敏感如果系统里有其他中断比如SysTick可能会打断延时导致读取失败。我的做法是在读DHT11之前关掉全局中断读完再打开。关中断的时间大概2ms左右不影响其他功能。实操心得仿真通过不代表实物通过实物通过不代表长期稳定。我第一版实物调试通过后连续跑了三天发现每天凌晨湿度读数会异常偏高。后来查出来是DHT11在低温环境下衣柜夜间温度低读数漂移换了SHT30后问题解决。SHT30是I2C接口精度比DHT11高一个档次价格也就贵几块钱建议对精度有要求的直接上SHT30。5. 常见问题排查与避坑指南5.1 硬件类问题速查表现象可能原因排查方法解决方案OLED不亮I2C地址错误用I2C扫描程序确认地址改代码中的地址宏定义OLED花屏I2C速率过高降低I2C时钟到100kHz修改I2C初始化参数DHT11读数失败上拉电阻缺失万用表测DATA线空闲电平加4.7k上拉电阻到3.3VDHT11读数失败电源噪声示波器看VCC纹波VCC并100nF电容制冷片不工作MOS管未导通测MOS管栅极电压检查IO口配置和上拉电阻制冷片不工作电源功率不足测12V输入电压换12V/6A以上适配器制冷片过热散热不良摸散热片温度加散热片和风扇系统频繁复位电源跌落示波器看3.3V波形加1000uF电解电容按键不灵敏消抖时间不当串口打印按键状态调整消抖计数阈值程序跑飞看门狗未喂检查喂狗代码位置主循环开头喂狗5.2 软件类问题排查思路软件问题最难查的是偶发性bug。我的经验是先加打印再抓波形最后单步调试。串口打印是最直接的在关键位置加printf看程序走到哪一步出错。但要注意printf本身耗时在时序敏感的地方不要加。I2C通信失败是常见问题。先确认地址再确认上拉电阻最后用逻辑分析仪看波形。I2C的SCL和SDA线必须有上拉电阻STM32的内部上拉电阻是弱上拉约40kI2C通信时上升沿太慢会导致数据错误。外部加4.7k上拉后波形就干净了。DHT11读取失败还有一种可能是中断干扰。SysTick中断每1ms触发一次如果DHT11的延时函数被中断打断时序就乱了。解决方法是在读DHT11时关中断或者用硬件定时器做延时不受中断影响。5.3 独家避坑经验分享第一个坑半导体制冷片的冷端凝水。这是物理现象没法避免但可以管理。我在冷端贴了一块吸水棉定期更换。更好的做法是设计一个导流槽让冷凝水流到衣柜外面的接水盒里。如果你不想折腾就在冷端下面放一个浅盘接水。第二个坑DHT11的长期稳定性。DHT11用久了几个月后读数会漂移尤其是高湿环境下。如果只是做毕业设计或者短期演示DHT11够用。如果要长期运行建议换SHT30或者AHT20I2C接口精度±2%RH价格十几块。第三个坑MOS管的栅极驱动。IRF540N的栅极阈值电压是2V到4VSTM32的3.3V输出刚好在临界点。有些批次的IRF540N在3.3V下导通不充分导通电阻变大发热严重。我后来换了IRLZ44N这是逻辑电平MOS管2.5V就能完全导通用3.3V驱动很稳。第四个坑OLED的寿命。OLED长时间显示固定内容会烧屏尤其是高亮度下。我在代码里加了屏幕保护每30分钟切换一次显示位置或者降低亮度到50%。SSD1306支持亮度调节通过发送0x81命令加亮度值0x00到0xFF实现。第五个坑电源适配器的选择。半导体制冷片标称12V/3A但启动瞬间电流可能到5A以上。我一开始用12V/3A的适配器制冷片一启动适配器就保护关机。换成12V/6A后一切正常。选电源的时候额定电流至少是负载电流的1.5倍留足余量。5.4 项目扩展方向这套系统的基础功能已经够用但扩展空间很大。第一个扩展方向是加WiFi模块ESP8266或ESP32把温湿度数据上传到手机远程查看和控制。ESP8266通过串口和STM32通信STM32发AT指令控制ESP8266连接网络。第二个扩展方向是加人体红外传感器检测到有人靠近时自动点亮OLED平时熄屏省电。第三个扩展方向是加蜂鸣器湿度超标时报警。第四个扩展方向是加SD卡模块记录温湿度历史数据方便分析衣柜的湿度变化规律。代码结构上我预留了扩展接口。主循环里有一个App_Update()函数所有扩展功能都往里面加。传感器读取、显示刷新、控制输出分别封装成独立函数改一个功能不影响其他功能。这种模块化的写法在调试时特别有用哪个模块出问题就单独测哪个模块。提示扩展WiFi功能时注意功耗。ESP8266工作时电流峰值能到300mA如果和制冷片共用12V转5V的电源要确认LM2596的输出电流够不够。LM2596最大输出3A5V给ESP8266和OLED供电没问题但加上制冷片的风扇0.2A就接近极限了。建议给WiFi模块单独一路LDO供电或者换输出电流更大的降压芯片。6. 开源资料说明与复现建议代码部分包含三个目录CoreSTM32外设初始化、DriversDHT11、OLED、按键驱动、App主循环和状态机。Keil工程文件在MDK-ARM目录下双击.uvprojx打开就能编译。标准库版本在StdPeriph分支HAL库版本在HAL分支按需选择。原理图用嘉立创EDA绘制源文件是.epro格式导出PDF在Doc目录下。PCB我画了一版但没打样验证所以开源资料里只放了原理图PCB文件仅供参考。如果你想自己打板建议先检查封装特别是DHT11和OLED的封装不同厂家的模块尺寸可能不一样。仿真文件是Proteus 8.13的工程在Simulation目录下。仿真里DHT11用模拟数据源代替OLED用I2C调试器代替主要验证主循环和状态机逻辑。仿真跑通后再上实物能省不少时间。复现建议先买最小系统板和DHT11跑通传感器读取再加OLED和按键跑通人机交互最后加除湿模块跑通闭环控制。每一步单独测试不要一上来就全部焊在一起。我见过太多人把所有元件焊好通电结果不工作查半天查不出问题。分步调试虽然慢但总时间更短。元器件清单和参考价格STM32F103C8T6最小系统板10元DHT11传感器5元0.96寸OLED 8元半导体制冷片TES1-12706 6元12V涡轮风扇5元IRLZ44N MOS管1元LM2596降压模块3元AMS1117-3.3模块1元12V/6A电源适配器15元洞洞板或PCB 5元按键和电阻电容若干2元。总计约60元比买成品除湿衣柜便宜太多。最后分享一个焊接技巧半导体制冷片的引线很脆焊接时不要用力拉焊完用热缩管包住焊点防止短路。制冷片的正负极不能接反接反了冷端变热端除湿变加热。制冷片上有字的一面通常是冷端具体看规格书接反了调换线序就行不会烧坏但除湿效果相反。