简介:这份资源围绕8253定时器、8259A中断控制器与8255可编程并行接口的协同使用,给出一个完整的中断实验方案,面向学习微机原理、接口技术及汇编语言的中高年级学生与自学者,帮助理解中断请求产生、中断响应与外部设备控制的完整链路。压缩包内为1个docx文档,约14KB,集中呈现实验说明与关键代码,便于对照阅读与复现。已有1264人学习下载,具备一定的参考基础。文档中给出了数据段、堆栈段与代码段的组织方式,包含中断向量表设置、中断屏蔽寄存器配置、8253通道初始化、8255输出控制以及中断服务程序INTPROC的完整逻辑,并说明如何通过COUNT变量控制中断次数、逐次点亮发光二极管直至程序返回DOS。读者可据此掌握三个芯片的初始化参数配置、中断服务流程与屏蔽恢复处理,形成可运行的实验参考。
1. 中断实验:为什么一条 IRET 能让整个系统“活”过来
很多人第一次看 8253、8259A、8255 联调的中断实验,注意力都放在“灯亮了没有”上,结果程序跑起来要么只中断一次就卡死,要么灯全亮完却不返回 DOS。真正反直觉的地方在于:中断能不能稳定跑完八次,不取决于中断服务程序写得多漂亮,而取决于你有没有在正确的位置关中断、发 EOI、恢复屏蔽字。这套机制解决的是“外设如何在不打扰主程序节奏的前提下,按自己的时间尺度插入执行”的问题。搞懂它,你才算真正理解 CPU 与外设之间那条看不见的握手链路,也才能看懂为什么 8259A 的屏蔽寄存器、8253 的计数初值、8255 的方式字三者必须按顺序初始化,少一步就是玄学翻车。
2. 中断链路拆解:8253 的脉冲是怎么变成 CPU 的一条指令的
2.1 从定时器溢出到 INTR 拉高:8253 在链路里到底扮演什么角色
这个实验里 8253 不是“计时给人看”的,它是整条中断链路的信号源。8253 内部有三个独立的 16 位递减计数器,每个通道有六种工作方式,实验里通道 0 和通道 1 都被写入初值 1000,控制字分别是00110111B和01110101B。这两个字节不是随便填的,拆开看才有意义:低两位选通道,中间三位选工作方式,高两位选读写格式。
; 8253 初始化:先写控制字,再写计数初值 MOV DX, IOPORT+283H ; 控制字寄存器端口 MOV AL, 00110111B ; 通道0,先低后高读写,方式3,二进制 OUT DX, AL MOV AL, 01110101B ; 通道1,先低后高读写,方式2,二进制 OUT DX, AL MOV DX, IOPORT+280H ; 通道0 数据端口 MOV AL, 00H ; 初值低字节 OUT DX, AL MOV AL, 10H ; 初值高字节,合起来 1000H OUT DX, AL00110111B里 bit7-6 为00表示通道 0,bit5-4 为11表示先读写低字节再读写高字节,bit3-1 为011表示方式 3(方波发生器),bit0 为0表示二进制计数。方式 3 的特点是计数到零后自动重装初值,输出连续方波,方波的下降沿正好可以作为 8259A 的 IRQ 输入触发信号。通道 1 用01110101B,方式 2 是频率发生器,输出负脉冲,常用来做级联或分频。两个通道初值都设 1000H,意味着计数 4096 次后翻转一次,配合外部时钟就能得到实验要求的“每秒一次”节奏。
这里有个容易被忽略的细节:8253 的控制字必须先写,再写初值。如果顺序反了,计数器会按上一次残留的方式字解释你写入的数据,现象就是中断周期完全不对,甚至一次都不来。常见的实现思路是初始化函数里严格按“控制字 → 低字节 → 高字节”三段式写,中间不插入其他端口操作。
2.2 8259A 的屏蔽与 EOI:为什么 CLI 和 STI 的位置决定成败
8259A 是可编程中断控制器,它把 8 路(级联后 15 路)中断请求排优先级后送给 CPU。实验里主片端口20H/21H,从片端口A0H/A1H,代码里对21H写AND AL,0FBH,对A1H写AND AL,0F7H,这是在清中断屏蔽寄存器(IMR)的对应位,把 8253 所在的那一路放行。
CLI ; 关中断,改向量表和屏蔽字期间不允许被打断 IN AL, 21H ; 读主片 IMR AND AL, 0FBH ; 清 bit2,放行 IRQ2(级联从片的那一路) OUT 21H, AL IN AL, 0A1H ; 读从片 IMR AND AL, 0F7H ; 清 bit3,放行从片 IRQ3 OUT 0A1H, AL STI ; 屏蔽字改完再开中断0FBH是11111011B,清的是 bit2;0F7H是11110111B,清的是 bit3。这两个位对应主片级联口和从片的具体中断源。为什么必须先CLI再改?因为 IMR 是 8259A 内部寄存器,读写不是原子操作,如果改到一半来了中断,CPU 可能按半新半旧的屏蔽字响应,行为不可预测。这就是血泪经验:凡是动中断向量表、动 IMR、动 8253 控制字,前后都要用CLI/STI夹住。
中断服务程序末尾的MOV AL,20H加OUT 20H,AL是发 EOI(中断结束命令),告诉 8259A 当前中断处理完了,可以把优先级状态复位。如果漏了这条,8259A 会认为该中断仍在服务中,同级和低级中断永远被挡住,现象就是“只中断一次,后面全哑”。从片的中断还要额外向A0H发一次 EOI,实验代码只对主片发了20H,这在单从片场景下能跑通,但严格来说从片也需要结束命令,属于可以深挖的取舍点。
2.3 8255 方式字与端口映射:灯为什么会“一个一个”亮
8255 负责把 CPU 的数据变成 LED 的亮灭。实验里控制字10001111B写到IOPORT+28BH,拆开是:bit7 为 1 表示方式设置有效,bit6-5 为00表示 A 组方式 0,bit4 为 0 表示端口 A 输出,bit3 为 0 表示端口 C 高四位输出,bit2 为 0 表示 B 组方式 0,bit1 为 0 表示端口 B 输出,bit0 为 0 表示端口 C 低四位输出。整体就是“三个端口全输出、全方式 0”的最简配置。
MOV DX, IOPORT+28BH ; 8255 控制口 MOV AL, 10001111B ; 方式0,A/B/C 全部输出 OUT DX, AL ; 中断服务程序里更新 LED 数据 MOV DX, IOPORT+288H ; 8255 端口 A MOV AL, LIGHT ; 初值 80H,即最高位亮 OUT DX, AL MOV CL, 01H SAR AL, CL ; 算术右移,80H→40H→20H→…→01H MOV LIGHT, ALLIGHT初值80H对应二进制10000000,每次中断算术右移一位,灯就从一端向另一端依次点亮。SAR而不是SHR的选择有讲究:SAR保留符号位,对80H这种最高位为 1 的值右移后不会突然变成C0H之类的意外值,移位轨迹干净。八次中断后LIGHT变成00H,COUNT从08H递减到 0,主循环里的CMP COUNT,0成立,程序跳到INTEND恢复屏蔽字并返回 DOS。
端口地址IOPORT EQU 5400H-280H配合+280H、+283H、+288H、+28BH这种偏移写法,本质是把基址和芯片内部寄存器偏移分开,改基址时只动一行。这种写法在实验代码里很常见,但要注意IOPORT+288H和IOPORT+28BH之间差了 3 个字节,正好对应 8255 的 A、B、C、控制口四个连续地址,偏移算错一位就会写到别的寄存器上,现象是灯乱亮或者根本不亮。
3. 中断服务程序的执行时序:从 IRET 返回主循环的那几微秒发生了什么
3.1 向量表、现场保护与 COUNT 递减的完整时序
程序开头用INT 21H的AH=25H功能把INTPROC的段基址和偏移写进中断向量表第73H号表项。MOV AX,2573H里25H是 DOS 设置中断向量的子功能,73H是中断号。这个中断号不是随便选的,它要和 8259A 实际送出的中断类型号对应,否则 CPU 收到中断后跳到一个空向量,直接跑飞。
中断发生时的硬件时序大致是:8253 输出脉冲 → 8259A 拉高 INTR → CPU 当前指令执行完 → 发 INTA 应答 → 8259A 把类型号放到数据总线 → CPU 查向量表 → 压栈 FLAGS、CS、IP → 跳转到INTPROC。服务程序里先CLI再MOV AX,DATA重设 DS,是因为中断可能发生在主程序任意位置,DS 不一定还指向数据段。接着用INT 21H的AH=09H显示字符串,再写 8255、移位LIGHT、发 EOI、DEC COUNT,最后STI加IRET。
IRET和普通RET的区别在于它会从栈里弹出 IP、CS、FLAGS 三个字,恢复被中断时的完整现场。如果服务程序里压栈和出栈不平衡,IRET弹出来的返回地址就是错的,现象是程序跳到一个莫名其妙的地方或者直接死机。这是排查中断问题时第一个要看的地方:数一数PUSH和POP是否成对。
3.2 主循环空转与 COUNT 的可见性:为什么 CMP 能等到第八次中断
主循环只有三条指令:CMP COUNT,0、JZ INTEND、JNZ LL。它本质上是一个忙等待,反复读COUNT变量。COUNT在数据段,中断服务程序里DEC AL后MOV COUNT,AL写回内存,主循环每次CMP都重新从内存取值,所以能看到中断带来的变化。这里依赖的是内存变量的可见性,而不是寄存器缓存,如果编译器或手写代码把COUNT提前读进寄存器再循环比较,就会永远等不到 0。
LL: CMP COUNT, 0 ; 每次循环都从内存重新读 COUNT JZ INTEND ; 减到 0 就退出 JNZ LL ; 否则继续空转等待下一次中断这种忙等待在实验环境里没问题,因为主程序没有别的活要干。但在真实系统里,空转浪费 CPU,常见做法是用HLT让 CPU 休眠到下一次中断唤醒,或者用信号量、事件标志让主任务阻塞。实验代码选择忙等,是为了让“中断插入主流程”这件事看得更直白,属于教学取舍。
3.3 退出路径的屏蔽字恢复:为什么 INTEND 里要 OR 80H
程序退出前执行IN AL,21H、OR AL,80H、OUT 21H,AL,把主片 IMR 的 bit7 置 1,屏蔽掉最高优先级那一路,再对A1H直接OUT写回。这一步的目的是把中断系统恢复到进入实验前的状态,避免程序返回 DOS 后残留的屏蔽字影响后续程序。OR 80H而不是写死某个值,是因为你只知道自己改了 bit2 和 bit3,其他位是系统原来的配置,不能一把覆盖掉。
同样的思路出现在开头对 9054 芯片的配置上:先IN AX,DX读回原值,OR AX,0900H置位,结束时AND AX,0F7FFH清位再写回。读-改-写是操作共享寄存器的标准手法,直接OUT一个常量会把别人配好的位冲掉,这种翻车在联调多芯片系统时特别常见。
4. 避坑与排查:中断实验里最容易翻车的五个点
4.1 只中断一次就停:EOI 漏发或发错端口
现象是屏幕只显示一次字符串,灯只亮一个,之后主循环永远等不到COUNT归零。原因通常是服务程序末尾没有MOV AL,20H/OUT 20H,AL,或者从片中断只给主片发了 EOI 没给从片发。8259A 在收到 EOI 前会一直认为当前中断在服务中,同级和低级请求全被压住。解决办法是主片发20H到20H端口,从片发20H到A0H端口,顺序上先从片后主片更稳妥。
4.2 灯全亮但不返回 DOS:COUNT 递减写错了位置
现象是八个灯依次亮完,程序卡住不退出。原因多半是DEC COUNT写在了MOV COUNT,AL之前,或者AL在DEC前被其他指令覆盖,导致内存里的COUNT根本没变。排查时在服务程序里临时把COUNT输出到某个空闲端口用逻辑分析仪看,或者单步跟踪确认DEC前后AL的值。正确顺序是先MOV AL,COUNT,再DEC AL,再MOV COUNT,AL。
4.3 中断周期完全不对:8253 控制字和初值顺序颠倒
现象是中断来得太快、太慢或者干脆不来。原因是先写了初值再写控制字,计数器按旧方式字解释数据。8253 要求每个通道先写控制字锁定工作方式,再按控制字里规定的读写格式写初值。排查时用示波器看 8253 输出引脚有没有预期频率的波形,没有就回头检查控制字字节的每一位是否符合预期通道和方式。
4.4 程序跑飞或死机:向量表设置与 CLI/STI 配对错误
现象是中断一来程序就跳到乱地址。原因是INT 21H设置向量时DS没指向代码段,或者CLI之后忘了STI,又或者服务程序里PUSH/POP不平衡导致IRET弹出错误返回地址。排查时先确认MOV AX,CS/MOV DS,AX在MOV AX,2573H之前执行,再数一遍服务程序里的压栈出栈是否成对,最后确认IRET前STI已经执行。
4.5 端口偏移算错:8255 写到了错误寄存器
现象是灯乱亮、常亮或者完全不响应。原因是IOPORT+288H、+28BH这类偏移和 8255 内部 A/B/C/控制口映射对不上,或者基址5400H-280H算错。排查时把每个OUT指令的端口地址打印出来,对照 8255 数据手册的寄存器偏移表逐项核对,特别注意控制口在最高偏移、端口 A 在最低偏移这个顺序。
5. 设计取舍:为什么教学代码敢用忙等待和简化 EOI
这套代码最值得琢磨的不是它写了什么,而是它故意没写什么。主循环用CMP/JNZ忙等而不是HLT,是因为实验环境里 CPU 没有别的任务,空转的代价可以忽略,换来的是“中断插入主流程”这件事在单步调试时一目了然。真实系统里更常见的做法是主任务阻塞在信号量上,中断服务程序只做最少的事——置标志、发 EOI、清中断源,把显示字符串和移位这种耗时操作放到主循环里做,避免中断嵌套过深。
从片 EOI 的简化也是同一个逻辑:实验只挂了一个从片中断源,主片 EOI 发完,从片的优先级状态虽然没复位,但因为没有其他从片中断竞争,现象上看不出问题。换成多从片源同时工作的场景,漏发从片 EOI 就会导致低优先级中断被永久屏蔽。我一般会这么理解:教学代码是“最小可工作闭环”,它把边界条件砍到刚好能演示机制,剩下的坑留给真实项目去踩。
| 设计点 | 教学代码做法 | 真实系统常见做法 | 取舍原因 |
|---|---|---|---|
| 主循环等待 | 忙等CMP/JNZ | HLT或阻塞在信号量 | 教学要可单步观察,真实要省 CPU |
| 中断服务内容 | 显示字符串+移位+发 EOI | 只置标志、清中断源、发 EOI | 缩短中断关闭窗口,降低嵌套风险 |
| 从片 EOI | 只发主片 | 从片、主片都发 | 单源场景可简化,多源必须完整 |
| 屏蔽字恢复 | 读-改-写 | 保存原值、退出时整体恢复 | 读-改-写够用,保存原值更稳 |
验证这套机制是否真的跑通,最直接的办法是在服务程序入口和IRET前各翻转一个空闲 IO 口,用逻辑分析仪量中断响应到退出的时间窗口,再对照 8253 输出波形看周期是否一致。如果窗口时间随中断次数线性增长,说明服务程序里有累积性的资源没释放;如果窗口时间稳定但周期漂移,问题就在 8253 初值或时钟源上。从那以后我每次调中断链路,都强制先把 EOI 和屏蔽字恢复这两步单独验证一遍,再去看业务逻辑,希望帮到你。
本文还有配套的精品资源,点击获取