基于STM32的智能衣柜环境监控系统设计与实现
简介基于STM32的智能衣柜设计项目资源包面向嵌入式系统、物联网及自动化控制的入门与进阶学习者。压缩包共138个文件以STM32标准库C源码58个.c与62个.h为主另有启动汇编、编译链接脚本、配置文件及hex固件整体仅534KB便于直接查看与编译。从文件结构看工程覆盖STM32定时器、ADC、LCD显示、Flash读写等常用外设内容预览中的定时器与液晶驱动模块文件清晰可帮助理解智能衣柜的传感采集与自动控制逻辑适合在MDK等环境中二次开发通过工程内的模块划分还可学习外设初始化、中断处理与状态机设计等实战技巧。标签中的C#暗示可能配套PC端上位机可用于学习串口/网络通信与上位机调试从而覆盖从底层寄存器配置到上层应用通信的完整开发链路。目前已有283人学习资料紧凑而完整适合作为课程设计或毕业设计的参考。1. STM32 衣柜在解决什么它不只做“自动开关门”把“基于STM32”和“衣柜”放在一起第一反应往往是给柜门加个电机做一个自动开合的概念产品。但真把衣柜当被控对象来看核心矛盾并不在门而在衣柜内部的微气候封闭空间里的湿度、温度和气流。回南天里柜壁挂水、棉被返潮、角落长霉这些不是通风能解决的通风只有在外部空气比柜内干燥时才有意义。引入 STM32 的价值是让衣柜具备“可判断的主动干预”能力——湿度超过阈值就启动排风或加热柜门开启就自动点亮照明经过一段时间的静态监测还可以本地记录环境曲线判断是否该放入除湿袋。这套设计在毕设、智能家居项目或嵌入式练手场景里都很常见知识结构不复杂传感器采集、GPIO 控制、定时器和状态机再加一个低成本的显示或通信模块。难点通通落在工程细节上传感器在潮湿环境的稳定性、继电器或 MOSFET 的驱动电流、PWM 频率选择、以及把多路任务塞进一颗小容量芯片时的调度取舍。这篇文章按一条主线走从硬件选型讲到第一轮可以复现的调试验证适合已经能建 STM32 工程但还没有完整做过一个小型物联网设备的开发者。2. 硬件选型与最小系统STM32 驱动衣柜的器件清单和电源设计2.1 主控选择的两个现实考量Flash 容量和烧录方式衣柜的传感器和执行器数量通常不超过 8 路最常用的主控是 STM32F103C8T6。它的 64KB Flash、20KB RAM 跑一个裸机状态机绰绰有余如果上 FreeRTOS 也还能剩一半资源。选这颗芯片不是因为性能而是因为文档、例程和引脚资源在各类开发板生态里最密集排查问题时搜到同类电路的概率高。资源更紧张的场景也可以选 STM32G030F6P6但要注意它的部分型号没有内部 RC 校准到适合做 UART 高波特率的精度外部 8MHz 晶振还是要留出来。另一个容易被忽略的点是烧录方式。衣柜设备往往缝进柜体侧板后就不想再拆Boot0 的跳线、SWD 接口的位置都要提前设计。我一般会在 PCB 上留 4-pin 的 SWD 排针同时把 Boot0 默认接地调试完再确认量产接法是烧录后再贴外壳还是直接预留烧录口。对于刚上手的人这一步直接决定了后边的迭代速度。2.2 电源树和驱动电流别让风扇把 MCU 拉复位衣柜内部电源常见两种方案电池供电或用 12V/1A 适配器。选适配器的话电压轨至少三条12V 给加热丝和排风扇5V 给舵机和传感器模块3.3V 给 STM32。AMS1117-3.3 做线性稳压足够但输入输出压差大时发热明显最好让 5V 从 12V 经过 MP1584 这类降压模块取电。执行器启动电流的处理是这里最容易出问题的一环。直流风扇标称 200mA启动瞬间可能到 400mA舵机堵转时瞬时电流能到 700mA 以上而这些电流全部从 5V 轨拉。如果 5V 和 3.3V 共用一组 LDO 前级舵机一转MCU 电压瞬间塌到 3.0V 以下直接触发 BOR 复位。常见做法是电源分层12V 输入后先到执行器驱动电路再由驱动板上独立的 5V 降压给逻辑部分。2.3 器件清单和引脚规划一个典型衣柜项目的器件清单大约如下器件规格作用STM32 外设DHT22温湿度传感器监测柜体环境单总线 GPIO无刷风扇12V / 0.2A除湿通风PWM 定时器通道PTC 加热片12V / 5W低温时辅助烘干GPIO MOSFET舵机SG90 / MG996R柜门或抽屉开合定时器 PWM 50HzOLED0.96 寸 SSD1306显示温度湿度I2C按键2 个手动模式切换GPIO 外部中断引脚分配时有个优先级的经验PWM 通道不要随便选先查对应定时器的引脚映射例如 TIM2_CH1 默认在 PA0但 PA0 也会被用作 ADC 输入或 WKUP。我习惯把 PWM 放在 PA0/PA1I2C 用 PB6/PB7I2C1UART 用 PA9/PA10剩下的 GPIO 分配给按键和继电器。这样的好处是串口调试不受影响且 PA9/PA10 在绝大多数开发板上都引出了方便直接用 USB-TTL 看日志。提示选型阶段把引脚的复用冲突列出来比画 PCB 的时候再飞线省两到三天时间。3. 传感器驱动与数据闭环从 DHT11 到单总线时序再到 I2C 方案3.1 温湿度采样的实现边界衣柜的湿度测量和气象站不同。衣柜内部空气近乎静止传感器周围如果紧贴背板或布料局部微环境会和整个柜体空间出现明显偏差。这个偏差是系统误差后边做阈值判断时必须留出余量否则控制逻辑会在临界湿度附近频繁启停。硬件方案分两个方向DHT11/DHT22 走单总线协议SHT30/SHT31 走 I2C。DHT 家族的问题是它对时序极度敏感MCU 主频变化、中断抢占、甚至长线连接时上拉电阻取值偏大都会导致读回数据全零。好处是驱动代码短容易讲解整个采样过程。SHT30 的可靠性高得多带 CRC 校验和可配置的测量频率但价格大概是 DHT22 的三到四倍。从项目设计的角度来看如果不考虑成本展示直接用 SHT30 会省去很多后期排错。3.2 用状态机实现单总线时序避免 Delay 卡死问题不少人在写 DHT22 驱动时用while循环等待电平变化一旦传感器没有响应整个系统就卡死在这条读函数里。这个现象在“stm32延时函数delay卡死”的搜索场景里极其常见。解决方案是给等待加超时计数或者更彻底一点用定时器输入捕获来测量电平宽度。这里给出一个推荐做法用 GPIO 中断加状态机超时后返回错误码。伪代码如下typedef enum { DHT_WAIT_START_LOW, DHT_WAIT_START_HIGH, DHT_READ_BYTES, DHT_DONE } dht_state_t; volatile dht_state_t dht_state; volatile uint32_t dht_tick; uint8_t dht_data[5]; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DHT_PIN_Pin) { dht_tick 0; // 在定时器中断里自增 // 根据 dht_state 切换状态记录当前电平持续时间 } }这段代码的思路是状态切换完全由外部中断驱动PT 里不再阻塞等待。对新手来说更简单的方案是把 DHT 的读取放在 RTOS 的一个独立任务里配合osDelay(1)让出 CPU。但无论如何都要在底层采样函数做这样的保护uint8_t dht_read_raw(uint8_t *temp_h, uint8_t *temp_l, uint8_t *humi_h, uint8_t *humi_l) { uint32_t timeout 0; while (HAL_GPIO_ReadPin(DHT_PORT, DHT_PIN) GPIO_PIN_SET) { if (timeout 60000) return DHT_ERR_TIMEOUT; } // 等待主机起始信号结束 }这段代码逻辑上解决的是“传感器没有拉低总线导致死循环”的问题。timeout 60000对应 60ms 左右的上限DHT22 正常起始信号是 20~40us这个余量足够宽不会误判正常波形。真正的价值在于异常返回后主逻辑可以把这次采样标记为无效不参与控制决策而不是让整个衣柜控制系统瘫痪。3.3 I2C 替代方案和寄存器级配置如果选用 SHT30读取流程更短。I2C 地址是 0x44常见测量命令是 0x2C 0x06高重复性中速率。驱动主函数如下void sht30_read(float *temperature, float *humidity) { uint8_t cmd[2] {0x2C, 0x06}; uint8_t buf[6]; HAL_I2C_Master_Transmit(hi2c1, 0x44 1, cmd, 2, 100); HAL_Delay(15); HAL_I2C_Master_Receive(hi2c1, 0x44 1, buf, 6, 100); if (buf[2] ! crc8(buf, 2) || buf[5] ! crc8(buf 3, 2)) { *temperature -1; *humidity -1; return; } *temperature -45.0f 175.0f * ((buf[0] 8 | buf[1]) / 65535.0f); *humidity 100.0f * ((buf[3] 8 | buf[4]) / 65535.0f); }这里CRC校验不是可选项。SHT30 数据手册明确要求在 I2C 读取后对温湿度原始值进行 CRC-8 校验多项式是 0x31初始值 0xFF。如果你不做这一步偶尔出现的跳变值会让衣柜工作在错误状态比如湿度从 60% 跳到 5% 导致加热器瞬间启动。提示I2C 总线上拉电阻取值在 2.2k~4.7k 之间。衣柜内部线材长于 30cm 时取下限短则取上限否则上升沿变缓通信距离和速率都受影响。4. 控制执行器与任务拆分PWM 通道配置、驱动电路和 FreeRTOS 调度4.1 风扇和加热执行器的驱动MOS 管比继电器更适合频繁开关衣柜里的风扇和 PTC 加热片都是典型低端驱动场景。很多人第一反应是继电器但继电器寿命在频繁开关场景并不理想触点寿命约 10 万次而除湿控制如果每 5 分钟切一次一年就是 10 万次接近极限。更好的方案是 N-MOS 管加续流二极管例如 AO3400A 搭配 1N5819。PTC 加热片是阻性负载没有反电动势但风扇是感性负载MOS 管的 D-S 极之间必须并一个快恢复二极管否则关断瞬间的尖峰电压会直接击穿 MOS。电路上MCU 的 GPIO 输出 3.3V 电平通过 100Ω 电阻接到 MOS 栅极再经 10kΩ 下拉电阻确保上电默认关断。栅极电阻的作用是限制 dv/dt延缓导通斜率减少对电源轨的冲击。驱动代码就是一个普通 GPIO 输出void fan_set_speed(uint8_t percent) { __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, percent * 100); } void heat_enable(bool on) { HAL_GPIO_WritePin(HEAT_GPIO_Port, HEAT_Pin, on ? GPIO_PIN_SET : GPIO_PIN_RESET); }对应的 PWM 频率选择是个参数细节。风扇驱动用 25kHz 可以消除人耳可听见的噪声但很多便宜风扇只能在 10kHz 以下正常工作频率过高会让驱动 IC 损耗变大、风扇转速反而降低。LDO 供电的 MCU 内部定时器跑到 72MHz预分频 72 时 PWM 频率为 20kHz周期 50us算是最稳妥的落点。4.2 舵机的 PWM 细节周期、脉宽和上电毛刺舵机和其他执行器不同它对 PWM 频率极其敏感。SG90 要求 50Hz也就是 20ms 周期脉宽 0.5ms 到 2.5ms 对应 0° 到 180°。频率偏高舵机会吱吱叫偏低则响应变慢。配置 HAL 时直接写htim2.Init.Period 2000 - 1; // 20ms / (72MHz / 72) 20ms / 1us 2000 htim2.Init.Prescaler 72 - 1;这里一个很实际的坑是舵机上电瞬间的毛刺。MCU 在复位期间所有引脚处于浮空或下拉状态如果舵机的 PWM 线恰好连着定时器通道复位瞬间舵机可能猛打到一个极端角度。接机械结构前先确认引脚默认电平或者在舵机电源上串一个 PMOS 做软启动等系统初始化完成后再给舵机供电。4.3 FreeRTOS 任务拆分和优先级设计任务拆分的核心不是“越细越好”而是把不同响应需求的任务分开。衣柜场景至少分三档PWM 舵机控制要求 ms 级响应温湿度采样允许几百 ms 级别的延迟OLED 显示和按键扫描则完全不敏感。任务清单规划如下任务名周期/触发优先级内容SensorTask2000ms中采样温湿度更新全局结构体ControlTask1000ms中比较阈值启动/停止执行器UiTask500ms低刷新 OLED更新按键状态ServoTask事件触发队列高设置舵机角度PWM 更新优先级设置有一个反直觉的点不要给 ControlTask 最高优先级。湿度是一个慢变量晚 200ms 处理没有任何问题而舵机动作往往伴随人体靠近如果给它低优先级在按键事件密集时角度更新会被延迟机械部分会有明显的不跟手感觉。代码层面的队列通信写法typedef struct { float humi; float temp; } env_data_t; QueueHandle_t env_queue; void sensor_task(void *arg) { env_data_t env; for (;;) { sht30_read(env.temp, env.humi); xQueueSend(env_queue, env, 0); vTaskDelay(pdMS_TO_TICKS(2000)); } } void control_task(void *arg) { env_data_t env; for (;;) { if (xQueueReceive(env_queue, env, pdMS_TO_TICKS(100))) { if (env.humi 65.0f) fan_set_speed(60); else if (env.humi 45.0f) fan_set_speed(0); } } }这里的pdMS_TO_TICKS(100)表示即使队列空ControlTask 也会等 100ms 再重试而不是无限阻塞在队列上。这样设计的好处是控制逻辑在一个心跳周期内能感知系统健康状态发现连续多次采样失败时执行安全策略关闭所有执行器OLED 显示异常代码。5. 上电后的第一轮验证串口日志、看门狗和去抖收敛5.1 三行日志定位 80% 的问题衣柜这类设备没有屏幕时串口是最直接的调试手段。初始化时只做一件事把当前运行状态的关键变量周期性地打在串口上printf(humi%.1f temp%.1f fan%d heat%d state%d\r\n, env.humi, env.temp, fan_speed, heat_state, system_state);这三个变量足够覆盖大部分异常场景如果湿度恒定在 99% 且温度不变大概率是传感器通信失败如果风扇已置高但转速没起来查 MOS 管栅极电压和 PWM 通道映射如果加热打开后湿度缓慢上升说明柜体密封太严加热产生的热气流把柜壁缝隙里的水汽蒸了出来需要增加排气风道。每次改完代码后保持同样的打印格式跑 10 分钟看变化比反复单步调试效率高得多。5.2 看门狗防呆喂狗一定要放在“主逻辑之后”工业或家电场景里STM32 程序跑飞最常见的原因就是某个外设挂死。单总线设备、SPI Flash 和 I2C 器件的驱动程序都可能因为总线上出现意外的毛刺而卡在等待状态。开启 IWDG 是兜底手段IWDG_HandleTypeDef hiwdg; hiwdg.Instance IWDG; hiwdg.Init.Prescaler IWDG_PRESCALER_64; hiwdg.Init.Reload 1000; HAL_IWDG_Init(hiwdg);喂狗位置有讲究。不要放在任务开头也不要放在任务最后一个字节处而是放在主循环中确保一个完整的周期包括传感器重试、控制决策和页面刷新跑完一遍后再喂。这样如果中间任何一步卡住看门狗都能重启系统。5.3 控制迟滞给阈值加上下边界最后建议在湿度控制逻辑上加迟滞区间。如果只设置“湿度超过 65% 开风扇低于 65% 关风扇”传感器噪声会让风扇在临界点反复抖动。正确做法是风扇启动阈值 68%停止阈值 58%中间 10% 的区间是不动作区。加热启动阈值 75%停止阈值 65%。临界状态不稳定时把区间再放宽到 15%牺牲一点精确度但换来了机械执行器寿命和听觉上的舒适感。调试时可以在串口定义一个参数调整命令通过 USART 中断改阈值避免每次调整都重新编译下载。本文还有配套的精品资源点击获取