一、什么是中断
在嵌入式裸机开发中,CPU 默认顺序执行主循环代码。 中断是 ARM 内核提供的硬件机制:当外设产生突发事件时,可以主动打断 CPU 当前正在执行的程序,优先处理紧急事件;中断处理完毕后,CPU 回到被打断的位置继续运行原有代码。
两种程序处理方式对比
| 方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 轮询方式 | CPU 不断循环查询外设状态 | 逻辑简单,无栈操作、无现场切换 | 持续占用 CPU,资源利用率极低;响应实时性差 |
| 中断方式 | 外设主动通知 CPU,无事时 CPU 正常运行主程序 | CPU 效率高,事件响应实时性强 | 需要理解中断堆栈、现场保护,逻辑相对复杂 |
没有中断机制时,想要检测按键、串口数据,只能不断循环查询;使用中断后,CPU 空闲时可以处理其他任务,外设产生事件后再通知 CPU 处理,是嵌入式实时开发的核心基础。
二、中断系统三层硬件架构(对应课堂示意图)
从上至下分为三层:ARM 内核层 → 中断控制器层 → 中断源外设层 信号流向:外设中断源 → 中断控制器 NVIC → ARM 内核
2.1 ARM 内核:标准 4 步中断处理流程(图中标注核心逻辑)
当 NVIC 向 ARM 内核发送有效的中断请求后,Cortex-M 内核硬件自动完成以下 4 个步骤:
- 暂停正在执行的程序,保存现场 CPU 停止当前代码执行,硬件自动将 R0~R3、LR、PC、程序状态寄存器 xPSR 压入栈中保存。
注意:只有部分寄存器由硬件自动保存,其余寄存器需要我们在中断服务函数手动保存。
-
查询异常向量表,跳转至中断处理函数 内核根据中断编号,在启动文件
startup_stm32f10x.s的异常向量表中,读取中断服务函数入口地址,实现程序跳转。 -
执行中断处理函数(ISR) 运行用户编写的中断服务函数,完成外设事件处理(读取串口数据、检测按键电平)。
-
恢复现场,继续执行被打断的程序 中断函数执行结束,硬件从栈中恢复之前保存的寄存器,CPU 回到中断发生前的代码位置继续运行。
💡 通俗理解:你正在看书(主程序),门铃响了(中断信号)。放下书本标记页码(保存现场)→ 开门处理访客(中断服务函数)→ 处理完毕,回到标记位置继续看书(恢复现场)。
2.2 中断控制器分类
不同架构 ARM 处理器搭配不同中断控制器:
| 控制器名称 | 适用架构 | 代表芯片 |
|---|---|---|
| NVIC 嵌套向量中断控制器 | Cortex‑M 系列(M0/M3/M4/M7) | STM32F1/F4/F103 等单片机 |
| GIC 通用中断控制器 | Cortex‑A 系列 | Linux 开发板(IMX6ULL、RK3568) |
| NIC | 小众低功耗处理器 | 极少使用 |
NVIC 两大核心工作
- 管理中断优先级 STM32F1 系列优先级寄存器为 16bit 宽度,硬件仅使用高 4bit 。通过优先级分组,将 4bit 切分为:抢占优先级(组优先) + 响应优先级(子优先)
- 为每个中断源分配唯一中断号 中断号由 ARM 内核标准定义,固化在启动文件异常向量表。内核依靠中断号区分不同外设中断,定位对应的中断服务函数。
2.3 中断源(产生中断请求的外设)
可以主动发出中断信号的外设统称为中断源,STM32F1 常用中断源:
- GPIO 外部中断(依托 EXTI 控制器)
- USART 串口通信
- IIC、SPI 串行外设
- RTC 实时时钟
- SysTick 系统定时器
- TIM 定时器、ADC 模数转换器
示例通路:按键 KEY0(PC9)外部中断 PC9 引脚 → GPIO 配置映射至 EXTI9 中断线 → EXTI 产生中断请求信号 → NVIC 接收信号、优先级仲裁 → ARM 内核响应中断,进入 EXTI9 中断服务函数
三、外部中断 EXTI(外部中断 / 事件控制器)
EXTI 全称 External Interrupt/Event Controller,专门捕获 GPIO 电平边沿变化,实现按键、外部传感器电平信号触发中断。
3.1 EXTI 硬件资源:合计 19 根中断线
- EXTI0 ~ EXTI15(16 根,绑定通用 GPIO 引脚)
- EXTI0:PA0 / PB0 / PC0 / PD0 / PE0 ... 所有端口的 0 号引脚
- EXTI1:PA1 / PB1 / PC1 / PD1 ...
- ......
- EXTI15:PA15 / PB15 / PC15 / PD15 ...
⚠️ 硬性限制(高频考点) 同一根 EXTI 中断线,同一时刻只能选择一个 GPIO 引脚。 例:EXTI9 配置为 PC9 触发中断,则不能同时使用 PA9、PB9 触发外部中断。
-
额外 3 根内部中断线(不连接 GPIO 引脚)
中断线 功能 EXTI16 PVD 电源电压监测中断 EXTI17 RTC 闹钟事件中断 EXTI18 USB 唤醒事件中断
3.2 EXTI 工作模式区分
- 中断模式:产生信号送入 NVIC,触发 CPU 中断;
- 事件模式:仅产生内部脉冲信号,不通知 CPU,用于触发 DMA、定时器等外设,不占用内核。
四、NVIC 中断优先级深度解析(4bit 优先级位)
4bit 优先级位被切分为两类优先级:
- 抢占优先级(组优先)
- 作用:支持中断嵌套;抢占优先级数值越小,优先级越高。
- 规则:高抢占优先级中断,可以打断当前正在运行的低抢占优先级中断。
- 响应优先级(子优先)
- 作用:不支持中断嵌套;多个中断同时触发、抢占优先级相同时,响应优先级数值越小优先执行。
✅ 核心铁律: 只有抢占优先级数值不同,才能够发生中断嵌套;仅仅子优先级不同,中断之间无法互相抢占!
4.1 优先级分组配置规则
使用函数 NVIC_PriorityGroupConfig() 设置分组,4bit 分配方案如下表:
| 优先级分组 | 抢占优先级占用位数 | 响应优先级占用位数 | 特性 |
|---|---|---|---|
| NVIC_PriorityGroup_0 | 0 bit | 4 bit | 禁止所有中断嵌套 |
| NVIC_PriorityGroup_1 | 1 bit | 3 bit | 可实现少量嵌套 |
| NVIC_PriorityGroup_2 | 2 bit | 2 bit | 项目最常用 |
| NVIC_PriorityGroup_3 | 3 bit | 1 bit | 支持多层中断嵌套 |
| NVIC_PriorityGroup_4 | 4 bit | 0 bit | 无响应优先级 |
⚠️ 致命踩坑点:整个工程优先级分组代码只能调用一次!多次配置分组会导致所有中断优先级全部错乱。
4.2 优先级配置标准库示例代码
cpp
#include "stm32f10x.h"
void NVIC_Config(void)
{
// 设置优先级分组2:抢占2bit,响应2bit,全局只调用一次!
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
// 配置EXTI9中断
NVIC_InitStruct.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; // 响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
五、课堂实战练习
练习 1:串口接收中断实验
实验需求 将 USART 配置为接收中断模式,上位机串口发送指令:
- 发送字符串
on→ 开启蜂鸣器 - 发送字符串
off→ 关闭蜂鸣器
开发思路流程图
- 初始化 GPIO(蜂鸣器引脚)
- 初始化 USART 外设,开启接收非空中断
- 在中断服务函数读取单字节数据,存入接收缓冲区
- 主循环持续解析缓冲区字符串
- 匹配指令,控制蜂鸣器高低电平输出
核心中断函数框架(标准库)
cpp
// 串口1中断服务函数,函数名固定不可修改
void USART1_IRQHandler(void)
{
uint8_t ch;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
ch = USART_ReceiveData(USART1); // 读取收到的数据
// 将ch存入自定义接收缓冲区
USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志
}
}
六、课后作业
基础作业
独立实现【练习 1】全部功能:串口接收指令,on开启蜂鸣器,off关闭蜂鸣器。
拓展搭档双人通信实验(两块开发板互联)
硬件连接 两块 STM32 开发板串口交叉相连: A 板 TX → B 板 RX B 板 TX → A 板 RX 两块开发板 GND 共地;PC 串口连接其中一块开发板。
通信需求 上位机发送 on → 当前开发板转发指令,开启搭档开发板蜂鸣器 上位机发送 off → 当前开发板转发指令,关闭搭档开发板蜂鸣器
💡拓展思考: 需要自定义简易通信标识,区分本地执行指令与转发指令,防止两块板子相互循环转发消息,造成程序卡死。
七、中断开发避坑清单(面试高频考点)
- 完整中断链路:中断源 → EXTI(外部中断) → NVIC → ARM 内核;内核固定 4 步:保存现场→跳转中断服务函数→执行中断程序→恢复现场
- 中断服务函数执行时间必须尽可能短,复杂运算、延时、循环尽量放到主循环,长时间中断会阻塞系统其他事件
- 绝大多数中断触发后,必须手动清除中断标志位,否则会持续重复进入中断
- EXTI 仅支持边沿触发(上升沿 / 下降沿 / 双边沿),不支持持续电平触发
- EXTI 同一根中断线同一时间只能选择一个 GPIO 引脚
- 中断服务函数名称固定,必须和启动文件
startup_stm32f10x.s异常向量表保持一致,不能随意改名 - NVIC 优先级分组全局唯一,禁止多次调用
NVIC_PriorityGroupConfig() - 中断内尽量不要使用 printf、复杂函数,容易造成栈溢出
八、拓展思考题(自我检验)
- 如果抢占优先级相同,两个中断同时到来,谁先执行?
- 能否使用 EXTI 同时实现 PA9 和 PC9 两个按键中断?为什么?
- 中断嵌套发生的前提条件是什么?