1. 中断系统基础:中断的概念、中断向量表、中断优先级与嵌套
各位同学,咱们今天聊聊中断系统。说实话,中断这玩意儿是嵌入式系统的灵魂之一。我做了十几年驱动开发,可以负责任地告诉你——不懂中断,你写出来的代码大概率是“能用但不好用”。
1.1 中断到底是什么?
中断,说白了就是CPU正在干一件事,突然被另一件事打断,先去处理那个紧急的事,处理完再回来接着干。你想想看,这像不像你正在写代码,突然领导喊你去开会?开完会回来继续写。
在硬件层面,中断是由外设或内部事件触发的信号。比如:
- 串口收到一个字节
- 定时器计数到了
- GPIO引脚电平变化
- DMA传输完成
这些事件发生时,硬件会拉高一根中断请求线(IRQ),CPU检测到后就会暂停当前任务,跳转到中断服务程序(ISR)去处理。
核心要点:中断机制让CPU从“轮询等待”中解放出来。没有中断,你的CPU就得不停地去问“串口有数据吗?有数据吗?”,效率极低。有了中断,CPU可以安心干别的活,等数据来了再被叫醒。
我在项目中遇到过这样一个坑:有个同事用轮询方式接收串口数据,结果CPU占用率飙到80%以上,系统响应慢得像蜗牛。改成中断后,CPU占用率直接降到5%。嗯,这就是中断的价值。
1.2 中断向量表——中断的“通讯录”
中断向量表,你可以把它理解成一本通讯录。每个中断源都有一个对应的编号,CPU收到中断信号后,就去查这个通讯录,找到对应的ISR入口地址,然后跳过去执行。
这张表通常放在芯片存储器的固定位置,比如ARM Cortex-M系列默认放在0x00000000地址。表里每一项是一个32位的地址指针,指向对应的中断服务函数。
| 中断编号 | 中断源 | 向量地址 | ISR函数 |
|---|---|---|---|
| 0 | 复位 | 0x00000000 | Reset_Handler |
| 1 | NMI | 0x00000004 | NMI_Handler |
| 16 | SysTick | 0x00000040 | SysTick_Handler |
| 32 | UART1 | 0x00000080 | UART1_IRQHandler |
我个人习惯在启动文件里把中断向量表定义好,然后在C代码里用weak属性声明弱函数。这样你只需要实现用到的中断函数,没实现的会自动链接到默认的“死循环”函数,方便调试。
// 启动文件中的向量表定义示例
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
&_estack, // 0: 栈顶指针
Reset_Handler, // 1: 复位
NMI_Handler, // 2: NMI
HardFault_Handler, // 3: 硬错误
// ... 其他中断
UART1_IRQHandler, // 串口1中断
DMA1_Channel1_IRQHandler, // DMA通道1中断
};
小技巧:调试时可以在向量表里临时把某个中断指向一个空函数,这样即使外设误触发中断,也不会导致系统崩溃。我曾经用这招排查过一个GPIO抖动导致的中断风暴问题。
1.3 中断优先级——谁更紧急谁先上
多个中断同时发生时,谁先处理?这就涉及到优先级了。每个中断源都有一个优先级数值,数值越小优先级越高(有些芯片是反的,注意看手册)。
常见的优先级模型有两种:
- 固定优先级:中断号越小优先级越高,简单粗暴
- 可编程优先级:软件可以配置每个中断的优先级,灵活性强
ARM Cortex-M系列用的是可编程优先级,而且支持抢占优先级和子优先级。抢占优先级决定能否打断正在执行的中断,子优先级决定同抢占优先级下的处理顺序。
// STM32中断优先级配置示例
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
{
uint32_t prioritygroup = 0x04; // 4位抢占优先级,0位子优先级
uint32_t preempt = PreemptPriority << 4;
uint32_t sub = SubPriority & 0x0F;
uint32_t priority = preempt | sub;
NVIC->IP[IRQn] = (uint8_t)((priority << 4) & 0xFF);
}
为什么会这样设计?说白了就是为了应对复杂场景。比如你的系统里既有高实时性的电机控制(必须立刻响应),又有不太紧急的按键扫描(晚几毫秒没事),通过优先级配置就能让CPU“先救火,再扫地”。
注意:优先级配置不是越多越好。我见过有人把32个中断分了32个优先级,结果调试时自己都搞不清谁先谁后。建议根据实时性要求分3~5个等级就够了:紧急(电机、故障)、中等(通信、定时器)、普通(按键、显示)。
1.4 中断嵌套——中断里还能有中断
中断嵌套,就是高优先级的中断可以打断正在执行的低优先级中断。你想想看,这就像你在开会(低优先级中断),突然接到老板电话(高优先级中断),你得先接老板电话,说完再继续开会。
中断嵌套的流程是这样的:
- CPU正在执行主程序
- 低优先级中断触发,CPU跳转到ISR_Low
- ISR_Low执行到一半,高优先级中断触发
- CPU暂停ISR_Low,保存现场,跳转到ISR_High
- ISR_High执行完毕,恢复现场,继续执行ISR_Low
- ISR_Low执行完毕,返回主程序
这里有个关键点:中断嵌套的深度是有限制的。每个中断都会消耗栈空间来保存现场,嵌套太深可能导致栈溢出。我曾经在一个项目中遇到诡异的重启问题,查了两天才发现是中断嵌套太深,栈空间不够了。
经验之谈:中断服务程序要“短小精悍”。ISR里只做最必要的事——比如读数据、清标志、发信号。复杂的处理放到主循环或任务里去。我见过有人把整个协议栈解析放在中断里,结果系统响应时间直接崩了。
1.5 知识体系总览
下面这张图把中断系统的核心逻辑串起来了,你可以对照着理解:
这张图把中断系统的四个核心模块串在了一起。你可以看到,中断概念是基础,向量表是桥梁,优先级是规则,嵌套是进阶应用。四者缺一不可。
我的建议:刚开始学中断时,别急着搞嵌套和复杂优先级。先跑通一个简单的中断(比如定时器中断),理解“触发→响应→处理→返回”这个完整流程。等基础打牢了,再慢慢加优先级和嵌套。我当年就是太心急,一上来就搞DMA+中断嵌套,结果调试了整整一周才跑通。
好了,中断系统的基础就讲到这里。记住一句话:中断是嵌入式系统的“事件驱动”核心。你把它理解透了,后面学DMA、定时器、外设驱动都会轻松很多。
可可精品资料库