当前位置: 首页 > news >正文

【STM32 CubeMX + Keil】 中断、NVIC 、EXTI

本篇前半部分以介绍中断概念为主,内容较为理论,篇幅稍长;

后半部分则通过按键实例,具体展示中断的配置流程回调函数编写以及可能出现的冲突与解决方案。


目录

一、前言

二、中断 概述

三、NVIC:中断控制

四、EXTI:外部中断控制器 

五、CubeMX 中断触发配置示范

六、keil 中编写回调函数

七、优先级冲突 


   

一、前言

   

STM32 入门通常需要跨过五道坎:新建工程、GPIO 控制、NVIC、EXTI 和 UART 通信。

跨过这几关,基本就掌握了开发的主流程。之后再学习其他功能,往往会顺利很多——毕竟可以参考大量现成的代码(说白了,就是“合理借鉴”)。

本篇将重点讲解 NVIC 和 EXTI。

这两部分本身并不复杂,但由于概念相对抽象、关联知识较多,成了很多初学者的难点。

以前使用标准库的时候,就算理解不深,至少手动配置中断的过程,也能让人留下印象。

而现在借助 CubeMX 自动生成代码,NVIC 的配置常常简化为“勾选一下”,导致中断的重要性容易被忽视。结果往往在项目做到一半时,才突然掉进坑里。

本文反复写了近一周,内容高度浓缩,只保留实际开发中最需要的干货。我们将避免枯燥的理论阐述,力求在十分钟内帮助您掌握 STM32 中断的应用。


二、中断 概述

   

1、先讲故事 

公司饭堂。

一个打饭阿姨 (CPU),一个饭堂经理 (NVIC)。

一群打饭的人 (主程序代码)。

左边窗口:用于正常排队打饭 ;

右边窗口:插队专用 !预留给有重要任务的团队(多数团队只有1人、少量团队有多人) 。

阿姨平时在左边窗口打饭。当右边窗口来人插队了,她需放下左边的饭盒,过去右边优先给插队者打饭。

阿姨一个人如何兼顾两个窗口呢?

简单!左边打饭时,每隔5秒,扭头看看右边是否有人排队。(低效的查询法)

这是STM32入门时最常用的方法:在main的while循环里,间隔5秒,if 判断某个状态标志是否达成,以执行对应工作。

这方法的坏处是,当经理要求每隔0.01秒检查一次时,阿姨工作就得卡壳,99%的时间都浪费在扭头检查右边窗口这事上, 无法专注打饭。

如何改进呢?

经理NVIC引入两则规则提升效率:

1、安装门铃:右边窗口装上门铃,阿姨只需在听到铃声时过来处理插队请求。

2、工牌等级制度:插队者需佩戴工牌,标注团队优先级(0~15,数字越小优先级越高)。

现在的工作流程:

1、阿姨安心处理左边队伍,无需主动检查右边。

2、右边来人A君,经理查看其工牌等级3,按铃,通知阿姨立即暂停左边工作,过来为A君打饭。

3、右边再来D5君(优先级4),经理比较优先级后,安排D5排在A君后方。

4、又来了两人,B君(优先级0)、D2君(优先级5)。经理要求全体后退,让优先级最高的B君排到第一。其余按优先级和到达时间排序:B → A → D5 → D2。(级别高的排前、同级先到先排)

5、B君完成后,阿姨继续为之前中断的A君服务(恢复现场)。

6、右边所有请求处理完毕后,阿姨返回左边继续之前工作。

不妨在阅读完后续内容后,再回看一遍这个“公司管理”的比喻,相信你会对 STM32 的中断机制有更深刻的理解。

2、中断是什么? 

中断是一种让 CPU 处理紧急任务机制:当系统正在执行主程序时,若有重要事件发生(如数据到达、异常出现),CPU 会立即暂停当前任务,转去处理该事件,处理完毕后再返回原任务继续执行。

在实际开发中,我们通常将中断机制、系统异常、内部中断、外部中断、事件等,统一称为“中断”。具体含义,在不同的场合需结合上下文理解:

  • 可指整个中断处理体系(如 NVIC 管理机制);
  • 也可指某个具体的中断源(如串口接收中断、EXTI 线路中断)。

