一、中断系统概述:从“轮询”到“打断”的思维转变

各位同学,咱们今天正式开讲中断。说实话,中断这个概念,是嵌入式开发里一道分水岭。没搞懂中断之前,你写的程序基本就是“顺序执行、死循环轮询”的幼儿园水平;一旦搞懂了中断,你才算真正开始跟硬件打交道。

我个人带项目时有个习惯——新人入职,先看他能不能把中断服务函数写得干净利落。能写好的,基本不会在项目后期出大乱子。为什么?因为中断管理直接决定了系统的实时性和稳定性。好,咱们一步步来。

1.1 什么是中断?

中断,说白了就是CPU正在干一件事,突然被一个外部或内部事件“打断”,CPU停下当前工作,转去处理这个紧急事件,处理完再回来接着干原来的活。

你想想看,这像不像你正在写代码,突然电话响了——你放下键盘接电话,讲完挂掉,继续敲代码。这个电话就是“中断请求”,你接电话的动作就是“中断响应”,通话内容就是“中断服务”,挂电话继续写代码就是“中断返回”。

在STM32里,中断源非常多:外部引脚电平变化(EXTI)、定时器溢出、串口收到数据、ADC转换完成……几乎每个外设都能产生中断。

核心概念:中断是一种硬件机制,它让CPU从“被动轮询”变成“主动响应”,是嵌入式系统实现实时处理的基础。

1.2 中断的作用与优势

为什么要用中断?不用行不行?当然行——用轮询。但轮询的代价,我举个例子你就明白了。

我曾经接手过一个老项目,按键检测用的是轮询——主循环里每隔几毫秒读一次GPIO。结果呢?系统在跑一个复杂算法时,按键响应延迟高达几百毫秒,用户体验极差。后来改成外部中断触发,响应时间直接降到微秒级。

中断的优势,归纳起来就三点:

对比项 轮询方式 中断方式
CPU利用率 低——CPU一直在“查”状态 高——无事时CPU干正事
实时性 差——响应延迟取决于轮询周期 好——事件触发立即响应
代码复杂度 简单但耦合度高 稍复杂但模块化好
功耗 高——CPU无法休眠 低——可进入低功耗模式

嗯,这里要注意:中断不是万能的。中断服务函数里不能做耗时操作,否则会阻塞其他中断。这个坑我后面会专门讲。

1.3 中断处理流程:四步走

一个完整的中断处理流程,分四步。我画了张图,你先看个整体印象:

中断处理四步流程 ① 中断请求 硬件事件触发 ② 中断响应 CPU保存现场 ③ 中断服务 执行ISR函数 ④ 中断返回 恢复现场继续 关键说明: • 中断请求:外设或内部事件置位中断标志位,向NVIC发送请求 • 中断响应:CPU自动压栈(PC、PSR、LR等),查找中断向量表 • 中断服务:跳转到对应的ISR入口,执行用户编写的处理代码 • 中断返回:执行BX LR指令,出栈恢复现场,继续原程序

下面我拆开细讲每一步:

  1. 中断请求:外设检测到特定事件(比如引脚电平变化、定时器计数溢出),硬件自动将中断标志位置1,并向NVIC(嵌套向量中断控制器)发送请求信号。
  2. 中断响应:NVIC根据优先级判断是否响应。如果响应,CPU自动完成:
    — 保存当前正在执行的指令地址(PC)到栈中
    — 保存程序状态寄存器(PSR)和链接寄存器(LR)
    — 从中断向量表中取出对应的中断服务函数地址
    — 跳转到该地址执行
  3. 中断服务:执行用户编写的ISR(Interrupt Service Routine)。这里有个铁律——ISR要短、快、干净。我见过有人把printf和延时放在ISR里,结果系统直接崩了。
  4. 中断返回:ISR执行完毕后,执行专用的中断返回指令(在Cortex-M里是BX LR),CPU自动出栈恢复现场,继续执行被中断的程序。
我的经验:调试中断时,我习惯先在ISR里只放一个GPIO翻转,用示波器看引脚波形。波形出来了,说明中断通路是通的;波形没有,先查NVIC配置和中断向量表。这招帮我省了无数时间。

1.4 STM32中断系统架构

STM32的中断系统,核心是三个东西:NVIC、EXTI、中断向量表。咱们一个一个说。

