第4章 中断服务函数 (ISR) 编写规范
各位同学,咱们今天聊聊中断服务函数。说白了,ISR 就是 CPU 响应中断后,跑去执行的那段代码。写得好不好,直接决定了系统的实时性和稳定性。我在 TC3xx 项目上见过太多因为 ISR 写得随意,导致系统跑飞或者响应延迟的案例了。
4.1 函数声明:别让编译器跟你对着干
先说说声明。TC3xx 用的是 TriCore 架构,编译器通常是 Tasking 或者 HighTec。我个人习惯在 ISR 前面加上特定的属性声明,告诉编译器:「这段代码是中断服务函数,别给我乱优化」。
// Tasking 编译器
__interrupt void isr_ADC(void);
// HighTec 编译器
__attribute__((interrupt)) void isr_ADC(void);
为什么要加这个?我刚开始做 TC3xx 开发时,有一次忘了加 __interrupt 属性。结果编译器把 ISR 当成普通函数优化,该保存的寄存器没保存,该恢复的没恢复。嗯,那次调试花了我整整两天。
__interrupt,HighTec 用 __attribute__((interrupt))。千万别混用,否则编译直接报错。
另外,ISR 的命名也有讲究。我建议用「模块名_中断源_ISR」的格式,比如 ADC1_Group0_ISR。你想想看,项目做到后期,中断源可能有几十个,名字乱写的话,自己都找不到。
4.2 上下文保存与恢复:CPU 的「记忆」不能丢
中断来了,CPU 正在干别的事。它得先把当前的工作状态保存下来,等 ISR 执行完再恢复。这个过程就叫上下文保存与恢复。
TC3xx 的 CPU 会自动保存一部分上下文,比如 PC(程序计数器)、PSW(程序状态字)。但有些东西它不管,比如你 ISR 里用到的局部变量、临时寄存器。这些得你自己操心。
核心原则: ISR 里用到的资源,进去前保存,退出前恢复。一个都不能少。
编译器其实会帮你做大部分工作。你只要加了 __interrupt 属性,编译器会自动生成保存和恢复的代码。但有个坑——如果你在 ISR 里调用了其他函数,那就要小心了。
// 错误示范:ISR 里调用普通函数
__interrupt void isr_ADC(void) {
ProcessData(); // 危险!这个函数可能修改全局变量
}
// 正确做法:ISR 里只做最必要的事
__interrupt void isr_ADC(void) {
g_AdcResult = ADC_GetResult(); // 只读寄存器,不调用复杂函数
g_AdcFlag = 1; // 设置标志,让主循环处理
}
我曾经在一个项目里,ISR 里调用了 printf 做调试输出。结果呢?printf 内部用了很多全局变量和锁,ISR 执行时间暴涨,还造成了死锁。从那以后,我 ISR 里只做三件事:读数据、设标志、清中断。
4.3 中断延迟:别让 CPU 等你
中断延迟,就是从中断事件发生,到 CPU 开始执行 ISR 第一条指令的时间。这个时间越短,系统响应越快。
影响中断延迟的因素有几个:
- 中断优先级仲裁: 如果有更高优先级的中断正在执行,你得等它完事。
- 当前指令执行时间: CPU 正在执行一条长指令(比如除法、浮点运算),得等它结束。
- 上下文保存时间: 保存寄存器需要时间,但这是必须的。
- 中断屏蔽: 如果你在代码里关了全局中断,那所有中断都得等着。
我的经验: TC3xx 的中断延迟通常在几十到几百纳秒级别。但如果你的 ISR 写得又长又臭,或者频繁开关中断,延迟会飙升到微秒甚至毫秒级。
怎么优化?我给你几个建议:
- ISR 要短小精悍。 能 10 行写完的,别写 100 行。复杂逻辑放到主循环或任务里处理。
- 别在 ISR 里关中断。 有些同学喜欢在 ISR 开头关中断,怕被打断。但这样会阻塞其他中断,增加延迟。
- 合理设置优先级。 时间敏感的中断(比如电机控制、通信协议)给高优先级,不敏感的(比如按键扫描)给低优先级。
- 避免在 ISR 里使用浮点运算。 浮点运算指令长,还涉及 FPU 上下文保存,延迟会明显增加。
| 操作 | 典型延迟影响 | 建议 |
|---|---|---|
| 简单赋值 | 几乎无影响 | 推荐 |
| 函数调用 | 几十纳秒 | 谨慎使用 |
| 浮点运算 | 几百纳秒 | 避免 |
| 关中断 | 取决于关中断时长 | 尽量避免 |
| printf/日志输出 | 毫秒级 | 绝对禁止 |
避坑指南: 我曾经在一个项目中,ISR 里用了 memcpy 复制数据。结果 memcpy 内部有循环,执行时间不确定,导致中断延迟抖动很大。后来我改成用 DMA 传输,问题就解决了。
4.4 实战总结:写 ISR 的黄金法则
说了这么多,我总结几条黄金法则,你写 ISR 时照着做就行:
- 法则一: 加对编译器属性声明,别让编译器乱优化。
- 法则二: ISR 里只做最必要的事,复杂逻辑扔出去。
- 法则三: 别在 ISR 里调函数,尤其是那些你不知道内部实现的。
- 法则四: 别关中断,除非你非常清楚后果。
- 法则五: 用标志位或消息队列跟主循环通信。
嗯,ISR 的编写规范就这些。说白了,就是「快进快出,别拖泥带水」。你想想看,中断本来就是用来处理紧急事件的,你却在里面磨磨蹭蹭,那还叫中断吗?
下一章咱们聊聊任务调度,看看怎么把 ISR 里收集的数据,交给任务去处理。到时候你会发现,ISR 写得规范,任务调度才能跑得顺畅。