中断系统入门:ARM Cortex-M内核中断向量表、NVIC控制器工作原理、中断优先级分组规则

各位同学,咱们今天聊聊中断系统的底层逻辑。很多工程师写SPI驱动时,中断配不好,通信就乱套。其实根子就在对Cortex-M的中断机制理解不透。别急,我带你一层层剥开。

1. 中断向量表:CPU的“紧急通讯录”

中断向量表,说白了就是一张地址表。CPU遇到中断事件后,该跳到哪里去执行代码?查这张表就行。

在Cortex-M内核中,向量表默认放在起始地址 0x00000000。表里每一项占4字节,存的是对应中断服务函数(ISR)的入口地址。

向量表结构(以STM32F4为例)

  • 第0项:栈顶指针(MSP初始值)
  • 第1项:Reset_Handler(复位入口)
  • 第2项:NMI_Handler
  • 第3项:HardFault_Handler
  • 第4~15项:系统异常
  • 第16项起:外部中断(如SPI、UART、TIM等)

我记得刚做第一个项目时,芯片一上电就跑飞。查了半天,发现是向量表偏移没配好。嗯,这里要注意:如果你把程序放在0x08000000(Flash),必须设置 SCB->VTOR = 0x08000000,否则CPU找不到中断入口。

2. NVIC控制器:中断的“交通警察”

NVIC(Nested Vectored Interrupt Controller),嵌套向量中断控制器。它管三件事:使能/禁止中断、挂起/清除中断、配置优先级。

你想想看,如果所有中断同时发生,谁先执行?NVIC说了算。它内部维护了一个优先级判决逻辑,每个外部中断都有一个8位优先级寄存器(实际有效位取决于芯片实现)。

个人习惯:我一般把NVIC的优先级寄存器当成“8位宽度,但只用高4位”。这样分组时更清晰,不容易搞混。

NVIC的核心寄存器就那么几个:

寄存器 功能
ISER[0..7] 中断使能设置寄存器(写1使能)
ICER[0..7] 中断清除寄存器(写1禁止)
ISPR[0..7] 中断挂起设置寄存器(软件触发中断)
ICPR[0..7] 中断挂起清除寄存器
IPR[0..59] 中断优先级寄存器(每个中断占8位)

操作很简单:想使能SPI中断,就写 NVIC->ISER[SPI_IRQn >> 5] |= (1 << (SPI_IRQn & 0x1F))。别嫌麻烦,底层就这么干。

3. 中断优先级分组规则:抢占优先级 vs 子优先级

这是最容易踩坑的地方。Cortex-M把优先级分成两组:抢占优先级(Preempt Priority)和子优先级(Sub Priority)。

怎么分?靠 SCB->AIRCR 寄存器的PRIGROUP位段。Cortex-M3/M4支持3位分组,M0/M0+只支持2位。具体规则如下:

PRIGROUP值 抢占优先级位数 子优先级位数 优先级分组
0 7 1 抢占优先
1 6 2 抢占优先
2 5 3 抢占优先
3 4 4 抢占优先
4 3 5 抢占优先
5 2 6 抢占优先
6 1 7 抢占优先
7 0 8 全部为子优先级

我曾经踩过的坑:项目里用了PRIGROUP=3(4位抢占+4位子优先级),结果两个中断的抢占优先级相同,子优先级不同。我以为高子优先级能打断低子优先级——错了!只有抢占优先级不同才能嵌套。子优先级只在同抢占优先级时决定谁先执行,不能打断。

所以,配置优先级时记住一句话:抢占优先级决定能否嵌套,子优先级决定排队顺序。

4. 优先级配置实战:HAL库 vs 寄存器操作

HAL库封装了 HAL_NVIC_SetPriority(),但底层还是操作IPR寄存器。我建议你至少看一遍寄存器级代码,心里有底。

// 设置SPI1中断优先级:抢占优先级2,子优先级1
// 假设PRIGROUP=3(4位抢占+4位子优先级)
// 优先级值 = (抢占优先级 << 子优先级位数) | 子优先级
// 这里子优先级位数=4,所以:
// 优先级值 = (2 << 4) | 1 = 0x21

NVIC_SetPriority(SPI1_IRQn, 0x21);
NVIC_EnableIRQ(SPI1_IRQn);

注意:数值越小,优先级越高。0是最高优先级,255是最低(但实际有效位只用到高几位)。

我的习惯:系统滴答定时器(SysTick)我固定给抢占优先级0,PendSV给抢占优先级15(最低)。这样任何中断都不会阻塞系统心跳,而PendSV只在空闲时执行上下文切换。

5. 中断嵌套与优先级分组关系图

下面这张图帮你理清中断嵌套的判决流程。我画了很多遍才满意,你仔细看。

中断嵌套判决流程 中断事件发生 NVIC查询中断优先级 抢占优先级 > 当前正在执行的中断? 是 嵌套执行新中断 否 等待当前中断完成 关键点: • 只有抢占优先级更高的中断才能嵌套打断当前中断 • 子优先级只在同抢占优先级时决定谁先执行,不能嵌套 • 优先级数值越小,实际优先级越高(0最高)

6. 避坑指南:优先级配置常见错误

  • 忘记设置PRIGROUP:默认值可能是0(7位抢占+1位子优先级),导致你设的优先级和预期不符。我建议在系统初始化时显式设置一次。
  • 优先级数值理解反了:0最高,255最低。有人习惯性认为255最高,结果高优先级中断被低优先级阻塞。
  • 中断使能顺序:先设优先级,再使能中断。否则中断可能在设置优先级前就触发,导致意外行为。
  • SPI中断与DMA中断优先级冲突:如果SPI用DMA传输,DMA中断优先级必须高于或等于SPI中断,否则可能丢数据。