
简介这是一份基于8086微处理器的交通灯控制系统课程设计说明书适合学习《微机原理与接口技术》、汇编语言及接口技术的本专科学生参考。文档围绕交通信号灯红、绿、黄定时切换与倒计时显示需求给出从任务分析、总体方案设计、8255A与8253等芯片选型到硬件电路搭建设计、控制程序流程图、汇编源程序编写与调试的完整流程并附设计心得与完整附录程序可直接作为课程设计模板或毕设参考。资源共1个文件为PDF格式整体大小944KB方便阅读与打印。目前已有139人学习下载。通过该设计读者可以掌握8086最小系统、I/O接口扩展、定时器应用及汇编程序调试方法理解实际交通灯系统的状态控制逻辑提升软硬件综合设计与排错能力。1. 项目概述一个经典到不能再经典的单片机课程设计做8086交通灯控制系统是绝大多数电子信息、自动化、计算机专业学生在微机原理或接口技术课程里绕不开的一道坎。说实话这个题目从70年代末8086诞生开始就出现在各类教材里一直到今天依然是实验室里的常客。为什么因为它在“麻雀虽小五脏俱全”这一点上做到了极致——一个完整的控制系统该有的要素它全都有输入、输出、定时、中断、状态转换、异常处理甚至还能扩展出紧急优先、夜间模式这种带点“智能”味的功能。用一块16位的CPU去控制几盏LED灯听起来有点大材小用但恰恰是这种“杀鸡用牛刀”的设定才让你有机会把微机原理里的每一个知识点都串起来用一遍。这类项目通常要求实现一个十字路口的红绿灯自动切换逻辑典型时序是主干道绿灯亮30秒→黄灯亮5秒→支干道绿灯亮20秒→黄灯亮5秒循环往复。硬件上需要用8086 CPU作为核心控制器配合8255A并行接口芯片输出控制信号8253定时器提供精确的计时基准再用LED灯模拟红黄绿信号灯稍微进阶一点的题目还会要求在数码管上显示倒计时秒数。整个系统单独看每一个模块都不难难的是把CPU、接口芯片、外设这三层结构正确对接起来并把时序逻辑在汇编层面跑通。这篇文章我不打算给你抄一份现成的代码了事那没意义实验室里随便找一份师兄的遗留工程都比我的完整。我想做的是把整个设计过程拆开揉碎让你搞清楚每一块电路为什么这么接、每一段程序为什么要这么写、调试时遇到怪问题该往哪个方向排查。不管你的题目是“基于8086的交通灯控制系统设计”还是“基于51单片机的智能窗帘控制系统设计”只要你想搞懂“控制器接口外设”这套底层的配合逻辑这篇东西都能用上。2. 系统整体架构与设计思路拆解2.1 为什么是808682558253这个组合先聊一个很多人会困惑的问题控制几个红绿灯用个51单片机甚至纯数字逻辑电路就能搞定为什么课程设计非要上8086答案很简单——教学目的。8086是典型的16位微处理器它的地址总线20位、数据总线16位带中断系统、DMA控制器、可编程定时器这套体系结构几乎是现代微处理器的教科书级范本。用8086做控制器你被迫去面对“怎样用一组通用I/O口驱动外部设备”这个本质问题而不是像单片机那样很多东西都给你集成好了写个寄存器就完事。具体到接口芯片的选型8255A承担的是“并行I/O口扩展”的角色。8086本身当然也有I/O端口但数量有限、驱动能力也弱直接接LED不是不行而是不优雅、也不利于你展示“可编程接口芯片”这个考点。8255A提供24个可编程I/O引脚分成A口、B口、C口三个8位端口工作方式有模式0基本输入输出、模式1选通输入输出、模式2双向总线三种做交通灯这种简单的开关量输出模式0就够了。8253定时器在这个系统里解决的是“时间基准从哪来”的问题。你当然可以用软件延时——写几个嵌套循环让CPU空转来凑时间。但软延时的致命伤是CPU被完全占用期间无法响应中断、无法处理其他任务而且延时精度取决于CPU主频换个频率就得重新调参数。8253是硬件定时器加载计数初值后自己倒计数倒到零输出一个信号或触发中断CPU该干嘛干嘛去这才是真实工业控制系统里普遍采用的做法。所以这个项目里用8253不仅是为了精确更是为了让你体会“定时器释放CPU”这个设计思想。2.2 需求分析与功能拆解从题目要求到系统规格拿到题目第一步别急着写代码先做需求拆解。一个标准的“基于8086的交通灯控制系统设计”别看只是一个PDF标题拆开来看至少包含三层功能要求。基础层是正常的红绿灯时序逻辑。假设路口是东西向主干道和南北向支干道交叉冲突方向的红绿灯必须互斥东西向绿灯亮时南北向必须是红灯同理南北向绿灯时东西向红灯。绿灯结束后要有一段黄灯过渡不能直接从绿灯跳红灯这是交通控制的常识——给正在路口的车辆一个清空时间。典型时序参数是东西向绿30秒、黄5秒南北向绿20秒、黄5秒循环周期60秒。为什么主干道时间更长因为主干道车流量大绿灯时间分配应该和车流量成正比这背后其实是个简单的统计学问题——你在一个路口多蹲一会儿数数车流就能验证。进阶层是倒计时显示。用两个数码管或四位数码管分别显示两个方向的剩余秒数让行人和司机能预判信号灯即将变化。这里就要引入“动态扫描”的概念了——四位数码管不可能同时点亮只能以极快速度轮流点亮每一位利用人眼的视觉暂留效应让人感觉四位数是同时亮的。扫描频率低于50Hz就会明显闪烁所以你的刷新延时不能太长。扩展层是特殊模式比如紧急车辆优先救护车、消防车通过时强制切换绿灯、夜间黄灯闪烁模式。这个层次的实现会用到外部中断比如按键触发INT引脚或轮询方式检测特殊输入信号然后在状态机里插入一个高优先级分支。做不做扩展层看你分数目标的定位但哪怕不做设计文档里也要写清楚扩展方案——这能明显拉高你报告的思想深度。做完整套需求拆解系统的模块划分就清晰了CPU控制核心、8253定时模块、8255输出驱动模块、LED信号灯组、数码管显示模块、按键输入模块。六个模块对应一张清楚的功能模块图我这里不画了你画图时注意标注好数据流向和控制信号方向考试和答辩都喜欢问这个。2.3 状态机交通灯控制的灵魂交通灯控制逻辑本质是一个有限状态机FSM千万别把它写成一大段顺序执行的延时翻转代码。用状态机的思维来设计逻辑会清晰得多而且后续加紧急模式会更加容易。这个系统至少包含六个状态我习惯把它们编号化管理状态编号状态含义持续时间东西向灯态南北向灯态下一状态S0东西向绿灯30s绿红S1S1东西向黄灯5s黄红S2S2东西向黄灯尾南北启动缓冲区1s红红S3S3南北向绿灯20s红绿S4S4南北向黄灯5s红黄S5S5南北向黄灯尾东西启动缓冲区1s红红S0注意我加了S2和S5这两个“全红缓冲状态”。现实中很多交叉路口在绿灯切换之间会有一段全红时间让已经进入路口的车辆完全清空时间一般12秒。这在纯理论设计中容易被忽略但在答辩时主动说出来会让老师觉得你真的思考过实际场景里的问题这就比其他学生高一个档次。每个状态的持续时间都可以由8253定时器产生的中断来计算。比如设定8253每隔1秒产生一次中断在中断服务程序里对当前状态的剩余秒数递减减到零就切换状态并重新加载倒计时初值。这种“中断状态变量”的模式其实就是如今嵌入式系统里最常用的时间片轮询框架的雏形以后你玩STM32、玩FreeRTOS时还会碰到一模一样的套路。3. 硬件设计要点与电路连接方案3.1 8086最小系统构成在实际做实验的时候你很少会真的从一片裸的8086开始搭建电路实验室通常提供现成的8086实验平台或仿真软件。但作为设计文档你依然需要讲清8086最小系统的构成8284时钟发生器给CPU提供时钟信号、8288总线控制器由它产生存储器及I/O读写信号因为8086在最大模式下不直接输出这些信号、地址锁存器74LS373锁存20位地址中的低16位或者在高8位和低8位分时复用时锁存对应地址、总线收发器74LS245增强数据总线的驱动能力。这些辅助芯片的名字你可能记不全但你要理解一个核心点8086的地址/数据总线是分时复用的所以必须靠锁存器把地址信息先“抓住”存起来否则后半个总线周期里地址线已经被数据信号覆盖了外设根本不知道你在访问谁。这就是教材里老说的“先传地址、后传数据”的总线时序理解了这个后面看8255A怎么被选中就顺理成章了。3.2 8255A端口配置与LED驱动电路8255A在系统里占用四个I/O地址A口地址、B口地址、C口地址和控制字寄存器地址。基地址由实验平台或你自己接的地址译码电路决定常见的是200H203H或280H283H。控制字要设置成方式选择控制字80H10000000B即A口、B口、C口全部工作在模式0输出状态。先说控制字为什么是80H。8255A的控制字格式是“最高位D71表示这是方式控制字D70则是对C口进行位操作的置位/复位字”。D6D5是A组方式选择00表示模式0D4是A口输入/输出选择0为输出D3是C口高四位方向0为输出D2是B组方式选择0是模式0D1是B口方向0为输出D0是C口低四位方向0也是输出。所以全输出就是10000000B也就是80H。注意8255A上电复位之后默认所有端口都是输入模式如果忘记写控制字直接往端口写数据数据根本送不出去。然后是LED驱动电路。8255A的端口引脚驱动能力很弱最大也就几毫安直接点LED虽然能亮但亮度不足且容易损伤芯片。正规做法是加驱动芯片比如74LS240/74LS244总线驱动器或ULN2803达林顿管驱动阵列灌电流方式驱动。驱动LED有两种接法灌电流方式和拉电流方式。灌电流方式LED阳极接5V电源阴极经过限流电阻接到8255A引脚。引脚输出低电平时电流从电源流过LED和电阻灌入引脚LED亮输出高电平时LED灭。这是最常用的接法因为TTL芯片的灌电流能力吸收电流比拉电流能力输出电流强得多。限流电阻取330Ω470Ω按5V电源、LED压降2V左右计算电流就是(5-2)/330≈9mA亮度合适。常见的一个低级错误是反向接法搞错结果程序里明明是“输出1亮灯”实测却是“输出1灭灯”。所以做硬件连接时最好把接口约定写死在程序注释里比如“东西向绿灯A口bit0低电平有效”避免后面写代码时逻辑混乱。3.3 8253定时器配置与计数初值计算8253有三个独立的16位计数器每个计数器有6种工作方式。在交通灯系统里最合理的使用方式是计数器0工作在方式3方波发生器或方式2频率发生器产生1秒周期的时间基准。方式3输出的是对称方波方式2输出的是低电平脉冲两者都可以用关键看你怎么接后续电路。假设实验平台的时钟频率是1MHz这个一定要查清楚不同平台差别很大有的是1.8432MHz有的是8MHz那么计数器0的计数初值就是1MHz ÷ 1Hz 1000000。但问题来了——8253是16位计数器最大只能计655351000000远远超出了范围。怎么解决这是这个项目里最经典的考点之一通常有三个方案方案一级联两个计数器。计数器0先做分频比如计1000次输出一个脉冲到计数器1的时钟输入端计数器1再计1000次最终得到1秒信号。这样每个计数器的初值都只有1000完事在16位范围内。代价是占用了两个计数器另外一个计数器的功能就没得用了。方案二把1MHz分频到10kHz。先用硬件分频器把1MHz降下来有些实验平台内部已经分了频或者用计数器0做100分频得到10kHz再让计数器1计10000次得1秒。10kHz的初值10000同样在65535以内。方案三只用一个计数器软件分频。计数器0计1000次输出周期1ms的信号然后用它中断CPU在中断服务程序里软件计数1000次凑够1秒。这个方案少占一个计数器缺点是CPU每秒要被中断1000次稍微增加了软件负担但对于1MHz主频的8086来说毫无压力。我推荐方案三因为它教会你“硬件定时软件计数”的混合思路以后写实时嵌入式代码时特别常用。但如果你为了让代码简单直接选方案一的二级级联也行答辩的时候能把级联原理讲清楚老师也认。3.4 数码管动态扫描电路与显示驱动数码管一般用共阴极或共阳极两种。动态扫描法的本质是“分时复用”四位数的段选线a~g、dp并联在一起接到8255A的B口每一位的位选线公共端分别由8255A的A口引脚控制注意这里不要混淆口的功能分配最好在原理图上标注清晰。显示时先往B口发送第一位要显示的字形码然后让第一位的位选线有效共阴极给高电平或共阳极给低电平看你电路维持12ms然后关掉第一位再送第二位的字形码选中第二位……以此类推循环扫描。每一位的刷新频率要保持在50Hz以上也就是完整扫描四位的周期要在20ms以内这样人眼看到的才是稳定的数字而不是闪烁的残影。字形码是另一个新手重灾区。共阴极数码管要显示数字“0”需要点亮a、b、c、d、e、f六段g和dp熄灭——段码是00111111B也就是3FH。注意显示“6”是7DH不是某个看着像的编码最好写一个查表程序把0~9的字形码提前存好而不是在程序里临时计算。这种查表思想在单片机编程里太常见了交通灯虽然不是啥高级项目但足够你练这一手。4. 软件系统的核心逻辑与汇编实现4.1 主程序流程与控制字初始化顺序主程序的初始化顺序是有讲究的乱写容易出诡异问题。我的建议是这样的固定套路按序执行屏蔽中断CLI指令初始化期间不响应任何外部中断防止状态混乱。8255A初始化把方式控制字80H写入控制字寄存器完成三个端口全部模式0输出的设定。数字字形码表装载将0~9的字形码常量写入数据段或代码段查表。8253初始化设置计数器0的工作方式写入控制字方式2是34H方式3是36H注意别忘了先写低字节再写高字节然后写入计数初值。设置中断向量如果使用8259A中断控制器或直接使用8086的INTR引脚接收定时脉冲需要把中断服务程序的入口地址填入中断向量表。开启中断STI指令一切就绪后再响应中断。进入主循环主循环里要么是空转等待中断要么是执行数码管动态扫描等周期性任务。这里特别提醒一个细节8253在写入计数初值时是“双字节操作”要先写低8位再写高8位。因为8253内部的初值寄存器是16位的但数据线只有8条分两次送进去。如果你只送了一个字节计数器加载的是不完整的数据定时周期会完全不对。这种错误在实验台上真的能让人调一下午属于“写代码时不觉得有问题、上机时懵一脸”的典型。4.2 中断服务程序设计状态切换与秒数递减既然用了8253定时器产生1秒中断那么中断服务程序ISR就是整个交通灯控制逻辑的核心引擎。ISR里要做的事情大概是保护现场PUSH AX、PUSH DX等凡是ISR里用到的寄存器都要先压栈保护这是汇编写中断程序的基本素养漏了会导致主程序数据被破坏从内存单元读取当前状态编号和当前状态的剩余秒数剩余秒数减1写回内存判断剩余秒数是否为0不为0就直接恢复现场、返回为0则切换到下一状态查状态表得到新的灯态组合和持续时间并把灯态输出到8255A端口更新数码管显示缓冲区把当前状态剩余秒数换算成两个BCD码或字形码填入显示缓冲区恢复现场执行IRET返回用文字描述觉得还好但初学者反复出的一个问题是状态切换时忘记了同时更新灯态输出。你光把状态编号改成了S1但8255A端口上还保持着S0的灯态那绿灯就永远亮下去了。所以状态切换和输出更新必须绑定在一个逻辑里我习惯直接用一个“状态转移输出映射”的查表结构而不是在程序里到处写判断分支。伪代码思路如下ISR: 保护现场 剩余秒数DX减1 若DX ! 0跳转到update_display 当前状态S加1若S5则S0 查状态表得到 当前状态的持续秒数 → DX 东西向灯态输出值 → AL_HIGH 南北向灯态输出值 → AL_LOW 组合成8位输出值 → 写入8255A端口A update_display: 将DX转换成两个十进制数字 → 写入显示缓冲区 恢复现场 IRET4.3 数码管动态扫描与主循环调度主循环的核心任务就是刷新数码管。因为中断服务程序只负责更新显示缓冲区里的字形码真正的显示输出动作放在主循环里做每次循环依次选中第一位、发送字形码、延时1~2ms、关闭第一位、选中第二位……如此往复。这样设计的好处是显示刷新不会干扰中断的实时性中断服务程序不会被冗长的显示扫描拖慢。扫描延时的实现又有讲究。用软件延时空循环最简单但要注意CPU频率——如果在8086实验平台上直接用1ms空循环还是10ms空循环需要根据主频换算循环次数。如果你刚好知道主频是5MHz那么1条空指令LOOP需要5个时钟周期即1μs想延时1ms就需要循环1000次。严谨的做法是写一个带参数的延时子程序输入循环次数避免在不同主频的实验台上反复试错。另一个问题是主循环里不要放HALT指令让CPU彻底停住因为数码管需要持续刷新才能维持显示。有人会问那我不显示数码管时主循环空转不也一样吗对空转就行只要循环体在跑动态扫描就在持续。可以顺便在主循环里轮询一下按键如果有急车优先的扩展功能检测到按键就跳到紧急处理逻辑。这种“主循环后台任务中断实时任务”的架构放到今天做RTOS程序依然是同样的套路。4.4 扩展功能的实现思路紧急优先与夜间模式如果你想把分数从“良”拉到“优”强烈建议做一两个扩展功能。最实用的是紧急车辆优先模式用一个按键模拟紧急信号外部中断8086的INTR引脚或8259A的某个IRQ触发后无论当前处于什么状态在状态表里插入一个紧急状态——四个方向全部红灯保持2秒然后紧急车辆所在方向比如东西向绿灯放行15秒之后再恢复正常状态机。实现要点是紧急状态本身也要用定时器管理不能死死卡在紧急状态里出不来。你可以设置一个标志位“紧急状态标志”中断服务程序查询这个标志若置位则走紧急状态的状态转移流程结束后自动恢复原来的状态编号所以进入紧急模式开局要把当前状态编号保存到另一个内存单元返回值再恢复。夜间模式实现更容易检测到一个开关输入或者大半夜通过按键切换进入黄灯闪烁模式——两个方向的黄灯同时以1Hz频率闪烁亮1秒、灭1秒。本质上就是状态机只有两个状态黄灯亮、黄灯灭各持续1秒。不需要数码管显示逻辑极其简单但写出来效果很好看演示的时候特别唬人。而且这个模式在现实交通控制中是真实存在的不是纸上谈兵答辩时会有额外加分。5. 系统调试与常见故障排查实录5.1 硬件层面的故障定位我做这类项目踩过的坑相当一部分来自硬件连接而非代码逻辑。灯全不亮优先检查8255A有没有被正确初始化。用示波器或万用表测8255A的CS片选引脚看有没有被正确选中的低电平信号再测一下A口的各引脚在程序运行后是高电平还是低电平。如果CS一直高说明地址译码电路有问题——访问的端口地址和译码电路设定的地址不一致。这种情况在实验平台上最常见的诱因是跳线帽插错位置地址拨码开关没拨对。灯亮度明显偏暗大概率是限流电阻选大了或者用了拉电流方式但8255A的输出驱动能力不够。换成灌电流接法同时把电阻降到330Ω基本能解决。数码管某一位不亮或一直亮先把动态扫描的问题放一边直接用万用表测对应位选线是否有正确的扫描信号。如果位选线没有周期性波形查主循环里选中位的逻辑如果有波形但LED不亮查公共端共阴/共阳配置是否和程序匹配。上电瞬间LED乱闪一下然后恢复这个是正常的。8086上电复位期间8255A的端口状态不确定输出可能是随机的等初始化程序跑完后才正常。如果你想让这一秒都不闪可以把LED电源用一个RC延时电路控制或加一个上电清零电路但一般来说没人计较这个文档里提一嘴即可。5.2 时序与逻辑层的经典问题红灯绿灯同时亮这个不用多想十有八九是状态表里的输出编码写错了两个方向的灯态组合没有保证互斥。排查方法是逐状态检查用手动单步实验平台的单步执行功能跑一遍状态转移每一步看端口输出值是否和设计一致。口诀就一句“东西亮绿则南北必红两方向不许同时绿/黄同亮”。切换时出现一瞬全灭有人为了省事在换状态时先清空整个端口再写新状态结果清空那一下灯全灭了。虽然只有几十微秒人眼可能看不出来但如果用示波器就能看到这个毛刺。解决办法是不要“先清后写”直接计算新的端口输出值一次性写入即可。黄灯时间不准黄灯应该持续5秒但实测只有4秒或者6秒。查8253的计数初值是不是算错了。最常见的一个坑是初值用十进制1000000直接写但没换算成16位十六进制——别笑我就见过有人直接把1000000塞进寄存器结果溢出后定时器行为完全不可控。正确做法如果时钟是1MHz用级联方案让计数器0计50000C350H、计数器1计200014H这样每秒产生一次中断初值好记、可调性也好。数码管数字严重闪烁扫描周期太长了。我实测的经验值是四位扫描总周期不要超过10ms也就是每一位点亮时间2ms左右。低于这个刷新率人眼能明显看出数字在跳。有时候不是延时参数的问题而是你在扫描循环里插入了太耗时的其他操作比如乘除法运算拖慢了整个循环的节奏。5.3 软硬件联调的排查顺序表为了让你调试时不至于东一榔头西一棒子我把排查顺序整理成一张表按这个顺序过一遍绝大多数问题都能暴露出来排查顺序检查目标方法典型结论1电源与地线万用表测各芯片电源引脚芯片没供电一切白搭2时钟信号示波器测8284或晶振输出无时钟则CPU不工作3地址译码测8255A的CS引脚CS不跳变说明地址没选通4控制字写入单步执行初始化程序测端口电平控制字没生效则端口方向错误5灯态输出手动置位端口看LED反应区分硬件连接错还是程序逻辑错6定时器中断让ISR里只做端口翻转看周期初值错则翻转周期不对7状态切换单步走状态表状态不对查查状态转移条件8数码管扫描只显示固定数字不显示则查字形码或位选第5步特别值得展开说说。“手动置位端口”是调试硬件和软件分歧点最有效的手段写一段最简单的测试程序直接往8255A的A口写0xAA如果LED呈现“亮灭亮灭亮灭亮灭”的交替状态说明硬件链路是通的问题肯定出在你的正式程序逻辑里如果LED没有对应亮灭那就要回头查硬件。这段小程序我建议你留在工程里不要删调试时反复用哪天改电路了也能快速验明链路通断。5.4 两个压箱底的调试技巧第一个技巧善用单步执行观察全局变量。很多实验平台支持汇编级单步执行。遇到状态切换不对的问题不要跑全速然后肉眼猜用单步逐条看指令执行情况同时观察内存单元里的当前状态编号、剩余秒数这些变量的变化。单步几次之后你基本就能锁定问题是在状态判决、初值加载还是输出映射环节。第二个技巧给关键操作加“调试LED”。如果你的实验板有多余的LED或引脚用它来指示程序运行到哪个分支了。比如进入紧急模式就点亮一个额外LED退出时熄灭。这种方式在嵌入式调试里被称为“GPIO调试法”现在的嵌入式工程师调试Linux驱动也还这么干——别看低级是真的好用。加上一个调试灯能帮你快速判断到底是中断没进去、还是中断进去了但状态没变、还是状态变了但输出没更新。6. 写在最后的一点个人体会做8086交通灯这个项目的过程对绝大多数人来说其实不是从零开始写代码——实验室里、网上、还有各种教材配套资料里完整的参考程序和电路图实在太多了想抄是很容易的。但我的建议是哪怕你有现成的参考也一定要自己动手把原理搞透控制字为什么这么写、时序为什么这么编排、接口为什么要加驱动芯片、定时初值是怎么算出来的这些才是设计报告里真正值钱的部分也是答辩老师最喜欢追问的点。我个人在做这个项目的过程中收获最大的一个认知是“控制器接口外设”三层架构的解耦思想。CPU不需要直接管理LED怎么亮、数码管怎么扫它只需要通过接口芯片下发控制字和输出数据剩下的事情由接口芯片和外围电路去完成。这个思想后来我做51单片机、做STM32甚至写上位机程序时都在用——高层逻辑和底层实现分离模块之间只通过定义良好的接口交互系统的可维护性和可扩展性会好出好几个量级。最后再分享一个小技巧装配和调试时每完成一个功能模块就立即写一小段测试代码验证比如初始化完8255A就写一个亮灯测试初始化完8253就写一个中断周期测试功能确认无误后再叠加下一个模块。一次把所有代码写完再上机调试出问题了你根本不知道是哪个环节的锅一次只验证一个模块出问题五分钟内就能定位。这个“小步快跑、随时验证”的习惯比任何代码技巧都值钱以后你写任何规模的程序都用得上。本文还有配套的精品资源点击获取