news 2026/9/19 7:04:28

微机原理课程设计:基于8086与8255的洗衣机控制系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微机原理课程设计:基于8086与8255的洗衣机控制系统

简介:一份围绕“简易全自动洗衣机”的微机原理课程设计完整文档,核心面向电气工程及其自动化、计算机等专业学生,可解决课程设计中系统方案不完整、芯片选型与接口设计缺乏参考的问题。资源包为单个Word文档,约184KB,包含课程设计内容、系统功能要求、系统方案论证、硬件设计、软件设计、总结与附录等完整章节,结构清晰。目前已有442人学习下载。文档以8051微控制器为核心,结合8255A、8254、8259A、0809等常用芯片,具体展示了数码管显示、倒计时、按键中断、水位传感、电机正反转控制及洗涤、甩干连续自动等模块的设计思路与实现方法,并附有遇到的问题及解决措施、方案对比与程序流程,能够直接作为设计报告的基础模板和功能调试的参考手册。

1. 一台洗衣机在微机原理课里到底在练什么

把洗衣机搬进微机原理课程设计,不是为了研究家电,而是把“CPU 怎么通过接口控制外部设备”这条主线压缩成 30 行状态转移表。一个全自动洗衣机的工作过程,本质上是进水、洗涤、排水、脱水这几个机械动作按时间片的轮转,而每个动作背后对应的是电机正转、反转、停止和阀门开关——这些恰恰是微处理器通过并行接口输出高低电平就能直接驱动的逻辑。做这个题目能覆盖的知识点很直接:8255 并行接口的初始化与读写时序、地址译码、按键防抖、数码管动态扫描,以及一个有限状态机如何在汇编或 C 语言里落地。对 5 年以上的人而言,这个题目的价值在于重新审视“状态机+外设时序”这种朴素组合在嵌入式里的边界,而不是把注意力放在洗衣逻辑本身——那只是换了一组输出引脚。

2. 系统方案与状态机设计:先定状态再定电路

2.1 选型理由:为什么课程设计大多用 8086+8255

微机原理与接口技术课程里,洗衣机控制最常见的教学方案基于 8086 最小系统和 8255 并行接口,原因和性能无关,纯粹是教学目的——8086 的地址线和数据线需要锁存器、译码器才能接外设,这个过程本身就是“接口技术”的核心训练。相比之下,用 8051 单片机做同样的事会省掉大量译码逻辑,直接查寄存器就能操作端口,课堂上的“地址总线—设备—I/O 空间”这条线索就弱了。

所以这道题的标准骨架是:8086 CPU 工作在最小模式,配合 74LS373 锁存地址、74LS138 译码产生片选信号,8255 作为唯一的并行接口芯片挂在系统总线上。8255 的 A 口设为输出,控制电机继电器和进水阀;B 口设为输入,读取按键;C 口可拆成高低两半,接数码管位选或状态指示灯。这个设计下,洗衣机模型不再是一个完整的电路系统,而是一个“CPU 看得见、摸得着”的外设集合,软件不直接操作硬件引脚,而是通过写 8255 端口寄存器来改变电机行为,这正是微机原理要传达的分层思想。

如果课程允许选择平台,也可以直接用 8051 的 P1/P2 口做同等功能,但要注意:那属于单片机应用设计,不是接口技术设计。两者的区别不在于最终效果,而在于“CPU 如何找到外设”这一环是否被刻意放大。做课程报告时,指导老师看重的也是这一环的完整度。

2.2 洗衣机工作状态与状态转移表

一个简易全自动洗衣机的机械动作可以压缩为六个状态:待机、进水、洗涤、排水、脱水、结束。每个状态的进入条件和退出条件都是”时间到了“或者”按键被按下“,不需要传感器参与——这非常关键,因为课程设计通常没有水位开关和温度传感器,所有判断都用定时器延时代替。

给状态编号最直接的方式是 S0 到 S5:

