免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

基于STM32的智能衣柜除湿系统设计与实现

基于STM32的智能衣柜除湿系统设计与实现 1. 项目缘起与整体设计思路1.1 为什么选择STM32做衣柜除湿控制南方梅雨季的痛住过一楼或者地下室的朋友应该都懂。衣柜里的衣服摸起来潮乎乎的时间一长就长霉斑那股味道洗都洗不掉。市面上卖的除湿盒、除湿袋治标不治本用几天就饱和了而且没法自动控制。成品除湿机又太大塞不进衣柜功耗也高。这个项目的出发点很简单用一块STM32最小系统板加上温湿度传感器、半导体除湿片、风扇和加热片做一个能塞进衣柜角落的闭环除湿系统。核心逻辑就是实时监测衣柜内部的温湿度当湿度超过设定阈值时启动除湿片和风扇湿度降下来之后自动停止如果温度过低影响除湿效率再补一点加热。选STM32F103C8T6作为主控理由很直接价格便宜十几块钱一片资料多到烂大街GPIO和定时器资源够用ADC精度对于DHT11这种传感器绰绰有余。而且这个芯片的生态太成熟了Keil、CubeMX、标准库、HAL库随便挑出了问题搜一下就有答案。对于想做毕业设计或者练手嵌入式项目的朋友来说这是最不容易卡壳的选择。整个系统涉及的知识点其实挺全的GPIO驱动、单总线时序、ADC采样、PWM调速、定时器中断、串口通信、OLED显示甚至还能加上OTA升级。麻雀虽小五脏俱全拿来当学习项目或者课程设计都挺合适。1.2 系统架构与模块划分整个系统我把它拆成四个层次来理解这样画原理图和写代码的时候思路会清晰很多。感知层DHT11温湿度传感器负责采集衣柜内的温度和湿度数据。这个传感器是单总线协议一根数据线搞定接线简单但时序要求比较严格后面会详细说。如果预算够可以换成SHT30或者AHT20I2C接口精度更高代码也更省心。控制层STM32F103C8T6作为核心负责读取传感器数据、执行控制逻辑、驱动执行器、更新显示。它要同时处理多个任务所以定时器中断和状态机的设计很关键。执行层包括半导体制冷片除湿核心、散热风扇加速空气循环、PTC加热片低温补偿。半导体制冷片通电后一面制冷一面发热冷面凝结空气中的水分热面需要风扇散热。这里用MOS管或者继电器来驱动PWM控制风扇转速。交互层0.96寸OLED显示屏显示当前温湿度和工作状态三个按键用于设置阈值和手动控制。蜂鸣器做报警提示LED做状态指示。电源部分需要特别注意半导体制冷片和加热片都是大电流负载不能直接从STM32的3.3V或者5V取电。我用的是12V/5A的直流电源适配器通过降压模块给STM32供5V再通过AMS1117-3.3给芯片供3.3V。大电流负载走独立的MOS管开关电路和MCU的电源完全隔开避免电机启动时把MCU拉复位。1.3 方案选型的几个关键取舍传感器选DHT11还是DHT22还是SHT30DHT11便宜几块钱但精度只有±2℃和±5%RH响应也慢1秒才能读一次。DHT22精度好一些但贵一倍。SHT30是I2C接口精度±0.3℃和±3%RH代码简单但价格要十几块。我最终选了DHT11因为这个项目对精度要求没那么高衣柜除湿差个5%湿度完全能接受省下的钱可以买更好的除湿片。除湿用半导体制冷片还是压缩機压缩机效率高但体积大、噪音大、成本高塞进衣柜不现实。半导体制冷片体积小、无噪音、控制简单虽然效率低一点但对于衣柜这种小空间完全够用。我用的是TEC1-1270612V供电最大电流6A实际工作电流大概3-4A。驱动用继电器还是MOS管继电器简单隔离好但有机械寿命频繁开关容易坏而且吸合时有咔嗒声。MOS管无触点、寿命长、可以PWM调速但需要加续流二极管保护。我选了MOS管方案用IRF540N配合PC817光耦隔离MCU的GPIO通过光耦控制MOS管栅极。显示用OLED还是LCD1602LCD1602便宜但只能显示两行字符OLED可以显示图形和中文I2C接口只占两个引脚看起来也高级。0.96寸OLED大概十块钱我选了OLED。2. 硬件设计细节与原理图要点2.1 STM32最小系统与引脚分配STM32F103C8T6有48个引脚实际可用的GPIO大概37个。这个项目用到的引脚不算多但分配的时候要考虑外设复用和布线方便。先列一下我的引脚分配表功能模块引脚模式说明DHT11数据PA0开漏输出/浮空输入单总线需要上拉电阻OLED SCLPB6复用开漏I2C1时钟OLED SDAPB7复用开漏I2C1数据风扇PWMPA8复用推挽TIM1_CH1除湿片控制PB0推挽输出通过光耦驱动MOS加热片控制PB1推挽输出通过光耦驱动MOS按键1PA1上拉输入设置键按键2PA2上拉输入加/减键按键3PA3上拉输入确认键蜂鸣器PB5推挽输出低电平触发状态LEDPC13推挽输出板载LED串口TXPA9复用推挽USART1_TX串口RXPA10浮空输入USART1_RX这里有几个点要说明。PA0接DHT11的时候因为单总线是双向的需要配置成开漏输出加外部上拉电阻4.7K到10K读数据的时候切换成浮空输入模式。PB6和PB7接OLEDI2C需要外部上拉电阻一般4.7K有些OLED模块自带上拉就不用加。PA8输出PWM给风扇TIM1的通道1频率设25KHz左右超出人耳听觉范围不会有啸叫声。PC13是板载LED低电平点亮用来做心跳指示每秒翻转一次方便判断程序有没有跑飞。2.2 DHT11驱动电路与上拉电阻计算DHT11的接线看起来简单VCC、GND、DATA三根线但实际用的时候有几个坑。首先是上拉电阻。DHT11的数据线是开漏输出必须接上拉电阻才能输出高电平。阻值怎么选太小了功耗大太大了上升沿变缓导致时序错误。一般4.7K到10K都可以我用的4.7K。计算依据是总线电容大概100pF上升时间τRC4.7K×100pF470ns远小于DHT11要求的1μs上升时间没问题。其次是电源去耦。DHT11对电源噪声比较敏感VCC和GND之间要加一个100nF的陶瓷电容靠近传感器引脚放置。我一开始没加读出来的数据偶尔会跳变加了之后稳定多了。还有一点DHT11的采样周期不能小于1秒。它内部有个电阻式湿度元件响应时间比较慢连续快速读取会返回上一次的值或者出错。我在代码里用定时器做了1.5秒的间隔确保每次读的都是新数据。如果换成SHT30电路就简单多了VCC、GND、SCL、SDA四根线I2C地址默认0x44不需要上拉电阻模块自带代码也简单直接读寄存器就行。但SHT30不能接触水汽衣柜里湿度大的时候要注意防护可以加一层透气防水膜。2.3 大功率负载驱动MOS管与光耦隔离半导体制冷片和加热片都是12V大电流负载不能直接接STM32的GPIO。GPIO输出高电平只有3.3V电流最大20mA连继电器线圈都驱动不了更别说几安培的负载了。我的方案是两级驱动STM32 GPIO → 光耦PC817 → MOS管IRF540N → 负载。光耦的作用是电气隔离。STM32的地和12V电源的地是分开的光耦把控制信号传过去但两边地不连通避免大电流负载的干扰串到MCU这边。PC817的发光二极管侧串联一个1K的限流电阻STM32输出高电平时电流大概(3.3-1.2)/10002.1mA光耦的电流传输比CTR大概50%-600%足够驱动光敏三极管侧。MOS管IRF540N是N沟道的Vgs(th)最大4VSTM32的3.3V可能驱动不充分所以光耦输出侧要接12V通过一个10K的上拉电阻把栅极拉到12V确保MOS管完全导通。栅极和源极之间加一个10K的放电电阻防止栅极悬空时误导通。漏极和源极之间加一个续流二极管1N5822肖特基因为除湿片和风扇都是感性负载关断时会产生反向电动势没有续流二极管会把MOS管击穿。这里有个经验IRF540N的导通电阻Rds(on)大概44mΩ通过4A电流时功耗是4×4×0.0440.7W需要加一个小散热片。如果负载电流更大建议换成IRF3205或者IRLZ44N逻辑电平MOS管3.3V就能完全导通。2.4 电源树设计与电流估算整个系统的电源需求STM32F103C8T63.3V最大50mAOLED显示屏3.3V最大20mADHT113.3V最大1mA风扇12V0.2A半导体制冷片12V3-4A加热片12V1-2A总计12V侧最大约6A3.3V侧约100mA。我选了一个12V/5A的电源适配器实际除湿片和加热片不会同时满功率工作所以5A够用。12V直接给风扇、除湿片、加热片供电。12V转5V用LM2596降压模块输出5V给STM32板子的5V引脚板上有AMS1117转3.3V。OLED和DHT11直接从板子的3.3V取电。这里要注意LM2596的输出电流最大3A但实际给STM32供电只需要100mA左右余量很大。如果直接用AMS1117从12V转3.3V压差太大发热会很严重所以必须先用开关电源降到5V再用线性稳压器降到3.3V。布线的时候大电流路径12V到除湿片、加热片走粗线至少1mm宽或者用双面走线。小信号路径传感器、I2C远离大电流路径避免干扰。地线采用星型接地所有模块的地最终汇聚到电源入口处不要形成地环路。3. 软件架构与核心代码实现3.1 主程序状态机设计这个系统如果用传统的顺序执行加延时会有一个问题DHT11读取需要至少1秒的间隔除湿片和风扇需要定时控制按键需要实时响应OLED需要刷新。如果全部用delay按键会卡顿显示会闪烁。我的做法是用一个基于定时器的时间片轮询架构。SysTick定时器每1ms产生一次中断在中断里维护几个软件计数器volatile uint32_t tick_1ms 0; volatile uint8_t flag_1s 0; volatile uint8_t flag_100ms 0; void SysTick_Handler(void) { tick_1ms; if (tick_1ms % 100 0) flag_100ms 1; if (tick_1ms % 1000 0) flag_1s 1; }主循环里根据这些标志位来调度任务while (1) { if (flag_100ms) { flag_100ms 0; Key_Scan(); // 按键扫描每100ms一次 OLED_Refresh(); // 刷新显示 } if (flag_1s) { flag_1s 0; DHT11_Read(); // 读取温湿度 Control_Logic(); // 执行控制逻辑 LED_Toggle(); // 心跳指示 } }这样每个任务都不会阻塞其他任务按键响应灵敏显示也不闪。状态机方面系统有四个状态待机、除湿、加热、报警。状态转换条件如下待机 → 除湿湿度 设定阈值 5%除湿 → 待机湿度 设定阈值 - 5%待机 → 加热温度 15℃ 且 湿度 阈值加热 → 待机温度 18℃任意状态 → 报警传感器读取失败连续3次加5%的迟滞是为了防止湿度在阈值附近波动时频繁启停保护除湿片和MOS管。3.2 DHT11单总线时序的精确控制DHT11的通信协议是单总线对时序要求比较严格。一次完整的读取过程包括主机发送起始信号、DHT11响应、传输40位数据。起始信号主机拉低数据线至少18ms然后拉高20-40us然后释放总线。DHT11检测到起始信号后等待20-40us然后拉低总线80us作为响应再拉高80us接着开始传输数据。数据格式40位前16位是湿度整数8位小数8位中间16位是温度最后8位是校验和。校验和等于前32位之和的低8位。代码实现的关键是微秒级延时。我用的是SysTick或者TIM来做us延时void delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t cycles us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) cycles); }DWT是Cortex-M3内核自带的数据观察点单元用它做us延时比空循环准确得多。需要先在初始化时使能DWTCoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk;读取一位数据的逻辑等待总线变低DHT11拉低表示数据位开始延时40us后采样总线电平。如果还是高电平说明这一位是1如果是低电平说明是0。然后等待总线变高进入下一位。uint8_t DHT11_ReadByte(void) { uint8_t data 0; for (int i 0; i 8; i) { while (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)); // 等待低电平结束 delay_us(40); data 1; if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) { data | 1; } while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)); // 等待高电平结束 } return data; }这里有个坑DHT11在传输数据的时候高电平持续时间是26-28us表示070us表示1。如果在40us的时候采样0已经变成低电平了1还是高电平正好能区分。但如果MCU主频太低或者中断干扰导致延时不准就会读错。所以读DHT11的时候最好关掉中断读完再开。3.3 PWM风扇调速与温度联动风扇的作用是加速衣柜内空气循环让除湿片冷面接触更多潮湿空气提高除湿效率。风扇转速不需要一直全速可以根据湿度偏差来调速湿度越高风扇越快。我用TIM1的通道1输出PWM频率25KHz占空比0-100%可调。配置如下void Fan_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 2880 - 1; // 72MHz / 25KHz 2880 TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }占空比计算TIM_Pulse / TIM_Period × 100%。比如TIM_Pulse设为1440占空比就是50%。调速策略湿度偏差 当前湿度 - 目标湿度。偏差小于5%时风扇30%转速5%-15%时50%转速大于15%时80%转速。这样既能保证除湿效果又能降低噪音和功耗。3.4 OLED显示与按键交互逻辑OLED我用的是SSD1306驱动I2C接口0.96寸128×64分辨率。显示内容分三行第一行显示当前温度和湿度第二行显示工作状态待机/除湿/加热/报警第三行显示设定阈值。I2C的驱动我用的是软件模拟因为STM32的硬件I2C有时候会卡死软件模拟更稳定。SCL和SDA分别接PB6和PB7开漏输出外部上拉4.7K。void I2C_WriteByte(uint8_t addr, uint8_t data) { I2C_Start(); I2C_SendByte(addr 1); I2C_WaitAck(); I2C_SendByte(data); I2C_WaitAck(); I2C_Stop(); }OLED初始化序列比较长包括设置对比度、显示模式、扫描方向等直接照搬SSD1306的数据手册就行。显示字符用取模软件生成字库我用的PCtoLCD2002阴码、逐列式、顺向取模。按键三个设置键、加键、减键。短按设置键进入阈值设置模式光标在湿度阈值和温度阈值之间切换。加键和减键修改数值长按可以快速调整。再按设置键保存并退出。按键消抖用软件延时20ms同时检测按下和释放避免连击。代码里用状态机实现uint8_t Key_Scan(void) { static uint8_t key_up 1; if (key_up (KEY1 0 || KEY2 0 || KEY3 0)) { delay_ms(20); key_up 0; if (KEY1 0) return 1; if (KEY2 0) return 2; if (KEY3 0) return 3; } else if (KEY1 1 KEY2 1 KEY3 1) { key_up 1; } return 0; }3.5 串口通信与OTA升级预留串口USART1接PA9和PA10波特率115200用于调试输出和预留OTA升级。调试的时候把温湿度数据、状态信息打印到串口方便观察运行情况。OTA升级的思路是STM32的Flash分成Bootloader区和App区。Bootloader负责接收新固件并写入App区App区运行主程序。上电时先跑Bootloader检查是否有升级标志有就进入升级模式没有就跳转到App。这个项目里我先预留了接口没有完整实现OTA。如果要加可以用Ymodem协议通过串口传输固件或者用ESP8266做WiFi升级。Bootloader的跳转代码void JumpToApp(void) { uint32_t app_addr 0x08004000; if (((*(uint32_t*)app_addr) 0x2FFE0000) 0x20000000) { JumpToApplication (void (*)(void))(*((uint32_t*)(app_addr 4))); __set_MSP(*(uint32_t*)app_addr); JumpToApplication(); } }App区的代码需要在Keil里设置起始地址为0x08004000同时修改中断向量表偏移NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000);4. 仿真验证与实物调试4.1 Proteus仿真环境搭建在打板之前我先用Proteus做了仿真验证。Proteus里没有DHT11的模型我用的是DHT22或者自定义的温湿度传感器模型输出类似的单总线信号。OLED用Proteus自带的SSD1306模型I2C接口。仿真电路和实际电路基本一致只是把大功率负载换成了LED指示灯方便观察状态。MOS管用Proteus的IRF540模型光耦用PC817。仿真的主要目的是验证代码逻辑DHT11时序对不对、OLED能不能正常显示、按键响应是否正常、状态机转换是否正确。仿真跑通之后再打板焊接实物成功率会高很多。Proteus仿真的一个坑是时序问题。Proteus的仿真速度受电脑性能影响有时候DHT11的时序会对不上读出来的数据全是0或者255。这时候可以把仿真速度调慢或者在代码里加一些容错处理。4.2 实物焊接与调试步骤实物调试我分成四步走第一步电源测试。先不焊MCU只焊电源部分。12V输入测LM2596输出是不是5VAMS1117输出是不是3.3V。用万用表测各路电压确认没有短路。第二步最小系统测试。焊上STM32和晶振、复位电路烧一个LED闪烁的程序看PC13的LED能不能正常闪。如果能闪说明MCU最小系统没问题。第三步外设逐个测试。先焊OLED烧I2C扫描程序看能不能检测到0x78地址。再焊DHT11烧读取程序串口打印温湿度。最后焊按键和蜂鸣器测试输入输出。第四步大功率负载测试。先不接除湿片和加热片用LED代替测试MOS管开关是否正常。确认无误后再接上除湿片和风扇观察工作电流。如果电流异常大立即断电检查。调试的时候我遇到过一个坑DHT11读出来的湿度一直是95%换了几个传感器都一样。后来发现是上拉电阻没焊数据线一直悬空读到的全是随机值。补上4.7K上拉电阻后正常。还有一个坑OLED显示乱码。检查发现是I2C地址搞错了有的模块是0x78有的是0x7A。用I2C扫描程序扫一下就知道。4.3 常见问题速查表问题现象可能原因排查方法解决方案DHT11读数全0或全1上拉电阻缺失或阻值不对万用表测数据线电压加4.7K上拉电阻DHT11读数偶尔跳变电源噪声或采样太快示波器看电源纹波加100nF去耦电容采样间隔1sOLED不亮I2C地址错误或接线反了I2C扫描程序确认地址0x78或0x7A检查SDA/SCL风扇不转PWM没输出或MOS没导通示波器测PA8波形检查TIM配置测MOS栅极电压除湿片不工作光耦或MOS损坏测光耦输出侧电压更换PC817或IRF540NMCU频繁复位电源被大负载拉低示波器看3.3V波形加大滤波电容分离大负载电源按键不灵敏消抖时间太短或太长调整delay_ms(20)改成10-30ms之间串口无输出波特率不对或TX/RX接反示波器测PA9波形确认115200TX接RX4.4 实测数据与效果评估实物跑起来之后我做了几组测试测试环境衣柜容积约0.5立方米初始湿度75%RH温度22℃。测试1除湿效果。设定湿度阈值55%开启除湿。30分钟后湿度降到58%60分钟后降到53%系统自动停止。除湿片冷面有明显水珠凝结风扇工作正常。测试2功耗测试。待机状态功耗约0.5WMCUOLED。除湿状态功耗约18W除湿片12W风扇2WMCU等。加热状态功耗约15W。按每天工作4小时算一个月耗电约2度电费一块多钱。测试3稳定性测试。连续运行72小时没有死机或复位。DHT11读数稳定OLED显示正常按键响应灵敏。测试4低温加热测试。把衣柜温度降到12℃用冰袋模拟系统自动进入加热模式加热片工作温度升到18℃后停止。除湿功能在低温下效率会下降加热补偿是必要的。5. 项目扩展与个人经验总结5.1 可以继续折腾的方向这个项目的基础功能已经跑通了但还有很多可以扩展的地方。加WiFi模块做远程监控用ESP8266或者ESP32通过MQTT协议把温湿度数据上传到云平台手机App远程查看和控制。ESP8266通过串口和STM32通信AT指令集代码量不大。加人体感应自动开关用HC-SR501红外传感器检测到有人靠近才点亮OLED没人时息屏省电。加数据记录功能用SD卡模块或者Flash芯片记录每天的温湿度变化方便分析衣柜的潮湿规律。加多路传感器一个衣柜可能分几个区域用多个DHT11或者SHT30分别监测不同位置的湿度除湿片根据平均湿度或者最高湿度来控制。加OTA升级前面提到的Bootloader方案配合ESP8266的WiFi可以实现远程固件升级不用拆机就能更新代码。5.2 踩过的坑与实操心得坑1DHT11的时序不能被打断。读DHT11的时候如果来了中断时序就乱了读出来的数据全是错的。我的做法是在读DHT11之前关中断读完再开。但关中断时间不能太长DHT11一次读取大概4ms关4ms中断对系统影响不大。坑2MOS管栅极电阻不能省。我一开始没加栅极放电电阻发现MOS管偶尔会误导通风扇自己转。后来在栅极和源极之间加了10K电阻问题解决。这个电阻的作用是在光耦不导通时把栅极电荷放掉确保MOS管可靠关断。坑3OLED的I2C上拉电阻。有些OLED模块自带上拉电阻有些没有。如果没加上拉I2C通信会不稳定。我的做法是先在模块上找有没有4.7K电阻没有就自己加。坑4电源滤波电容要够大。除湿片启动瞬间电流很大会把12V电源拉低导致MCU复位。我在12V输入端加了1000uF电解电容3.3V输出端加了100uF电解加100nF陶瓷问题解决。坑5PCB布线要分地。大电流负载的地和小信号的地要分开走最后在电源入口处单点连接。我第一版没注意DHT11读数一直跳重新布线后稳定。坑6Keil的芯片包要装对。STM32F103C8T6需要装Keil.STM32F1xx_DFP包不然找不到芯片型号。如果同时装了C51和STM32的包注意不要冲突可以在Keil的Pack Installer里管理。5.3 代码与原理图获取方式这个项目的完整代码和原理图我已经整理好了包括Keil工程文件标准库版本HAL库版本后续补充Proteus仿真文件原理图PDF和源文件DHT11、OLED、PWM等模块的驱动代码详细的注释和说明文档代码结构清晰每个模块独立成文件方便移植到其他项目。原理图用Altium Designer画的也导出了PDF版本方便查看。如果你也在做类似的衣柜除湿或者环境监测项目可以直接参考这个方案。STM32F103C8T6的生态很成熟遇到问题基本都能搜到答案。关键是先把最小系统跑通再逐个加外设不要一次全焊上去出了问题不好排查。最后分享一个小技巧调试的时候在串口里加一个命令行接口可以通过串口发送指令来修改阈值、手动开关除湿片、查看运行日志。这样不用重新烧程序就能调整参数调试效率高很多。
返回列表