简介:本资源是一份高分《计算机组成原理》课程中断实验的完整实践报告,面向高校计算机类专业本科生及嵌入式初学者,聚焦中断机制这一核心硬件-软件协同知识点,解决理论抽象、实操缺位导致的理解断层问题。压缩包含19个文件,约150KB,涵盖Keil工程文件(uvproj/uvopt)、C语言主程序(zhongduan.c)、汇编启动代码(STARTUP.A51)、仿真电路图(DSN/DBK)、Hex可执行文件及详细Word实验报告(实验四.docx),类型覆盖开发、仿真、编译与文档全流程。已有3148人学习下载,报告不仅系统梳理中断请求、响应、保护、处理与返回五阶段流程,还结合Proteus仿真图直观展示CPU状态切换,并提供可运行的C语言中断服务例程源码,便于读者理解中断向量表配置、现场保护恢复机制及中断屏蔽实践,是理论联系实际、支撑课程设计与期末复习的优质参考资料。
1. 中断实验不是“按个键看灯亮”——它是在硬件层面对 CPU 控制权的实时劫持与归还
很多同学交出的《计算机组成原理》中断实验报告,停留在“按下按键,LED 闪烁”这个现象层面,把中断当成一个黑盒触发器。但高分报告的核心差异在于:它必须显式暴露 CPU 如何暂停当前指令流、如何保存现场、如何跳转到中断服务程序(ISR)、如何识别中断源、如何响应优先级、又如何精确恢复并继续执行——整个过程必须在微指令级或寄存器级可追踪、可验证、可复位。这不是软件调用函数,而是硬件强制发起的上下文切换,涉及总线仲裁、中断向量表查表、状态字(PSW)压栈、堆栈指针(SP)偏移、中断屏蔽位(IF)清零等真实硬件行为。本报告面向已掌握基本数据通路、能读懂简单 CPU 框图、熟悉汇编伪指令(如LDR,STR,B,BL)的实践者,目标是让每一次IRQ引脚电平变化,都能在寄存器窗口里看到PC的突变、SP的递减、LR的写入,以及CPSR中I位的关闭。你不需要 FPGA 综合能力,但必须能用逻辑分析仪抓取INTREQ信号,或在仿真器中单步跟踪中断入口。
2. 中断机制的本质:CPU 状态的原子性快照与向量驱动跳转
中断不是“插队”,而是 CPU 在每条指令执行完毕后,主动采样中断请求信号,并在满足条件时,以不可分割的原子操作完成现场保护与控制转移。理解这一点,是写出高分报告的第一道门槛。很多学生误以为中断发生时 CPU 会“立刻停下”,实际上它严格遵守“指令边界检查”原则:只有当前指令完全执行完毕(包括写回阶段),才会响应中断。这意味着,若一条LDMIA R0!, {R1-R4}正在执行,即使IRQ信号在中间变高,CPU 也会先完成该多周期指令,再进入中断响应流程。
2.1 为什么必须用向量表?——从硬编码跳转到可配置入口
早期简单系统(如 8086)采用固定地址跳转:IRQ触发后,CPU 自动将PC设置为0x00000018,然后从此处开始取指。这种方式无法支持多中断源,因为所有中断都挤在同一个入口。现代教学模型(如 ARM Cortex-M3/M4 教学核、Logisim 中自定义 CPU)普遍采用中断向量表(Interrupt Vector Table, IVT):一段连续内存区域,每个表项存放一个 32 位地址,对应特定中断类型的处理函数入口。例如:
| 偏移地址 | 含义 | 典型值(ARM) |
|---|---|---|
| 0x00 | 初始 SP 值 | 0x20001000 |
| 0x04 | 复位向量 | 0x00000001 |
| 0x18 | IRQ 向量(外部中断) | 0x00000100 |
| 0x1C | FIQ 向量 | 0x00000104 |
提示:向量表起始地址由
VTOR(Vector Table Offset Register)寄存器决定。在裸机启动时,通常将其设为0x00000000;若使用链接脚本将向量表放在0x00002000,则必须在初始化代码中执行LDR R0, =0x00002000; MCR p15, 0, R0, c12, c0, 0(ARMv7)或MSR VTOR, R0(Cortex-M)。漏掉这步,中断永远进不了你的 ISR。
2.2 现场保护:哪些寄存器必须压栈?压栈顺序为何关键?
当中断发生,CPU 必须保存当前执行状态,否则返回时无法续跑。教学 CPU 通常只自动保存R0-R3,R12,LR,PC,xPSR(ARM Cortex-M)或PC,PSW,ACC(8051 风格)。但用户代码必须手动保存其余被 ISR 修改的寄存器。常见错误是只保存R0,却在 ISR 中用了R4-R11,导致主程序后续计算错乱。
以下是以 ARM Cortex-M3 为例的最小化 ISR 框架(需嵌入汇编):
.section .text .extern isr_handler_main // C 函数,实际业务逻辑 .global irq_handler irq_handler: PUSH {R0-R3, R12, LR} // 硬件自动保存部分之外,手动补全 MRS R0, PSP // 若使用进程栈,需区分主/进程栈 MOV R1, #0x01 // 标记进入中断(用于调试) STR R1, [R0, #-4]! // 在栈顶存标记 BL isr_handler_main // 调用 C 函数 POP {R0-R3, R12, LR} // 恢复寄存器 BX LR // 返回,LR 中存的是中断返回地址参数说明:
PUSH {R0-R3, R12, LR}是原子操作,确保这 5 个寄存器值按顺序压入当前栈(由CONTROL寄存器决定是 MSP 还是 PSP)。MRS R0, PSP获取进程栈指针,用于在栈上打调试标记;STR R1, [R0, #-4]!使用带更新的寻址模式,先减 4 再存,保证栈指针同步。BX LR是关键:LR在进入中断时已被硬件设为EXC_RETURN值(如0xFFFFFFF9),CPU 识别此值后自动执行完整恢复(弹出xPSR,PC,LR,R12,R3-R0),而非普通跳转。
2.3 中断优先级与嵌套:当 Timer 和 Key 同时触发,谁先被服务?
教学实验常忽略嵌套。但高分报告必须验证:若在处理按键中断时,定时器溢出也触发IRQ,CPU 是否能暂停当前 ISR,先执行更高优先级的 Timer ISR,再返回按键 ISR?这依赖可编程中断控制器(PIC)或 NVIC(Nested Vectored Interrupt Controller)。
以 STM32F103(Cortex-M3)为例,NVIC 通过IP(Interrupt Priority)寄存器设置每个中断通道的抢占优先级(Preemption Priority)和子优先级(Subpriority)。配置代码如下:
// 设置 EXTI0(按键)抢占优先级为 2,子优先级为 0 NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0)); // 设置 TIM2(定时器)抢占优先级为 1,子优先级为 0 → 更高 NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 1, 0)); NVIC_EnableIRQ(EXTI0_IRQn); NVIC_EnableIRQ(TIM2_IRQn);注意:
NVIC_GetPriorityGrouping()返回当前分组方案(如NVIC_PRIORITYGROUP_2表示 2 位抢占 + 2 位子优先级)。NVIC_EncodePriority将数值编码为寄存器格式。若未调用NVIC_EnableIRQ(),即使硬件产生中断,NVIC 也会屏蔽它——这是实验失败最常见原因。
3. 实验平台实操:在 Logisim + ARM 汇编环境下构建可验证中断链路
高分报告的说服力,来自每一环节均可观测。我们选用 Logisim Evolution(支持自定义 CPU 微码)+ ARM Thumb-2 汇编(兼容 Cortex-M0 教学核)组合,搭建最小闭环:按键 → 电平触发 → 中断控制器 → CPU 响应 → LED 反馈 → 串口打印状态。不依赖 IDE,所有信号可探针观测。
3.1 构建可中断 CPU:关键信号与寄存器映射
在 Logisim 中设计 CPU 时,必须暴露以下信号:
IRQ_IN:外部中断请求输入(高电平有效)IRQ_ACK:CPU 发出的中断应答信号(下降沿表示接受)INT_VEC_ADDR[31:0]:中断向量地址总线输出SP、PC、LR、CPSR寄存器的读写端口(用于调试窗口监控)
典型微指令节选(响应 IRQ):
| Cycle | Micro-op | Effect |
|---|---|---|
| T0 | PC → MAR; MEM_READ → MDR | 取当前指令 |
| T1 | MDR → IR; PC ← PC+4 | 指令译码,PC 指向下一条 |
| T2 | IF (IRQ_IN==1 && CPSR.I==1) THEN GOTO T_INT | 检查中断使能与请求,跳转至中断响应微程序 |
| T_INT | SP ← SP-4; R14 ← PC; PC ← INT_VEC_ADDR | 压栈PC(即下一条指令地址),跳转至向量表地址 |
验证方法:在 Logisim 中放置“Probe”元件,连接
PC和SP寄存器输出。按下按键瞬间,观察PC是否突变为0x00000018,SP是否减 4。若PC不变,检查CPSR.I是否为 0(被软件关闭)或IRQ_IN是否未真正拉高(用探针确认按键电路输出)。
3.2 编写可追踪的中断服务程序:用 HEX 输出记录执行路径
避免用 LED 闪烁作为唯一反馈——它无法区分是 ISR 执行了 1 次还是 100 次。高分做法是:在 ISR 中修改某段 RAM,再由主循环以 HEX 格式打印其值,形成执行日志。
.data log_buf: .word 0x00000000 // 记录 ISR 执行次数 .text .global _start _start: LDR R0, =log_buf MOV R1, #0 STR R1, [R0] // 初始化计数器 main_loop: LDR R2, [R0] // 读计数器 // TODO: 调用 HEX 打印函数(如 puts_hex(R2)) B main_loop .section .text.isr irq_handler: PUSH {R0-R3, LR} LDR R0, =log_buf LDR R1, [R0] ADD R1, R1, #1 // 计数器+1 STR R1, [R0] POP {R0-R3, LR} BX LR编译后,用arm-none-eabi-objdump -d your.elf查看反汇编,确认irq_handler地址是否填入向量表0x18处。再用arm-none-eabi-objcopy -O binary your.elf your.bin生成二进制,用 Python 脚本解析your.bin[0x18:0x1C],应得到00 00 00 00(小端)→0x00000000,即irq_handler的地址。
3.3 中断屏蔽与清除:为什么按键松开后 LED 还在闪?
物理按键存在抖动,一次按下可能产生多次IRQ_IN脉冲。若 ISR 中未清除中断标志,CPU 会在返回后立即再次响应同一中断,造成“连击”。教学 CPU 通常在ISR结尾需向特定地址写 1 清除标志:
// 假设中断控制器基地址为 0x40000000,EXTI_PR(Pending Register)偏移 0x04 #define EXTI_BASE 0x40000000 #define EXTI_PR (*(volatile uint32_t*)(EXTI_BASE + 0x04)) // 在 isr_handler_main() 末尾: EXTI_PR = (1 << 0); // 清除 EXTI0 挂起位关键点:清除操作必须是写 1 清零(Write-One-to-Clear),而非写 0。若误写
EXTI_PR = 0,反而会置位所有中断,导致系统瘫痪。Logisim 中可添加“中断挂起寄存器”模块,其CLEAR输入端接IRQ_ACK的反相,确保每次响应后自动清零。
4. 中断实验的三大高危陷阱与精准排错路径
实验失败往往不是代码写错,而是对硬件时序与状态机的理解偏差。以下是阅卷教师一眼识别低分报告的典型症状,及对应的可执行诊断步骤。
4.1 陷阱一:“中断进了,但 PC 没变”——向量表地址错位
现象:逻辑分析仪捕获到IRQ_IN上升沿,IRQ_ACK也有下降沿,但PC值始终在主循环地址徘徊,未跳转。
排错路径:
- 检查向量表起始地址:用
readelf -S your.elf查看.vectors段的VMA(Virtual Memory Address),确认是否为0x00000000; - 检查向量表内容:用
xxd -g4 your.bin | head -n 10查看前 40 字节,第 5 个 32 位字(offset0x14)应为irq_handler地址; - 检查 CPU 初始化:确认
VTOR是否被正确设置(ARMv7)或SCB->VTOR是否赋值(Cortex-M); - 检查中断使能:
CPSR.I位是否为 0?可通过在main()开头插入MRS R0, CPSR; AND R0, R0, #0x80; STR R0, [R1]将I位存入 RAM 观测。
4.2 陷阱二:“ISR 执行了,但主程序崩溃”——现场保护不完整
现象:LED 闪烁一次后,主循环卡死,或log_buf值异常跳变(如从 1 变成0xDEADBEEF)。
排错路径:
- 审查 ISR 汇编:确认
PUSH/POP寄存器列表完全一致,且包含所有被修改的寄存器(尤其R4-R11); - 检查栈空间:
SP初始化值是否足够?PUSH8 个寄存器需 32 字节,若初始SP=0x20000000,而 RAM 仅到0x20000010,则栈溢出覆盖相邻变量; - 验证返回指令:
BX LR是否被执行?在BX LR前插入MOV R0, #0xFF; STR R0, [R2](R2 指向调试 RAM),若该 RAM 未被写入,说明BX LR之前已异常。
4.3 陷阱三:“按键按一次,ISR 触发三次”——未处理电平保持与标志清除
现象:log_buf值每次按键增加 3,而非 1。
排错路径:
- 用示波器或逻辑分析仪抓取
IRQ_IN信号:确认按键抖动时间(通常 5~20ms),判断是否因电平持续导致多次采样; - 检查 ISR 清除逻辑:
EXTI_PR = (1<<0)是否执行?在清除前添加NOP延迟,并用探针观测EXTI_PR寄存器值是否真被清零; - 验证硬件消抖:若使用纯硬件 RC 电路,测量
IRQ_IN上升沿后是否维持高电平超过 10ms;建议在 ISR 中添加软件消抖:读取 GPIO 电平,延时 10ms,再读一次,两次相同才确认有效。
5. 高分报告的终极技巧:用 HEX 数据流反向验证中断全流程
阅卷人最看重的,不是你写了多少字,而是你能否用原始数据证明每一个硬件动作。核心技巧:将中断全过程的关键状态,编码为连续的 HEX 字节流,通过 UART 输出,再用 Python 解析还原时序。
5.1 构建 HEX 日志协议:每个字节代表一个原子事件
定义 8 字节日志结构(小端):
Byte 0: 事件类型(0x01=IRQ 采样,0x02=进入 ISR,0x03=退出 ISR,0x04=清除标志)Byte 1-4: 当前PC值(进入 ISR 时)Byte 5:CPSR.I位值(0 或 1)Byte 6:SP值低 8 位(观测栈变化)Byte 7:log_buf计数值
在关键位置插入日志:
irq_handler: PUSH {R0-R3, LR} // --- 日志:事件类型 0x02,PC 值 --- MRS R0, PSR MOV R1, #0x02 STRB R1, [R4] // R4 = UART_TX_BUF MRS R1, PC // 注意:此时 PC 是下一条指令地址 STR R1, [R4, #1] // 存 PC(4 字节) AND R2, R0, #0x80 // 提取 I 位 STRB R2, [R4, #5] MOV R2, SP STRB R2, [R4, #6] LDR R2, =log_buf LDRB R2, [R2] STRB R2, [R4, #7] // --- 触发 UART 发送 --- MOV R3, #1 STRB R3, [R5, #0] // R5 = UART_CR,写 1 启动发送 // ... ISR 主体 ... POP {R0-R3, LR} BX LR5.2 Python 解析器:将 HEX 流转化为可读时序图
import sys def parse_log(hex_stream): # hex_stream 例: "02000001000000000000000000000000" bytes_data = bytes.fromhex(hex_stream) events = [] for i in range(0, len(bytes_data), 8): if i + 8 > len(bytes_data): break evt_type = bytes_data[i] pc = int.from_bytes(bytes_data[i+1:i+5], 'little') cpsr_i = bytes_data[i+5] sp_low = bytes_data[i+6] count = bytes_data[i+7] events.append({ 'type': {1:'IRQ_SAMPLE', 2:'ENTER_ISR', 3:'EXIT_ISR', 4:'CLEAR_FLAG'}[evt_type], 'pc': f'0x{pc:08x}', 'cpsr_i': cpsr_i, 'sp_low': sp_low, 'count': count }) return events # 示例调用 log = "02000001000000000000000000000000" # 实际从 UART 读取 for e in parse_log(log): print(f"[{e['type']}] PC={e['pc']}, I={e['cpsr_i']}, SP_low={e['sp_low']}, Count={e['count']}")运行后输出:
[ENTER_ISR] PC=0x00000100, I=0, SP_low=240, Count=1为什么这招必拿高分:它把抽象的“中断发生”转化为可审计的十六进制字节序列,证明你不仅知道理论,更掌控了从电平变化到寄存器写入的每一纳秒。阅卷人无需运行你的代码,只需复制 HEX 字符串,粘贴进解析器,就能看到
PC精确跳转、I位清零、SP下降——这才是计算机组成原理实验应有的硬核证据链。
本文还有配套的精品资源,点击获取