一、中断基础概念

1.1 中断到底是什么?

先说说我对中断的理解。说白了,中断就是让CPU停下当前工作,去处理一个更紧急的事情。

想象一下你正在写代码,突然电话响了。你会怎么做?当然是停下手头的事,接电话。接完再继续写。这就是中断的日常版。

在嵌入式系统里,中断是硬件向CPU发出的一个信号。这个信号告诉CPU:「嘿,有急事,先处理我!」CPU收到信号后,会保存当前的工作状态,跳去执行中断服务程序(ISR),处理完再回来继续原来的工作。

中断的核心三要素:

  • 触发源:谁发的中断?可能是定时器、GPIO引脚、串口接收等
  • 中断向量:CPU该跳到哪里去执行?每个中断源对应一个入口地址
  • 中断服务程序:到了入口地址后,具体要做什么?这就是ISR的内容

我刚开始做嵌入式开发时,总觉得中断是个很玄乎的东西。后来调试一个串口丢数据的问题,折腾了两天,最后发现是中断优先级没配好。嗯,从那以后我对中断就格外上心了。

1.2 中断到底有什么用?

中断的作用,我总结为三点:

  1. 提高CPU利用率:没有中断时,CPU只能轮询等待事件发生,白白浪费时钟周期
  2. 实时响应:外部事件发生时,CPU能立刻感知并处理
  3. 多任务处理:让CPU看起来能同时处理多个事情

举个例子。你写了一个温度采集程序,每100ms读一次传感器。如果用轮询,CPU这100ms里啥也干不了,就死等着。用中断的话,CPU可以去做其他计算任务,定时器到了再通知CPU去读数据。你想想看,这效率差了多少?

我个人习惯:在设计系统时,能用中断的地方尽量用中断。但要注意,中断服务程序要短小精悍,别在里面做复杂运算。我曾经见过有人把FFT计算放在中断里,结果系统直接崩了。

1.3 中断 vs 轮询:到底选哪个?

这个问题我经常被问到。先看个对比表格:

特性 中断 轮询
CPU利用率 高,无事时CPU可做其他工作 低,CPU被白白占用
响应延迟 快,事件发生立即响应 慢,取决于轮询周期
实现复杂度 较高,需要配置中断控制器 简单,一个while循环搞定
适用场景 实时性要求高、事件不频繁 事件频繁、实时性要求低
代码可维护性 需要小心处理共享数据 逻辑清晰,容易调试

我一般这样选:如果事件发生的频率超过1kHz,或者对延迟要求很苛刻,那就用中断。如果只是偶尔检查一下按键状态,轮询就够了。

举个例子,你写一个UART接收程序。数据随时可能来,用轮询的话CPU得一直盯着接收寄存器。用中断的话,数据来了再通知CPU去取。哪个更合理?一目了然。

我曾经踩过的坑:在一个电机控制项目中,我用轮询检测过零信号。结果电机转速一高,过零信号就丢了。后来改成中断,问题立刻解决。记住:轮询的响应时间取决于轮询周期,而中断的响应时间取决于中断优先级。

1.4 中断在实时系统中的重要性

实时系统最核心的要求是什么?两个字:确定。

确定性能保证系统在规定时间内完成响应。中断是实现确定性的关键手段。

你想想看,一个飞行控制系统,传感器检测到姿态异常,必须在几毫秒内做出调整。如果用轮询,CPU可能正在处理其他任务,等它轮询到传感器时,飞机可能已经翻了。

中断在实时系统中的几个关键作用:

  • 事件驱动:系统只在需要时才被唤醒,节省功耗
  • 优先级管理:高优先级的中断可以抢占低优先级的任务
  • 时间确定性:中断响应时间是可预测的
  • 资源隔离:不同中断源之间互不干扰

我参与过一个工业控制项目,要求PLC的IO响应时间不超过100微秒。用轮询根本做不到,最后靠精心设计的中断优先级和嵌套机制才搞定。

1.5 中断处理的基本流程

下面这张图展示了中断从发生到处理完成的完整流程:

中断处理流程图 1. 中断发生 2. CPU保存当前上下文 3. 查找中断向量表 4. 执行中断服务程序 5. 恢复上下文,继续执行

这个流程看起来简单,但每个步骤都有讲究。比如保存上下文,要保存哪些寄存器?中断向量表放在哪里?ISR里能不能调用printf?这些问题我在后面的章节会详细讲。

1.6 一个简单的中断代码示例

拿STM32的GPIO外部中断举个例子:

// GPIO外部中断配置
void GPIO_EXTI_Init(void)
{
    // 1. 使能GPIO时钟
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    
    // 2. 配置PA0为输入模式
    GPIOA->MODER &= ~(0x3 << 0);
    
    // 3. 配置中断触发方式:上升沿触发
    EXTI->RTSR |= EXTI_RTSR_TR0;
    
    // 4. 使能中断线0
    EXTI->IMR |= EXTI_IMR_MR0;
    
    // 5. 配置NVIC优先级
    NVIC_SetPriority(EXTI0_IRQn, 2);
    NVIC_EnableIRQ(EXTI0_IRQn);
}

// 中断服务程序
void EXTI0_IRQHandler(void)
{
    // 检查中断标志位
    if(EXTI->PR & EXTI_PR_PR0)
    {
        // 处理中断事件
        // 注意:这里不要做耗时操作
        
        // 清除中断标志位
        EXTI->PR = EXTI_PR_PR0;
    }
}

避坑指南:ISR里一定要清除中断标志位,否则会一直触发中断。我曾经因为这个原因,调试一个按键中断时,发现按一次按键,串口打印了十几条消息。后来才发现是忘了清标志位。

1.7 中断的优先级与嵌套

实时系统里,中断优先级是个关键概念。高优先级的中断可以打断低优先级的中断,这叫中断嵌套。

举个例子:

  • 系统正在执行定时器中断(优先级3)
  • 突然来了一个外部紧急中断(优先级1)
  • CPU会暂停定时器中断,先去处理紧急中断
  • 处理完紧急中断,再回来继续执行定时器中断

我建议在设计系统时,把时间敏感的中断设高优先级,比如电机控制、通信协议等。把不那么紧急的中断设低优先级,比如按键扫描、LED闪烁等。

注意:中断嵌套不是越多越好。嵌套层数太多,会导致栈溢出。而且低优先级中断可能长时间得不到响应。我一般控制在3层以内。

1.8 中断延迟:实时系统的命门

中断延迟是指从中断发生到CPU开始执行ISR的时间。这个时间决定了系统的实时性能。

影响中断延迟的因素:

  • CPU正在执行关中断的代码
  • 高优先级中断正在执行
  • 中断控制器仲裁时间
  • CPU保存上下文的时间

我做过一个测试,在Cortex-M4上,中断延迟大约在12个时钟周期左右。如果CPU跑100MHz,那就是120纳秒。这个时间对于大多数实时应用来说足够了。

但要注意,如果你在代码里频繁关中断,那延迟就会大大增加。我曾经接手过一个项目,代码里到处都是__disable_irq(),结果中断响应时间从微秒级变成了毫秒级。嗯,后来我花了一周时间重构了那部分代码。


好了,中断的基础概念就讲到这里。记住一句话:中断是实时系统的灵魂,用好了事半功倍,用不好系统崩溃。

公众号:蓝海资料掘金营,微信deep3321