状态含义输入条件输出动作下一状态
S0待机/停止按下启动键所有设备关闭S1
S1进水启动后自动进入进水阀打开,电机停延时 60s 后到 S2
S2洗涤进水完成电机正转 4s→停 1s→反转 4s→停 1s循环 5 次后到 S3
S3排水洗涤结束排水阀打开,电机停延时 30s 后到 S4
S4脱水排水完成电机单向高速运转延时 20s 后到 S5
S5结束脱水完成蜂鸣器响 3s,回到 S0S0

这个表的作用在课程设计里远超流程图。它把“洗衣机”的领域逻辑和“微机”的底层控制彻底分离:状态转移表描述行为,下面所有代码都围绕这张表展开,输出动作那一列直接对应 8255 PA 口某一位的高低电平。扩展功能时也只需要往表里加行,比如增加“漂洗”状态,就是从 S3 回到 S1 而不是去 S4,表的修改成本远远小于改代码。

实际写报告时,建议再补一列“位定义”,例如 PA0 控制进水阀继电器、PA1 控制排水阀继电器、PA2 控制电机正转、PA3 控制电机反转,这一列是后续驱动程序和电路图之间的桥梁。没有这一列,代码和硬件图对不上,是课程设计里最常见也最致命的错误。

2.3 把机械流程拆成软件流程:洗涤节奏如何划分

洗涤环节的节奏拆分是软件设计的核心难点,也是这道题最容易做“假”的地方。很多人直接把正转、反转写成两条死循环,中间靠延时函数切换,看起来实现了功能,但按钮没有响应——因为 CPU 被循环占死了,根本轮不到扫描按键。

正确做法是把“一次洗涤”拆成不可再分的最小动作单元:正转 4 秒、停 1 秒、反转 4 秒、停 1 秒。这四个动作组成一个洗涤周期,每个周期完成后检查一个计数器,满 5 个周期才进入排水。整个软件结构因此变成两层:外层是状态机,决定现在是进水、洗涤还是排水;内层是策略表,决定洗涤状态下当前该做什么动作。这种拆分方式让程序每个循环的耗时可预测,即使按键扫描被插入在任意两个动作之间,也不会破坏洗衣节奏。

更精细的做法是引入“剩余时间递减”的思路:用软定时器(比如 10ms 中断或简单的轮询计数)作为全局心跳,每个状态只关心“心跳计数到没到阈值”,而不是自己在延时循环里发呆。这样启动键和暂停键在任何时刻都能被响应,因为主循环每圈都经过按键扫描,只不过状态机只有在特定状态才接受特定按键。后面第 4 章会给出这种结构的代码骨架。

3. 接口电路与驱动设计:8255 怎么接电机和按键

3.1 地址译码与 8255 端口基地址计算

8086 访问外设采用独立 I/O 空间,8255 内部有 A 口、B 口、C 口和控制字寄存器四个端口,地址通过 A1、A0 选择。常见做法是把 8255 的 A1、A0 接到系统地址总线 A1、A0 上,再用译码器产生片选信号。以 74LS138 为例,若将 G1 接高电平、G2A 和 G2B 接译码使能,且输入选择端 C、B、A 分别接入 A5、A4、A3:

  • 当 A7 到 A3 的组合为 00000 时,Y0 有效,此时 8255 被选中;
  • I/O 地址范围落在 00H~07H 区间(A2 可悬空或参与译码,A1/A0 决定内部端口);
  • 为避免和系统板卡常用地址冲突,一般会把基地址拨到 280H~28FH 这类高位地址段,即让 A9 参与译码、且高位地址线取特定组合。

端口地址的计算要同步写入手册表格:基地址 +0 是 A 口,+1 是 B 口,+2 是 C 口,+3 是控制字寄存器。日常调试时最容易犯的错是基地址算对了但控制字写错,导致输出口一直被当成输入口用,电平怎么都拉不低。建议写完译码器接线后,先用万用表量 8255 的 CS 引脚,在程序写端口时看它有没有被拉低一次,这比反复改地址快得多。