2、中断事件分类

STM32 拥有近百个可中断事件(可理解为有权“插队”的任务),从开发者角度可简单分为三类:

  • 内部中断(约10个):包括不可控的复位、硬件错误中断,以及可控的 SysTick、PendSV 等系统中断。
  • 外设中断(约80个):如 UART 空闲中断、DMA 传输结束等由外设触发的中断。
  • 外部中断(约19个):由 GPIO 电平或边沿变化触发,经 EXTI 控制器检测并上报。(本文重点讲解此类)

需要再次强调的是,“中断”这一术语在不同语境下具有不同的含义,必须结合上下文来理解:

  • 它可以指整个中断处理体系,包括 NVIC 的管理机制、系统异常、内部中断、外部中断以及事件等,这些常被统称为“中断”;

  • 它也可以指某个具体的中断源,例如串口接收中断、某一条 EXTI 线路中断等。


  

三、NVIC:中断控制

  

简单来说,STM32 的各个外设都可能产生紧急任务,而 NVIC 就是负责处理这些“插队请求”的调度中心,根据优先级决定处理顺序。

1、NVIC 概述

NVIC(Nested Vectored Interrupt Controller),即嵌套向量中断控制器,是 Cortex-M 内核中用于统一管理中断的硬件模块。

在 STM32 运行时,存在两套并行的工作机制:

  • 主程序执行流:正常顺序执行的代码
  • 中断系统:由 NVIC 管理和调度的紧急事务处理机制

NVIC 负责实时监控各类中断事件。一旦发生中断,它会立即保存当前程序现场、暂停主程序、转去执行相应的中断服务程序,处理完毕后再恢复主程序的运行。

2、NVIC 与各类中断的关系

可以把中断管理系统看作一家公司。

NVIC 是经理,是核心调度者,负责统一接收和调度所有紧急事务(中断)。

它管理着三个主要部门:

  • 内部中断:如复位中断、硬件错误中断、 SysTick中断等
  • 外设中断:如定时器溢出、串口收发完成等
  • EXTI中断:专门处理来自 GPIO 引脚的外部信号

所有中断请求——无论来自芯片内部还是外部引脚——最终都会汇总到经理(NVIC)这里。由它根据优先级规则进行仲裁,决定哪些中断可以立即“插队”处理,并安排给 CPU 执行。

3、优先级管理

无论是使用寄存器、标准库还是 HAL 库,底层硬件架构相同,但操作方式完全不同。

传统配置方式(寄存器/标准库):

  • 需掌握 STM32F4 的 60 个可屏蔽中断 + 10 个系统异常
  • 支持中断优先级分组(0-4 组)
  • 优先级规则复杂:抢占优先级 > 子优先级 > 硬件编号
  • 需设置系统优先级分组(整个系统只设置一次)
  • 需初始化 NVIC_InitTypeDef 结构体并手动使能中断

CubeMX 配置方式

  • 自动生成配置代码,极大简化操作流程
  • 默认配置为 16 级抢占优先级,不再区分子优先级
  • 优先级数值 0-15,数字越小优先级越高
  • 高优先级(数值小)可打断低优先级(数值大)
  • 相同优先级的中断按到达顺序依次执行
  • 所有中断默认优先级为 0,建议根据实际需求在 NVIC 配置中重新分配(0-15)


   

四、EXTI:外部中断控制器 

   

EXTI 是中断系统的“三大部门”之一,专门负责处理来自 GPIO 引脚的外部信号变化。

1、EXTI 简介

EXTI(External Interrupt/Event Controller)是 STM32 中专门用于监控 GPIO 引脚电平变化的硬件模块。

其核心作用是将外部信号的跳变(如上升沿、下降沿)转换为内核可识别和处理的中断或事件请求(IRQ)。

2、主要特点:

  • 23 条中断线:其中 16 条连接 GPIO 引脚,其余用于 RTC、USB 等内部外设
  • 灵活触发:每条线可独立配置为上升沿、下降沿、双边沿触发
  • 中断模式:产生中断,由 CPU 执行中断服务函数
  • 事件模式:直接触发其他外设(如 DMA)或唤醒内核,无需 CPU 参与
  • 硬件级响应:检测和响应速度极快,远优于软件轮询

