首页 / 资讯中心 / 文章详情

51单片机全自动洗衣机控制系统设计与Proteus仿真实现

51单片机全自动洗衣机控制系统设计与Proteus仿真实现 ★ FEATURED ARTICLE
1. 项目缘起与整体方案拆解1.1 为什么选择51单片机做洗衣机控制全自动洗衣机这个东西拆开来看其实就是一个进水—洗涤—排水—脱水的循环执行器核心控制逻辑并不复杂但涉及到的外设类型很全电机正反转、水位检测、阀门开关、按键输入、数码管或液晶显示。对于学习嵌入式控制的人来说它是一个非常理想的综合练习项目。那为什么偏偏选51单片机而不是STM32或者别的我个人的判断是51的硬件资源刚好卡在这个项目的需求线上。全自动洗衣机需要控制的IO口大概在15到20个之间定时器需要2到3个一个做系统时基一个做电机PWM或者正反转切换计时一个做蜂鸣器或者数码管扫描RAM需求不超过256字节ROM不超过8KB。STC89C52RC这颗芯片有32个IO口、3个定时器、8KB Flash、512字节RAM价格便宜到几块钱开发工具链成熟得不能再成熟Keil C51加一个USB转串口的下载器就能搞定全部开发。另一个现实原因是仿真。Proteus里面51单片机的模型非常稳定外围器件库也齐全像L298电机驱动、LCD1602、按键、LED、蜂鸣器这些都有现成模型。你可以在没有一块实物电路板的情况下把整个洗衣机的控制逻辑跑通、验证时序、调试状态机。这对于学生做课程设计或者刚入门的工程师练手来说试错成本几乎为零。1.2 系统功能定义与状态机设计思路在动手画原理图之前必须先想清楚这台洗衣机到底要干什么。我把它定义成四个核心状态加一个待机状态待机状态数码管显示00或者--等待用户按键操作进水状态打开进水阀实时检测水位传感器到达设定水位后关闭进水阀洗涤状态电机按照正转—停—反转的节奏循环运行持续时间由用户选择的洗涤模式决定排水状态打开排水阀检测水位降到低位后关闭排水阀脱水状态电机单向高速运转持续一段时间后停止这五个状态之间的切换条件构成了整个程序的主干。我见过很多初学者一上来就写代码结果写到一半发现状态跳转逻辑乱成一团。正确的做法是先画状态转移图把每个状态的进入条件、退出条件、执行动作全部列清楚再开始写代码。这个习惯能帮你省下大量调试时间。状态转移的核心逻辑是这样的待机状态下按下启动键进入进水状态进水到高水位后进入洗涤状态洗涤计时结束后进入排水状态排水到低水位后进入脱水状态脱水计时结束后回到待机状态。如果中途按下暂停键系统保存当前状态并停止所有输出再次按下继续键则从断点恢复。1.3 仿真方案与实物方案的差异考量仿真和实物之间有几个关键差异必须在设计阶段就考虑到第一电机模型的差异。Proteus里面的电机模型是理想化的它不会反馈堵转电流、不会产生电磁干扰、也不会因为负载变化而改变转速。实物中电机启动瞬间的电流冲击可能达到额定电流的5到7倍如果电源设计余量不够单片机会因为电压跌落而复位。所以在实物方案中电机驱动部分必须独立供电并且要在电源端加足够大的滤波电容。第二水位传感器的差异。仿真中通常用一个按键或者电位器来模拟水位信号但实物中常用的水位传感器有机械式和电子式两种。机械式靠气压推动膜片触发触点电子式靠电极间的水电阻变化来判断。两者的信号处理电路完全不同仿真时用按键模拟最简单但实物中需要考虑信号抖动和干扰问题。第三安全逻辑的差异。仿真中你不需要考虑漏电保护、过流保护、门锁检测这些东西但实物方案中这些是强制要求。我在设计时会预留这些检测接口即使仿真阶段不实现原理图上也要把位置留出来。注意仿真验证的是控制逻辑的正确性不是电路的安全性和可靠性。仿真跑通了只代表你的状态机没问题不代表你的实物方案可以直接用。2. 硬件原理图设计与核心器件选型2.1 单片机最小系统与IO分配最小系统这部分没什么好说的51单片机的经典配置12MHz晶振加两个22pF瓷片电容构成时钟电路10uF电解电容加10K电阻构成上电复位电路EA引脚接高电平使用内部程序存储器。这里唯一需要注意的是晶振频率的选择——12MHz和11.0592MHz的区别在于串口通信的波特率精度这个项目不需要串口通信所以12MHz完全够用而且12MHz的机器周期刚好是1微秒算定时器初值的时候非常方便。IO口分配我建议按照功能模块来划分不要东一个西一个否则画PCB的时候走线会很难看。我的分配方案是这样的IO口功能方向备注P1.0进水阀控制输出高电平有效经三极管驱动P1.1排水阀控制输出高电平有效经三极管驱动P1.2电机正转输出接L298的IN1P1.3电机反转输出接L298的IN2P1.4电机使能输出接L298的ENA可用PWM调速P1.5蜂鸣器输出低电平有效P2.0-P2.3数码管段选输出接74HC245驱动P2.4-P2.5数码管位选输出经三极管驱动P3.0启动/暂停键输入低电平有效外部上拉P3.1模式选择键输入低电平有效外部上拉P3.2水位高传感器输入低电平有效P3.3水位低传感器输入低电平有效这个分配方案的好处是P1口全部用于输出控制P2口用于显示P3口用于输入检测功能分区清晰编程的时候直接按口操作就行不需要频繁位寻址。2.2 电机驱动电路L298N还是继电器电机驱动是这个项目里最关键的功率部分。51单片机的IO口输出电流最大也就20mA左右驱动能力很弱直接驱动电机是不可能的必须加驱动电路。常见的选择有两种继电器方案和H桥方案。继电器方案简单粗暴两个继电器分别控制正转和反转成本低隔离性好。但缺点也很明显继电器有机械寿命限制频繁切换会缩短寿命响应速度慢一般需要5到10毫秒的吸合时间而且继电器切换时会产生反向电动势需要加续流二极管保护。H桥方案以L298N为代表它是双H桥驱动芯片可以同时驱动两个直流电机或者一个步进电机。L298N的每个桥臂可以承受2A的持续电流峰值3A工作电压范围4.5V到46V逻辑电平5V TTL兼容可以直接用51单片机的IO口控制。用它来驱动洗衣机电机余量充足。我最终选的是L298N方案理由有三个一是响应速度快可以做PWM调速二是没有机械磨损寿命长三是保护功能完善芯片内部有续流二极管外部只需要加几个滤波电容就行。L298N的接线逻辑是这样的IN1和IN2控制电机的转向IN1高IN2低时正转IN1低IN2高时反转两个都为低时电机停止自由停车两个都为高时电机制动短路制动。ENA是使能引脚接PWM信号可以调速接高电平则全速运行。实操心得L298N的使能引脚千万不要悬空悬空时输出状态不确定电机可能会轻微抖动。另外L298N的散热片一定要装即使仿真中不发热实物中持续运行10分钟以上芯片温度会明显升高。2.3 水位检测与阀门控制电路水位检测在仿真中用两个按键来模拟是最简单的方案。P3.2接高水位传感器P3.3接低水位传感器按键按下时对应引脚被拉低程序检测到低电平就认为水位到达。实物方案中如果用水位传感器常见的有两种浮球式和电极式。浮球式靠磁簧开关水位到达时浮球翻转触发开关输出开关信号可以直接接单片机IO口。电极式靠水的导电性需要设计一个比较器电路把电阻变化转换成数字信号。考虑到这个项目的定位是仿真验证我建议仿真阶段就用按键模拟原理图上标注清楚实际传感器接口的位置即可。阀门控制电路相对简单进水阀和排水阀都是电磁阀工作电压一般是12V或者24V需要用三极管或者光耦加三极管来驱动。我用的是S8050三极管加1K基极电阻的方案集电极接电磁阀线圈发射极接地线圈两端并联一个1N4007续流二极管。当P1.0输出高电平时三极管导通电磁阀得电打开输出低电平时三极管截止电磁阀失电关闭。这里有一个细节需要注意电磁阀线圈在断电瞬间会产生反向电动势如果没有续流二极管这个反向电压可能高达上百伏会击穿三极管。1N4007的反向耐压是1000V完全够用。另外基极电阻的选择也有讲究1K电阻对应基极电流大约4.3mA5V减去0.7V的BE结压降S8050的放大倍数在100左右集电极可以流过430mA的电流驱动小功率电磁阀足够了。2.4 显示与按键人机交互设计显示部分我用的是两位共阳数码管显示洗涤剩余时间或者当前状态代码。共阳数码管的段选信号是低电平有效位选信号是高电平有效。P2.0到P2.3输出段码经过74HC245缓冲后驱动数码管的a到dp段P2.4和P2.5经过S8550三极管驱动两个数码管的公共端。为什么要加74HC245因为51单片机的IO口在高电平输出时驱动能力很弱拉电流只有几十微安而数码管每个段的电流大约需要5到10mA。74HC245是一个八路双向总线收发器它可以提供足够的驱动电流同时还能起到隔离保护的作用。按键部分用三个独立按键启动/暂停键、模式选择键、复位键。按键一端接地另一端接IO口并接一个10K上拉电阻。按下时IO口被拉低程序通过检测低电平来判断按键动作。这里必须做软件消抖因为机械按键在按下和释放的瞬间会产生5到10毫秒的抖动如果不处理一次按键会被识别成多次。软件消抖的常规做法是检测到低电平后延时20毫秒再检测一次如果还是低电平就确认按键有效然后等待按键释放。这个逻辑我会在后面的代码部分详细展开。3. 软件架构与核心状态机实现3.1 定时器配置与系统时基建立整个程序的心跳是定时器0我把它配置成每50毫秒产生一次中断。为什么是50毫秒因为按键消抖需要20毫秒状态机的最小时间单位是秒50毫秒作为系统时基可以兼顾两者——每20次中断就是1秒每0.4次中断就是20毫秒实际用1次中断做消抖因为50毫秒已经大于抖动时间了。定时器0的初值计算过程是这样的晶振频率12MHz机器周期 12 / 12MHz 1微秒。定时器工作在模式116位定时器最大计数值65536。要定时50毫秒需要计数50000次所以初值 65536 - 50000 15536 0x3CB0。高8位是0x3C低8位是0xB0。TMOD 0xF0; // 清除定时器0的控制位 TMOD | 0x01; // 定时器0工作在模式1 TH0 0x3C; // 装载初值高8位 TL0 0xB0; // 装载初值低8位 ET0 1; // 使能定时器0中断 EA 1; // 使能总中断 TR0 1; // 启动定时器0中断服务程序里做三件事一是重装定时器初值二是递增系统时钟计数器三是调用按键扫描函数。注意中断服务程序要尽量短不要在里面做延时或者复杂运算否则会影响其他中断的响应。void Timer0_ISR() interrupt 1 { TH0 0x3C; TL0 0xB0; sys_tick; if(sys_tick 20) // 50ms * 20 1秒 { sys_tick 0; one_second_flag 1; } Key_Scan(); // 按键扫描放在中断里 }3.2 状态机框架与状态转移条件状态机我用switch-case结构来实现这是最直观也最容易调试的方式。每个状态对应一个case分支分支内部执行该状态的动作然后判断转移条件满足条件就修改状态变量。typedef enum { STATE_IDLE, // 待机 STATE_FILL, // 进水 STATE_WASH, // 洗涤 STATE_DRAIN, // 排水 STATE_SPIN, // 脱水 STATE_PAUSE // 暂停 } WashState; WashState current_state STATE_IDLE; WashState saved_state STATE_IDLE;状态转移的核心逻辑我写成一个函数在主循环里不断调用void State_Machine(void) { switch(current_state) { case STATE_IDLE: Motor_Stop(); Valve_Close_All(); Display_Show(0x00); if(start_key_pressed) { current_state STATE_FILL; } break; case STATE_FILL: Valve_Open_Inlet(); Display_Show(0x01); if(water_level_high) { Valve_Close_Inlet(); current_state STATE_WASH; wash_time wash_duration; // 装载洗涤时间 } break; case STATE_WASH: Wash_Motor_Run(); Display_Show(wash_time); if(one_second_flag) { one_second_flag 0; wash_time--; if(wash_time 0) { Motor_Stop(); current_state STATE_DRAIN; } } break; case STATE_DRAIN: Valve_Open_Drain(); Display_Show(0x02); if(water_level_low) { Valve_Close_Drain(); current_state STATE_SPIN; spin_time spin_duration; } break; case STATE_SPIN: Motor_Spin(); Display_Show(spin_time); if(one_second_flag) { one_second_flag 0; spin_time--; if(spin_time 0) { Motor_Stop(); current_state STATE_IDLE; } } break; case STATE_PAUSE: Motor_Stop(); Valve_Close_All(); if(start_key_pressed) { current_state saved_state; } break; } }这个框架的好处是逻辑清晰每个状态做什么、什么时候跳转一目了然。调试的时候如果发现某个状态卡住了直接在那个case里面加打印或者点灯就能定位问题。3.3 电机正反转控制与PWM调速洗涤状态下电机需要正转、停、反转循环运行这个节奏一般是正转3秒、停1秒、反转3秒、停1秒循环往复。实现方式是用一个计数器来跟踪当前处于哪个阶段。void Wash_Motor_Run(void) { static uint8_t phase 0; static uint16_t phase_timer 0; if(one_second_flag) { one_second_flag 0; phase_timer; switch(phase) { case 0: // 正转阶段 Motor_Forward(); if(phase_timer 3) { phase 1; phase_timer 0; } break; case 1: // 停止阶段 Motor_Stop(); if(phase_timer 1) { phase 2; phase_timer 0; } break; case 2: // 反转阶段 Motor_Reverse(); if(phase_timer 3) { phase 3; phase_timer 0; } break; case 3: // 停止阶段 Motor_Stop(); if(phase_timer 1) { phase 0; phase_timer 0; } break; } } }PWM调速这部分如果只是仿真验证逻辑可以不做。但如果想让仿真更接近真实洗衣机的运行效果可以用定时器1来产生PWM信号。定时器1配置成模式28位自动重装载TH1和TL1都装载同一个值这个值决定了PWM的频率。然后在中断里翻转使能引脚的电平通过改变占空比来调速。注意L298N的PWM频率建议在1kHz到10kHz之间。频率太低电机会有啸叫声频率太高L298N的开关损耗会增大。我一般用2kHz左右听感上比较安静。3.4 数码管动态扫描与显示逻辑两位数码管用动态扫描的方式驱动也就是快速轮流点亮每一位利用人眼的视觉暂留效应形成同时显示的效果。扫描频率一般在50Hz以上也就是每20毫秒完成一轮扫描人眼就感觉不到闪烁了。void Display_Scan(void) { static uint8_t digit 0; // 消隐 P2 0xFF; if(digit 0) { // 显示十位 P2 seg_code[display_value / 10]; DIGIT_TENS 1; DIGIT_UNITS 0; } else { // 显示个位 P2 seg_code[display_value % 10]; DIGIT_TENS 0; DIGIT_UNITS 1; } digit !digit; }这个函数需要每5到10毫秒调用一次我把它放在定时器中断里每两次中断调用一次也就是100毫秒一轮每位数码管点亮50毫秒。实际上50毫秒的刷新周期对应20Hz会有点闪烁更好的做法是每1毫秒切换一次但51单片机的定时器中断里做太多事情会影响实时性。折中方案是用5毫秒切换一次对应100Hz刷新率完全看不出闪烁。段码表根据共阳数码管的特性来定义0到9的段码分别是const uint8_t seg_code[] { 0xC0, // 0 0xF9, // 1 0xA4, // 2 0xB0, // 3 0x99, // 4 0x92, // 5 0x82, // 6 0xF8, // 7 0x80, // 8 0x90 // 9 };4. 仿真调试与常见问题排查4.1 Proteus仿真环境搭建要点Proteus仿真这个项目有几个设置必须注意否则你会遇到一些莫名其妙的问题。第一单片机模型的选择。Proteus里面51单片机有好几个型号我建议选AT89C52或者STC89C52这两个模型的定时器行为最接近实物。有些简化模型不支持定时器中断仿真的时候程序跑不起来你会以为是代码问题其实是模型问题。第二晶振频率的设置。双击单片机在属性里把Clock Frequency改成12MHz。这个值必须和代码里的定时器计算一致否则仿真速度会不对。我见过有人代码里按12MHz算的初值仿真里晶振设的11.0592MHz结果定时时间差了8%左右洗涤时间明显偏长。第三电源和地的连接。Proteus里面所有器件的电源和地都要显式连接不像实物电路板有铺铜层可以自动连通。L298N的VS和GND、数码管的公共端、按键的上拉电阻这些都要手动连线。漏连一个地线整个电路都不工作。第四仿真速度的调节。菜单栏的Debug里面可以设置仿真速度默认是实时仿真。如果你想快速验证长时间的逻辑比如洗涤30分钟可以把速度调快但要注意调太快可能会丢失按键响应。4.2 常见故障现象与排查思路仿真过程中遇到的问题我整理了一个速查表故障现象可能原因排查方法数码管不亮位选三极管接反检查S8550的E极是否接VCC数码管显示乱码段码表错误确认共阳/共阴类型核对段码电机不转L298N使能脚未接高测量ENA引脚电压电机只往一个方向转IN1/IN2接线错误交换IN1和IN2的连线按键无响应上拉电阻未接检查按键引脚是否有上拉状态不跳转传感器信号未触发手动触发传感器看状态变化定时时间不准晶振频率设置错误核对Clock Frequency和初值程序跑飞中断嵌套或堆栈溢出检查中断服务程序是否过长这个表格里的问题我几乎全遇到过特别是数码管乱码和电机不转这两个新手最容易卡住。数码管乱码九成是因为共阳共阴搞反了段码表要全部取反。电机不转先查使能脚再查电源最后查控制信号按这个顺序排查效率最高。4.3 从仿真到实物的移植注意事项仿真跑通之后如果你打算做实物有几个地方必须改。电源部分要重新设计。仿真里单片机、电机、电磁阀都接同一个5V电源实物中电机和电磁阀的电流很大会把电源电压拉低导致单片机复位。正确的做法是单片机用5V稳压供电电机和电磁阀用12V独立供电两路电源共地但不共正极。IO口驱动能力要重新评估。仿真里IO口可以驱动任何负载实物中51单片机的IO口拉电流只有几十微安灌电流也只有10mA左右。驱动LED要加限流电阻驱动三极管要加基极电阻驱动数码管要加缓冲芯片。保护电路要补齐。实物中电机和电磁阀都是感性负载断电瞬间会产生高压反电动势。每个感性负载两端都要并联续流二极管电机驱动芯片的电源端要加TVS管或者压敏电阻。程序要加看门狗。仿真中程序不会跑飞实物中电机启停会产生电磁干扰可能导致程序跑飞。STC89C52内部有看门狗定时器启用之后如果程序没有按时喂狗看门狗会强制复位单片机。实操心得从仿真到实物最大的坑是电源干扰。我建议在电机电源端并联一个1000uF的电解电容和一个0.1uF的瓷片电容前者吸收低频波动后者滤除高频噪声。这个组合我用了很多次效果很稳。4.4 程序优化与功能扩展方向基础功能跑通之后可以往几个方向扩展。增加洗涤模式选择。现在只有一种洗涤模式可以扩展成标准、轻柔、强力三档不同模式对应不同的洗涤时间和电机运转节奏。实现方式是用一个数组存储每种模式的参数按键切换模式时改变数组索引。增加蜂鸣器提示。按键按下时蜂鸣器短鸣一声洗涤结束时长鸣三声。这个功能很简单但能显著提升用户体验。蜂鸣器接P1.5低电平有效用定时器产生不同频率的方波就能发出不同音调。增加故障检测。比如进水超时检测——如果打开进水阀30秒后水位还没有到达高水位就判定为进水故障关闭进水阀并报警。排水超时检测同理。这个功能在实物中很有必要仿真中也可以加上来验证逻辑。增加EEPROM存储。把用户选择的洗涤模式保存到EEPROM里下次上电时自动恢复上次的设置。STC89C52内部有1KB的EEPROM用IAP方式读写不需要外接存储芯片。增加红外遥控。用NEC协议的红外接收头接P3.2的外部中断引脚实现遥控启动和暂停。这个扩展涉及到红外解码稍微复杂一些但网上有很多现成的代码可以参考。5. 关键代码模块与参数计算实录5.1 定时器初值的精确计算方法定时器初值的计算是很多初学者容易出错的地方我把完整推导过程写一遍。晶振频率12MHz51单片机的机器周期是晶振周期的12倍所以机器周期 12 / 12MHz 1微秒。这意味着定时器每经过1微秒计数一次。定时器0工作在模式1时是16位定时器计数范围从0到65535溢出时产生中断。如果要定时T微秒需要计数的次数N T / 1 T。初值 65536 - N。以50毫秒为例T 50000微秒N 50000初值 65536 - 50000 15536。把15536转换成十六进制15536 / 256 60余17660 0x3C176 0xB0。所以TH0 0x3CTL0 0xB0。验证一下从0x3CB0开始计数到0xFFFF溢出计数次数 0xFFFF - 0x3CB0 1 0xC350 50000。50000个机器周期 50000微秒 50毫秒。计算正确。如果要用11.0592MHz的晶振机器周期 12 / 11.0592MHz ≈ 1.085微秒。定时50毫秒需要计数50000 / 1.085 ≈ 46083次初值 65536 - 46083 19453 0x4BFD。所以TH0 0x4BTL0 0xFD。注意定时器中断服务程序里一定要重新装载初值因为模式1不是自动重装载的。如果忘了重装下次中断的时间就不对了。5.2 按键消抖的状态机实现按键消抖我推荐用状态机的方式比延时消抖更优雅也不会阻塞主循环。typedef enum { KEY_STATE_IDLE, // 空闲 KEY_STATE_DEBOUNCE, // 消抖确认 KEY_STATE_PRESSED, // 已按下 KEY_STATE_RELEASE // 等待释放 } KeyState; KeyState key_state KEY_STATE_IDLE; uint8_t key_pressed_flag 0; void Key_Scan(void) { static uint8_t debounce_cnt 0; switch(key_state) { case KEY_STATE_IDLE: if(KEY_PIN 0) // 检测到低电平 { key_state KEY_STATE_DEBOUNCE; debounce_cnt 0; } break; case KEY_STATE_DEBOUNCE: debounce_cnt; if(debounce_cnt 4) // 4次中断 200ms足够消抖 { if(KEY_PIN 0) { key_state KEY_STATE_PRESSED; key_pressed_flag 1; } else { key_state KEY_STATE_IDLE; } } break; case KEY_STATE_PRESSED: if(KEY_PIN 1) // 按键释放 { key_state KEY_STATE_RELEASE; } break; case KEY_STATE_RELEASE: key_state KEY_STATE_IDLE; break; } }这个状态机每50毫秒执行一次消抖确认需要4次也就是200毫秒。200毫秒对于按键来说稍微有点长但胜在稳定可靠不会误触发。如果你觉得响应太慢可以把消抖次数改成2次也就是100毫秒。5.3 水位检测的防抖动处理水位传感器在仿真中虽然用按键模拟但实物中水位信号会有波动比如水面晃动会导致传感器反复触发。所以水位检测也需要做防抖处理逻辑和按键消抖类似但确认时间要更长一些一般500毫秒左右。uint8_t Water_Level_Check(void) { static uint8_t high_cnt 0; static uint8_t low_cnt 0; if(WATER_HIGH_PIN 0) { high_cnt; if(high_cnt 10) // 10次 * 50ms 500ms { high_cnt 10; return 1; // 确认高水位 } } else { high_cnt 0; } if(WATER_LOW_PIN 0) { low_cnt; if(low_cnt 10) { low_cnt 10; return 2; // 确认低水位 } } else { low_cnt 0; } return 0; // 水位未确认 }这个函数返回0表示水位未确认返回1表示高水位确认返回2表示低水位确认。状态机里根据返回值来决定是否跳转状态。5.4 完整主程序框架与执行流程把上面所有模块串起来主程序的框架是这样的void main(void) { // 系统初始化 Timer0_Init(); GPIO_Init(); Display_Init(); // 变量初始化 current_state STATE_IDLE; display_value 0; while(1) { // 状态机执行 State_Machine(); // 显示刷新 Display_Scan(); // 水位检测 water_level Water_Level_Check(); // 其他任务 Buzzer_Control(); } }主循环里不要放延时函数所有需要定时的操作都交给定时器中断来处理。主循环只负责状态判断和逻辑执行这样能保证系统的实时性。整个程序编译出来大概占用4KB左右的Flash512字节的RAM用了不到一半STC89C52RC完全够用。如果优化一下代码去掉一些冗余逻辑可以压缩到3KB以内。6. 项目复盘与个人实操体会这个项目我从画原理图到仿真跑通前后花了大概三天时间。其中大部分时间不是在写代码而是在调试状态机的跳转逻辑和排查仿真中的各种小问题。最大的一个坑是数码管的段码表。我一开始用的是共阴数码管的段码结果仿真里显示全是乱码。后来查了半天才发现Proteus里面默认的数码管模型是共阳的段码表要全部取反。这个教训告诉我在Proteus里放器件之前一定要双击器件看清楚它的属性确认是共阳还是共阴、是高电平有效还是低电平有效。另一个坑是L298N的使能引脚。我一开始把ENA悬空了仿真里电机时转时不转我以为是我的PWM代码有问题查了很久。后来把ENA直接接高电平电机立刻就正常了。所以L298N的使能引脚要么接高电平要么接PWM信号绝对不能悬空。还有一个经验是关于状态机的。我一开始把状态跳转条件写得很复杂一个状态里判断了七八个条件结果调试的时候根本不知道程序卡在哪一步。后来我把每个状态的判断逻辑拆成独立的函数每个函数只做一件事代码立刻清晰了很多。状态机的每个状态应该只负责一件事跳转条件越简单越好。如果你也在做类似的项目我的建议是先把状态图画出来把每个状态的进入条件、退出条件、执行动作全部写清楚然后再开始写代码。画状态图花的那半个小时能帮你省下至少半天的调试时间。另外仿真跑通之后不要急着做实物先把代码里的保护逻辑补齐比如超时检测、故障报警、看门狗这些实物中这些比功能本身更重要。这个项目后续还可以扩展成带称重功能的智能洗衣机用压力传感器检测衣物重量自动调整水位和洗涤时间。也可以加一个蓝牙模块用手机APP来控制洗衣机的启动和模式选择。这些扩展方向都很有意思等有时间了我再慢慢折腾。
阅读完成 · 觉得有帮助?
咨询建站