从8253到8259A:中断链路与IRET机制实验解析 简介这份资源面向微机原理与接口技术的学习者围绕8253定时器、8259A中断控制器与8255可编程并行接口三块芯片给出一个完整的中断实验方案。实验以8253输出周期性信号作为中断请求经8259A转交CPU处理在屏幕上显示提示字符串并逐次点亮发光二极管每秒触发一次中断八灯全亮后退出返回DOS可帮助读者理解中断向量表设置、中断屏蔽与恢复、芯片初始化等关键环节。压缩包内为1个docx文档约14KB集中呈现实验说明与完整汇编源码包含数据段、堆栈段、代码段及中断服务程序、8253与8255初始化模块便于对照阅读与上机调试。该资源已有1264人学习下载适合课程实验、接口芯片练习及中断机制复习时参考读者可从中获得可运行的代码框架与配置思路并据此排查中断响应与端口操作中的常见问题。1. 中断实验为什么一条 IRET 能让整个系统“活”过来很多人第一次跑通 8253、8259A、8255 这套组合时都会有一种玄学般的错觉代码明明只是往几个端口写了几条OUT屏幕上却开始周期性冒字符串发光二极管一个接一个亮起来。更反直觉的是中断服务程序里既没有循环也没有延时它凭什么“记住”自己已经执行了八次答案藏在COUNT这个变量和 8259A 的 EOI 时序里。搞懂这套机制你才算真正理解“中断”不是函数调用而是一次由硬件发起、由软件收尾的协作。这份实验资源把 8253 的定时输出、8259A 的请求仲裁、8255 的端口输出串成一条完整链路正好用来拆解“定时器怎么变成中断源、中断怎么被 CPU 接住、外设怎么被驱动”这三个底层问题。2. 中断链路8253 的方波怎么变成 CPU 的一条指令2.1 从计数到请求8253 通道 0 的角色8253 在这套系统里不是“定时器”这么笼统它干的事非常具体通道 0 被配置成方式 3方波发生器初值 1000输出引脚上持续翻转电平。这个翻转信号物理上接到 8259A 的某一路 IR 输入上于是“时间到了”这件事被翻译成一个电平跳变。方式 3 的好处是占空比接近 50%对中断请求来说边沿触发和电平触发都能稳定识别不会因为脉宽太窄被漏掉。初始化代码里MOV AL,00110111B写的是通道 0 的控制字选择通道 0、先读写低字节再读写高字节、方式 3、二进制计数。紧接着MOV AL,01110101B是通道 1 的控制字方式 2速率发生器初值同样 1000。这里有个容易看漏的细节通道 1 的初值写到了IOPORT281H而通道 0 的初值写到IOPORT280H两个通道各占一个端口控制字寄存器共用283H。初值 1000 分两次写先低字节00H再高字节10H合起来就是 0x1000 即 4096不对这里写的是00H和10H实际装入值是 0x1000但 8253 是递减计数装入 0x1000 意味着计 4096 个时钟。如果输入时钟是 1MHz分频后大约 4ms 一个周期实验描述里说“每秒一次中断”那说明前级时钟或者级联分频另有安排通道 1 很可能就是用来做这个慢速分频的。读这段代码时不要孤立看通道 0两个通道的初值一样但方式不同通道 1 的 out 很可能又回接到通道 0 的 CLK形成两级分频这是实验箱上常见的接法。I_8253 PROC NEAR MOV DX, IOPORT283H ; 控制字寄存器 MOV AL, 00110111B ; 通道0先低后高方式3二进制 OUT DX, AL MOV AL, 01110101B ; 通道1先低后高方式2二进制 OUT DX, AL MOV DX, IOPORT280H ; 通道0数据端口 MOV AL, 00H OUT DX, AL ; 初值低字节 MOV AL, 10H OUT DX, AL ; 初值高字节 MOV DX, IOPORT281H ; 通道1数据端口 MOV AL, 00H OUT DX, AL MOV AL, 10H OUT DX, AL RET I_8253 ENDP这段代码里OUT DX, AL的目标端口由DX决定AL是数据。控制字 00110111B 逐位拆开bit7-6 选通道 0bit5-4 读/写低高字节bit3-1 方式 3bit0 二进制。方式 2 的通道 1 控制字 01110101B 对应 bit3-1 为 010即速率发生器输出负脉冲。两个通道初值都写 0x1000但方式不同导致输出波形不同通道 1 的脉冲输出更适合做级联触发。2.2 8259A 的屏蔽与 EOI为什么先关再开主程序里有一段很关键的顺序先CLI关中断然后读21H端口、AND AL,0FBH、写回再读0A1H、AND AL,0F7H、写回最后才STI。21H是主片 8259A 的中断屏蔽寄存器IMR0A1H是从片的 IMR。0FBH是 11111011B把 bit2 清零意思是允许 IR2 这一路中断0F7H是 11110111B清 bit3允许从片 IR3。为什么必须先关中断再改 IMR因为如果中途来了一个未屏蔽的中断CPU 会跳进一个还没准备好的服务程序轻则跑飞重则死循环。这种“先关后开”的写法是中断初始化的血泪经验不是可有可无的仪式。中断服务程序末尾的MOV AL,20H和OUT 20H,AL是向主片发 EOIEnd of Interrupt。8259A 在服务完一个中断后必须收到 EOI 才会把当前中断标记为结束否则同优先级或更低优先级的请求会被一直压着。这里只发主片 EOI是因为实验里从片的中断最终汇聚到主片 IR2主片收到 EOI 后整条链路才算释放。如果从片也直接挂 CPU那就得分别发。很多人在实验箱上遇到“只中断一次就再也不来了”八成就是漏了这条 EOI。2.3 中断向量表2573H 这五个字符在干什么MOV AX,2573H配合INT 21H是 DOS 时代设置中断向量的标准写法AH25H 表示“设置中断向量”AL73H 是中断号DS:DX 指向服务程序入口。这里选 73H 不是随便挑的它落在用户可用的中断号区间避开系统占用的 08H-0FH、1CH 等。MOV AX,CS和MOV DS,AX先把 DS 指向代码段再MOV DX,OFFSET INTPROC取偏移这样 DS:DX 就完整指向INTPROC。这条链路一旦建立8259A 送来 IRQ 后CPU 查向量表第 73H 项直接跳进INTPROC。向量表本质就是内存最低端的 1024 字节每项 4 字节前两字节偏移、后两字节段基址INT 21H的 25H 功能只是帮你把这两个字填进去。3. 中断服务程序COUNT 递减背后的状态机3.1 为什么 INTPROC 里要重新装 DS进入中断服务程序后第一件事是CLI然后MOV AX,DATA、MOV DS,AX。主程序里 DS 已经被改成 CS 用来设向量表中断发生时 CPU 不会自动恢复 DS所以服务程序必须自己把 DS 重新指向数据段否则后面MOV DX,OFFSET STRING取到的字符串地址就是错的。这是实模式下中断编程的经典坑段寄存器不会随中断自动保存除了 CS:IP 和 FLAGS 由硬件压栈DS、ES 都得自己保护。这段代码没做完整的现场保护没 PUSH 所有寄存器是因为实验环境简单但严格来说一个合格的中断服务程序应该在入口 PUSH 受影响寄存器出口再 POP否则主程序里的寄存器状态会被悄悄改掉。INTPROC PROC NEAR CLI MOV AX, DATA MOV DS, AX ; 恢复数据段寻址 MOV DX, OFFSET STRING MOV AH, 9 INT 21H ; 显示字符串 MOV DX, IOPORT288H ; 8255 端口 A 数据口 MOV AL, LIGHT OUT DX, AL ; 点亮当前 LED MOV CL, 01H SAR AL, CL ; 算术右移准备下一个 LED MOV LIGHT, AL MOV AL, 20H OUT 20H, AL ; 主片 EOI MOV AL, COUNT DEC AL MOV COUNT, AL ; 中断次数减一 STI IRET INTPROC ENDPLIGHT初值80H即 10000000B第一次输出点亮最高位对应的 LED然后SAR AL,1变成 01000000B下一次点亮次高位八次之后LIGHT变成 0。COUNT初值 08H每次中断减一主循环CMP COUNT,0判断是否退出。这里的状态机完全靠内存变量维持中断服务程序本身不保存“第几次”的上下文它只负责“读变量、改变量、写回”。这种设计的好处是服务程序极短坏处是变量在主循环和服务程序之间共享如果主循环正在读COUNT时被中断打断读到的可能是半新半旧的值。实模式下DEC AL和MOV COUNT,AL不是原子操作严格场景要用LOCK前缀或者关中断保护实验里因为主循环只是空转比较风险被掩盖了。3.2 8255 的端口地址与方式字IOPORT288H是 8255 端口 A 的数据口IOPORT28BH是控制口。初始化写10001111Bbit7 为 1 表示方式设置bit6-5 选 A 组方式 0bit4 端口 A 输出bit3 端口 C 高四位输出bit0 端口 C 低四位输出。方式 0 是基本 I/O没有握手信号CPU 写什么端口就输出什么电平正好用来直接驱动 LED。IOPORT定义为5400H-280H所以288H实际是5408H28BH是540BH。这种“基址减偏移再加偏移”的写法是为了让IOPORTxxx直接对应实验箱上的绝对端口读代码时先把IOPORT算清楚不然很容易把288H误当成段内偏移。I_8255 PROC NEAR MOV DX, IOPORT28BH ; 8255 控制口 MOV AL, 10001111B ; 方式0A/B/C 全输出 OUT DX, AL RET I_8255 ENDP方式字 10001111B 里 bit4 置 1 让端口 A 输出bit3 置 1 让端口 C 高四位输出bit0 置 1 让端口 C 低四位输出B 组由 bit2-1 选方式 0、bit1 置 1 输出。实验只用了端口 A 驱动八个 LED但控制字把 C 口也设成输出属于“多配了不亏”的保守写法。3.3 主循环的空转与退出条件主程序LL: CMP COUNT,0、JZ INTEND、JNZ LL是一个忙等待循环CPU 在这里反复比较COUNT直到它变成 0 才往下走。这种写法在裸机实验里很常见因为没有任何操作系统调度主程序除了等中断没别的事可做。COUNT从 08H 减到 00H 需要八次中断每次中断点亮一个 LED八次之后八个 LED 全亮循环退出然后恢复中断屏蔽寄存器、关闭 9054 芯片的中断设置、MOV AH,4CH返回 DOS。注意退出前把21H端口OR AL,80H再写回是把主片 IMR 的 bit7 置 1屏蔽掉所有可屏蔽中断防止程序返回 DOS 后还有残留中断请求进来。这个收尾动作和开头的“先关后开”对称体现的是同一套防御性编程习惯。4. 避坑与排查实验箱上最容易翻车的五个点4.1 只中断一次就停EOI 漏发或发错片现象是字符串只显示一次LED 只亮一个之后程序卡死。原因通常是OUT 20H,AL没执行或者AL里不是20H。8259A 在非自动 EOI 模式下服务完必须收到 EOI 才释放当前中断否则同级和低级请求全被挡住。排查时在IRET前加一句往某个空闲端口写标记用逻辑笔或示波器看有没有执行到也可以把 EOI 提到服务程序最前面试一下如果变成连续中断就说明原来的 EOI 位置或数值有问题。4.2 字符串乱码或打印不出来DS 没重装现象是屏幕输出一堆乱码或者INT 21H的 9 号功能完全没反应。原因是进入服务程序后 DS 还指着代码段OFFSET STRING算出来的是代码段内偏移指向的根本不是字符串。解决就是在服务程序入口立刻MOV AX,DATA、MOV DS,AX并且确认DATA段寄存器装的是数据段基址。如果用了ASSUME但没实际装 DS汇编器不会报错运行时才翻车这类问题只能靠单步调试看 DS 值。4.3 LED 不按顺序亮LIGHT 初值或移位方向错现象是八个 LED 要么全亮、要么乱亮、要么只亮一个不往下走。先查LIGHT初值是不是80H再查移位用的是SAR还是SHR。SAR是算术右移最高位补符号位80H是负数右移后变成C0H而不是40HLED 就会一次亮两个。正确做法是用SHR逻辑右移或者初值用01H配合左移SHL。这个坑很隐蔽因为SAR和SHR在正数上行为一样只有最高位为 1 时才分道扬镳。4.4 中断根本不进IMR 位没清对或向量号冲突现象是程序跑完初始化就卡在LL循环里COUNT永远不减。先确认21H端口读改写时清的是哪一位0FBH清 bit2如果实验箱上 8253 实际接的是 IR3那就该用0F7H。再确认INT 21H设的向量号73H没有被别的程序占用DOS 下可以用调试器看0000:01CC附近四个字节是不是指向INTPROC。还有一个常见原因是 8253 的 GATE 没使能通道不计数自然没有输出用示波器量一下 8253 的 OUT 引脚有没有波形能快速把问题切成“定时器没动”和“中断控制器没放行”两半。4.5 返回 DOS 后系统异常中断屏蔽没恢复现象是程序退出后键盘失灵或者系统直接挂起。原因是退出前没有把 IMR 恢复成进入时的状态残留的中断请求在 DOS 环境下被触发而对应的服务程序已经不存在了。解决就是在INTEND里把21H和0A1H的原始值写回最稳妥的做法是程序开头先把两个 IMR 读出来存到变量里退出时原样写回而不是靠OR AL,80H这种“一刀切屏蔽”。实验代码里用OR AL,80H是简化处理真实项目里应该保存-恢复。5. 设计取舍为什么这套实验至今仍值得逐行读这套代码最值得琢磨的地方不是它用了多新的技术而是它把“中断”拆成了三个可以独立验证的层次8253 负责“什么时候发生”8259A 负责“让不让 CPU 知道”8255 负责“发生后做什么”。三层之间通过物理引脚和端口地址耦合任何一层出问题现象都能定位到具体芯片。这种分层不是教科书硬分的而是硬件本身就这么长出来的。你在代码里看到的OUT DX,AL写控制字、读 IMR 改位、发 EOI每一步都对应芯片内部一个真实的状态机没有抽象泄漏。从设计取舍上看这段代码选择了“最短服务程序 内存变量记状态”的路子。INTPROC里没有循环、没有延时只做显示、输出、移位、减计数、发 EOI 五件事把“第几次中断”这种上下文完全外置到COUNT和LIGHT两个变量里。好处是中断响应快坏处是共享变量没有保护主循环和服务程序之间存在竞态窗口。如果换成更严谨的写法应该在服务程序入口PUSH所有会用到的寄存器对COUNT的读改写加CLI/STI包住退出时再POP。但实验环境里主循环只是空转比较竞态被时间掩盖了这也是为什么很多人跑通之后回头再看才发现“能跑”和“对”之间还有一段距离。验证这套机制是否真的被理解有个很直接的办法把COUNT初值从08H改成03H看是不是只亮三个 LED 就退出把 8253 通道 0 初值从1000改成2000看中断周期是不是翻倍把SAR换成SHR看 LED 点亮顺序是否从“一次亮两个”变成“逐个亮”。每一次改动都对应一个可观测的现象变化这比背控制字位定义有用得多。我自己的习惯是每次碰这类端口级代码先把所有OUT的目标端口和值列成一张表再逐条对照芯片手册的位定义最后才上电跑。从那以后我每次调中断相关的东西都强制先把 IMR 的读改写和 EOI 的发送顺序在纸上走一遍再动手改代码。希望帮到你。本文还有配套的精品资源点击获取