3、典型应用场景

  • 按键唤醒与检测
  • 旋转编码器解码
  • 紧急安全信号检测(如急停开关)
  • 脉冲计数与捕获:结合定时器输入捕获功能,可实现高精度脉冲宽度或频率测量。
  • 外设同步与低功耗定时唤醒:通过RTC闹钟等事件实现定时唤醒,或借助通信同步信号触发数据传输。

4、共中断线共享机制

STM32 的 EXTI 中断线数量有限,因此多个引脚共享同一条中断线

你需要特别注意中断源的区分。

中断线共享规则:

  • 所有引脚0,共享一条线:EXTI0   (覆盖:PA0、PB0、PC0… )
  • 所有引脚1,共享一条线:EXTI1   (覆盖:PA1、PB1、PC1… )
  • 所有引脚2,共享一条线:EXTI2   (覆盖:PA2、PB2、PC2… )
  • 所有引脚3,共享一条线:EXTI3   (覆盖:PA3、PB3、PC3… )
  • 所有引脚4,共享一条线:EXTI4   (覆盖:PA4、PB4、PC4… )
  • 引脚5至9, 共享一条线:EXTI9_5(覆盖PA5-PA9, PB5-PB9等)。
  • 引脚10至15, 共享一条线:EXTI15_10(覆盖PA10-PA15, PB10-PB15等)。

可能引发的问题:

当多个配置为中断模式的引脚共享同一条 EXTI 线(如 PA1 和 PB1)时,产生中断后系统无法直接区分具体是哪个引脚触发了中断。

解决方法:

  • 在共用的中断服务函数中,通过软件方式依次检查各引脚的电平状态来判断中断来源
  • 在项目规划阶段,尽量避免将多个需要同时使用的关键中断源分配到同一条中断线上

总结一下:

EXTI 就是一个高效的事件触发器。对于需要快速响应外部信号(如按键、报警、状态切换)的场景,中断法是正确且高效的选择。你只需要告诉CubeMX用哪个引脚何时触发(上升沿/下降沿)以及在keil里决定触发后干什么即可,剩下的硬件会自动搞定。


五、CubeMX 中断触发配置示范

   

下面以按键为例,演示如何使用 STM32CubeMX 配置 GPIO 引脚,通过外部按键的电平变化来触发中断。

1. 硬件连接确认

查看原理图,确认按键连接的 GPIO 引脚及其默认电平状态。

本篇所用开发板,其按键1的原理图如下所示:

  • 按键1连接的是 PA0
  • 当按键按下后,PA0 为高电平
  • 电路设计中未为 PA0 设置外部下拉电阻。为确保该引脚在空闲时保持稳定的低电平,需开启内部下拉电阻
  • 按键两端并联有电容,可实现硬件消抖,因此无需在软件中额外添加消抖延时逻辑

综上,PA0 应配置为:下拉输入模式,上升沿触发

2、配置引脚为EXTI

若已有 CubeMX 工程,可直接双击打开项目目录中的 .ioc 配置文件。

若无现有工程,也可新建一个工程用于测试。

具体操作:

  • 在芯片引脚示意图上,找到 PA0 引脚(通常位于左下角)。
  • 单击 PA0 引脚,在弹出的功能菜单中选择 GPIO_EXTI0 选项。

 3、配置引脚工作参数

进入GPIO页面,  点击对应的引脚,打开该引脚的详细配置:

  • 触发模式:选 ....Interrupt...Rising...,  (上升沿触发中断)
  • 上、下拉:选Pull-down,   (开启内部下拉电阻)

GPIO Mode(可触发模式)

常用模式为边沿触发,主要选项包括:

  • External Interrupt Mode with Rising edge trigger detection:上升沿触发(适用于引脚电平由低变高的瞬间)
  • External Interrupt Mode with Falling edge trigger detection:下降沿触发(适用于引脚电平由高变低的瞬间)

GPIO Pull-up/Pull-down(上下拉模式)

共三种配置选项:

  • No pull-up and no pull-down:浮空输入。引脚空闲电平完全由外部电路决定。

  • Pull-up:     启用内部上拉电阻。确保引脚空闲时保持高电平。

  • Pull-down:启用内部下拉电阻。确保引脚空闲时保持低电平。

