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 ① 中断使能检查 (ISER) ② 挂起管理 (ISPR/ICPR) ③ 优先级裁决 (IPR) 允许响应 CPU 内核 响应中断 挂起队列(等待中) 优先级排序 高优先级抢占 图例: 外设 NVIC 处理 CPU 挂起队列 抢占路径

从图里你能看到,外设发来中断请求后,NVIC 先检查使能位,再管理挂起状态,最后根据优先级决定是否送到 CPU。如果 CPU 正在忙,中断就进挂起队列排队。

3.7 避坑指南

最后,分享几个我踩过的坑:

  • 使能位没清干净:我曾经在复用中断时,忘了清除之前的中断使能,导致两个中断源共用一个中断号,互相干扰。建议每次配置前先 NVIC_DisableIRQ 一下。
  • 挂起标志没处理:如果中断触发频率极高,挂起位可能一直被置位。这时候可以考虑在中断服务程序里加一个计数,超过阈值就主动清除挂起。
  • 优先级配置冲突:NVIC 支持抢占优先级和子优先级,配置错了会导致中断响应顺序混乱。我一般习惯把所有中断的抢占优先级都设成不同值,避免嵌套太复杂。

嗯,NVIC 的内容就讲到这里。记住一句话:中断使能是“开关”,挂起是“等待区”,优先级是“裁判”。搞懂这三样,NVIC 你就拿捏了。