资讯动态

STM32洗衣机控制器课设:状态机+Proteus仿真全流程

发布时间:2026/10/19 9:32:06 来源:尧图企业网站定制
简介这是一套面向高校单片机课程设计与嵌入式入门者的STM32自动洗衣机控制器完整方案基于STM32单片机与Proteus仿真环境搭建配合Keil工程实现水位选择、程序选择与启动停止等交互逻辑并模拟标准洗与轻柔洗两种自动洗衣流程。包内共138个文件以c与h源码、d与o编译中间文件、crf与uvprojx工程配置为主另含hex固件、asm汇编、map映射及Proteus工程文件压缩包约2.32MB结构完整便于直接编译与仿真复现。资源通过LED模拟进水电磁阀、开关模拟六档水位检测点并用PWM波控制电机正反转与转速轻柔洗换向快、甩干转速最高各步骤时间已缩短以便演示。目前已有4040人学习下载适合需要课程设计参考、仿真调试与排错思路的读者借鉴。1. 从一次答辩翻车说起为什么洗衣机控制器成了嵌入式课设的“硬骨头”去年帮几个学生看课设答辩有个做洗衣机控制器的组PPT 上功能列了一长串——进水、洗涤、漂洗、脱水、定时、报警看着挺全。结果导师问了三个问题就卡住了洗涤和漂洗的电机正反转时序怎么保证不冲突水位检测如果传感器抖动程序会不会反复启停进水阀Proteus 仿真里电机负载突变时你的 PWM 输出有没有做限幅三个人面面相觑因为代码里全是delay_ms(500)堆出来的“顺序执行”根本没有状态机的概念。这不是个例。STM32 课程设计里洗衣机控制器几乎是出现频率最高的题目之一但真正能把它做“完整”的并不多。原因很直接它同时踩中了嵌入式开发的四个核心难点——多状态流程控制、外设时序配合、中断与主循环的职责划分、仿真与实物差异。你只写一个能转的电机不难难的是让进水、洗涤、排水、脱水这几个阶段在状态机里干净地切换还要在 Proteus 里跑出可验证的波形。这套方案要解决的就是这件事用 STM32 做核心控制器在 Keil 里完成代码工程在 Proteus 里搭出完整仿真电路把洗衣机从“按键按下到电机转动”的全流程跑通。适合正在做嵌入式课设、需要交一份能演示、能答辩、能写进报告的学生也适合想拿一个完整状态机案例练手的初级工程师。下面按需求、设计、实现、避坑、验收的顺序把这份方案拆开讲。2. 需求拆解与技术选型为什么是 STM32 Proteus Keil 这套组合2.1 功能需求到底该列到什么颗粒度很多课设报告的需求分析写得像产品说明书——“具有洗涤、漂洗、脱水功能”这种颗粒度答辩时一定被追问。我一般会要求学生把需求拆到“可观测、可测试”的层级。洗衣机控制器的核心需求通常包括这几类需求类别具体条目可观测指标模式选择标准洗、快速洗、单脱水按键切换后 LED 指示对应模式水位控制低/中/高三档水位传感器信号变化后进水阀关闭电机控制正转、反转、间歇PWM 占空比与方向引脚电平时间管理各阶段倒计时数码管或 LCD 显示剩余时间安全保护脱水时盖板闭合检测盖板打开立即停机异常提示水位超时、电机堵转蜂鸣器报警 故障码显示这张表的价值在于每一行都能直接映射到一个函数或一个中断服务程序。比如“水位超时”对应一个定时器计数“盖板检测”对应一个外部中断。需求写到这个程度后面的状态机设计才有依据。2.2 主控与外设的选型逻辑STM32F103 系列几乎是这类课设的默认选择原因不复杂Cortex-M3 内核、72MHz 主频、丰富定时器和 GPIO关键是 Keil 和 Proteus 对它的支持最成熟库函数和寄存器两套写法都有大量参考。具体到洗衣机控制器需要的外设资源大致是GPIO按键输入模式、启动/暂停、水位选择、LED 指示、继电器/电机方向控制定时器 TIM一路输出 PWM 驱动电机调速一路做系统时基比如 1ms 中断用于状态机轮询和倒计时ADC如果用水位传感器模拟量输出需要一路 ADC 采集水位电压外部中断 EXTI盖板检测、紧急停止按钮USART调试阶段打印状态信息答辩时也能作为加分项Proteus 这边的选型要注意仿真库里的电机模型和实物有差异通常用MOTOR或DC MOTOR模型配合L293D或L298N驱动芯片来模拟。水位传感器可以用滑动变阻器模拟模拟量输出也可以用几个开关模拟数字量水位档位。盖板检测用一个按键接外部中断引脚即可。Keil 工程这边我建议用标准外设库StdPeriph而不是 HAL原因是课设代码量不大标准库的寄存器级操作更直观答辩时解释“为什么这个位要置 1”也更容易讲清楚。如果学校要求用 HAL逻辑完全一样只是函数名不同。2.3 仿真先行还是代码先行这是一个容易被忽略但影响效率的选择。我的习惯是先在 Proteus 里把最小系统搭出来——STM32 最小系统、电源、复位、晶振、下载口然后加一个 LED 和按键写一段最简的 GPIO 翻转代码确认仿真能跑、Keil 能生成 hex、Proteus 能加载。这一步跑通之后再往上加电机驱动、水位检测、显示模块。顺序反过来的话很容易出现“代码写了几百行仿真里连时钟都没起来”的情况。Proteus 仿真 STM32 时晶振频率、复位电路、BOOT 引脚配置都会影响程序运行先把最小系统验证一遍后面调试外设时就不会怀疑是底层问题。3. 总体设计与状态机建模把洗衣流程拆成可切换的状态3.1 状态划分与迁移条件洗衣机控制器的核心是一个有限状态机。我一般会把它拆成七个状态待机、进水、洗涤、排水、漂洗、脱水、结束。每个状态的迁移条件必须明确不能靠延时函数“等一会儿就切”。// 状态枚举定义 typedef enum { STATE_IDLE 0, // 待机等待启动 STATE_FILL, // 进水等待水位到达 STATE_WASH, // 洗涤电机正反转间歇运行 STATE_DRAIN, // 排水等待排水完成 STATE_RINSE, // 漂洗进水短时搅拌 STATE_SPIN, // 脱水电机高速单向运行 STATE_DONE // 结束蜂鸣器提示 } WashState_t; // 状态迁移条件示例进水状态 void State_Fill_Handler(void) { // 打开进水阀 GPIO_SetBits(GPIOA, GPIO_Pin_1); // 检测水位是否到达目标档位 if (WaterLevel_Get() target_level) { GPIO_ResetBits(GPIOA, GPIO_Pin_1); // 关闭进水阀 fill_timeout 0; // 清除超时计数 current_state STATE_WASH; // 迁移到洗涤 } // 超时保护进水超过30秒仍未到达水位 if (fill_timeout 30000) { GPIO_ResetBits(GPIOA, GPIO_Pin_1); Fault_Report(FAULT_FILL_TIMEOUT); // 报故障 current_state STATE_IDLE; } }这段代码的关键点在于进水阀的开关和状态迁移是绑定的不会出现“阀门还开着但状态已经切走”的情况。超时保护用了一个软件计数器在 1ms 定时中断里递增主循环里判断。这种写法比delay_ms(30000)可靠得多因为延时期间 CPU 什么都做不了按键和盖板检测都会失效。3.2 电机正反转与 PWM 调速的实现思路洗涤阶段需要电机正转几秒、停几秒、反转几秒循环若干次。脱水阶段则是单向高速运行。这两个阶段对 PWM 的要求不同我通常用同一个定时器输出 PWM通过改变占空比和方向引脚来实现。// 电机控制结构体 typedef struct { uint8_t direction; // 0:停止 1:正转 2:反转 uint16_t speed; // PWM占空比 0-1000 uint16_t run_time; // 当前方向已运行时间(ms) } MotorCtrl_t; // 洗涤阶段的电机控制逻辑在1ms中断中调用 void Motor_Wash_Tick(void) { static uint16_t phase_timer 0; phase_timer; // 每3秒切换一次方向中间停1秒 if (phase_timer 3000) { Motor_Set(MOTOR_FORWARD, 600); // 正转60%占空比 } else if (phase_timer 4000) { Motor_Set(MOTOR_STOP, 0); // 停转 } else if (phase_timer 7000) { Motor_Set(MOTOR_REVERSE, 600); // 反转 } else if (phase_timer 8000) { Motor_Set(MOTOR_STOP, 0); } else { phase_timer 0; // 循环 } }这里 PWM 占空比设为 600满值 1000对应 60% 占空比。实际仿真时Proteus 里的电机模型对占空比响应比较敏感太低可能转不起来太高又看不出调速效果。我一般建议洗涤用 50%~70%脱水用 80%~90%这样在仿真波形上能明显看到区别。3.3 定时器中断与主循环的职责划分这是状态机能不能跑稳的关键。我的划分原则是中断里只做“必须准时做”的事主循环做“可以等一等”的事。1ms 定时中断里做三件事系统时基计数、电机 PWM 相位切换、超时计数器递增。主循环里做按键扫描、状态机迁移判断、显示刷新、串口打印。这样分工的好处是即使主循环里某个函数执行时间稍长电机的正反转时序也不会乱。// 1ms定时中断服务程序 void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); sys_tick; // 系统时基 if (current_state STATE_WASH) { Motor_Wash_Tick(); // 洗涤电机相位控制 } else if (current_state STATE_SPIN) { Motor_Set(MOTOR_FORWARD, 850); // 脱水高速 } // 各阶段倒计时 if (countdown 0) countdown--; // 进水超时计数 if (current_state STATE_FILL) fill_timeout; } }注意Motor_Set函数里只操作 GPIO 和定时器比较寄存器不做任何延时或等待。这是中断服务程序的基本纪律也是很多课设代码“跑着跑着就死机”的根源。4. 关键模块实现从按键消抖到水位检测的代码落地4.1 按键扫描与模式切换课设里的按键通常用机械按键Proteus 仿真时如果不加消抖按一次会触发多次。我一般用状态机做消抖而不是delay_ms(20)因为延时消抖会阻塞主循环。// 按键消抖状态机在10ms周期任务中调用 typedef struct { uint8_t last_state; // 上次采样电平 uint8_t stable_cnt; // 稳定计数 uint8_t stable_state; // 稳定后的电平 } KeyDebounce_t; uint8_t Key_Scan(KeyDebounce_t *key, uint8_t current_level) { if (current_level ! key-last_state) { key-stable_cnt 0; // 电平变化重置计数 key-last_state current_level; } else { if (key-stable_cnt 5) { // 连续5次相同才认为稳定 key-stable_cnt; } else { if (key-stable_state ! current_level) { key-stable_state current_level; if (current_level 0) { // 按下低电平有效 return 1; // 返回按键事件 } } } } return 0; }这个函数每 10ms 调用一次连续 5 次采样一致才确认按键状态对应 50ms 消抖时间。参数stable_cnt的阈值可以根据实际按键手感调整Proteus 里通常 3~5 就够了。4.2 水位检测与 ADC 采样如果用水位传感器模拟量输出需要用 ADC 采集电压并映射到水位档位。STM32F103 的 ADC 是 12 位参考电压 3.3V所以满量程是 4095。// 水位档位定义 #define LEVEL_LOW 1000 // 低水位对应ADC值 #define LEVEL_MID 2000 // 中水位 #define LEVEL_HIGH 3000 // 高水位 uint8_t WaterLevel_Get(void) { uint16_t adc_val ADC_GetConversionValue(ADC1); if (adc_val LEVEL_LOW) return 0; // 无水 else if (adc_val LEVEL_MID) return 1; // 低水位 else if (adc_val LEVEL_HIGH) return 2; // 中水位 else return 3; // 高水位 }实际调试时ADC 读数会有波动我通常会在软件里做 8 次采样取平均或者加一个迟滞判断——比如水位从低到中需要超过 2100 才切换从中到低需要低于 1900 才切换避免在阈值附近反复跳动导致进水阀频繁开关。4.3 显示模块与状态指示课设里常用的显示方案有两种数码管和 LCD1602。数码管便宜、仿真简单但显示内容有限LCD1602 能显示字符适合展示当前状态和剩余时间。如果选 LCD1602初始化流程和写指令/写数据的时序要严格按手册来Proteus 仿真时如果时序不对屏幕会一片空白。// LCD1602显示当前状态和倒计时 void Display_Update(void) { char buf[16]; LCD_SetCursor(0, 0); switch (current_state) { case STATE_IDLE: LCD_WriteString( Ready ); break; case STATE_FILL: LCD_WriteString( Filling... ); break; case STATE_WASH: LCD_WriteString( Washing... ); break; case STATE_SPIN: LCD_WriteString( Spinning... ); break; default: LCD_WriteString( Working... ); break; } LCD_SetCursor(1, 0); sprintf(buf, Time: %3d s , countdown / 1000); LCD_WriteString(buf); }显示刷新不要放在中断里也不要在主循环里每次循环都刷我一般用一个 200ms 的软件定时标志到时间才更新一次避免 LCD 忙等待拖慢状态机响应。5. 避坑与常见问题排查仿真能跑但实物不行的几个原因5.1 现象Proteus 里电机转实物上电后电机不转原因Proteus 的电机模型是理想化的驱动电流很小实物电机启动电流可能是额定电流的 3~5 倍L293D 或 L298N 的驱动能力不够或者电源没有共地。解决检查驱动芯片的使能引脚和电源引脚确保电机电源和 STM32 电源共地。实物调试时先用小功率电机或加续流二极管仿真里则要确认驱动芯片的输入引脚电平和 STM32 输出一致。5.2 现象状态机跑着跑着卡在某个状态不动原因最常见的是某个迁移条件永远不满足比如水位检测的 ADC 值一直在阈值以下或者超时计数器没有在进入状态时清零。解决在串口里打印当前状态和关键变量观察是哪个条件没满足。我一般会在每个状态的入口处加一句printf(Enter State: %d\r\n, current_state);出口处加一句这样一眼就能看出卡在哪。5.3 现象按键按一次触发多次或者按了没反应原因消抖参数不合适或者按键扫描周期和消抖计数不匹配。Proteus 里按键抖动比实物短但仿真步长设置不当也会导致采样异常。解决先确认按键扫描的调用周期是否稳定再调整消抖计数阈值。如果用的是外部中断做按键一定要在中断里加消抖或者用定时器重新采样不能直接在中断里处理业务逻辑。5.4 现象LCD 显示乱码或空白原因初始化时序不对或者写数据前没有检查忙标志。Proteus 仿真 LCD 时如果仿真速度设置太快LCD 可能来不及响应。解决在 LCD 写操作之间加适当的延时或者严格检查忙标志位。Proteus 的仿真速度可以在菜单里调慢一点给 LCD 留出响应时间。5.5 现象Keil 编译通过Proteus 加载 hex 后没反应原因晶振频率设置不匹配、BOOT 引脚配置错误、或者 hex 文件路径不对。解决检查 Proteus 里 STM32 属性的晶振频率是否和 Keil 工程里的SystemInit一致BOOT0 接地、BOOT1 接地从 Flash 启动。hex 文件重新生成后在 Proteus 里重新加载一次。6. 验收与答辩怎么证明你的洗衣机控制器真的“能跑”验收不是把仿真跑起来就完事。导师看的是你能不能把设计逻辑讲清楚以及异常情况下系统怎么响应。我一般会让学生准备三样东西一份状态迁移表、一段关键波形截图、一个故障注入演示。状态迁移表把七个状态和迁移条件列成表格答辩时直接指着表讲比翻代码快得多。波形截图用 Proteus 的虚拟示波器抓 PWM 输出和电机方向引脚证明洗涤阶段的正反转时序和脱水阶段的高速运行确实不同。故障注入演示则是手动触发一个异常——比如拔掉水位传感器模拟线看系统是否在超时后报警并回到待机这比只演示正常流程有说服力得多。如果答辩时被问到“为什么不用 RTOS”可以回答这个项目的状态数量和实时性要求用前后台架构足够引入 RTOS 反而增加代码复杂度和调试难度。如果被问到“PWM 频率怎么选的”回答电机驱动通常用 1kHz~20kHz我选的是 10kHz避开音频范围同时保证 Proteus 仿真能看清波形。从那以后我每次带课设都要求学生先把状态迁移表画出来再写代码表画不清楚代码一定乱。希望这套方案能帮你把洗衣机控制器从“能演示”做到“能答辩”。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