4、使能中断线、配置优先级

进入到NVIC页面,找到对应的中断线,打勾,NVIC即可响应它的中断请示。

  • 打勾:EXTI line0 interrupt
  • 优先级:默认 0;  即最高优先级,我们先作不修改,使用默认配置。

5、生成工程配置

完成上述三步配置后,点击"Generate Code"生成工程代码。


六、keil 中编写回调函数

  

1、中断回调函数机制

使用标准库开发时,我们需要手动编写与启动文件中名称严格匹配的中断服务函数。若未编写或函数名不一致,中断发生时程序将无法找到入口地址,导致运行时错误。

使用 CubeMX 后,它会自动生成完整的中断服务函数,并在其中调用相应的回调函数。这些回调函数在生成代码中已以弱定义(weak)形式存在,有效避免了因遗漏中断服务函数而引发的程序跑飞问题。

我们只需在外部重新实现所需的中断回调函数即可。当相应中断触发时,中断服务函数会自动调用我们编写的回调函数。

重要提示:所有 EXTI 线的中断最终都会调用同一个回调函数:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);  

2、编写回调函数

  • 打开 main.c 文件,在 /* USER CODE BEGIN 4 */ 和 /* USER CODE END 4 */ 之间添加以下代码 (也可在其他工程源文件中编写,确保编译通过即可):
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{// 当按键触发中断时,反转LED灯的电平状态HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);    
}

完成后,是这个样子的

编译并烧录程序后,按下按键即可观察到 LED 灯状态反转,这表明已成功实现了基本的中断触发功能。

3、相同 EXTI 线 不同引脚触发的处理

如前所述,多个 GPIO 引脚共享同一条 EXTI 线:

  • 所有引脚0,共享一条线:EXTI0   (覆盖:PA0、PB0、PC0… )
  • 所有引脚1,共享一条线:EXTI1   (覆盖:PA1、PB1、PC1… )
  • 所有引脚2,共享一条线:EXTI2   (覆盖:PA2、PB2、PC2… )
  • 所有引脚3,共享一条线:EXTI3   (覆盖:PA3、PB3、PC3… )
  • 所有引脚4,共享一条线:EXTI4   (覆盖:PA4、PB4、PC4… )
  • 引脚5至9, 共享一条线:EXTI9_5(覆盖PA5-PA9, PB5-PB9等)。
  • 引脚10至15, 共享一条线:EXTI15_10(覆盖PA10-PA15, PB10-PB15等)。

回顾刚才编写的中断回调函数:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{// 当按键触发中断时,反转LED灯的电平状态HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);    
}

存在一个问题:
如果项目中同时使用了 PA0 和 PC0(两者均属于 EXTI0),或者同时使用了 PA5、PA8、PB6、PC9(同属 EXTI9_5),应如何区分具体是哪个引脚触发的中断?

解决方法:
由于所有共享中断线的引脚最终都会调用同一个回调函数,可以在函数内部通过判断引脚的电平状态来确定触发源。

示例代码:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{// 判断PA0是否为高电平if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 1){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);    // 反转LED}// 判断PB0是否为高电平if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == 1){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);    // 反转LED}// 判断PD8是否为低电平if (HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_8) == 0){HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);    // 反转LED}
}

注意:

在使用 HAL_GPIO_ReadPin() 判断触发源时,该函数读取的是引脚当前的实时电平,而非按键是否被按下的逻辑状态。

实际电平与按键状态的关系完全取决于硬件电路设计:

  • 有些电路设计中,按键按下时引脚为高电平

  • 有些电路设计中,按键按下时引脚为低电平

因此,务必根据实际电路确定应与何种电平状态进行比较判断。

很多从事纯软件开发的开发者在初次接触嵌入式中断时,常有一个疑问:
这个回调函数最初是由 EXTI0_IRQHandler() 调用的,但 EXTI0_IRQHandler() 又是被谁调用的呢?

这是一个从纯软件转向嵌入式开发时容易困惑的问题。
请注意:中断服务函数是由硬件直接调用的,而不是由某个软件函数发起的

