中断原理 
Author:余生
一、什么是中断?—— 先从 “生活场景” 讲起 
想象你正在写作业(主程序),突然电话响了(外部事件),你必须暂停写作业,去接电话(处理事件),接完电话再回来继续写作业。
这个 “暂停当前任务 → 处理突发事件 → 回到原来任务” 的过程,就是中断(Interrupt)。
在 STM32 中:
- 写作业 = 主程序(main 函数里的代码)
- 电话响了 = 某个外设发出了中断请求(比如按键按下、定时器溢出)
- 接电话 = 执行中断服务函数(ISR)
- 继续写作业 = 返回主程序继续执行
中断的本质:让 CPU 能 “及时响应” 外部或内部的重要事件,而不是傻等或不断轮询。
二、为什么要用中断? vs 轮询(Polling) 
| 对比项 | 轮询方式 | 中断方式 | 
|---|---|---|
| CPU 是否空转 | 是,一直在查状态 | 否,平时正常工作 | 
| 响应速度 | 慢,取决于轮询频率 | 快,事件一发生立刻响应 | 
| 效率 | 低,浪费 CPU 资源 | 高,CPU 可做其他事 | 
| 实时性 | 差 | 强 | 
结论:
轮询适合简单、不紧急的任务;
中断适合对实时性要求高的场景,如按键检测、串口收数据、定时任务等。
三、中断系统的三大核心组件 
STM32 的中断系统由三个关键部分组成:
- 中断源(Interrupt Source)
- EXTI(外部中断 / 事件控制器)
- NVIC(嵌套向量中断控制器)
我们一个一个深入讲。
四、中断源(Interrupt Source) 
中断源就是 “谁发起了中断请求”。
常见中断源类型 
| 类型 | 举例 | 
|---|---|
| 外部中断 | 按键按下、外部传感器信号 | 
| 定时器中断 | TIM2 溢出、PWM 周期结束 | 
| 串口中断 | USART1 收到一个字节数据 | 
| ADC 中断 | 模数转换完成 | 
| DMA 中断 | 数据传输完成 | 
| 系统异常 | 硬件错误、NMI、SysTick 等 | 
所有这些外设都可以配置为 “产生中断”,一旦条件满足,就会向 NVIC 发出请求。
五、EXTI(External Interrupt/Event Controller)—— 外部中断控制器 
5.1 什么是 EXTI? 
EXTI 是 STM32 中专门用来管理外部引脚中断的模块。它就像一个 “门口保安”,负责监听哪些 GPIO 引脚发生了电平变化,并决定是否上报给 NVIC。
注意:虽然叫 “外部中断”,但它其实是芯片内部的一个控制器,专门处理来自 GPIO 引脚的中断请求。
5.2 EXTI 的结构(以 STM32F1 为例) 
STM32F1 系列有 19 条 EXTI 线路(Line 0~15 + 16~18)
- Line 0 ~ 15:对应每个 GPIO 引脚(PA0、PB0、PC0…)
- Line 16:PVD(可编程电压检测)
- Line 17:RTC 闹钟
- Line 18:USB 唤醒
关键点:
虽然有多个 GPIO 端口(A/B/C/D…),但每个编号的引脚共用一条 EXTI 线。
例如:PA0、PB0、PC0 都连接到 EXTI Line 0,但同一时间只能有一个能触发中断(需要软件选择)。
5.3 EXTI 的工作流程 
- 配置 GPIO 为输入模式(如上拉输入)
- 选择哪个引脚作为中断源(通过 AFIO 寄存器选择 PA0 还是 PB0)
- 设置触发方式: - 上升沿触发(从低变高)
- 下降沿触发(从高变低)
- 双边沿触发(高低都触发)
 
