最近又把微机原理课上的经典题目重新做了一遍用8086最小系统配合8253可编程定时器在Proteus里搭一个能输出不同频率方波的信号发生器。这个项目表面上是“产生几个不同频率的方波”实际做下来会发现它把总线时序、地址译码、I/O指令、定时器工作方式全串起来了是接口技术与计算机组成原理课程里性价比很高的一道综合设计题。我这次做的是三路输出计数器0输出1kHz方波、计数器1输出2kHz方波、计数器2输出4kHz方波8086只在初始化阶段写控制字和计数初值写完后就完全由8253硬件自动定时CPU不再参与。用Proteus仿真最大的好处是能实时看波形、改参数非常方便不用搭真实电路也能把整个读写时序理解透。如果你正在做类似的课程设计或者想搞明白8086和8253到底怎么连、地址怎么分配、初值怎么算这篇文章应该能帮你把全流程走通。1. 项目思路拆解为什么是8086加8253而不是单片机很多人在拿到这个题目时第一反应是随便用个单片机定时器就能输出几路不同频率的方波为什么非要折腾8086和8253这个问题正好是这个项目的核心价值所在。1.1 硬件定时器比软件延时靠谱用单片机做方波最常见的办法是延时翻转IO口。但延时占用CPU且频率越高误差越大中断一多波形就不稳定。8253是硬件定时器只要把计数初值写进去它自己就按照输入时钟做减法计数计数到0后自动翻转输出并重新装载初值。整个过程完全不占用CPU输出频率只取决于输入时钟频率和初值稳定、精确、可预测。对于一个“方波发生器”来说8253的工作方式3就是为这个场景设计的。所以这个题目的核心不是“怎么写出方波”而是“怎么用8086正确地把初值配置进8253”。1.2 8086在这里的角色是“配置者”而不是“发生器”8086在这个系统里做的事情非常少上电后执行一段初始化程序向8253的控制字寄存器写入工作方式再向计数器写入16位初值然后就可以进入死循环或者干别的事。8253的三个计数器会各自独立地输出方波直到复位或重新编程前不会停止。这也体现了计算机接口设计中的一个常见思想CPU负责控制外围芯片负责具体功能。用这个题目来学习8086的I/O接口编程、地址译码和时序配合比单纯背概念要直观得多。1.3 为什么选择三路不同频率做三路输出不是为了炫技而是为了验证8253三个计数器彼此独立。三个计数器共用一个输入时钟我这里统一用1MHz只要初值不同输出频率就不同。如果三个通道输出的频率都符合“时钟频率/初值”的理论值就足以证明初始化程序、地址分配和时序连接全部正确。另外如果你后面想扩展成“按键切换频率”或者“数码管显示当前频率”三路通道也能分别承担不同功能。这个题目天然适合一步步往完整仪器方向扩展。2. 硬件接线与端口地址设计把每个引脚落到实处很多教程画原理图时很随意直接画几个框连几根线但真正自己搭的时候会发现8086最小模式、地址锁存、片选译码、数据总线连接每一步都有隐藏的问题。这一节我按自己实际连接经验把每一个关键引脚说清楚。2.1 8086最小模式连接要点Proteus里用8086时先要把MN/MX引脚接高电平5V让芯片工作在最小模式。最小模式下CPU直接输出RD、WR、M/IO这些控制总线方便我们直接接8253这类I/O芯片。8086的低8位地址/数据总线是AD0-AD7这是复用引脚总线周期的第一个时钟周期输出地址之后变成数据线。所以不能直接把AD0-AD7接到8253的数据线就算完地址必须经过锁存器在ALE地址锁存允许信号的控制下锁存下来。我用了两片74LS373一片把AD0-AD7锁存为A0-A7地址线供8253的地址输入端和译码器使用AD0-AD7本身则直接连到8253的D0-D7数据线。这里有个关键点8253在WR或RD信号有效时才会锁存数据总线上的数据而ALE信号发生在WR有效之前所以即使在地址锁存阶段数据线上有地址值也不会被8253误读。时序上没有问题这是8086接口设计的标准用法。复位电路也要注意。8086是高电平复位仿真时我加了一个手动复位按钮按下后RESET引脚短暂拉高再拉低。实际仿真中也可以直接给一个持续几毫秒的高电平脉冲确保CPU完成复位。8086复位后从FFFF0H开始取第一条指令这个后面在调试部分会详细说。2.2 8253片选译码与端口地址计算8253内部有4个可访问的寄存器计数器0、计数器1、计数器2和控制字寄存器。它只有两根地址输入线A0、A1用来选择这4个寄存器。8086的数据总线是16位而8253是8位接口芯片连接时有一个容易踩的坑8位I/O接口芯片通常接在低8位数据总线AD0-AD7上而8086访问偶地址端口时才会使用低8位数据总线访问奇地址端口时使用高8位数据总线AD8-AD15。因此给8253分配端口地址时地址必须是偶地址也就是A0要保持为0。最经典的做法是把8253的A0接到8086的A18253的A1接到8086的A2这样8253的4个寄存器就占用了4个连续的偶地址。我从8086锁存出来的地址线A1、A2分别接8253的A0、A1用74LS138译码器产生片选信号。74LS138的连接方式如下输入端A、B、C分别接地址线A3、A4、A5G1接5VG2A接8086的M/IO引脚I/O访问时为低电平正好满足G2A低有效G2B接地输出端Y0接8253的CS这样设计后只有当M/IO为低电平且A5A4A3000时8253才被选中。再结合地址线A2、A1作为8253内部寄存器选择最终的端口地址见下表。寄存器8253引脚A18253引脚A08086地址线A28086地址线A1端口地址计数器000000000H计数器101010002H计数器210100004H控制字11110006H这个地址分配非常整齐写程序时直接用MOV DX, 0000H、0002H、0004H、0006H访问即可。为什么不用A0和A1直连8253的A0、A1因为如果直连端口地址就会变成0000H、0001H、0002H、0003H其中0001H、0003H是奇地址8253接在低8位数据总线上是收不到正确数据的仿真时很可能出现“波形乱跳”或“完全没有输出”的怪问题。2.3 时钟、门控和输出接线8253每个计数器都有CLK、GATE、OUT三个引脚。三个计数器可以共用一个时钟源我这里用一个时钟元件产生1MHz方波同时接到CLK0、CLK1、CLK2上。三个GATE引脚全部接5V让计数器始终处于允许计数状态。如果GATE接地计数器不会工作这是新手最容易忽略的地方。三个OUT引脚分别接示波器的A、B、C通道。用示波器观察波形最直观可以同时看到三路输出是否独立、频率是否准确。如果只是粗略验证也可以把LED接在OUT上但注意只有频率很低比如1Hz以下时LED才会明显闪烁1kHz以上的方波LED看起来就是常亮必须用示波器或逻辑分析仪观察。另外Proteus里电源和地不能漏接。8086、74LS373、74LS138、8253这些芯片都需要接VCC和GND。Proteus默认隐藏电源引脚但如果你在属性里把“Hidden Pins”显示出来就能看到VCC和GND引脚不接的话芯片是不可能正常工作的。3. 8253工作方式原理与控制字计算8253能做的事情非常多它有6种工作方式但“方波发生器”这个题目只需要用方式3。要写对控制字必须先搞清楚8253的控制字格式和计数初值到底怎么算。3.1 六种工作方式为什么只用方式38253的6种工作方式分别是方式0计数结束中断、方式1可编程单脉冲、方式2频率发生器/分频器、方式3方波发生器、方式4软件触发选通、方式5硬件触发选通。其中方式2和方式3都可以输出周期性波形区别在于方式2输出的是窄脉冲大部分时间输出高电平每N个时钟周期输出一个低电平脉冲方式3输出的则是方波高电平和低电平均有持续时间。题目要求“方波发生器”所以选方式3是最合理的。如果选方式2输出的是分频脉冲不是方波用示波器一看就知道不对。3.2 方式3的精确定时原理方式3的计数过程可以这样理解计数器每收到一个CLK下降沿就减1当减到一半时输出翻转继续减到0时再次翻转并自动重装初值形成连续的方波。如果初值N是偶数输出高电平和低电平的时间各占N/2个时钟周期如果N是奇数高电平持续(N1)/2个时钟周期低电平持续(N-1)/2个时钟周期整体上仍然是接近50%占空比的方波。所以初值N本质上是一个“分频系数”输出频率等于输入时钟频率除以N。公式为N f_CLK / f_OUT这里f_CLK是8253的工作时钟频率f_OUT是想要的输出方波频率。我用的是1MHz时钟三个计数器的目标频率分别为1kHz、2kHz、4kHz那么初值分别是计数器0N0 1000000 / 1000 1000 0x03E8计数器1N1 1000000 / 2000 500 0x01F4计数器2N2 1000000 / 4000 250 0x00FA3.3 控制字格式与初值写入顺序8253的控制字寄存器只能写不能读格式为8位位D7D6D5D4D3D2D1D0含义SC1SC0RL1RL0M2M1M0BCDSC1、SC0用来选择写哪个计数器00表示计数器001表示计数器110表示计数器211在控制字写入时没有意义。RL1、RL0表示初值读写格式00为计数器锁存01只读写低字节10只读写高字节11为先读写低字节再读写高字节。M2、M1、M0选择工作方式方式3对应的编码是011。BCD位为0时使用二进制计数为1时使用BCD码计数后者初值范围有限一般都用二进制。我三个计数器都用方式3、先低字节后高字节、二进制计数得到三个控制字计数器0SC00、RL11、M011、BCD0合成00110110B即36H计数器1SC01、RL11、M011、BCD0合成01110110B即76H计数器2SC10、RL11、M011、BCD0合成10110110B即0B6H写入初值时要注意RL11表示先写低字节再写高字节16位初值要分两次OUT指令写入同一个计数器端口。还有一个很容易犯的错8086的OUT指令只支持输出AL寄存器不能直接OUT AX。所以写16位初值时要先MOV AL低字节OUT再把初值的高字节送入AL再一次OUT。4. 汇编程序设计与实现程序逻辑很清晰写控制字到0006H然后分别向0000H、0002H、0004H写入对应初值。这里我直接给出完整可用的8086汇编代码用的是MASM/TASM风格编译成HEX后加载到Proteus的8086模型里。4.1 控制字和初值先算好我习惯在写代码前把控制字和初值列成一张表避免边写边算出错。计数器控制字初值(十进制)初值(十六进制)端口地址计数器036H100003E8H0000H计数器176H50001F4H0002H计数器20B6H25000FAH0004H控制字---0006H这样写代码时每一条OUT指令都有依据后面调试时检查也方便。4.2 初始化三个计数器的完整代码DATA SEGMENT ; 端口地址定义 PORT_C0 EQU 0000H PORT_C1 EQU 0002H PORT_C2 EQU 0004H PORT_CTRL EQU 0006H DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV AX, DATA MOV DS, AX ; ---------- 初始化计数器0方式3输出1kHz ---------- MOV DX, PORT_CTRL MOV AL, 36H ; 计数器0方式3先低后高二进制 OUT DX, AL MOV DX, PORT_C0 MOV AX, 1000 ; 初值 1MHz / 1kHz 1000 OUT DX, AL ; 写低字节 0xE8 MOV AL, AH OUT DX, AL ; 写高字节 0x03 ; ---------- 初始化计数器1方式3输出2kHz ---------- MOV DX, PORT_CTRL MOV AL, 76H ; 计数器1方式3先低后高二进制 OUT DX, AL MOV DX, PORT_C1 MOV AX, 500 ; 初值 1MHz / 2kHz 500 OUT DX, AL ; 写低字节 0xF4 MOV AL, AH OUT DX, AL ; 写高字节 0x01 ; ---------- 初始化计数器2方式3输出4kHz ---------- MOV DX, PORT_CTRL MOV AL, 0B6H ; 计数器2方式3先低后高二进制 OUT DX, AL MOV DX, PORT_C2 MOV AX, 250 ; 初值 1MHz / 4kHz 250 OUT DX, AL ; 写低字节 0xFA MOV AL, AH OUT DX, AL ; 写高字节 0x00 WAIT_LOOP: JMP WAIT_LOOP ; 初始化完成死循环等待 CODE ENDS END START程序里最容易写错的是OUT指令。很多人会顺手写“OUT DX, AX”这在8086中是不合法的。8086的I/O指令只允许累加器与端口之间传输数据而且OUT只能输出AL8位或AX16位到DX指定的端口。对于8253这种8位计数器必须分两次写低字节和高字节而且每次都从AL输出。写每条OUT指令前我都重新把DX设置为端口地址避免连续访问不同端口时端口号残留。这个习惯能减少很多莫名其妙的问题。4.3 编译成HEX文件写好的汇编代码需要编译成HEX文件才能在Proteus中加载到8086。常用的编译工具是MASM配合LINK或者直接用EMU8086。我习惯用MASM的方式命令大致是masm SOURCE.ASM; link SOURCE.OBJ;LINK生成的是EXE文件需要再用工具把可执行文件转成Intel HEX格式Proteus里加载的是HEX而不是EXE。如果你用的是EMU8086它可以直接生成HEX文件操作上更省事。编译成功后在Proteus里双击8086芯片在“Program File”属性中选择生成的HEX文件即可。这一步很多人会漏8086没加载程序仿真自然什么都不动。5. Proteus仿真全流程实操硬件连法和程序逻辑都清楚了下面就是Proteus里的完整操作流程。我按自己实际动手的顺序来写照着做基本不会卡壳。5.1 新建工程与器件选型Proteus中新建工程从元件库添加以下器件8086处理器8253或8254可编程定时器/计数器74LS373八D锁存器两片74LS138译码器时钟信号源用于产生1MHz CLK示波器OSCILLOSCOPE或逻辑分析仪电阻、按钮、电源端子等辅助元件如果你在元件搜索框里找不到“8253”可以搜“8254”8254是8253的向上兼容型号引脚和功能基本一致Proteus里8254的出现频率更高。我这次实际用的就是8254加载到仿真里没有任何问题。5.2 绘制原理图与接线要点接线的顺序建议是先接8086最小系统再接地址锁存再接译码器最后接8253数据与控制线。一步步来不容易乱。8086这边MN/MX接5V进入最小模式RESET接复位按钮CLK接时钟源这里都用1MHz8253的时钟也接同一个1MHzRD、WR分别连接到8253的RD、WRM/IO连接到74LS138的G2AALE连接到74LS373的LEAD0-AD7连接到两片74LS373的D0-D7输入以及8253的D0-D774LS373这边LE接8086的ALEOE接地让输出一直有效Q0-Q7输出锁存后的A0-A774LS138这边A、B、C分别接A3、A4、A5G1接5VG2A接8086的M/IOG2B接地Y0接8253的CS8253这边A0接8086的A1A1接8086的A2地址错位连接CS接74LS138的Y0RD接8086的RDWR接8086的WRD0-D7接8086的AD0-AD7CLK0、CLK1、CLK2接1MHz时钟源GATE0、GATE1、GATE2接5VOUT0、OUT1、OUT2接示波器的A、B、C通道接完以后先数一遍每个芯片的电源和地Proteus中隐藏电源引脚容易漏接。漏接电源的典型现象是芯片发热、引脚电平不确定或者仿真直接报错。5.3 加载程序与启动仿真双击8086在属性中找到“Program File”一栏选择编译生成的HEX文件。8086的时钟频率也可以在这里设置我设置为1MHz。频率太高的话Proteus仿真速度会明显变慢波形刷新可能跟不上。点击运行后先按一下复位按钮让8086重新初始化。接着示波器窗口应该能看到三路方波信号。如果什么都看不到不要急着改程序先查片选、时钟、复位这几项。5.4 用示波器验证三种频率打开示波器后把三路信号分别接到A、B、C通道。调整示波器的时基旋钮如果看1kHz的方波时基可以设为0.5ms/格到1ms/格这样屏幕上能看到2到3个完整周期。2kHz和4kHz的方波周期更短可以适当调小时间刻度。实测结果应该是OUT0频率约1kHz周期约1msOUT1频率约2kHz周期约0.5msOUT2频率约4kHz周期约0.25ms如果示波器显示的数字频率和你设定的频率一致说明整个系统的时序、地址、控制字都正确。如果只是想粗略看频率还可以在示波器上利用网格刻度数周期一个完整周期所占的格数乘以时基值就是周期再取倒数就是频率。6. 常见问题排查与调试心得仿真过程中我遇到的坑比预想的多这里挑几个典型的整理成排查清单。如果你照着做完没出波形多半能从这几条里找到原因。6.1 芯片找不到用8254替代8253Proteus的元件库版本不同有的里面没有8253只有一个8254。8254在硬件上兼容8253控制字格式、引脚定义、工作方式完全一致只是增加了读回状态等高级功能。直接搜“8254”替换8253原理图和程序都不用改。如果你用了8254但程序还是按8253写也一样能工作因为基本功能是兼容的。6.2 程序不运行时的三板斧我调试时遇到过8086加载HEX后完全没反应的情况排查顺序是固定的。第一看复位。8086是高电平复位上电后RESET引脚必须有一个从高到低的过程。Proteus里如果没有明确的复位按钮或复位电路CPU可能没处于正常的运行状态。加一个按钮运行后手动按一下往往就正常了。第二看HEX有没有真正加载。双击8086打开属性必须确认“Program File”指向了编译出来的HEX文件。很多人编译后忘了加载或者加载的是旧的HEX改了程序没生效。第三检查启动地址。8086复位后从FFFF0H开始取指令如果HEX文件里没有启动引导CPU可能跑飞。最简单的办法是在代码最前面用ORG伪指令把启动入口放到FFFF0H或者用编译器的启动引导机制。在仿真中也有不少人直接让代码从0000H开始也能跑这取决于Proteus版本和处理器的加载方式。稳妥的做法是检查代码入口确保一跳转就能进入START。6.3 波形异常与频率偏差修正如果示波器上有波形但频率和理论值差很多通常有三种原因。一种是时钟频率和初值不匹配。比如你实际给8253的CLK是2MHz但初值是按1MHz算的输出频率就会翻倍。先确认CLK引脚上的时钟源频率是多少再重新计算初值。另一种是初值写入顺序错了。RL位设为11之后必须先写低字节再写高字节。如果只写了低字节就结束计数值就不对如果顺序反了写入的高字节可能被当成低字节使用。还有一种是控制字写入方法不对。控制字必须写入控制字寄存器地址0006H不能写到计数器端口。反过来初值必须写入对应计数器端口不能往控制字寄存器里写初值。这两类地址一旦搞混波形基本出不来。6.4 仿真卡顿与调参技巧Proteus仿真8086这类复杂芯片时仿真速度会比单片机慢。如果出现“波形半天不动”或者“示波器刷新迟钝”的情况可以尝试降低8086的时钟频率。我的方案里CPU时钟和8253时钟都用的1MHz已经足够演示方波发生器。如果你非要让CPU跑5MHz以上Proteus的实时性会下降很多这时候观察到的波形可能带有明显的仿真延迟不要误以为是硬件有问题。另外示波器通道数量有限时可以一次只观察两路输出或者用逻辑分析仪代替示波器逻辑分析仪对数字方波的观察和测量更方便。调整完频率后建议先看计数器0的波形确认单路正常后再看三路方便定位是哪一路配置出错。最后分享一个我自己的扩展思路这个项目做完方波发生器后还能往“频率可调”方向改。例如把初值放在变量里用按键修改计数值再重新写入8253就能做成手动可调的信号发生器或者把三路输出接到喇叭上用不同频率的组合来播放简单音调。我当时做完这个仿真后又把三路输出分别接到三位数码管的位选信号上配合另一个计数器做扫描显示算是对8253多通道能力的一次深入练习。如果你正好做完方波发生器想加点功能这几个方向都可以试试。