一、中断基础概念
大家好,我是你们的嵌入式系统讲师。今天咱们聊聊中断——这个嵌入式开发里绕不开的核心话题。说实话,我做了十几年嵌入式开发,见过太多因为中断处理不当导致的“灵异事件”。
先问大家一个问题:你正在厨房炒菜,突然门铃响了,你会怎么做?
正常人会关小火,去开门,处理完门铃的事,再回来继续炒菜。对吧?
中断,说白了就是这个道理。
1.1 什么是中断
中断是CPU处理突发事件的一种机制。当外部设备或内部事件需要CPU立即处理时,它会“打断”CPU当前正在执行的任务,转而去处理这个紧急事件。
我刚开始学单片机时,总觉得中断是个很玄乎的东西。后来想通了——它就是CPU的“多任务处理能力”。
中断的核心流程:
- 中断请求:外设或内部模块发出信号,告诉CPU“我有事找你”
- 中断响应:CPU暂停当前任务,保存现场(就像你炒菜前记住火候大小)
- 中断处理:CPU跳转到中断服务程序(ISR)执行
- 中断返回:处理完毕,恢复现场,继续原来的工作
关键点:中断不是轮询!轮询是CPU主动去问“你有事吗?”,中断是外设主动告诉CPU“我有事!”
我在项目中遇到过这样一个坑:有个同事用轮询方式检测按键,结果CPU 99%的时间都在空转。换成中断后,功耗直接降了70%。嗯,这就是中断的价值。
1.2 中断向量表
中断向量表,你可以把它想象成一个“电话本”。
每个中断源都有一个唯一的编号,这个编号对应着中断向量表中的一个条目。条目里存放的是中断服务程序的入口地址。
举个例子:
// 典型的ARM Cortex-M中断向量表
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
&__StackTop, // 0: 栈顶指针
Reset_Handler, // 1: 复位
NMI_Handler, // 2: 不可屏蔽中断
HardFault_Handler, // 3: 硬件错误
MemManage_Handler, // 4: 内存管理
BusFault_Handler, // 5: 总线错误
UsageFault_Handler, // 6: 用法错误
// ... 后续中断向量
TIM2_IRQHandler, // 28: 定时器2中断
USART1_IRQHandler, // 37: 串口1中断
};
你看,每个中断源都对应一个处理函数。当定时器2产生中断时,CPU就会去查这个表,找到第28号条目,然后跳转到 TIM2_IRQHandler 执行。
个人经验:我习惯在写代码前先画好中断向量表分配图。特别是多中断源的项目,提前规划好编号和优先级,能避免很多后期调试的麻烦。
1.3 中断优先级
多个中断同时发生时,谁先处理?这就涉及到中断优先级了。
中断优先级说白了就是给每个中断排个队——谁更重要,谁先处理。
常见的优先级模型:
| 优先级模型 | 特点 | 典型芯片 |
|---|---|---|
| 固定优先级 | 中断号越小优先级越高 | 8051 |
| 可编程优先级 | 用户可以设置每个中断的优先级 | STM32 |
| 抢占优先级+子优先级 | 支持中断嵌套,更灵活 | ARM Cortex-M |
以STM32为例,它的NVIC(嵌套向量中断控制器)支持最多16级可编程优先级:
// STM32中断优先级配置示例
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 配置USART1中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 配置定时器2中断优先级
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级较低
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
避坑指南:我曾经在一个项目中,把定时器中断和串口中断设成了相同优先级。结果在高负载下,两个中断互相抢CPU,系统直接卡死。从那以后,我坚持给关键中断留出明确的优先级差距。
1.4 中断嵌套
中断嵌套,就是中断里套中断。
想象一下:你在处理门铃的事,电话又响了。如果你先去接电话,处理完再继续处理门铃的事——这就是中断嵌套。
中断嵌套的条件:
- CPU支持中断嵌套(大部分现代MCU都支持)
- 高优先级中断可以打断低优先级中断
- 同级中断不能互相打断
下面我用一张图来说明中断嵌套的流程:
从这张图可以看出:
- 主程序执行时,低优先级中断来了,CPU去处理
- 处理到一半,高优先级中断来了,CPU暂停低优先级,去处理高优先级
- 高优先级处理完,回来继续处理低优先级
- 低优先级处理完,回到主程序
核心要点:中断嵌套虽然强大,但也要慎用。嵌套层数太多,栈空间容易爆掉。我一般控制在3层以内,超过这个数,我会重新审视优先级设计是否合理。
1.5 中断处理的关键注意事项
说了这么多,最后分享几个实战中的要点:
- 中断服务程序要短小精悍——别在ISR里做复杂运算,能放主循环的就放主循环
- 注意临界区保护——共享资源访问时,记得关中断或使用信号量
- 合理分配优先级——实时性要求高的中断(如电机控制)给高优先级,非紧急的(如按键)给低优先级
- 避免在中断中调用不可重入函数——比如printf,用了你就等着死机吧
我的习惯:每个中断ISR里只做三件事——读取数据、设置标志位、清除中断标志。真正的处理逻辑放在主循环里根据标志位去执行。这样既保证了实时性,又避免了中断处理时间过长的问题。
好了,中断的基础概念就讲到这里。记住一句话:中断是嵌入式系统的灵魂,用好中断,你的程序就成功了一半。