中断系统入门: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. 中断嵌套与优先级分组关系图
下面这张图帮你理清中断嵌套的判决流程。我画了很多遍才满意,你仔细看。
6. 避坑指南:优先级配置常见错误
- 忘记设置PRIGROUP:默认值可能是0(7位抢占+1位子优先级),导致你设的优先级和预期不符。我建议在系统初始化时显式设置一次。
- 优先级数值理解反了:0最高,255最低。有人习惯性认为255最高,结果高优先级中断被低优先级阻塞。
- 中断使能顺序:先设优先级,再使能中断。否则中断可能在设置优先级前就触发,导致意外行为。
- SPI中断与DMA中断优先级冲突:如果SPI用DMA传输,DMA中断优先级必须高于或等于SPI中断,否则可能丢数据。