STM32智能晾衣架课设:从传感器采集到状态机控制的工程实现
简介面向STM32课程设计与期末大作业的智能晾衣架完整工程源码适合单片机初学者及需要高分课设参考的在校生。项目基于STM32实现智能晾衣控制代码注释详细从硬件驱动到主逻辑均有清晰说明新手也能快速上手部署。压缩包共51个文件包含h头文件、c源文件、obj与lst编译中间文件、hex烧录文件及uvproj工程文件等可直接用Keil打开编译下载无需额外移植配置。包体仅133KB轻量完整便于本地调试与二次修改。目前已有261人学习下载作为课设参考具备较高人气。资源内含CORE、HARDWARE、SYSTEM、USER等标准工程目录结构规整覆盖传感器采集、电机控制、晾衣状态显示等核心模块并附带README说明与编译日志方便查错与验收。整体实用性强既能支撑课程设计报告撰写也能帮助理解STM32外设与嵌入式项目组织方式是期末大作业和满分课设的高性价比参考资源。1. 为什么基于STM32单片机的智能晾衣架能成为高分课设晾衣服收到一半雨滴传感器被水珠触发电机带着挂满衣物的晾杆往回缩整个过程没有人工干预。这个看似简单的动作背后串联了 GPIO 采集、ADC 采样、电机驱动、状态机设计和异常保护恰好覆盖了 STM32 单片机课设评审时最看重的几个能力点。基于 STM32 的智能晾衣架源码之所以常年是高分课设的热门方向不是因为电路复杂而是因为它能在一套实物上同时展示传感器输入、执行机构输出和可靠的逻辑控制。做一个合格的智能晾衣架课设重点不是把雨滴传感器、光敏电阻、电机这些模块接上电而是要让整套逻辑在雨量抖动、光照突变、机械卡死这些真实场景下仍然不误动作、不烧电机。对新手来说这个项目能让你从点灯直接跨到“带外设的小型控制系统”对已有基础的人来说这套方案里真正值钱的反而是那些被反复验证过的边界处理细节。下文从系统拆分、传感器采集、电机控制、联调取证到代码优化逐步展开聚焦“拿到源码之后怎么改、怎么答、怎么提分”。2. STM32智能晾衣架的系统拆分与硬件选型先不要急着打开源码把整个晾衣架拆成“输入—决策—执行”三层。输入层负责感知环境包括雨滴传感器、光敏传感器和手动按键决策层由 STM32 单片机完成数据读取、阈值判断和状态流转执行层是直流电机驱动晾杆伸出或收回。绝大部分课设源码的工程结构都是按这个思路组织的只是文件名和函数命名不同。2.1 不用盲目堆料先拆功能再选芯片STM32 系列里做智能晾衣架课设最常见的做法是选 STM32F103C8T6主频 72MHzFlash 64KBRAM 20KB。这套资源跑传感器采集、电机控制和状态机完全够用。Cortex-M3 内核的资料量在课设领域最大遇到问题容易搜到现成答案。如果你的开发板是 STM32F407 或 GD32、APM32 这类兼容芯片答案也基本成立。换型号时只需确认引脚复用功能是否一致比如把 PC13 换成 PB1改一下 GPIO 初始化就能跑。选型时还要考虑传感器模块的输出形式。市面上的雨滴传感器模块常见做法是板载 LM393 比较器一路输出模拟量 AO、一路输出数字量 DODO 输出会随可调电位器改变触发阈值。光敏传感器模块同理。用数字量接口可以减少 ADC 采样代码但模拟量接口能让你在答辩时展示“实时曲线”和“动态阈值”观感更丰满建议两个都接。2.2 模块清单与引脚分配建议模块推荐型号信号类型推荐引脚说明雨滴传感器LM393 雨滴模块DO / AOPA1 / PA2检测雨水DO 低电平有效光敏传感器光敏电阻模块DO / AOPA3 / PA4检测光照判断是否需要收衣直流电机6V 减速电机PWM/方向PA8 / PB12带动升降机构需 H 桥驱动H 桥驱动L298N / DRV8833IN1/IN2PB12 / PB13控制电机正反转和刹车行程限位微动开关GPIO 输入PB14 / PB15防止晾杆超出机械行程手动按键轻触按键GPIO 输入PA5切换自动/手动模式显示屏OLED SSD1306 (可选)I2CPB6 / PB7显示传感器数值和状态通信模块ESP8266 (可选)UARTPA9 / PA10扩展手机远程通知PA 口的 ADC 通道在 F103 上默认对应 ADC1 的 IN1/IN2/IN3/IN4不用额外映射初始化 ADC 时会省不少事。注意 PB2 是 BOOT1 引脚不建议占用PB3、PB4 在 SWD 调试模式下会被复用接传感器会干扰下载。选用 PA1、PA2、PA3、PA4 避开这些坑属于课设常见的最稳接法。2.3 用 Keil5 建立工程时的引脚初始化示例初始化代码的关键是把数字量输入引脚的上下拉模式配对传感器的默认电平。雨滴传感器 DO 在无雨时输出高电平有雨时输出低电平所以内部上拉要打开防止浮空误触发。光敏模块则按“光线不足输出高电平”选内部下拉让默认状态更安全避免初始化瞬间的毛刺被当成收衣信号。#include stm32f1xx_hal.h void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; // 雨滴传感器数字输出 DO 接 PA1默认无雨为高电平低电平表示检测到雨水 GPIO_InitStruct.Pin GPIO_PIN_1; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 光敏传感器数字输出 DO 接 PA3光线充足为低电平光线不足为高电平 GPIO_InitStruct.Pin GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLDOWN; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 电机方向控制引脚 PA8、PA9推挽输出 GPIO_InitStruct.Pin GPIO_PIN_8 | GPIO_PIN_9; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }提示如果你用的是标准外设库或 LL 库核心任务仍然是配置模式、速度和上下拉电阻。HAL 库只是把这些参数封装成了结构体原理没有变化。代码中最容易出错的是 Pull 字段的选取。按键和传感器 DO 输出建议全部用内部上拉除非模块手册明确说明输出是开漏结构。上下拉选错读到的电平正好相反状态机会表现为“不下雨却收衣、有大太阳却伸杆”这类问题在联调阶段非常消耗时间。3. 雨滴与光照传感器采集数据读了还要会判3.1 ADC 采样与比较器输出怎么接数字量 DO 适合做简单的干燥/下雨二值判断但课设评审时最好能展示模拟量 AO 的实时变化。接 AO 的 PA2 引脚需要初始化 ADC1配置为单通道软件触发模式。采样周期和分辨率不必追求极限12 位分辨率、采样周期 55.5 周期、软件触发就够用因为晾衣架对环境变化的响应速度要求以秒计不是微秒级控制。模拟量接法要注意模块和单片机的电平匹配。多数模块 AO 输出范围是 0~3.3V 或 0~5V如果模块带 5V 供电且 AO 输出满幅 5V直接进 STM32 会超压。常见做法是模块用 3.3V 供电或者用一个 10k 电阻串联后进 ADC 引脚再并联一个 3.3V 稳压二极管保护。课设环境里直接 3.3V 供电通常最省事。3.2 雨滴传感器读取代码示例与阈值调整#include adc.h static uint16_t read_rain_analog(void) { HAL_ADC_Start(hadc1); // 启动一次 ADC 转换 if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { return HAL_ADC_GetValue(hadc1); // 返回 0~4095 的原始值 } return 4095; // 超时默认按“无雨”处理 } uint8_t is_raining(uint16_t threshold) { uint16_t raw read_rain_analog(); // 干燥时模块输出接近 4095雨水越多输出电压越低所以小于阈值视为有雨 if (raw threshold) { return 1; } return 0; }逻辑说明雨滴传感器 AO 输出随水珠覆盖面积增大而下降干燥环境读到的值通常高于 3000一滴水落下会掉到 2000 以下雨量增大时接近 0。因此 is_raining 的判定条件是“原始值小于阈值”而不是大于。阈值默认放在 2000实测时用串口监视 AO 曲线再修正到 1500~2500 区间比较可靠。注意不要在 while 循环里连续频繁调用 ADC 而不加延时会造成模块加热效应导致干燥基准缓慢漂移。3.3 按键消抖与手动模式切换状态机模式切换最常遇到的现象是按键按一次模式跳了两次。这是机械按键的固有抖动问题不是 STM32 的 Bug。常见做法是加 50ms 的延时消抖也可以做一个 GPIO 电平连续采样计数消抖。uint8_t key_scan(void) { static uint8_t key_last 1; uint8_t key_now HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); if (key_last ! key_now) { HAL_Delay(30); // 避开按下瞬间的电平抖动 key_now HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5); if (key_last ! key_now) { key_last key_now; if (key_now GPIO_PIN_RESET) { return 1; // 返回一次有效的按下事件 } } } key_last key_now; return 0; }逻辑说明第一次检测到电平变化后延时 30ms 再读一次如果电平仍为低才认为是一次有效按下。把 key_last 设为静态变量可以让函数在每次循环中只返回一次按键事件避免长按期间重复触发。延时 30ms 是机械按键消抖的常见经验值太低消不掉抖动太高影响长按响应。模式切换代码拿到返回值后将 system_mode 做一次取反即可不需要在按键函数内部修改全局状态。4. 电机控制与晾衣架状态机把“收衣”做成可靠动作4.1 直流电机与 H 桥驱动的电平组合电机驱动是这套源码中“有点硬件含量”的部分。STM32 的 GPIO 不能直接驱动直流电机需要 H 桥芯片来换向。L298N 和 DRV8833 是课设最常用的两种。DRV8833 电压范围更低、发热小L298N 对电源要求高但容易买到模块。两者控制逻辑一致两个输入引脚 IN1、IN2 的组合决定电机正转、反转和刹车。IN1IN2电机状态晾衣架动作00制动立即停止01正转晾杆伸出10反转晾杆收回11不确定禁止使用正反转的“正”和“反”完全取决于电机线序和机械装配方向。现场接好线之后不要迷信代码注释里的“正转伸出”先用一次短时手动测试确认方向错了就把电机两根线对调或者在代码里把方向宏定义取反。这一条在答辩演示时能省下大量尴尬时间。4.2 用状态机管理晾衣架动作智能晾衣架源码里最常见的代码结构是“主循环里读传感器传感器超阈值就立刻操作电机”。这种写法在演示“下雨收衣”时能通过但存在风险雨滴传感器信号抖动、电机堵转、限位开关失效会把最简单的问题放大成机械事故。用有限状态机管理动作是进阶实现的标志也对应到做课设时最容易被追问的点。typedef enum { IDLE, // 待机状态不动作 EXTENDING, // 晾杆伸出中 RETRACTING, // 晾杆收回中 PROTECT_BLOCKED // 保护状态电机堵转或超时 } SystemState; volatile SystemState current_state IDLE; void machine_loop(void) { switch (current_state) { case IDLE: if (is_raining(RAIN_THRESHOLD) || !is_daytime(LIGHT_THRESHOLD)) { motor_start(MOTOR_IN); // 有雨或天黑回收晾杆 current_state RETRACTING; } else if (auto_enable is_daytime(LIGHT_THRESHOLD)) { motor_start(MOTOR_OUT); // 晴天有光先不主动伸出避免误闭 } break; case RETRACTING: if (limit_in_is_pressed()) // 收到位触发限位开关 { motor_stop(); current_state IDLE; } else if (motor_run_timeout()) // 超出最长时间认为机械卡死 { motor_stop(); current_state PROTECT_BLOCKED; } break; case EXTENDING: if (limit_out_is_pressed()) { motor_stop(); current_state IDLE; } else if (motor_run_timeout()) { motor_stop(); current_state PROTECT_BLOCKED; } break; default: // 保护状态只能手动恢复 break; } }提示状态机的好处是把“发现问题”和“处理问题”拆开。限位开关触发、电机超时、按键切换模式都只是把状态转移的条件而不是在传感器读取函数里到处插 GPIO 置位代码。参数说明motor_run_timeout 的计时基准不能用 HAL_Delay否则传感器读取和按键扫描会被阻塞。推荐用 SysTick 或者定时器产生一个 1ms 的 tick 变量在电机启动时清零在循环中累加并与最大运行时间比较。课设晾衣架通常最大行程 2 米电机减速比 1:48 左右运行时间 8 到 12 秒超时阈值取 15 秒这个数值可以留出安全余量且不会造成“已经卡死但仍然通电”的尴尬。4.3 行程限位、超时保护与防误动作限位开关是课设作品中经常被省略但最值得保留的硬件。没有限位开关电机到顶后仍继续转动轻则皮带打滑重则齿轮扫齿。常见的微动开关有三根引脚接 COM 和 NO按下时导通。GPIO 配置为内部上拉输入开关导通时读到低电平。注意微动开关的机械抖动同样存在在判断限位触发的代码里加 10ms 左右的去抖即可不需要像按键那么长。在状态机中加入超时保护能同时避免“传感器误触发却找不到原因”的问题。举例来说雨滴传感器被鸟粪盖住导致 DO 持续低电平晾衣架会在收回位置反复伸缩。加入超时保护后电机运行超过 15 秒没有触发限位就自动进入 PROTECT_BLOCKED 状态只有重新上电或按键复位才恢复。这个细节在答辩时稍微讲出来评委对系统的可靠性质疑会大幅下降。5. 实物联调与高分课设答辩需要的测试数据5.1 通过串口观察传感器原始值联调的第一步不是按住“自动模式”就完事而是打开装好驱动的 USB-TTL 串口连接 STM32 的 USART1把传感器原始值、系统状态和“最近一次动作原因”都通过 printf 打印出来。这样做既能验证 ADC 是否正常也能给答辩准备实测数据表。#include stdio.h int fputc(int ch, FILE *f) // 重定向 printf 到串口在 Keil 中需勾选 MicroLIB { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 10); return ch; } void debug_print_status(void) { static uint32_t last_tick 0; if (HAL_GetTick() - last_tick 500) // 500ms 打印一次避免刷屏 { last_tick HAL_GetTick(); printf(rain%d light%d state%d auto%d\r\n, rain_raw_value, light_raw_value, current_state, auto_enable); } }参数说明打印周期设为 500ms 是因为人眼在串口助手上的观察舒适区间是每秒一到三行。太快的打印会拖慢主循环而且容易让 OLED 显示和状态机响应出现偶发卡顿。printf 重定向函数必须配合 Keil 的 MicroLIB 才能工作否则会链接报错。HAL_GetTick 在默认配置下按 1ms 递增拿来确认电机运行是否超时也很方便。5.2 三个必做的边界测试测试阶段不要只试“下雨就收杆”这一条快乐路径。要刻意制造边界条件看系统会不会误动作。下表是课设联调时最值得记录的三组用例。测试场景操作方式预期行为通过标准干燥环境突然滴水用滴管在雨滴传感器上滴一滴水1 秒内触发收衣没有二次出发反转收回过程中断电收衣中途拔掉电机电源重新上电后回到 IDLE不自动伸出不烧电机、不反转光敏传感器被遮蔽用手完全遮住光敏窗口判断为夜间触发收衣连续 3 次测试结果一致第一项测试最容易踩坑一滴水落下时传感器先输出一次低电平然后水膜逐渐干涸输出又回到高电平这本来是正确的响应过程。如果代码把“恢复干燥”当成“可以伸出”就会又伸出去。要解决这个问题常见做法是状态机里再加一个稳定计时器进入 IDLE 归还后需持续无雨 3 分钟以上才允许再次伸出。这个策略模拟的是“等雨停”的常识答辩时能体现系统设计思考。5.3 答辩追问为什么不直接用光敏电阻查表评委大概率会问你光敏传感器用 ADC 读取后为什么要转换开关量而不是在代码里写一个光照查表函数映射到晾衣指数这两种做法都能跑但查表方案把传感器的非线性响应曲线误当成线性关系来处理而且不同批次光敏电阻的阻值差异很大换一个模块阈值就要重写。用开关量加阈值的方式更可靠因为判据是“天黑”还是“天亮”这个二值事件而不是精确到多少流明的照度。如果你想让作品显得更高级可以在 ADC 原始值基础上做 5 次滑动平均再用滞回比较器做阈值判断区分“正在变暗”和“暗”的区别能有效防止黄昏时分光敏值在阈值附近反复跳动。6. 提分技巧给智能晾衣架源码做一次模块化重构课设源码拿高分通常是“能跑”之外的工程规范性胜出。把散落在 main.c 里的所有逻辑拆到 sensor.c、motor.c、mode_switch.c 这几个独立模块中是一个成本很低但印象分很高的操作。常见做法是为雨滴传感器和光敏传感器声明结构体结构体统一管理 ADC 原始值、转换后的逻辑状态和滤波缓冲。主循环只剩状态机调用代码变得更像“工程实现”而不是“大作业拼盘”。typedef struct { uint16_t raw_value; // 最近一次 ADC 采样值 uint8_t filtered_level; // 滤波后的逻辑电平 uint16_t threshold; // 判定阈值可以在运行时修改 uint16_t buffer[5]; // 中值滤波缓冲 } Rain_Sensor; uint8_t read_rain_filtered(Rain_Sensor *sensor, uint16_t new_value) { uint8_t i, j; uint16_t tmp; // 把新数据放入循环缓冲覆盖最旧数据 for (i 0; i 4; i) { sensor-buffer[i] sensor-buffer[i 1]; } sensor-buffer[4] new_value; // 简单插入排序取中值样本数只有 5冒泡也足够 for (i 0; i 4; i) { for (j i 1; j 5; j) { if (sensor-buffer[j] sensor-buffer[i]) { tmp sensor-buffer[i]; sensor-buffer[i] sensor-buffer[j]; sensor-buffer[j] tmp; } } } sensor-raw_value new_value; sensor-filtered_level (sensor-buffer[2] sensor-threshold) ? 1 : 0; return sensor-filtered_level; }参数说明中值滤波取 5 个样本应对雨滴传感器被风吹动时产生的毛刺非常合适比简单均值滤波更能保留真实的阶跃响应。buffer[2] 即为排序后的中间值。threshold 字段从全局宏改成结构体成员后你就可以在串口指令里动态修改阈值不需要每次改完重新编译下载。最后一个具体的提分技巧在系统启动后延迟 10 秒再进入自动模式。这 10 秒内完成三件事等待电源电压稳定、读取一次限位开关状态、打印当前模式和传感器基准值。很多课设作品上电瞬间会抽搐一下电机就是因为 3.3V 还没稳定时 GPIO 引脚输出短暂异常而 H 桥恰好在那个瞬间收到一个错误的方向信号。加上启动延时后这类“看起来莫名其妙”的故障基本不会出现。把这段启动逻辑放在 state_machine_init 的最后一步并在代码注释里写明原因答辩时就能成为一个清晰的工程决策展示。本文还有配套的精品资源点击获取