3.2 PA 口输出驱动继电器与互锁保护

8255 PA 口的驱动能力只有毫安级,不能直接驱动继电器线圈,课程设计中常见做法是加一级 NPN 三极管(如 9013)或直接使用 ULN2003 达林顿管阵列。ULN2003 的好处是内部自带续流二极管,接感性负载继电器时不需要额外再并一个二极管,这在学生焊接的板子上能减少一个常见故障点。每个继电器线圈串接在 +12V 和 ULN2003 输出端之间,输入端接 PA0~PA3,同时在三极管基极串 1kΩ 限流电阻。

比驱动电流更关键的问题是互锁。电机正转和反转如果同时接通,继电器触点可能短路电源,轻则跳闸重则烧板。软件层面当然可以通过状态机保证同一时刻只写一个位为 1,但硬件上必须再加一道保险:正转继电器和反转继电器采用双刀双掷触点,把对方的常闭触点串进自己的供电回路。这样即使软件发生异常,两个继电器在物理上也不可能同时吸合。这个设计在课程答辩时是一个明显的加分点,因为很多学生只做了软件互锁,回答不了“如果程序跑飞了怎么办”这个问题。

如果只是演示模型,用 LED 模拟电机转向也能过,但驱动的电平逻辑和继电器方案完全一致——电机正转时 A 口某位为 1,反转时另一位为 1,全 0 时停止。切换电机转向时,需要先输出全 0,再延时 100ms 以上,再输出反方向电平,给继电器触点充分的释放时间,否则会出现瞬间的机械卡顿或火花。这个 100ms 间隔应该在代码注释里写清楚。

3.3 PB 口按键输入的服务流程

B 口作为输入口接 3×2 矩阵键盘或直接接独立按键,简易设计用独立按键即可,每个按键一端接 PB0~PB1,另一端接地,PB 口内部或外部加上拉电阻。很多 8255 学习板上 PB 口已经有上拉,可以直接接按键到地。但程序初始化时要把 B 口方式设为输入,控制字写错的话,按下按键读到的永远是 0xFF 或 0x00,这种问题通常不是硬件故障而是配置字错误。

独立按键的软件处理流程很简单:主循环里周期读取 PB 口,检测到某位为 0,先延时 20ms 再读一次,确认还是 0 才算有效按键,然后转入相应状态处理函数,等待按键释放。在状态机设计里,启动键在 S0 态被按下进入 S1,暂停键在 S2、S3、S4 态被按下则冻结当前剩余时间计数。暂停功能的实现不建议用中断,直接在主循环里判断一个暂停标志位即可,因为洗衣机的实时性要求极低,毫秒级响应完全够用。

3.4 数码管动态扫描显示

显示部分至少要给出当前状态或剩余时间,常见的是一组两位或三位共阴数码管,通过 8255 C 口输出段码、经 74LS07 或 ULN2003 驱动位选。动态扫描的原理在这里值得展开:人眼视觉暂留约 20ms,所以轮流点亮每一位数码管,刷新周期控制在 4~8ms 内,看起来就是所有位同时亮的。比如三个数码管,主循环里每轮只点亮第 1 位 2ms,然后关断切到第 2 位 2ms,再切到第 3 位,循环往复。切换位选之前必须先把段码清空,否则会看到拖影,这是动态扫描最典型的显示瑕疵。

在代码实现上,显示刷新最好挂在主循环里而不是延时函数内部,否则一旦进入长延时子程序,数码管会停住不动,影响使用体验。后面第 4 章的代码把显示刷新放在了主循环最高层,任何状态处理函数返回后都会立即执行一次显示刷新,这样即使洗涤循环里做了几秒的延时,显示也不会长时间静止。状态机里的“剩余时间递减”是在延时子程序返回后才更新,所以显示数值是跳跃式的,但在课程演示中完全可接受。

4. 软件实现:状态机、防抖与延时的具体写法

4.1 主程序框架与 8255 初始化

