一、中断基础概念

大家好,我是你们的嵌入式系统讲师。今天咱们聊聊中断——这个嵌入式开发里绕不开的核心话题。说实话,我做了十几年嵌入式开发,见过太多因为中断处理不当导致的“灵异事件”。

先问大家一个问题:你正在厨房炒菜,突然门铃响了,你会怎么做?

正常人会关小火,去开门,处理完门铃的事,再回来继续炒菜。对吧?

中断,说白了就是这个道理。

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都支持)
  • 高优先级中断可以打断低优先级中断
  • 同级中断不能互相打断

下面我用一张图来说明中断嵌套的流程:

中断嵌套执行流程图 主程序执行 低优先级中断ISR 高优先级中断ISR 高优先级ISR返回 继续低优先级ISR 返回主程序 时间轴 → 主程序 低优先级中断 高优先级中断

从这张图可以看出:

  • 主程序执行时,低优先级中断来了,CPU去处理
  • 处理到一半,高优先级中断来了,CPU暂停低优先级,去处理高优先级
  • 高优先级处理完,回来继续处理低优先级
  • 低优先级处理完,回到主程序

核心要点:中断嵌套虽然强大,但也要慎用。嵌套层数太多,栈空间容易爆掉。我一般控制在3层以内,超过这个数,我会重新审视优先级设计是否合理。

1.5 中断处理的关键注意事项

说了这么多,最后分享几个实战中的要点:

  1. 中断服务程序要短小精悍——别在ISR里做复杂运算,能放主循环的就放主循环
  2. 注意临界区保护——共享资源访问时,记得关中断或使用信号量
  3. 合理分配优先级——实时性要求高的中断(如电机控制)给高优先级,非紧急的(如按键)给低优先级
  4. 避免在中断中调用不可重入函数——比如printf,用了你就等着死机吧

我的习惯:每个中断ISR里只做三件事——读取数据、设置标志位、清除中断标志。真正的处理逻辑放在主循环里根据标志位去执行。这样既保证了实时性,又避免了中断处理时间过长的问题。

好了,中断的基础概念就讲到这里。记住一句话:中断是嵌入式系统的灵魂,用好中断,你的程序就成功了一半。

可可精品资料库