基于STM32的智能衣柜环境监控系统设计与实现
2026/9/12 21:57:08 网站建设 项目流程

简介:基于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 50Hz
OLED0.96 寸 SSD1306显示温度湿度I2C
按键2 个手动模式切换GPIO 外部中断

引脚分配时有个优先级的经验:PWM 通道不要随便选,先查对应定时器的引脚映射,例如 TIM2_CH1 默认在 PA0,但 PA0 也会被用作 ADC 输入或 WKUP。我习惯把 PWM 放在 PA0/PA1,I2C 用 PB6/PB7(I2C1),UART 用 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 中断改阈值,避免每次调整都重新编译下载。

本文还有配套的精品资源,点击获取

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

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

立即咨询