嵌入式学习 | STM32 裸板驱动开发(Day05)超详细复习笔记外部中断 EXTI、NVIC 优先级、串口中断实战

一、什么是中断

在嵌入式裸机开发中,CPU 默认顺序执行主循环代码。 中断是 ARM 内核提供的硬件机制:当外设产生突发事件时,可以主动打断 CPU 当前正在执行的程序,优先处理紧急事件;中断处理完毕后,CPU 回到被打断的位置继续运行原有代码。

两种程序处理方式对比

方式 原理 优点 缺点
轮询方式 CPU 不断循环查询外设状态 逻辑简单,无栈操作、无现场切换 持续占用 CPU,资源利用率极低;响应实时性差
中断方式 外设主动通知 CPU,无事时 CPU 正常运行主程序 CPU 效率高,事件响应实时性强 需要理解中断堆栈、现场保护,逻辑相对复杂

没有中断机制时,想要检测按键、串口数据,只能不断循环查询;使用中断后,CPU 空闲时可以处理其他任务,外设产生事件后再通知 CPU 处理,是嵌入式实时开发的核心基础。

二、中断系统三层硬件架构(对应课堂示意图)

从上至下分为三层:ARM 内核层 → 中断控制器层 → 中断源外设层 信号流向:外设中断源 → 中断控制器 NVIC → ARM 内核

2.1 ARM 内核:标准 4 步中断处理流程(图中标注核心逻辑)

当 NVIC 向 ARM 内核发送有效的中断请求后,Cortex-M 内核硬件自动完成以下 4 个步骤:

  1. 暂停正在执行的程序,保存现场 CPU 停止当前代码执行,硬件自动将 R0~R3、LR、PC、程序状态寄存器 xPSR 压入栈中保存。

注意:只有部分寄存器由硬件自动保存,其余寄存器需要我们在中断服务函数手动保存。

  1. 查询异常向量表,跳转至中断处理函数 内核根据中断编号,在启动文件 startup_stm32f10x.s异常向量表中,读取中断服务函数入口地址,实现程序跳转。

  2. 执行中断处理函数(ISR) 运行用户编写的中断服务函数,完成外设事件处理(读取串口数据、检测按键电平)。

  3. 恢复现场,继续执行被打断的程序 中断函数执行结束,硬件从栈中恢复之前保存的寄存器,CPU 回到中断发生前的代码位置继续运行。

💡 通俗理解:你正在看书(主程序),门铃响了(中断信号)。放下书本标记页码(保存现场)→ 开门处理访客(中断服务函数)→ 处理完毕,回到标记位置继续看书(恢复现场)。

2.2 中断控制器分类

不同架构 ARM 处理器搭配不同中断控制器:

控制器名称 适用架构 代表芯片
NVIC 嵌套向量中断控制器 Cortex‑M 系列(M0/M3/M4/M7) STM32F1/F4/F103 等单片机
GIC 通用中断控制器 Cortex‑A 系列 Linux 开发板(IMX6ULL、RK3568)
NIC 小众低功耗处理器 极少使用
NVIC 两大核心工作
  1. 管理中断优先级 STM32F1 系列优先级寄存器为 16bit 宽度,硬件仅使用高 4bit 。通过优先级分组,将 4bit 切分为:抢占优先级(组优先) + 响应优先级(子优先)
  2. 为每个中断源分配唯一中断号 中断号由 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 根中断线

  1. 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 触发外部中断。

  1. 额外 3 根内部中断线(不连接 GPIO 引脚)

    中断线 功能
    EXTI16 PVD 电源电压监测中断
    EXTI17 RTC 闹钟事件中断
    EXTI18 USB 唤醒事件中断

3.2 EXTI 工作模式区分

  • 中断模式:产生信号送入 NVIC,触发 CPU 中断;
  • 事件模式:仅产生内部脉冲信号,不通知 CPU,用于触发 DMA、定时器等外设,不占用内核。

