中断机制基础:中断向量、优先级、使能与屏蔽、嵌套
各位同学,咱们今天聊聊中断机制的四大支柱。说白了,这四样东西就像中断系统的“四肢”,缺了谁都会出问题。我做了十几年嵌入式,每次遇到奇怪的中断延迟问题,回来一查,十有八九是这四个地方没搞对。
中断向量:CPU识别中断源的“门牌号”
中断向量是什么?就是一张表,CPU一查就知道哪个外设喊它。每个中断源对应一个固定的向量号,CPU拿到这个号,跳转到对应的服务函数(ISR)。
| 中断源 | 向量号(ARM Cortex-M) | 典型优先级 |
|---|---|---|
| 复位 | 1 | 最高(-3) |
| NMI | 2 | -2 |
| DMA完成中断 | 16+n(依MCU) | 可配置 |
| 定时器 | ... | ... |
我个人习惯,启动时就把向量表基址配好。千万别在主循环里改它——我见过有人在运行期动态改向量表,结果中断一来CPU直接跑飞,那叫一个惨。
中断优先级:谁更紧急,谁先跑
优先级这个事儿吧,很多同学以为“数值越小优先级越高”就是一切。其实背后还有分组、抢占、子优先级等等坑。你想想看,如果两个中断同时来,谁的优先级高谁先执行。可如果高优先级的中断正在跑,低优先级的来了怎么办?——这就是抢占(即中断嵌套)。
我建议你画一张表,把项目中所有中断优先级提前规划好。不要让DMA中断和定时器中断抢优先级,否则你的DMA完成响应可能被卡死。
// 以STM32为例,设置优先级分组为4位抢占 + 0位子优先级
NVIC_SetPriorityGrouping(3); // 4 bits for pre-emption priority
NVIC_SetPriority(DMA1_Stream0_IRQn, 0); // 最高抢占优先级
NVIC_SetPriority(TIM2_IRQn, 1); // 次高
NVIC_SetPriority(USART1_IRQn, 2); // 最低
中断使能与屏蔽:开关控制与临界区
使能中断很简单:设置对应的寄存器位。但屏蔽呢?我见过不少人把全局中断一关就是几十微秒,结果DMA完成中断错过了死等。这里有个技巧:用局部屏蔽而不是全局屏蔽。比如你只想屏蔽定时器中断,那就操作NVIC的对应清除使能位,别动PRIMASK。
- 全局屏蔽:__disable_irq() / __enable_irq() —— 慎用,时间尽量短
- 局部屏蔽:NVIC_DisableIRQ() / NVIC_EnableIRQ() —— 只关某个中断
- 优先级屏蔽:BASEPRI寄存器 —— 屏蔽低于某优先级的中断,高于的继续跑
中断嵌套:高优打断低优
嵌套就是正在处理一个中断时,来了更高优先级的中断,CPU暂停当前ISR去处理新的。等新的处理完,再回来继续老的ISR。这本身是好特性,但处理不好会导致堆栈溢出或者优先级反转。
嗯,这里要注意:嵌套深度不要太深。我曾经在一个项目里嵌套了5层,结果堆栈只有512字节,直接溢出。从那以后我规定嵌套不超过3层,且每个ISR都静态检测堆栈余量。
// 嵌套流程示例(伪代码)
ISR_LowPriority:
save context
enable higher priority interrupts // 允许嵌套
handle interrupt
disable higher priority interrupts
restore context
return from interrupt
ISR_HighPriority:
// 直接执行,因为优先级高,CPU已自动关嵌套
handle interrupt
return from interrupt
SVG:中断处理流程全景图
我把这四者的关系画在一张图里,你一看就明白。
你看这条线:从DMA触发中断,先查向量表找到ISR入口,接着优先级判定决定它能不能立即执行,然后使能/屏蔽检查——如果没使能就丢掉。接下来是嵌套检查,最后才执行ISR。这每一步都影响延迟。
- 向量表表项必须正确、对齐
- 优先级分组和数值提前规划
- 全局屏蔽时间要极短,多用BASEPRI
- 嵌套深度设上限,并监控栈使用
好了,中断机制这四样东西,搞清楚了,DMA响应延迟就有了基础保障。下一步我们来看具体怎么测延迟、找瓶颈。