4. 硬件基础:中断系统基础(NVIC、优先级、中断向量表)
中断系统,说白了就是CPU的“紧急事务处理机制”。没有它,你的单片机只能傻乎乎地轮询——就像你每隔5秒去检查一下邮箱,而不是等邮件到了铃声响了再去看。我刚开始做嵌入式时,总觉得中断就是个“跳转”,后来踩了坑才明白,这里面门道深着呢。
4.1 中断到底是个什么玩意儿?
想象一下你正在写代码,突然有人拍你肩膀。你停下手中的活,记下当前写到哪一行,然后转头处理那个人的事。处理完了,再回头接着写。这就是中断的完整流程:暂停 → 保存现场 → 处理中断 → 恢复现场 → 继续执行。
在单片机里,这个“拍肩膀”的动作,就是硬件给你CPU发的一个电信号。ADC转换完成、定时器溢出、串口收到数据……这些都能触发中断。我个人习惯把中断看作“硬件主动通知软件”的唯一高效途径。
4.2 NVIC:中断的“总调度官”
NVIC(Nested Vectored Interrupt Controller),嵌套向量中断控制器。名字很长,但功能很纯粹:管理所有中断的使能、挂起、优先级和响应顺序。它是ARM Cortex-M系列内核的标配,也是我们做ADC中断+DMA协同调度的基石。
NVIC能干几件事:
- 使能/禁止中断——决定哪个外设能“拍肩膀”
- 设置优先级——决定谁先被响应
- 处理中断嵌套——高优先级可以打断低优先级
- 管理挂起状态——中断来了但CPU正忙,先记着
我记得第一次用STM32时,以为只要使能中断就能跑,结果发现中断响应顺序完全不对。后来查手册才意识到,NVIC的优先级分组没配好。嗯,这里要注意:优先级分组一旦设定,整个系统都得按这个规则来。
4.3 优先级:抢占优先级 vs 响应优先级
Cortex-M的优先级分两档:抢占优先级和响应优先级(也叫子优先级)。
| 概念 | 作用 | 类比 |
|---|---|---|
| 抢占优先级 | 决定能否打断正在执行的中断 | VIP通道,来了就能插队 |
| 响应优先级 | 同抢占优先级时,决定谁先执行 | 排队顺序,不能插队 |
举个例子:中断A的抢占优先级为0,响应优先级为1;中断B的抢占优先级为1,响应优先级为0。那么A可以打断B,因为A的抢占优先级更高。但如果A和B的抢占优先级相同(比如都是1),那就看响应优先级,谁小谁先执行,且不能互相打断。
4.4 优先级分组:4位怎么分?
Cortex-M用4位二进制数表示优先级,但这4位怎么拆成“抢占”和“响应”两部分,由优先级分组寄存器(PRIGROUP)决定。常见的分组方式有5种:
| 分组编号 | 抢占优先级位数 | 响应优先级位数 | 抢占优先级等级数 | 响应优先级等级数 |
|---|---|---|---|---|
| 0 | 0 | 4 | 1 | 16 |
| 1 | 1 | 3 | 2 | 8 |
| 2 | 2 | 2 | 4 | 4 |
| 3 | 3 | 1 | 8 | 2 |
| 4 | 4 | 0 | 16 | 1 |
你想想看,如果你的系统里中断数量不多,但每个中断的实时性要求差异很大,那就选分组4(16级抢占,无子优先级)。如果中断很多,但实时性要求差不多,那就选分组0(所有中断同一抢占级,靠子优先级排队)。
4.5 中断向量表:中断的“电话簿”
中断向量表,就是一张地址映射表。每个中断源对应一个固定的表项,表项里存放的是该中断服务函数(ISR)的入口地址。CPU收到中断信号后,直接查表跳转,不需要if-else判断。
这张表通常放在Flash的起始地址(0x00000000),但Cortex-M允许你把它重映射到SRAM或其他位置。为什么需要重映射?
- Bootloader场景:APP程序需要把向量表搬到自己的起始地址
- 动态更新ISR:运行时修改中断服务函数(很少用,但确实有)
- 双Bank启动:从备份区启动时,向量表指向备份区
在STM32上,重映射向量表只需要写一行代码:
SCB->VTOR = (uint32_t)APP_BASE_ADDRESS;
就这么简单。但要注意:向量表必须256字节对齐,因为VTOR寄存器的低9位是保留位。我见过有人把地址写成0x08010001,结果中断全乱套了——因为地址没对齐,VTOR直接忽略了低9位。
4.6 中断响应流程:从硬件到软件
一个完整的中断响应,硬件和软件各干各的活:
- 硬件自动完成:
- 压栈:自动把R0-R3、R12、LR、PC、xPSR压入当前栈
- 取向量:从向量表中读取ISR地址
- 更新PC:跳转到ISR执行
- 软件需要完成:
- 保存现场:如果ISR中使用了R4-R11,需要手动压栈
- 清除中断标志:避免重复触发
- 处理中断逻辑:读取ADC数据、启动DMA传输等
- 恢复现场:出栈,返回主程序
这里有个细节:硬件压栈只压8个寄存器,如果你的ISR里调用了函数,编译器会自动生成额外的压栈/出栈代码。所以ISR越短越好,我一般只在ISR里设一个标志位,真正的处理放到主循环或任务里。
4.7 中断优先级与DMA的协同关系
在ADC+DMA的系统中,中断优先级的设计直接影响数据采集的可靠性。我一般遵循以下原则:
- DMA传输完成中断:最高优先级。因为DMA传输完成意味着缓冲区已满或半满,必须尽快处理,否则新数据会覆盖旧数据。
- ADC转换完成中断:次高优先级。如果ADC是连续转换模式,转换完成中断会频繁触发,优先级太高会饿死其他中断。
- 其他外设中断:根据实时性需求分配,但不要高于DMA中断。
你想想看,如果ADC转换完成中断的优先级高于DMA传输完成中断,会发生什么?ADC每转换一次就触发中断,而DMA传输完成中断一直被推迟。等到DMA缓冲区满了,数据还没来得及处理就被新数据覆盖了。这就是典型的优先级设计失误。
4.8 本章知识结构图
下面这张图展示了中断系统的核心组成和它们之间的关系:
4.9 小结
中断系统是实时嵌入式系统的基石。NVIC负责调度,优先级决定响应顺序,中断向量表提供跳转目标。这三者配合好了,你的ADC中断和DMA传输才能协同工作,不丢数据、不卡顿。
我个人建议,在开始写代码之前,先把优先级分组和每个中断的优先级画出来。用一张表格列清楚:中断源、抢占优先级、响应优先级、ISR名称。这样写代码时思路清晰,调试时也容易定位问题。