四、NVIC 中断优先级深度解析(4bit 优先级位)

4bit 优先级位被切分为两类优先级:

  1. 抢占优先级(组优先)
  • 作用:支持中断嵌套;抢占优先级数值越小,优先级越高。
  • 规则:高抢占优先级中断,可以打断当前正在运行的低抢占优先级中断。
  1. 响应优先级(子优先)
  • 作用:不支持中断嵌套;多个中断同时触发、抢占优先级相同时,响应优先级数值越小优先执行。

✅ 核心铁律: 只有抢占优先级数值不同,才能够发生中断嵌套;仅仅子优先级不同,中断之间无法互相抢占!

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 → 关闭蜂鸣器

开发思路流程图

  1. 初始化 GPIO(蜂鸣器引脚)
  2. 初始化 USART 外设,开启接收非空中断
  3. 在中断服务函数读取单字节数据,存入接收缓冲区
  4. 主循环持续解析缓冲区字符串
  5. 匹配指令,控制蜂鸣器高低电平输出

核心中断函数框架(标准库)

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 → 当前开发板转发指令,关闭搭档开发板蜂鸣器

💡拓展思考: 需要自定义简易通信标识,区分本地执行指令与转发指令,防止两块板子相互循环转发消息,造成程序卡死。

七、中断开发避坑清单(面试高频考点)

  1. 完整中断链路:中断源 → EXTI(外部中断) → NVIC → ARM 内核;内核固定 4 步:保存现场→跳转中断服务函数→执行中断程序→恢复现场
  2. 中断服务函数执行时间必须尽可能短,复杂运算、延时、循环尽量放到主循环,长时间中断会阻塞系统其他事件
  3. 绝大多数中断触发后,必须手动清除中断标志位,否则会持续重复进入中断
  4. EXTI 仅支持边沿触发(上升沿 / 下降沿 / 双边沿),不支持持续电平触发
  5. EXTI 同一根中断线同一时间只能选择一个 GPIO 引脚
  6. 中断服务函数名称固定,必须和启动文件startup_stm32f10x.s异常向量表保持一致,不能随意改名
  7. NVIC 优先级分组全局唯一,禁止多次调用NVIC_PriorityGroupConfig()
  8. 中断内尽量不要使用 printf、复杂函数,容易造成栈溢出

八、拓展思考题(自我检验)

  1. 如果抢占优先级相同,两个中断同时到来,谁先执行?
  2. 能否使用 EXTI 同时实现 PA9 和 PC9 两个按键中断?为什么?
  3. 中断嵌套发生的前提条件是什么?
相关推荐
小蒋学算法10 小时前
算法-M个非重叠子数组最大和II-WQS二分学习
数据结构·学习·算法
WD1372980155711 小时前
WD5030L,超高集成度、大电流输出、高效率低发热,限流24V转12V,6A
stm32·单片机·嵌入式硬件·51单片机
Lee_jerome13 小时前
《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》
c语言·开发语言·arm开发·c++·编译·cmake·cmakelists
重庆小透明13 小时前
带你从不同视角了解三大MQ
java·学习·spring·kafka·rabbitmq·rocketmq
~光~~13 小时前
【xv6学习】L0_环境配置
单片机·嵌入式硬件·学习
实验如有神祝女士15 小时前
不同参数规模大模型在医学翻译场景的适配差异
论文阅读·人工智能·深度学习·学习·算法·语言模型·论文笔记
若衹如初見18 小时前
一步一步学习使用LiveBindings() LiveBindings图像绑定与自定义绑定方法()
学习·命令模式
kdxiaojie18 小时前
Linux 驱动研究 —— V4L2 (15)
linux·运维·笔记·学习
fanged19 小时前
udev番外--动态驱动加载(TODO)
学习
醉城夜风~19 小时前
HTML列表标签学习博客:有序列表、无序列表、自定义列表详解
前端·学习·html