主程序的骨架是一个死循环加一个状态分发器。初始化部分只做两件事:向 8255 控制字寄存器写入工作方式控制字,把端口方向配好;然后给全局变量赋初值。8255 的方式控制字格式是 D7=1 表示方式字有效,D6D5 为 A 组方式(00 为方式 0),D4 为 A 口方向(1 为输入),D3 为 C 口高半方向,D2 为 B 组方式,D1 为 B 口方向,D0 为 C 口低半方向。若 A 口输出、B 口输入、C 口输出,则控制字为 10000010B,即 82H。

#define PA 0x0280 /* 8255 A 口地址 */ #define PB 0x0281 /* 8255 B 口地址 */ #define PC 0x0282 /* 8255 C 口地址 */ #define CTRL 0x0283 /* 8255 控制字地址 */ void sys_init(void) { outp(CTRL, 0x82); /* A口输出,B口输入,C口输出 */ outp(PA, 0x00); /* 初始状态:电机停,阀门关 */ outp(PC, 0x00); /* 数码管全部熄灭 */ wash_round = 0; /* 洗涤周期计数清零 */ state = S0; /* 进入待机状态 */ } void main(void) { sys_init(); while (1) { key_scan(); /* 按键扫描,带防抖 */ state_dispatch(); /* 状态机主分发 */ display_refresh(); /* 数码管动态刷新 */ } }

这段代码的逻辑很直白:三个函数各司其职,主循环里按键扫描优先级最高,保证任何状态下按键都能被及时读取;状态分发根据当前状态执行对应动作;显示刷新放到最后,确保它不会阻塞前两个函数。outp是 Turbo C 时代直接操作 I/O 端口的函数,在真实编译环境中也可以用outportb,或者用内嵌汇编替代。三个地址宏定义要和 3.1 节算出的端口地址严格对应,任何一处地址写错都会导致读写到错误的寄存器,外表看起来就是“控制字发了没反应”。

4.2 按键防抖与状态读取

机械按键按下和松开时,触点会有约 5~20ms 的抖动,期间电平快速跳变。不处理抖动的话,一次按压会被判定成多次,程序可能在 S0 和 S1 之间来回跳。经典方案是“两次读数确认”:第一次读到按下后延时 20ms,再读一次;两次都是按下状态才确认。注意这个延时不能用空循环里的递减计数器直接糊弄,否则不同主频下防抖时间会相差很大,最好用一个可调用的延时函数,具体实现见 4.3。

void key_scan(void) { unsigned char key = inp(PB) & 0x03; /* 只读取 PB0、PB1 */ static unsigned char last_key = 0; static unsigned char confirmed = 0; if (key != last_key) { /* 电平发生变化 */ delay_ms(20); /* 跳过抖动区 */ key = inp(PB) & 0x03; /* 抖动后再读一次 */ if (key != last_key) { last_key = key; confirmed = 1; /* 确认是一次有效变化 */ } } if (confirmed) { if ((last_key & 0x01) == 0) { set_event(EVENT_START); /* 启动键按下 */ } else if ((last_key & 0x02) == 0) { set_event(EVENT_PAUSE); /* 暂停键按下 */ } confirmed = 0; } }

这段代码里last_key记录的是上一次确认后的稳定电平,任何一次抖动没有超过 20ms,都不会被计入状态变化。confirmed标志位确保一次按压只产生一次按键事件,而不是在电平稳定前反复触发。初学者常犯的错误是只判断按键电平为低就立即产生事件,结果启动一次洗衣机直接跳过整个洗涤流程,那基本就是防抖没做或者把事件设置放在了防抖判断之前。

4.3 延时子程序的精确控制

洗衣机的所有动作切换都依赖延时。延时子程序的写法决定了状态机的时间基准是否可靠。简易课程设计里不需要高精度时钟,但至少要保证 1 秒的延时误差在可接受范围内,否则“洗涤 5 分钟”可能变成“洗涤 3 分钟”。8086 汇编里的经典双重循环延时,内层循环次数决定毫秒级时基,外层循环进行秒级累加。下面以 8086 主频 5MHz 为例,给出一个粗略的秒级延时计算过程。

