3. 中断向量表:中断向量表的定义、中断服务函数(ISR)的编写规范
中断向量表,说白了就是一张“紧急事件处理人员名单”。
CPU 平时按部就班地执行主程序,突然某个外设(比如定时器、串口)发出了中断请求。CPU 得知道该跳到哪里去处理这个请求。这时候,中断向量表就派上用场了——它告诉 CPU:每种中断对应哪个处理函数,地址是多少。
3.1 中断向量表的定义与结构
中断向量表通常放在程序存储器的起始地址(比如 0x0000 或 0x00000000)。每个中断源占用一个固定位置,里面存放的是该中断服务函数(ISR)的入口地址。
我刚开始做项目时,总觉得向量表是编译器自动搞定的,不用管。直到有一次调试一个串口接收丢数据的问题,查了两天才发现是向量表被意外覆盖了……从那以后,我对向量表的态度就认真多了。
| 中断源 | 向量地址(以 STM32F1 为例) | 说明 |
|---|---|---|
| 复位 | 0x0000_0004 | 系统上电或复位后跳转 |
| NMI | 0x0000_0008 | 不可屏蔽中断 |
| HardFault | 0x0000_000C | 硬件错误处理 |
| SysTick | 0x0000_003C | 系统滴答定时器 |
| EXTI0 | 0x0000_0058 | 外部中断线 0 |
| TIM1_UP | 0x0000_006C | 定时器 1 更新中断 |
这张表是硬件设计时就定好的。你写代码时,只需要把对应的 ISR 函数名放到正确的位置。编译器会帮你完成地址填充。
核心要点:中断向量表是 CPU 与中断服务函数之间的“桥梁”。桥断了,中断就进不去。
3.2 中断服务函数(ISR)的编写规范
写 ISR 和写普通函数不一样。我见过不少新手把 ISR 写成几百行的庞然大物,结果系统响应慢得像蜗牛。嗯,这里要注意几个硬性规则。
3.2.1 ISR 的基本结构
void TIM2_IRQHandler(void)
{
// 1. 检查中断标志位
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
// 2. 用户处理逻辑(尽量精简)
led_toggle();
// 3. 清除中断标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
三步走:检查标志 → 处理 → 清除标志。缺一不可。
我曾经遇到过一个 bug,ISR 里忘了清标志位,结果 CPU 一直卡在中断里出不来,主程序完全瘫痪。那次之后,我每次写完 ISR 都会反复确认标志位有没有清掉。
3.2.2 ISR 的编写铁律
- 短小精悍:ISR 里只做最紧急的事。比如接收一个字节、设置一个标志位。复杂计算、延时、打印都放到主循环里做。
- 避免阻塞:不要在 ISR 里用 while 等待、delay 延时、或者调用可能阻塞的函数(比如 printf)。
- 保护共享数据:如果 ISR 和主循环共享变量,记得加 volatile 关键字,必要时关中断保护。
- 不要嵌套太深:除非你很清楚自己在做什么,否则尽量保持中断优先级简单,避免多层嵌套导致栈溢出。
避坑指南:我曾经在一个电机控制项目里,ISR 里调用了 printf 打印调试信息。结果 printf 本身用了串口中断,形成了中断嵌套死锁。系统跑几秒就死机。后来改成用 DMA + 环形缓冲区,问题才解决。
3.2.3 ISR 的命名与声明
不同 MCU 的 ISR 命名规则不一样。以常见的 Cortex-M 内核为例:
| MCU 系列 | ISR 命名示例 | 说明 |
|---|---|---|
| STM32 (HAL) | HAL_GPIO_EXTI_Callback() |
弱函数,用户重写 |
| STM32 (标准库) | USART1_IRQHandler() |
直接写中断处理函数 |
| AT32 | TMR1_OVF_IRQHandler() |
类似 STM32 风格 |
| GD32 | USART0_IRQHandler() |
与 STM32 兼容 |
你想想看,如果函数名写错了,编译器不会报错,但中断向量表里就找不到你的 ISR。中断来了,CPU 跳到一个空地址,系统直接跑飞。这种 bug 最难查。
我的习惯:每次新建工程,我都会先打开启动文件(startup_xxx.s),把里面所有的中断向量名过一遍。然后在代码里用 __attribute__((weak)) 或者直接写空函数,确保每个中断都有默认处理。这样即使忘了写某个 ISR,系统也不会崩溃。
3.3 中断向量表与 ISR 的协作流程
为了让你更直观地理解整个过程,我画了一张流程图:
这张图把整个流程串起来了。你注意看,从“中断发生”到“执行 ISR”,中间只经过“保存现场”和“查向量表”两步。这就是中断响应快的原因——硬件帮你做了大部分工作。
3.4 实战中的注意事项
- 向量表重映射:有些场景(比如 Bootloader + App 架构)需要把向量表搬到 RAM 里。Cortex-M 系列可以通过
SCB->VTOR寄存器设置偏移量。我做过一个远程升级项目,就是靠这个实现的。 - 中断优先级分组:STM32 支持抢占优先级和子优先级。配置错了会导致低优先级中断抢了高优先级的事。我的建议是:统一用一种分组方式,别混用。
- ISR 的栈使用:ISR 里尽量少用局部大数组。栈空间有限,一不小心就溢出了。我习惯在 ISR 里只设标志位,数据处理放主循环。
总结一下:中断向量表是硬件与软件之间的契约。你按规矩填表,CPU 按规矩跳转。ISR 写得好,系统稳如泰山;ISR 写得烂,系统随时翻车。