- 使能 EXTI 中断输出
- EXTI 检测到信号变化 → 向 NVIC 发送中断请求
5.4 EXTI 的 “中断” vs “事件” 
EXTI 不仅能产生中断,还能产生 “事件”(Event)。
| 对比 | 中断(Interrupt) | 事件(Event) | 
|---|---|---|
| 是否进入 CPU | 是,会跳转到 ISR | 否,不进 CPU | 
| 是否需要 NVIC | 是 | 否 | 
| 用途 | 需要 CPU 参与处理 | 触发其他外设(如启动 ADC、DMA) | 
| 延迟 | 有中断响应延迟 | 极快,硬件直连 | 
举例:
你可以设置 “按键按下” 产生一个事件,直接触发 ADC 开始采样,全程不需要 CPU 参与,效率极高!
六、NVIC(Nested Vectored Interrupt Controller)—— 嵌套向量中断控制器 
这是整个中断系统的 “大脑” 和 “调度中心”。
6.1 NVIC 的功能 
- 接收所有中断请求(来自外设或 EXTI)
- 判断优先级,决定先响应哪个
- 支持中断嵌套(高优先级可打断低优先级)
- 自动保存 / 恢复上下文(CPU 寄存器)
- 跳转到正确的 ISR
NVIC 是 ARM Cortex-M 内核的一部分,不是 STM32 厂商自己设计的,所有 Cortex-M 芯片都有 NVIC。
6.2 中断优先级(Priority) 
STM32 的中断优先级分为 4 位(F1 系列),可以分成:
- 抢占优先级(Preemption Priority):决定是否可以 “打断” 其他中断
- 子优先级(Subpriority):决定多个中断同时发生时的执行顺序
优先级分组(Priority Group) 
由于只有 4 位,需要事先分配多少位给抢占,多少位给子优先级。这叫 “优先级分组”。
| 分组模式 | 抢占位数 | 子优先级位数 | 最大组数 | 
|---|---|---|---|
| Group 0 | 0 位 | 4 位 | 1 组,16 子 | 
| Group 1 | 1 位 | 3 位 | 2 组,8 子 | 
| Group 2 | 2 位 | 2 位 | 4 组,4 子 ✅ 常用 | 
| Group 3 | 3 位 | 1 位 | 8 组,2 子 | 
| Group 4 | 4 位 | 0 位 | 16 组,无子 | 
规则:
- 抢占优先级高的可以打断抢占优先级低的(嵌套)
- 抢占相同,子优先级高的先执行
- 抢占和子都相同,看中断号(越小越优先)
推荐使用 Group 2:4 个抢占优先级 + 4 个子优先级,够用且不易出错。
6.3 中断向量表(Interrupt Vector Table) 
这是 NVIC 的 “电话簿”,记录了每个中断对应的处理函数地址。
特点 
- 存放在 Flash 开头(地址 0x0000_0000)
- 每个中断占 4 字节,存的是函数指针
- 包括: - 异常(Exception):复位、NMI、Hard Fault、SysTick 等
- 外设中断:TIM2_IRQn、USART1_IRQn、EXTI0_IRQn…
 
