共计 1221 个字符,预计需要花费 4 分钟才能阅读完成。
实测中断延迟:从示波器看到的真相
用 100KHz 方波触发 INT0 中断,使用示波器捕获 P1.0 引脚电平变化(进入 ISR 时拉高,退出时拉低),测量到原始版本中断响应延迟约 42μs(12MHz 晶振下 50 个机器周期)。关键耗时点分布在:

- 中断向量跳转:7 个周期(LCALL 指令 +ROM 访问延迟)
- 自动压栈保护:14 个周期(PSW+ACC+B+DPTR)
- 库函数现场保护:12 个周期(?_?C51_ISR_ENTRY 宏展开)
- 用户代码前导操作:17 个周期(包括判断中断源等逻辑)
三级优化实战方案
基础优化:寄存器操作替代库函数
传统写法的问题:
void timer0_isr() interrupt 1 {
TF0 = 0; // Keil 库函数操作,实测 5 周期
TH0 = 0x3C;
TL0 = 0xB0;
}
优化后版本:
void timer0_isr() interrupt 1 {
TCON &= 0x7F; // 直接清 TF0,实测 2 周期
*(unsigned char idata *)0x8C = 0x3C; // 直接写 TH0 地址
*(unsigned char idata *)0x8A = 0xB0; // 直接写 TL0 地址
}
效果对比:
| 操作类型 | 机器周期 | 节省比例 |
|—————-|———|———|
| 库函数操作 | 5 | – |
| 直接寄存器访问 | 2 | 60% |
进阶优化:流水线友好编码
51 架构的 3 级流水线(取指、译码、执行)特性导致:
- 跳转指令会清空流水线(惩罚 3 周期)
- 对同一寄存器的连续操作会产生数据冒险
优化前问题代码:
if(TF0) { // 分支跳转
TF0 = 0;
count++; // 与 TF0 操作无关联
}
优化后方案:
char tmp = TF0;
TF0 = 0;
if(tmp) { // 改用标志变量判断
count++;
}
终极优化:汇编级现场保护
标准现场保护(Keil 默认)的问题:
PUSH ACC ; 2 周期
PUSH B ; 2 周期
PUSH DPH ; 2 周期
PUSH DPL ; 2 周期
... ; 共 14 周期
自定义精简方案(需分析实际使用情况):
PUSH 0xE0 ; 直接压栈 ACC 地址,1 周期
PUSH 0x00 ; 仅保护 R0,1 周期
; 省略未使用的 B 寄存器保护
关键数据对比
| 优化等级 | 总机器周期 | 时间 (12MHz) | 中断嵌套安全 |
|---|---|---|---|
| 原始版本 | 50 | 42μs | 是 |
| 基础优化 | 38 | 32μs | 是 |
| 流水线优化 | 29 | 24μs | 是 |
| 汇编优化 | 22 | 18μs | 需验证 |
中断嵌套的临界区处理
-
在高级优化中如需关闭中断,必须严格限制时长:
EA = 0; // 建议不超过 10 周期 critical_code(); EA = 1; -
避免在 ISR 内调用可能阻塞的函数(如某些库函数)
-
对于多字节全局变量访问,使用__atomic 保护(C51 扩展)
思考题
当晶振频率升至 24MHz 时:
– 流水线冒险惩罚周期数不变,但实际时间减半
– 需重新评估指令时序(特别是 MOVX 类访问外部 RAM 的指令)
– 中断嵌套时的临界区时间预算更紧张
通过这组优化方案,我们在电机控制项目中成功将 PWM 周期中断的抖动从±5μs 降低到±1μs,证明了方案的实际价值。
正文完
