3、中断控制器:NVIC(嵌套向量中断控制器)工作原理、中断使能、中断挂起
好,咱们接着聊中断。上一节讲了中断的宏观流程,这一节咱们深入看看中断的“大脑”——NVIC。
NVIC,全称嵌套向量中断控制器。名字挺长,但说白了,它就是 Cortex-M 内核里专门管中断的那个硬件模块。我刚开始接触 STM32 时,总觉得中断就是“来了就跳转”,后来发现远没那么简单。NVIC 的职责包括:哪个中断能进来、哪个中断优先级高、哪个中断正在排队、哪个中断被挂起了……全归它管。
3.1 NVIC 的核心职责
我个人习惯把 NVIC 理解成一个“中断调度中心”。它主要干三件事:
- 中断使能控制:决定某个中断源是否被允许触发 CPU。
- 中断挂起管理:当中断来了但 CPU 暂时处理不了,就把它“挂起”等着。
- 中断优先级裁决:多个中断同时来,谁先上?靠优先级说话。
你想想看,如果没有 NVIC,所有中断都直接连到 CPU 上,那系统早就乱套了。嗯,这里要注意,NVIC 是 Cortex-M 内核的一部分,不同厂商(ST、NXP、GD 等)的芯片虽然外设不同,但 NVIC 的寄存器接口是统一的。
3.2 中断使能:让中断“活”过来
每个中断源都有一个对应的使能位。只有使能了,中断信号才能从外设传到 NVIC,再传到 CPU。
我曾经在调试一个电机驱动项目时,发现定时器中断死活不触发。查了半天,原来是忘了使能 NVIC 里的对应位。外设的中断已经开了,但 NVIC 这关没放行,信号就卡在半路了。
使能中断的代码很简单,以 STM32 为例:
// 使能 USART1 中断
NVIC_EnableIRQ(USART1_IRQn);
这个 USART1_IRQn 是中断号,芯片厂商已经在头文件里定义好了。你只需要调用这个函数,NVIC 就会把对应中断的使能位置 1。
核心要点:中断使能是“两级开关”——外设级(外设自己的中断使能位)和 NVIC 级(NVIC 的使能位)。两个都得打开,中断才能最终到达 CPU。
3.3 中断挂起:中断的“等待区”
有时候中断来了,但 CPU 正在处理更高优先级的事情,怎么办?NVIC 会把中断请求“挂起”,也就是存到一个挂起寄存器里。等 CPU 有空了,再根据优先级决定什么时候处理。
挂起状态是 NVIC 内部的一个标志位。你可以通过软件主动挂起一个中断,也可以查询某个中断是否处于挂起状态。
举个例子:
// 软件触发挂起(常用于测试)
NVIC_SetPendingIRQ(USART1_IRQn);
// 查询是否挂起
uint32_t pending = NVIC_GetPendingIRQ(USART1_IRQn);
我记得有一次调试一个通信协议,需要模拟中断触发场景。直接用硬件信号太麻烦,我就用 NVIC_SetPendingIRQ 软件触发挂起,省了不少事。
小技巧:如果你发现某个中断一直不响应,可以读一下挂起寄存器,看看中断请求到底有没有到达 NVIC。这比猜来猜去高效多了。
3.4 中断挂起的清除
当中断被 CPU 响应后,NVIC 会自动清除挂起标志。但有些特殊情况,比如你在中断服务程序里想手动清除挂起,也是可以的:
// 清除挂起标志
NVIC_ClearPendingIRQ(USART1_IRQn);
为什么要手动清除?我遇到过一种情况:中断触发频率特别高,CPU 处理不过来,导致挂起位一直被置着。这时候手动清除一下,可以避免重复进入中断。
警告:不要随意清除挂起标志,除非你非常清楚自己在做什么。否则可能导致中断丢失,系统行为异常。
3.5 NVIC 的寄存器结构
NVIC 的寄存器其实不多,但每个都很关键。我整理了一张表,方便你对照:
| 寄存器 | 功能 | 位宽 |
|---|---|---|
| ISER | 中断使能设置寄存器 | 32位 |
| ICER | 中断使能清除寄存器 | 32位 |
| ISPR | 中断挂起设置寄存器 | 32位 |
| ICPR | 中断挂起清除寄存器 | 32位 |
| IABR | 中断活动状态寄存器 | 32位 |
| IPR | 中断优先级寄存器 | 8位/中断 |
每个寄存器对应 32 个中断(一位控制一个)。如果中断数超过 32,就会有多个相同的寄存器实例。比如 ISER0、ISER1……
3.6 核心逻辑:一张图看懂 NVIC
说了这么多,咱们用一张图把 NVIC 的工作流程串起来。我画了个 SVG,你看一眼就明白了:
从图里你能看到,外设发来中断请求后,NVIC 先检查使能位,再管理挂起状态,最后根据优先级决定是否送到 CPU。如果 CPU 正在忙,中断就进挂起队列排队。
3.7 避坑指南
最后,分享几个我踩过的坑:
- 使能位没清干净:我曾经在复用中断时,忘了清除之前的中断使能,导致两个中断源共用一个中断号,互相干扰。建议每次配置前先
NVIC_DisableIRQ一下。 - 挂起标志没处理:如果中断触发频率极高,挂起位可能一直被置位。这时候可以考虑在中断服务程序里加一个计数,超过阈值就主动清除挂起。
- 优先级配置冲突:NVIC 支持抢占优先级和子优先级,配置错了会导致中断响应顺序混乱。我一般习惯把所有中断的抢占优先级都设成不同值,避免嵌套太复杂。
嗯,NVIC 的内容就讲到这里。记住一句话:中断使能是“开关”,挂起是“等待区”,优先级是“裁判”。搞懂这三样,NVIC 你就拿捏了。