举例: 当你按下按键触发 EXTI0 中断,NVIC 查表发现 EXTI0_IRQn 对应的函数是 EXTI0_IRQHandler ,于是跳过去执行。
6.4 NVIC 的寄存器 
| 寄存器 | 功能 | 
|---|---|
| ISER(Set Enable Register) | 使能中断 | 
| ICER(Clear Enable Register) | 关闭中断 | 
| ISPR(Set Pending Register) | 手动触发中断(软件中断) | 
| ICPR(Clear Pending Register) | 清除中断挂起状态 | 
| IPR(Interrupt Priority Register) | 设置中断优先级 | 
实际编程中我们用库函数(如
NVIC_EnableIRQ())操作,不用直接写寄存器。
七、中断的完整执行流程(从触发到返回) 
我们以 “按键触发 EXTI0 中断” 为例,详细走一遍流程:
步骤 1:事件发生 
- 用户按下按键 → PA0 引脚从高电平变为低电平(下降沿)
步骤 2:EXTI 检测 
- EXTI Line 0 检测到下降沿
- 触发条件满足 → 设置 “挂起寄存器”(Pending Bit)
步骤 3:发送请求给 NVIC 
- EXTI 向 NVIC 发出中断请求(IRQ)
步骤 4:NVIC 判断是否响应 
- 当前是否有更高优先级中断正在运行?
- 全局中断是否使能?(CPSR 寄存器 I 位)
- 如果可以响应 → 进入响应阶段
步骤 5:CPU 响应中断(异常进入) 
- 自动保存上下文(R0~R3, R12, LR, PC, xPSR)
- 读取中断向量表,跳转到 EXTI0_IRQHandler
步骤 6:执行中断服务函数(ISR) 
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        // 处理按键逻辑:比如翻转LED
        LED_Toggle();
        // 清除中断标志位(重要!)
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}2
3
4
5
6
7
8
步骤 7:中断返回 
- 执行 BX LR指令
- CPU 自动恢复之前保存的上下文
- 继续执行被中断的主程序
八、中断服务函数(ISR)编写注意事项 
正确做法 
- 函数名必须和启动文件中定义的一致(如 EXTI0_IRQHandler)
- 尽量简短,不要做耗时操作(如延时、打印)
- 及时清除中断标志位(否则会反复进入中断)
- 可以设置标志位,让主程序去处理复杂逻辑
错误做法 
void EXTI0_IRQHandler(void) {
    Delay_ms(1000);  // 千万不要在这里延时!
    printf("Key pressed!\n"); // 打印太慢,影响实时性
}2
3
4
推荐做法 
volatile uint8_t key_flag = 0;  // 全局标志
void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0)) {
        key_flag = 1;  // 只设标志
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}
// 主程序中处理
while (1) {
    if (key_flag) {
        key_flag = 0;
        LED_Toggle();
        printf("Key pressed!\n");
    }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
九、中断嵌套(Nested Interrupt) 
什么是嵌套? 
高优先级中断可以打断正在执行的低优先级中断。
举例 
- 中断 A:优先级 2(抢占)
- 中断 B:优先级 1(抢占) ← 更高
当 CPU 正在执行 A 的 ISR 时,B 发生 → CPU 会暂停 A,先执行 B,B 执行完再回到 A。
注意:
- 只有抢占优先级更高才能打断
- 子优先级不能打断
十、常见中断编号与命名(STM32F1 参考) 
| 中断名 | 对应外设 | 说明 | 
|---|---|---|
| NMI_IRQn | 非屏蔽中断 | 不受 NVIC 控制,必须响应 | 
| HardFault_IRQn | 硬件错误 | 如访问非法地址 | 
| SysTick_IRQn | 系统滴答定时器 | 操作系统常用 | 
| WWDG_IRQn | 窗口看门狗 | |
| EXTI0_IRQn ~ EXTI15_IRQn | 外部中断线 0~15 | |
| TIM2_IRQn | 定时器 2 | |
| USART1_IRQn | 串口 1 | |
| ADC1_2_IRQn | ADC1 和 ADC2 | 
所有中断名定义在
stm32f10x.h文件中。
十一、软件配置流程(以 HAL 库为例) 
// 1. 使能时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE();  // EXTI需要AFIO
// 2. 配置GPIO为输入
GPIO_InitTypeDef gpio;
gpio.Pin = GPIO_PIN_0;
gpio.Mode = GPIO_MODE_IT_FALLING;  // 下降沿中断
gpio.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &gpio);
// 3. 配置NVIC
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);  // 抢占2,子0
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 4. 编写回调函数(HAL库风格)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) {
        LED_Toggle();
    }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
十二、中断的优缺点总结 
| 优点 | 缺点 | 
|---|---|
| 响应快,实时性强 | 配置复杂 | 
| 节省 CPU 资源 | 多中断时优先级管理复杂 | 
| 支持嵌套,灵活性高 | ISR 中不能调用阻塞函数 | 
| 可实现事件驱动编程 | 调试困难(断点难打) | 
总结:一张图看懂 STM32 中断系统 
[GPIO 引脚] 
    ↓ (电平变化)
[EXTI 控制器] —— 判断是否触发、是中断还是事件
    ↓ (中断请求)
[NVIC] —— 查优先级、查向量表、决定是否响应
    ↓
[CPU] —— 保存现场 → 跳转到 ISR → 执行 → 恢复现场
    ↓
[主程序继续运行]2
3
4
5
6
7
8
9