第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 当成普通函数优化,该保存的寄存器没保存,该恢复的没恢复。嗯,那次调试花了我整整两天。

注意: 不同编译器语法不同。Tasking 用 __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 写得又长又臭,或者频繁开关中断,延迟会飙升到微秒甚至毫秒级。

怎么优化?我给你几个建议:

  1. ISR 要短小精悍。 能 10 行写完的,别写 100 行。复杂逻辑放到主循环或任务里处理。
  2. 别在 ISR 里关中断。 有些同学喜欢在 ISR 开头关中断,怕被打断。但这样会阻塞其他中断,增加延迟。
  3. 合理设置优先级。 时间敏感的中断(比如电机控制、通信协议)给高优先级,不敏感的(比如按键扫描)给低优先级。
  4. 避免在 ISR 里使用浮点运算。 浮点运算指令长,还涉及 FPU 上下文保存,延迟会明显增加。
操作 典型延迟影响 建议
简单赋值 几乎无影响 推荐
函数调用 几十纳秒 谨慎使用
浮点运算 几百纳秒 避免
关中断 取决于关中断时长 尽量避免
printf/日志输出 毫秒级 绝对禁止

避坑指南: 我曾经在一个项目中,ISR 里用了 memcpy 复制数据。结果 memcpy 内部有循环,执行时间不确定,导致中断延迟抖动很大。后来我改成用 DMA 传输,问题就解决了。

4.4 实战总结:写 ISR 的黄金法则

说了这么多,我总结几条黄金法则,你写 ISR 时照着做就行:

  • 法则一: 加对编译器属性声明,别让编译器乱优化。
  • 法则二: ISR 里只做最必要的事,复杂逻辑扔出去。
  • 法则三: 别在 ISR 里调函数,尤其是那些你不知道内部实现的。
  • 法则四: 别关中断,除非你非常清楚后果。
  • 法则五: 用标志位或消息队列跟主循环通信。

嗯,ISR 的编写规范就这些。说白了,就是「快进快出,别拖泥带水」。你想想看,中断本来就是用来处理紧急事件的,你却在里面磨磨蹭蹭,那还叫中断吗?

下一章咱们聊聊任务调度,看看怎么把 ISR 里收集的数据,交给任务去处理。到时候你会发现,ISR 写得规范,任务调度才能跑得顺畅。