; 延时子程序 DELAY_SEC ; 入口: AX = 延时秒数 DELAY_SEC PROC PUSH CX PUSH BX MOV CX, AX ; 外层秒数计数器 SEC_LOOP: MOV BX, 5000 ; 内层毫秒循环基数, 具体值需按主频标定 MS_LOOP: NOP NOP NOP DEC BX JNZ MS_LOOP LOOP SEC_LOOP POP BX POP CX RET DELAY_SEC ENDP

参数说明:内层循环中三条 NOP 加上 DEC 和 JNZ,大约消耗 5+3+3+3+3+16=33 个时钟周期,主频 5MHz 下每个周期 0.2µs,单次内层循环约 6.6µs,BX=5000 时内层总耗时约 33ms,外层循环 30 次差不多就是 1 秒。这里的 5000 不是算出来的精确值而是标定出来的,实际调试时用示波器测某个输出引脚的周期,然后微调 BX 初值。使用 C 语言时,delay_ms函数可以同样用嵌套循环实现,但某编译器在优化等级改变后循环可能被整体优化掉,导致延时消失,解决方法是把循环变量声明为 volatile 或直接查编译器的内建延迟函数。

务必注意:延时期间 CPU 被占用,所以所有长延时都必须拆成“一小段延时就检查一次暂停标志”的结构。比如洗涤状态下正转 4 秒,不要直接调用 delay 4 秒,而是循环调用 delay_ms(50) 共 80 次,每次循环里检查暂停键。这样暂停才能被执行,否则按暂停键要等当前动作结束后才有反应,体验上就和死机了差不多。

4.4 用分支表驱动状态跳转

状态机最稳妥的实现方式是函数指针表或跳转表,C 语言里就是一个数组,数组下标是状态编号,元素是处理该状态的函数指针。这样state_dispatch里不需要写一长串 switch-case,只需要一行state_table[state](),新增状态时只扩展数组不修改分发函数。下面是洗涤状态的处理函数,展示动作拆分的粒度。

void do_wash(void) { static int step = 0; /* 当前洗涤步骤 */ static int round = 0; /* 第几个洗涤周期 */ static int tick = 0; /* 当前步骤剩余毫秒数 */ if (tick <= 0) { step = (step + 1) % 4; /* 0正转 1停止 2反转 3停止 */ tick = (step % 2 == 0) ? 4000 : 1000; /* 正反转4s, 停1s */ if (step == 0) { round++; if (round >= 5) { /* 完成5个洗涤周期 */ set_state(S3); /* 进入排水 */ return; } } } outp(PA, motor_output[step]); /* 查表: 正转/停止/反转/停止 */ tick -= 50; /* 每次分发消耗50ms */ delay_ms(50); /* 等待 */ }

motor_output是一个长度为 4 的常量数组:{0x04, 0x00, 0x08, 0x00},分别对应 PA2 置位(正转)、全零、PA3 置位(反转)、全零。状态函数内部用一个 static 的tick做倒计时,每次进入函数就减 50,小于等于 0 时切换步骤。这样做的好处是状态函数退出后主循环还能做按键扫描和显示刷新,不会因为洗涤耗时 20 秒而丢按键。set_state负责切换状态机并刷新显示缓冲区,进入新状态时应当重新初始化该状态内部的 static 变量,否则可能出现上次洗涤的剩余步骤残留,常见做法是给每个状态函数配一个独立的 reset 函数或直接把这个变量改成全局结构体。

这种结构的分发函数非常短,代码审查时一眼就能看出系统在哪个状态、每个状态消耗了多长时间。总流程用状态表表达,执行用分支表表达,整个程序的逻辑从“一堆循环嵌套”变成了“数据驱动”,这在课程设计的报告里就是一篇能讲清楚的设计。

5. 调试与验证:先仿真再上机的实践顺序