1.4.1 NVIC——中断的“调度中心”

NVIC(Nested Vectored Interrupt Controller)是Cortex-M内核自带的嵌套向量中断控制器。它负责:

  • 使能/禁止中断
  • 设置中断优先级(抢占优先级和子优先级)
  • 处理中断嵌套(高优先级中断可以打断低优先级中断)
  • 管理中断挂起和清除状态

STM32的NVIC最多支持240个中断(具体芯片型号不同),每个中断都可以独立配置优先级。优先级数值越小,优先级越高——这个跟直觉相反,我刚学时也搞反过。

避坑指南:我曾经在一个项目里,把两个中断的抢占优先级设成一样,结果中断触发时出现了“优先级反转”现象——低优先级中断反而先执行了。后来查手册才发现,同优先级中断按中断号排序,号小的优先。所以,务必给每个中断分配明确的优先级,不要偷懒全用默认值。

1.4.2 EXTI——外部中断的“门卫”

EXTI(External Interrupt/Event Controller)是STM32专门管理外部引脚中断的模块。它支持:

  • 最多16条外部中断线(EXTI0~EXTI15),每条线对应一组GPIO引脚
  • 可配置触发方式:上升沿、下降沿、双边沿
  • 支持软件触发中断(调试时很有用)
  • 每个EXTI线可以独立屏蔽

举个例子:你想用PA0引脚检测按键按下,就需要把PA0连接到EXTI0线,配置下降沿触发。这样按键按下时,EXTI0就会向NVIC发送中断请求。

1.4.3 中断向量表——中断的“电话簿”

中断向量表是一张地址映射表,每个中断源对应一个表项,表项里存放的是该中断服务函数的入口地址。STM32上电后,从0x00000000地址开始读取向量表,第一个字是栈顶地址,第二个字就是复位中断的入口地址。

在标准库或HAL库中,中断向量表通常定义在启动文件(startup_stm32xxxx.s)里。比如:

; STM32F103启动文件中的中断向量表示例
__Vectors       DCD     __initial_sp           ; 栈顶地址
                DCD     Reset_Handler          ; 复位中断
                DCD     NMI_Handler            ; NMI中断
                DCD     HardFault_Handler      ; 硬错误中断
                DCD     MemManage_Handler      ; 内存管理错误
                DCD     BusFault_Handler       ; 总线错误
                DCD     UsageFault_Handler     ; 使用错误
                ...
                DCD     EXTI0_IRQHandler       ; EXTI0中断
                DCD     EXTI1_IRQHandler       ; EXTI1中断
                ...

你写的ISR函数名必须和向量表里的名字完全一致,否则中断来了找不到入口,直接跑飞。嗯,这个坑我踩过——少写了一个下划线,查了整整半天。

1.5 一张图看懂STM32中断架构

我把NVIC、EXTI、中断向量表的关系画成了一张结构图,方便你理解:

STM32中断系统架构图 外部中断源 GPIO / EXTI 内部中断源 定时器 / 串口 / ADC 系统异常 HardFault / SysTick NVIC 嵌套向量中断控制器 优先级管理 中断使能/屏蔽 挂起/清除控制 中断嵌套处理 中断向量表 (存储在Flash起始地址) Reset_Handler → 0x08000004 EXTI0_IRQHandler → 0x080000XX TIM2_IRQHandler → 0x080000YY ... 架构要点: ① 外设中断源通过NVIC统一管理优先级和嵌套 ② NVIC根据优先级裁决后,从向量表取出ISR地址,CPU跳转执行

1.6 小结:中断是嵌入式系统的“灵魂”

这一章我们讲了中断的基本概念、处理流程和STM32的中断架构。说白了,中断就是让CPU学会“分心”——在干正事的同时,随时准备响应紧急事件。NVIC是调度中心,EXTI是外部中断的入口,中断向量表是ISR的索引目录。

下一章我们会深入NVIC的寄存器配置,手把手教你如何设置抢占优先级和子优先级。但在此之前,我建议你把今天的内容消化透——尤其是中断处理流程那四步,搞懂了,后面写代码才不会懵。

课后小练习:打开你的STM32参考手册,找到中断向量表那一章,数一数你的芯片支持多少个中断。再找到NVIC寄存器描述,看看优先级配置寄存器是怎么工作的。动手翻手册,比看十遍教程都管用。