在完成中断配置后,当 EXTI 中断触发时,硬件会向 NVIC(嵌套中断向量控制器)发送请求,NVIC 再根据中断向量表中对应的入口地址,引导 CPU 跳转至相应的中断服务函数开始执行。


  

七、优先级冲突 

  

如前所述,中断优先级数值越小,优先级越高。

CPU 会优先处理高优先级任务,然后再处理低优先级任务。

这一概念理解起来并不复杂,但在实际开发中,新手很容易遇到优先级冲突问题,且这类问题往往难以排查。

我们对之前的 EXTI 回调函数稍作修改:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{    if (1 == HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)){for (uint8_t i = 0; i < 20; i++){HAL_Delay(500);                         // 间隔延时HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_2);  // 当按键触发中断时,反转LED灯的电平状态}}
}

这段代码的意图是:触发中断后让 LED 闪烁 20 次。

从逻辑上看似乎没有问题。

尝试编译并烧录运行后,会发现按键触发中断后程序会出现卡死现象。为什么?

如果仅检查 PA0 引脚的工作模式、中断配置或程序逻辑,可能无法找到问题根源,甚至可能怀疑芯片硬件故障。

而有经验的开发者会首先关注 HAL_Delay() 函数,因为它是依靠 SysTick 进行计时的。

虽然在我们的工程中没有显式配置 SysTick,但在 CubeMX 生成的代码中,它已被默认配置,且SysTick 的中断优先级默认为 15(最低优先级)。回顾之前在 NVIC 配置中的设置,EXTI 的中断优先级为 0(最高优先级)。

问题根源
EXTI 中断(优先级 0)在执行过程中调用了 HAL_Delay(),而该函数依赖于低优先级(15)的 SysTick 中断。由于高优先级中断无法被低优先级中断打断,导致 SysTick 中断永远无法执行,HAL_Delay() 也就无法正常返回。

解决方案
如果中断 A 的执行需要依赖另一个中断 B,必须将 B 的优先级设置为高于 A(数值更小)。同级优先级也不行,必须确保 B 有权限打断 A,才能保证系统正常运行。

作如上图的修改,编译、烧录后,程序装正常运行。


如有错漏,欢迎指正!!~

http://www.dtcms.com/a/390763.html

相关文章:

  • BIGO一面面试总结
  • Ansible-fetch模块
  • DevExpress WPF中文教程:DataGrid - 服务器数据和大型数据源
  • Vue项目不同页面显示不同的title
  • NW820NW825美光固态闪存NW829NW832
  • aosp13/14/15/16如何实现窗口局部区域高斯模糊毛玻璃效果及Winscope原生重大bug发现
  • Java微服务架构设计模式精解
  • 设计模式面试之单例模式常问知识点
  • 深入解析 MySQL 元数据锁 (MDL) 与 SHOW PROCESSLIST 实战
  • 能不能写一个可以在linux使用的类nano编辑器
  • Rocky10 使用kubeadm部署K8s v1.34 一主两从
  • 深入理解Buffer:数据世界的“蓄水池“
  • 通义万相开源 Wan2.2-S2V-14B,实现图片+音频生成电影级数字人视频
  • windows c++环境 使用VScdoe配置opencv
  • JVM(四)-- 对象的实例化内存布局和直接内存
  • G1垃圾回收器的优势
  • 内存分配策略
  • Python采集Tik Tok视频详情,Tik TokAPI接口(json数据返回)
  • 实时通信技术大比拼:长轮询、短轮询、WebSocket 与 SSE 深度解析及实战指南
  • ICML 2025|图像如何与激光雷达对齐并互补?迈向协调的多模态3D全景分割
  • 基于Web的3D工程应用图形引擎——HOOPS Communicator技术解析
  • 【每日一问】运放的失调电压是什么?对于电路有何影响?
  • 【轨物方案】轨物科技新型储能管理系统:以AIoT技术驱动储能资产全生命周期价值最大化
  • 线性回归 vs 逻辑回归:从原理到实战的全面对比
  • HashMap的底层原理
  • 股指期货超短线如何操作?
  • 【洛谷】算法竞赛中的树结构:形式、存储与遍历全解析
  • 育苗盘补苗路径规划研究
  • API Gateway :API网关组件
  • conda激活虚拟环境