☰
基于STM32的洗衣机控制器方案:状态机+Proteus仿真+Keil代码
2026/10/19 4:24:23 网站建设 项目流程

简介:这份资源是面向电子信息、自动化等专业学生的STM32单片机课程设计参考方案,围绕自动洗衣机控制器展开,涵盖水位选择、程序选择、启动停止及标准洗与轻柔洗两种洗衣流程的完整实现。包内共138个文件,以34个c源文件与34个h头文件为核心,配合Keil工程文件、Proteus仿真工程及编译中间文件,压缩包约2.32MB,可直接用于代码阅读与仿真验证。资源重点呈现了进水电磁阀LED模拟、六路水位开关检测、电机正反转与PWM调速等关键环节,轻柔洗转速慢、换向快,甩干转速最高,并可通过缩短各步骤时间加快演示节奏。目前已有4040人学习下载,适合需要完成同类课设、理解STM32外设驱动与Proteus联合仿真的读者参考借鉴。

1. 从一次“翻车”的课设答辩说起:这套洗衣机控制器方案到底解决什么问题

每年到了课设季,总能看到类似的场景:某同学抱着开发板上去答辩,演示到脱水环节,电机突然不转了,台下导师问了一句“你这状态机怎么跳转的”,答不上来,最后只能尴尬收场。问题往往不在代码写得多烂,而在于一开始就没把“洗衣机到底有几种工作状态、状态之间怎么切换、异常怎么处理”想清楚,直接照着例程改,改到最后自己都不知道程序在跑什么。

这套基于 STM32 的洗衣机控制器方案,要解决的就是这个问题。它用一颗 STM32 做主控,配合 Proteus 做整机仿真,在 Keil 里完成代码编译与下载调试,把波轮洗衣机的进水、洗涤、漂洗、脱水、排水这几个核心阶段完整跑通。整套东西包含源代码和仿真工程,适合正在做嵌入式课设、毕设的学生,也适合想拿一个完整状态机案例来练手的初级工程师。你拿到手之后,不用从零搭电路、不用猜电机怎么驱动,直接打开仿真就能看到洗衣机按流程走完一遍。

2. 需求分析与技术选型:为什么是 STM32 + Proteus + Keil 这套组合

2.1 功能需求拆解:一台波轮洗衣机到底要做什么

先把需求理清楚,不然后面写代码全是坑。一台最基础的波轮洗衣机控制器,功能上通常拆成这么几块:

  • 工作模式选择:标准洗、轻柔洗、快速洗,不同模式对应不同的洗涤时长和电机转速。
  • 水位检测与进水控制:通过水位传感器判断当前水位,控制进水阀开闭。
  • 电机正反转控制:洗涤和漂洗阶段需要电机周期性正转、停、反转,模拟波轮搓洗动作。
  • 脱水控制:脱水阶段电机单向高速旋转,同时排水阀打开。
  • 排水控制:每个阶段结束后排空桶内水,再进入下一阶段。
  • 按键输入:启动/暂停、模式切换、预约时间设置。
  • 显示输出:用数码管或 LCD 显示剩余时间、当前阶段、故障代码。
  • 安全保护:开盖停机、过流保护、水位异常报警。

这些功能看起来多,但落到 STM32 上,其实就是 GPIO 输出控制继电器、定时器产生 PWM 调速、ADC 或外部中断读传感器、状态机调度流程。真正难的不是单个模块,而是把它们串成一个能稳定跑完的流程。

2.2 主控选型:为什么用 STM32 而不是 51

很多人做课设第一反应是 51 单片机,便宜、资料多、上手快。但这套方案选 STM32,原因很实际:

第一,洗衣机控制器需要同时处理按键扫描、数码管动态刷新、电机 PWM 输出、传感器采样、状态机计时,51 跑起来会非常吃力,尤其动态刷新和 PWM 同时跑的时候,闪烁和抖动几乎不可避免。STM32 的定时器资源丰富,可以一个定时器做系统滴答,一个做 PWM,一个做数码管刷新,互不打架。