课程设计最后一周的调试效率,取决于验证顺序是否合理。我的经验是先 Proteus 仿真,后焊板子,再在硬件上做最小功能确认,每一步都有明确的测量点,不要一上来就把整套系统全跑通——预期目标应该是“在 2 小时内把任一环节的错误定位到一个芯片引脚”。

5.1 用示波器确认延时基准

程序烧进硬件后,第一个要验证的不是洗衣流程,而是时间基准。用示波器探头夹在 PA2 引脚,把状态机强制设为“点动模式”——按一次键输出 1 秒高电平、再按一次输出低电平——这样可以通过示波器读到的电平宽度直接确认延迟函数的实际值。主频 5MHz 的 8086 系统上,写了 4 秒的延时实测 5.2 秒并不罕见,因为内存访问插入的等待周期会让 CPU 实际执行速度低于标称。调整 4.3 节的循环基数,让示波器读数稳定在 4.0±0.1 秒,之后再放回去跑完整状态机。

5.2 分支表状态跳转的可观测方法

状态机跑起来后,看不到内部状态是最大的调试障碍。推荐在代码里做一个调试钩子:把状态编号回写到 C 口,接 3 个 LED。程序里任何一次set_state调用都在切换后立即更新 C 口输出,这样洗衣运行时 LED 的组合直接告诉观察者当前在哪个状态。LED 亮灭顺序和 2.2 节的状态转移表对不上,哪一步电压不对一目了然,不需猜。

5.3 动态扫描拖影和按键误触发的快速修复

如果数码管显示有拖影,优先调整位选切换时序而不是换驱动芯片。把“清段码”提前到位选切换之前,并在打开下一位之前插入 1ms 延时,绝大多数拖影问题可以解决。按键误触发则去查防抖分支的时间常数:在防抖的 20ms 延时期间,把按键电平再采样一次,和触发前的电平比较,如果中途发生过反弹,就不产生事件。这两类问题在课程设计的成品阶段最常出现,修复成本都极低,但需要在代码里留出清晰的调试钩子——比如把防抖延时和扫描间隔做成带#define的宏,改一个数就能测试不同取值的效果。

硬件上电前,用万用表把 8255 的 PA0~PA3 对地电阻测一遍,排除焊锡短路——这一点做得越早,后续调试时间越短。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 7:03:14

AXI_IIC调试实战:从寄存器映射到FIFO设计的稳定性优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 7:02:02

HID报告描述符实战解析:Usage驱动的设计方法论

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 7:01:59

AI博客创作准备:如何提供项目标题与关键词摘要

明白了&#xff0c;所有的规则我都已仔细确认并会严格执行。现在缺少的只是你的项目输入信息。请把博文对应的项目标题&#xff08;以及可选的正文、关键词、摘要描述&#xff09;发给我&#xff0c;我马上按规范开始创作。

作者头像 李华
网站建设 2026/9/19 7:01:09

前端模块化与部署优化实战指南

1. 项目概述"前端&#xff1a;第二十章-导入导出与部署"这个标题看似简单&#xff0c;实则涵盖了现代前端工程化中三个至关重要的环节。作为一名长期奋战在一线的前端开发者&#xff0c;我深知这三个环节在实际项目中的重要性。导入导出决定了代码的组织结构和模块化…

作者头像 李华
网站建设 2026/9/19 7:00:42

Flutter在OHOS上滑动卡顿丢帧与时延的定位与优化指南

你们有没有遇到过这种情况&#xff1a;同一个Flutter工程&#xff0c;跑在Android和iOS上丝滑流畅&#xff0c;一旦打包到OHOS设备上&#xff0c;滑动列表就露馅——掉帧、卡顿、跟手性变差&#xff0c;点按反馈明显慢半拍。排查半天&#xff0c;Build也没问题&#xff0c;图片…

作者头像 李华
网站建设 2026/9/19 6:59:52

晶振电路设计实战:选型、匹配电容、PCB布局与负性阻抗测试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华