2、中断基础概念:ARM Cortex-M4/M7中断向量表、NVIC配置、中断优先级与抢占
各位同学,咱们今天聊聊中断。说实话,搞嵌入式开发,中断是绕不开的坎。你想想看,一个飞控系统要同时处理传感器数据、控制电机、接收遥控信号,没有中断机制,CPU得忙死。我刚开始做PX4驱动时,就吃过中断配置不对的亏——电机转着转着突然卡死,查了两天才发现是优先级设错了。
2.1 中断向量表:CPU的“紧急通讯录”
ARM Cortex-M4/M7内核启动时,第一件事就是找中断向量表。这玩意儿说白了就是一张地址列表,告诉CPU每种中断来了该跳到哪里去执行。
向量表通常放在Flash的起始地址(0x00000000),但也可以重定位到SRAM。我个人习惯把向量表放在SRAM里,这样可以在运行时动态修改——虽然实际项目中很少这么干,但调试时特别方便。
向量表结构(以STM32F4为例):
- 偏移0x00:栈顶指针(MSP初始值)
- 偏移0x04:复位向量(Reset_Handler)
- 偏移0x08:NMI异常
- 偏移0x0C:硬错误(HardFault)
- ... 其他系统异常 ...
- 偏移0x40之后:外部中断(IRQ0~IRQn)
// 典型的向量表定义(汇编启动文件)
__Vectors DCD __initial_sp ; 栈顶指针
DCD Reset_Handler ; 复位
DCD NMI_Handler ; NMI
DCD HardFault_Handler ; 硬错误
; ... 省略中间 ...
DCD TIM2_IRQHandler ; TIM2中断
DCD TIM3_IRQHandler ; TIM3中断
; ... 更多中断 ...
嗯,这里要注意:向量表里每个条目占4字节,顺序绝对不能乱。我曾经见过有人把TIM2和TIM3的中断处理函数写反了,结果定时器中断触发时跑到了错误的函数里——飞控直接炸机。
2.2 NVIC配置:中断的“总调度中心”
NVIC(嵌套向量中断控制器)是ARM Cortex-M系列的核心外设。它负责管理所有中断的使能、挂起、优先级和抢占逻辑。
说白了,NVIC就是个交通警察。它决定哪个中断能插队,哪个得排队等着。
2.2.1 中断使能与屏蔽
每个中断都有独立的使能位。在NVIC中,通过ISER(中断设置使能寄存器)和ICER(中断清除使能寄存器)来控制。
// 使能TIM2中断
NVIC_EnableIRQ(TIM2_IRQn);
// 禁用TIM2中断
NVIC_DisableIRQ(TIM2_IRQn);
// 设置挂起位(软件触发中断)
NVIC_SetPendingIRQ(TIM2_IRQn);
// 清除挂起位
NVIC_ClearPendingIRQ(TIM2_IRQn);
我建议你在初始化外设时,先配置好所有中断参数,最后再使能。顺序搞反了容易产生意外中断——比如你还没设置好中断处理函数,中断就来了,直接跑飞。
2.2.2 中断优先级分组
Cortex-M4/M7支持最多256级优先级(实际芯片会裁剪,比如STM32只用了4位,共16级)。优先级分为抢占优先级和子优先级,通过PRIGROUP寄存器来分配位数。
| 分组 | 抢占优先级位数 | 子优先级位数 | 抢占优先级等级数 | 子优先级等级数 |
|---|---|---|---|---|
| NVIC_PriorityGroup_0 | 0 | 4 | 1 | 16 |
| NVIC_PriorityGroup_1 | 1 | 3 | 2 | 8 |
| NVIC_PriorityGroup_2 | 2 | 2 | 4 | 4 |
| NVIC_PriorityGroup_3 | 3 | 1 | 8 | 2 |
| NVIC_PriorityGroup_4 | 4 | 0 | 16 | 1 |
我的经验:在PX4飞控中,我通常使用NVIC_PriorityGroup_4(16级抢占优先级,无子优先级)。这样每个中断的优先级都是独立的,逻辑清晰。如果你用子优先级,嵌套逻辑会变得复杂,容易出bug。
2.3 中断优先级与抢占:谁说了算?
中断优先级决定了中断能否打断另一个中断。规则很简单:
- 抢占优先级高的中断可以打断抢占优先级低的中断
- 抢占优先级相同的中断,不能互相打断(按子优先级排队)
- 子优先级只在同抢占优先级的中断同时挂起时,决定谁先执行
为什么会这样?因为ARM Cortex-M内核的硬件设计就是这样——它只允许更高抢占优先级的中断进行嵌套。你想想看,如果两个同优先级的中断互相嵌套,那不就成死循环了?
避坑指南:我曾经在PX4的SPI驱动中,把DMA中断和定时器中断设成了相同抢占优先级。结果DMA传输完成时,定时器正好触发,两个中断同时挂起。由于优先级相同,NVIC按硬件编号顺序处理——先处理编号小的中断。这导致DMA数据读取延迟了几个微秒,传感器数据偶尔跳变。查了三天才找到原因。
2.3.1 中断优先级设置示例
// 设置中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// 设置TIM2中断优先级为2(抢占优先级2,无子优先级)
NVIC_SetPriority(TIM2_IRQn, 2);
// 设置DMA1中断优先级为1(抢占优先级1,高于TIM2)
NVIC_SetPriority(DMA1_Stream0_IRQn, 1);
// 设置UART中断优先级为3(抢占优先级3,低于TIM2)
NVIC_SetPriority(USART1_IRQn, 3);
数值越小,优先级越高。这是ARM的规定,别记反了。我刚开始做时总搞混,后来在代码里加了个注释才记住。
2.4 中断处理流程:从触发到返回
一个完整的中断处理流程包括:
- 中断触发:外设产生中断信号,NVIC检测到挂起位被置1
- 优先级判断:NVIC比较当前中断与正在执行的中断的优先级
- 压栈:如果允许抢占,CPU自动将xPSR、PC、LR、R0-R3、R12压入当前栈
- 取向量:从向量表中读取中断处理函数地址
- 执行ISR:跳转到中断服务函数执行
- 出栈:执行完ISR后,CPU自动恢复寄存器,返回被中断的代码
关键点:压栈和出栈是硬件自动完成的,不需要软件干预。但ISR中如果使用了R4-R11这些寄存器,需要手动保存和恢复——编译器通常会自动处理,但如果你写汇编ISR,就得自己操心。
2.5 中断延迟:你永远躲不开的问题
中断延迟是指从中断触发到ISR开始执行的时间。影响因素包括:
- 当前是否在执行更高优先级的中断
- CPU是否在执行不可中断的指令(如多周期乘法)
- 内存访问等待周期
- 中断向量表是否在SRAM(Flash有等待周期)
在PX4中,我要求所有中断延迟不超过1微秒。对于高优先级中断(如电机控制PWM),我会把向量表放在SRAM中,并禁用所有可屏蔽的中断嵌套——说白了就是让这个中断独占CPU,处理完再说。
小技巧:如果你发现中断响应慢,可以检查一下是否在ISR中调用了printf或malloc这类耗时函数。我见过有人把日志打印放在中断里,结果中断延迟从0.5us飙升到50us——飞控直接失控。
2.6 本章知识体系图
这张图把中断体系的三个核心模块串起来了。你想想看,向量表是入口,NVIC是调度器,优先级是规则——三者缺一不可。我在PX4中调试中断时,总是先检查向量表地址对不对,再看NVIC配置,最后确认优先级。按这个顺序排查,90%的问题都能解决。
最后提醒:中断服务函数要短小精悍。我见过有人把整个传感器数据处理放在中断里,结果中断执行时间超过100us,导致其他中断丢失。记住:中断里只做最紧急的事,比如读取数据寄存器、设置标志位。耗时的处理放到任务或主循环中。
公众号:蓝海资料掘金营,微信deep3321