第二,STM32 的 GPIO 驱动能力更强,配合继电器模块可以直接控制进水阀和排水阀,不需要额外加驱动芯片。ADC 分辨率也够用,读水位传感器比 51 稳定得多。

第三,从课设答辩角度,STM32 看起来更“像样”,导师也更认可。而且这套代码结构如果写得好,后面换成其他家电控制器,改改状态机就能复用。

2.3 仿真工具链:Proteus + Keil 的配合方式

Proteus 负责画电路、跑仿真,Keil 负责写代码、编译出 hex 文件,然后把 hex 加载到 Proteus 里的 STM32 模型上运行。这个流程的好处是:不需要真实硬件,在电脑上就能看到电机转、数码管跳、继电器吸合,答辩演示风险低。

但这里有个血泪经验:Proteus 里的 STM32 模型和真实芯片有差异,尤其是时钟配置和中断响应速度。如果你在 Keil 里用了外部晶振 8MHz 倍频到 72MHz,Proteus 里可能跑不起来,或者时序完全不对。通常的做法是,仿真阶段把时钟配置成内部 HSI 或者较低频率,等仿真验证通过后,再改回真实硬件的时钟配置。

提示:Proteus 仿真通过不代表真实硬件一定能跑,反过来也一样。仿真主要验证逻辑,硬件主要验证电气特性,两者不能互相替代。

3. 总体架构设计:状态机 + 模块化驱动的分工方式

3.1 系统状态机设计:五个核心状态与跳转条件

洗衣机的核心就是一个状态机。这套方案里,我把整机流程拆成五个状态:

状态含义进入条件退出条件
IDLE待机上电复位按下启动键
FILL进水启动或漂洗开始水位到达目标值
WASH洗涤进水完成洗涤计时结束
RINSE漂洗排水完成漂洗次数达到设定值
SPIN脱水漂洗完成脱水计时结束
DRAIN排水洗涤或漂洗结束水位低于下限

状态跳转不是随便跳的,必须满足条件才能走。比如 FILL 状态里,如果水位一直不到,超过 2 分钟就跳故障状态,报警并停机。这个超时保护是课设里最容易漏掉的,但导师偏偏爱问。

3.2 模块划分:驱动层、逻辑层、界面层的职责边界

代码结构上,我一般分成三层:

  • 驱动层:GPIO 初始化、定时器配置、PWM 输出、ADC 采样、数码管扫描。这一层只负责“怎么操作硬件”,不关心业务逻辑。
  • 逻辑层:状态机调度、计时管理、模式解析、故障判断。这一层只负责“什么时候做什么”,不直接碰寄存器。
  • 界面层:按键处理、显示刷新、蜂鸣器提示。这一层负责“人怎么和机器交互”。

三层之间用接口函数通信,比如逻辑层要电机转,就调Motor_Run(speed, direction),不直接写 GPIO 寄存器。这样后面换硬件平台,只需要改驱动层,逻辑层几乎不动。

3.3 关键数据结构:状态表与定时器管理

状态机用表驱动方式实现,比一堆 if-else 清晰得多。核心结构体大概长这样:

typedef struct { uint8_t state; // 当前状态 uint8_t next_state; // 下一个状态 uint16_t timeout; // 超时时间(秒) uint16_t elapsed; // 已运行时间 void (*entry)(void); // 进入状态时的动作 void (*run)(void); // 状态运行时的动作 void (*exit)(void); // 退出状态时的动作 } StateNode; // 状态表,按顺序排列 StateNode state_table[] = { {IDLE, FILL, 0, 0, Idle_Entry, Idle_Run, Idle_Exit}, {FILL, WASH, 120, 0, Fill_Entry, Fill_Run, Fill_Exit}, {WASH, DRAIN, 600, 0, Wash_Entry, Wash_Run, Wash_Exit}, // ... 其他状态 };

每个状态有自己的超时时间,比如进水最多 120 秒,洗涤默认 600 秒。elapsed在定时器中断里累加,主循环里比较elapsed和timeout,超了就跳故障。这种写法比在 while 里塞一堆 delay 靠谱得多,也不会阻塞按键和显示。

4. 关键模块实现:从电机 PWM 到水位检测的代码落地

4.1 电机正反转与 PWM 调速:定时器配置与死区处理

波轮洗衣机的电机通常是单相电容电机,正反转靠继电器切换绕组接线。调速用可控硅或者继电器通断时间比例,但课设里为了简单,一般用 PWM 控制继电器通断,或者直接控制电机使能。

STM32 这边,我用 TIM3 的 CH1 输出 PWM,频率设在 1kHz 左右。频率太低电机会嗡嗡响,太高继电器跟不上。占空比对应转速,标准洗 70%,轻柔洗 40%。

// TIM3 PWM 初始化,用于电机调速 void Motor_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_BaseStruct; TIM_OCInitTypeDef TIM_OCStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // PB4 作为 PWM 输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); // 定时器基础配置:72MHz / 72 = 1MHz,再 /1000 = 1kHz TIM_BaseStruct.TIM_Prescaler = 72 - 1; TIM_BaseStruct.TIM_Period = 1000 - 1; TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_BaseStruct); // PWM 模式配置 TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_Pulse = 0; // 初始占空比 0 TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); } // 设置电机转速,duty 范围 0-100 void Motor_SetSpeed(uint8_t duty) { uint16_t pulse = (uint16_t)(duty * 10); // 1000 * duty / 100 TIM_SetCompare1(TIM3, pulse); }

正反转靠两个 GPIO 控制继电器,切换前必须先停电机,等 200ms 再反向,否则继电器触点会拉弧,仿真里可能没事,真实硬件上继电器很快就坏了。这个死区时间在代码里用Motor_Stop()加延时实现。

4.2 水位检测与进水阀控制:ADC 采样与阈值判断

水位传感器我用的是压力式,输出 0.5V 到 4.5V 模拟电压,对应空桶到满桶。STM32 的 ADC1 通道 0 采样,每 100ms 读一次,做 8 次平均滤波。

// 读取水位,返回 0-100 的百分比 uint8_t Water_Level_Read(void) { uint16_t adc_sum = 0; uint8_t i; for (i = 0; i < 8; i++) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); adc_sum += ADC_GetConversionValue(ADC1); } uint16_t adc_avg = adc_sum / 8; // 假设 12 位 ADC,0-4095 对应 0-100% uint8_t level = (uint8_t)(adc_avg * 100 / 4095); return level; } // 进水控制:打开进水阀,直到水位到达目标 void Fill_Control(uint8_t target_level) { GPIO_SetBits(GPIOA, GPIO_Pin_1); // 打开进水阀 while (Water_Level_Read() < target_level) { // 超时保护:120 秒还没到就报故障 if (fill_elapsed > 120) { Fault_Handler(FAULT_FILL_TIMEOUT); return; } Delay_ms(100); } GPIO_ResetBits(GPIOA, GPIO_Pin_1); // 关闭进水阀 }

这里有个细节:进水阀和排水阀不能同时打开,否则水直接流走了。代码里用互锁逻辑,开进水阀前先确保排水阀关闭。

4.3 数码管动态刷新与按键扫描:定时器中断里的时间片调度

数码管用 4 位共阴,段选接 GPIOB,位选接 GPIOC。动态刷新放在 TIM2 中断里,每 2ms 换一位,4 位下来 8ms 一轮,人眼看不到闪烁。

// TIM2 中断服务函数,2ms 一次 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 数码管刷新 Display_Scan(); // 按键扫描,每 10ms 一次 static uint8_t key_tick = 0; if (++key_tick >= 5) { key_tick = 0; Key_Scan(); } // 状态机计时,每 1s 一次 static uint16_t sec_tick = 0; if (++sec_tick >= 500) { sec_tick = 0; State_Timer_Tick(); } } }

按键扫描用状态机消抖,不用 delay。检测到按下后等 20ms 再确认,松开后等 20ms 再认为释放。这样按键响应快,也不会误触发。

5. 避坑与常见问题:仿真能跑但硬件不行的几个原因

5.1 现象:Proteus 里电机转得好好的,接上真实电机就不动

原因:Proteus 里的电机模型是理想化的,不需要驱动电流。真实电机启动电流可能是额定电流的 3-5 倍,STM32 的 GPIO 最大输出 20mA,根本推不动继电器线圈,更别说电机。

解决:继电器线圈必须加三极管驱动,电机回路加光耦隔离。代码里 GPIO 输出后,要留至少 10ms 给继电器吸合时间,再启动 PWM。

5.2 现象:数码管显示乱跳,或者某一位特别暗

原因:动态刷新频率不对,或者段选和位选冲突。常见的是刷新放在主循环里,被其他任务阻塞,导致某一位亮太久。

解决:刷新必须放在定时器中断里,优先级设高一点。段选和位选之间加 100us 消隐,防止拖影。限流电阻别省,否则某一位电流过大,亮度不均。

5.3 现象:状态机卡在某个状态出不来

原因:超时判断写在了状态入口,但计时变量没在中断里累加,或者累加了但没清零。还有一种情况是状态跳转条件用了==判断浮点数,永远不相等。

解决:计时变量统一在 1s 中断里累加,状态切换时清零。跳转条件用范围判断,比如elapsed >= timeout,不用==。每个状态入口加个调试打印,仿真时能看到跳转顺序。

5.4 现象:Keil 编译通过,Proteus 加载 hex 后没反应

原因:时钟配置不对。Keil 工程里用了外部晶振,Proteus 里的 STM32 模型默认是内部 HSI,起振失败,程序跑飞。

解决:仿真阶段把SystemInit()里的时钟配置改成 HSI 或者 8MHz 外部晶振,别用 PLL 倍频。等仿真验证完,再改回真实硬件的 72MHz 配置。

5.5 现象:进水阀和排水阀同时打开,水直接排走

原因:两个阀的控制逻辑没做互锁,状态切换时旧状态没退出,新状态就进来了。

解决:在驱动层加互锁,Valve_Inlet_Open()里先调Valve_Drain_Close(),反之亦然。状态机退出动作里强制关闭所有阀门,再进入下一个状态。

6. 验收与答辩:怎么证明这套控制器真的能跑

6.1 仿真验收:按流程走一遍,记录关键节点

验收不是打开仿真看电机转两下就完事。我一般会按这个清单走一遍:

步骤操作预期现象记录内容
1上电,不按键数码管显示 00,所有阀门关闭初始状态截图
2按启动键进水阀打开,水位逐渐上升进水时间
3水位到 50%进水阀关闭,电机开始正反转电机转向波形
4洗涤计时结束排水阀打开,水位下降排水时间
5进入脱水电机单向高速转,排水阀保持开脱水转速
6流程结束回到待机,蜂鸣器响一声总耗时

每一步都截图或者录屏,答辩的时候直接放,比口头说“能跑”有说服力得多。

6.2 代码审查:导师最爱问的几个点

导师问问题一般集中在几个地方:状态机怎么设计的、超时保护有没有、中断优先级怎么分配的、有没有考虑异常情况。你提前把这些点准备好:

  • 状态机画个状态转移图,标清楚跳转条件。
  • 超时保护每个状态都要有,时间参数写清楚。
  • 中断优先级:TIM2 刷新数码管最高,TIM3 PWM 次之,串口调试最低。
  • 异常情况:水位超时、电机过流、开盖停机,每个都要有对应的故障代码和处理流程。

6.3 答辩演示技巧:先讲架构,再跑流程,最后说坑

答辩时间通常很短,我一般这么安排:

前 2 分钟讲架构,画个框图,说清楚三层结构和状态机。中间 3 分钟跑仿真,按验收清单走一遍,重点展示状态跳转和异常保护。最后 1 分钟说踩过的坑,比如 Proteus 时钟配置、继电器死区时间、数码管消隐,这些细节导师一听就知道你真做过。

从那以后我每次做课设,都强制自己先画状态图再写代码,仿真验收清单提前列好,答辩前至少完整跑三遍。希望这套方案能帮你少走点弯路,顺利把课设交出去。

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

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

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

立即咨询