
07_STM32_中断篇
本文最后更新于 2025-04-19,学习久了要注意休息哟
参考资料
<STM32F4xx中文参考手册> 第十章 中断和事件
<ARM Cortex™-M4F 技术参考手册> NVIC
第一章 NVIC 中断系统
中断是一种打断当前程序执行,转而处理紧急任务的机制。
在嵌入式系统或操作系统中,中断的存在就像一个“电话铃声”——当你在做事时,有一个更重要的事情来了,你会先放下手头的事去接电话,处理完再回来继续原来的工作。
1.1 中断的概念
1.2 嵌套向量中断控制器
1.2.1 NVIC 介绍
NVIC :嵌套向量中断控制器,属于内核外设,管理着包括内核和片上所有外设的中断相关的功能。
片上外设与内核外设他们都在芯片里面,但内核外设是在内核CPU里面,片上外设就是内核之外。
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以STM32 的 NVIC 是 Cortex-M的 NVIC 的一个子集。
1.2.2 中断向量表
完整的Cortex-M4有256个可编程中断(16个内核中断和240个外部中断),而stm32f40x共有92个中断(10内核中断+82可编程),意思是说STM32F40X这个单片机没有完全释放CM4内核的资源。
F407 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中系统异常有 10 个,外部中断有 82 个。
除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。
有关具体的系统异常和外部中断可在标准库文件 stm32f4xx.h 这个头文件查询到,在IRQn_Type 这个结构体里面包含了 F4 系列全部的异常声明。
通过查询 《STM32F4xx中文参考手册》中的 <TM32F405xx/07xx 和 STM32F415xx/17xx 的向量表> ,可以查看STM32F4中所有的中断,下面的部分为系统中断清单,其他的为外部中断。
1.2.3 中断优先级
1、中断编号
ARM 为 Cortex-M3 内核 一共设计了 255 个中断,编号为 1~255,而 0 表示没有异常。这里的编号单纯只是这些中断的一个序号,而不是优先级编号 1-15 是内核中产生的、而 16-255 属于来自内核外。
3、中断优先级分类
STM32分为抢占式优先级和响应优先级,每个中断源都需要被指定这两种优先级。
类型 | 说明 |
---|---|
抢占优先级(Preempt Priority) | 决定是否可以打断其他中断。 |
响应优先级(Sub Priority) | 在不能抢占的情况下,决定先后响应顺序。 |
在 Cortex-M 内核中,中断优先级是通过一个 8 位寄存器字段实现的,但并非全部 8 位都可用。STM32 系列中通常只使用高 4 位(低 4 位固定为 0)。
STM32 将这 4 位优先级位划分为两个部分:抢占优先级位数、响应优先级位数。
分组 | 抢占优先级位数 | 响应优先级位数 | 优先级设置函数参数 |
---|---|---|---|
Group 0 | 0 | 4 | NVIC_PriorityGroup_0 |
Group 1 | 1 | 3 | NVIC_PriorityGroup_1 |
Group 2 | 2 | 2 | NVIC_PriorityGroup_2 |
Group 3 | 3 | 1 | NVIC_PriorityGroup_3 |
Group 4 | 4 | 0 | NVIC_PriorityGroup_4 |
4、优先级判定规则
当多个中断同时发生或嵌套时,NVIC 按以下顺序决定谁先执行:
✅ 规则 1:抢占优先级不同
-
抢占优先级高的中断可以打断抢占优先级低的中断。
-
例如:优先级为 1 可以打断优先级为 3 的中断。
✅ 规则 2:抢占优先级相同,比较响应优先级
-
无法打断彼此,但可以按照响应优先级先后执行。
-
响应优先级高(数值更小)者先响应。
✅ 规则 3:抢占优先级和响应优先级都相同
-
此时比较中断号,中断号小者优先。
-
中断号可以从中断向量表中查到。
1.4 NVIC寄存器
NVIC(嵌套向量中断控制器)是 Cortex-M 内核中用于管理中断的重要模块,它通过一组寄存器来实现对中断的使能、失能、优先级配置、挂起状态等操作。
寄存器 | 名称 | 作用 |
---|---|---|
ISER[n] | 中断使能寄存器 | 写 1 使能对应中断 |
ICER[n] | 中断清除使能寄存器 | 写 1 关闭对应中断 |
ISPR[n] | 中断挂起寄存器 | 写 1 使中断处于挂起状态 |
ICPR[n] | 清除中断挂起寄存器 | 写 1 清除挂起状态 |
IABR[n] | 活跃中断标志寄存器 | 显示哪些中断正在执行 |
IPR[n] | 中断优先级寄存器 | 设置中断的优先级(抢占 + 响应) |
1.5 NVIC库函数
在 STM32 的标准固件库中,提供了一系列用于配置中断的 NVIC 函数,方便我们对 NVIC 的寄存器进行操作。
1.5.1 常用函数
// 设置中断优先级分组(抢占+响应优先级分配方式)
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
优先级组参数可选值(宏定义):
NVIC_PriorityGroup_0
到NVIC_PriorityGroup_4
(见上一节)
// 配置指定中断通道
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
1.5.2 结构体介绍
typedef struct
{
uint8_t NVIC_IRQChannel; // 中断通道
uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级
uint8_t NVIC_IRQChannelSubPriority; // 响应(子)优先级
FunctionalState NVIC_IRQChannelCmd; // 使能或失能中断
} NVIC_InitTypeDef;
NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序不会报错,只会导致不想要中断。具体的成员配置可参考 stm32f4xx.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
NVIC_IRQChannelPreemptionPriority:抢占优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表。
NVIC_IRQChannelSubPriority:子优先级,具体的值要根据优先级分组来确定,具体参考表格优先级分组真值表 。
NVIC_IRQChannelCmd:中断使能(ENABLE)或者失能(DISABLE)。操作的是 NVIC_ISER和 NVIC_ICER 这两个寄存器。
=
1.7 中断程序设计
在配置每个中断的时候一般有 3 个编程步骤:
1、使能外设某个中断,这个具体由每个外设的相关中断使能位控制。比如串口有发送完成中断,接收完成中断,这两个中断都由串口控制寄存器的相关中断使能位控制。
2、初始化 NVIC_InitTypeDef 结构体,配置中断优先级分组,设置抢占优先级和子优先级,使能中断请求。
3、编写中断服务函数
需要注意的是,中断优先级分组我们只在程序中设置一次,当我们已经设定好中断优先级的分组后,后续的程序就不能随意进行修改了。
第二章 EXTI 外部中断
2.1 EXTI 简介
EXTI(External Interrupt/Event Controller)外部中断/事件控制器,用于管理来自外部引脚的中断请求和事件信号。STM32 的 GPIO 引脚具备复用功能,可以被映射为外部中断线,实现引脚级别的中断响应。
2.2 功能框图
2.2.1 输入信号处理部分
-
输入线(右侧):这是外部信号输入,如按钮、电平变化等。
-
边沿检测电路:用于判断输入信号是上升沿还是下降沿。
2.2.2 触发条件配置
-
上升沿触发选择寄存器:设置是否对上升沿响应。
-
下降沿触发选择寄存器:设置是否对下降沿响应。
-
这两个选择寄存器会控制边沿检测电路的工作方式。
2.2.3 中断或事件的产生
-
软件中断事件寄存器:软件可以通过设置这个寄存器来模拟一个中断事件。
-
事件屏蔽寄存器:用于屏蔽特定事件,不让其触发信号流向后续。
-
脉冲发生器:对有效事件产生一个短时间的脉冲,用于触发后续逻辑。
2.2.4 中断逻辑控制
-
中断屏蔽寄存器:用于控制是否允许某一路中断信号产生中断请求。
-
挂起请求寄存器:当检测到边沿并且未屏蔽,就会设置这个寄存器,表示该中断已经发生,等待处理。
-
中断输出到 NVIC(嵌套向量中断控制器):由 NVIC 管理中断优先级和处理。
2.2.5 总线与时钟
-
AMBA APB 总线接口:连接到系统总线,CPU 可通过它访问 EXTI 寄存器进行配置。
-
PCLK2:提供 EXTI 的工作时钟。
EXTI 模块实现了以下功能:
-
对输入引脚的上升沿/下降沿进行检测。
-
可以通过软件触发中断。
-
提供中断屏蔽、事件屏蔽等机制。
-
与 NVIC 连接,实现中断请求的发出。
-
所有配置都通过 APB 总线进行。
这使得微控制器能够对外部的变化(如按钮按下、传感器信号)做出快速反应,是嵌入式开发中的重要组成部分。
2.3 中断线
另外七根 EXTI 线连接方式如下:
-
EXTI 线 16 连接到 PVD 输出
-
EXTI 线 17 连接到 RTC 闹钟事件
-
EXTI 线 18 连接到 USB OTG FS 唤醒事件
-
EXTI 线 19 连接到以太网唤醒事件
-
EXTI 线 20 连接到 USB OTG HS(在 FS 中配置)唤醒事件
-
EXTI 线 21 连接到 RTC 入侵和时间戳事件
-
EXTI 线 22 连接到 RTC 唤醒事件
2.4 EXTI寄存器
EXTI_IMR // 中断屏蔽寄存器:设置是否屏蔽某条中断线,1 = 不屏蔽(允许中断),0 = 屏蔽(禁止中断)
EXTI_EMR // 事件屏蔽寄存器:设置是否屏蔽某条事件线,事件用于触发外设,不会进入中断处理函数
EXTI_RTSR // 上升沿触发选择寄存器:设置为1表示该线对上升沿敏感
EXTI_FTSR // 下降沿触发选择寄存器:设置为1表示该线对下降沿敏感
EXTI_SWIER // 软件触发中断寄存器:设置对应位为1可软件触发该中断线
EXTI_PR // 挂起寄存器:中断发生后会自动置1,写1清除中断标志位
2.5 EXTI 库函数
2.5.1 初始化和配置函数
void EXTI_DeInit(void);
-
功能:将 EXTI 外设的所有配置(中断线配置、触发方式、模式等)恢复为出厂默认状态。
-
使用场景:需要清除所有 EXTI 配置,重新初始化时使用。
-
底层操作:清除所有中断线使能、触发方式设置、模式设置等寄存器位。
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
-
功能:根据传入结构体配置 EXTI 的中断线、模式、触发方式和使能状态。
-
说明:这是 EXTI 的核心初始化函数,通常配合
EXTI_StructInit
使用。
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
-
功能:对 EXTI_InitStruct 结构体赋默认值,防止结构体未初始化引发异常行为。
-
使用建议:建议在
EXTI_Init()
之前调用一次该函数,再对其字段做定制化修改。
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
-
功能:软件手动触发某条中断线(模拟外部中断事件)。
-
使用场景:无需真实信号输入,仅用于测试中断处理函数是否工作正常。
-
示例:
EXTI_GenerateSWInterrupt(EXTI_Line0);
相当于人为触发了 EXTI0 中断。
2.5.2 中断与标志位管理函数
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
-
功能:查看 EXTI 线是否被触发过(查看中断标志是否被置位)。
-
返回值:
SET
(已触发)或RESET
(未触发) -
说明:该函数不考虑中断是否真正被 NVIC 响应,仅看是否被外部事件激活。
void EXTI_ClearFlag(uint32_t EXTI_Line);
-
功能:清除中断标志位(写 1 清除对应
PR
寄存器位)。 -
常用于:中断服务函数结束时,清除中断标志,避免反复触发。
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
-
功能:检查是否真正产生了中断请求(中断触发+NVIC允许中断)。
-
返回值:
SET
或RESET
-
使用场景:中断服务函数中判断是哪一条 EXTI 线触发的中断。
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
-
功能:清除中断挂起标志位(功能等同
EXTI_ClearFlag
)。 -
语义区别:表示中断处理结束,适用于中断处理流程中。
2.5.3 结构体定义回顾
typedef struct
{
uint32_t EXTI_Line; // 选择 EXTI 线(EXTI_Line0 ~ EXTI_Line22)
EXTIMode_TypeDef EXTI_Mode; // 中断模式或事件模式(中断或触发外设事件)
EXTITrigger_TypeDef EXTI_Trigger;// 上升沿/下降沿/双边沿
FunctionalState EXTI_LineCmd; // 该中断线是否使能
} EXTI_InitTypeDef;
2.6 SYSCFG 系统配置控制器
SYSCFG(System Configuration Controller)是 STM32F4 系列新增的一个系统配置模块,用于配置系统级别的功能,其中最常用的功能之一就是配置 EXTI 中断线连接到哪个 GPIO 引脚。
2.6.1 SYSCFG 时钟开启
在使用 SYSCFG 配置 EXTI 中断前,必须先开启其时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-
功能:使能 SYSCFG 模块时钟。
-
为什么需要:SYSCFG 是一个外设模块,必须开启其时钟后才能使用其配置功能。
-
使用场景:在配置 EXTI 中断线前的初始化步骤之一。
2.6.2 EXTI 与 GPIO 的连接配置
void SYSCFG_EXTILineConfig(uint8_t EXTI_PortSourceGPIOx, uint8_t EXTI_PinSourcex);
-
功能:配置 EXTI 中断线连接到具体的 GPIO 引脚上。
-
参数说明:
-
EXTI_PortSourceGPIOx
:GPIO 端口(如EXTI_PortSourceGPIOA
) -
EXTI_PinSourcex
:GPIO 引脚(如EXTI_PinSource0
)
-
-
作用:决定某个 EXTI_Line 是监听哪一个 GPIO 口的哪个引脚的电平变化。
-
使用示例:
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
表示将 EXTI_Line0 映射到 GPIOA 的第 0 号引脚(PA0)。
2.6.3 为什么要用 SYSCFG_EXTILineConfig?
在 STM32F1 系列中,EXTI 和 GPIO 的映射是固定的;但在 STM32F4 中,一个 EXTI 线可以挂载在多个不同的 GPIO 引脚上(如 EXTI_Line0 可以连接到 PA0、PB0、PC0 等)。
因此,需要专门的函数SYSCFG_EXTILineConfig
来配置这个“映射关系”。
2.7 程序设计流程
2.7.1 开发步骤
使用 STM32 的 EXTI 外部中断功能时,一般按照以下顺序配置:
步骤 | 说明 | 相关函数 |
---|---|---|
1️⃣ | 开启 GPIO 和 SYSCFG 的时钟 | RCC_AHB1PeriphClockCmd(GPIOx, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); |
2️⃣ | 配置 GPIO 为输入模式(浮空/上拉/下拉) | GPIO_Init() |
3️⃣ | 配置 EXTI 与 GPIO 引脚的映射关系 | SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOx, EXTI_PinSourcex); |
4️⃣ | 初始化 EXTI 外设,设置中断线、触发条件等 | EXTI_Init() |
5️⃣ | 配置 NVIC 中断优先级及使能中断响应 | NVIC_Init() |
6️⃣ | 编写中断服务函数,处理具体中断事件 | void EXTIx_IRQHandler(void) |
7️⃣ | 清除中断标志位,防止重复触发 | EXTI_ClearITPendingBit(EXTI_LineX); |
2.7.2 示例流程
// 1. 开启 GPIO 与 SYSCFG 时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
// 2. 初始化 GPIO 为输入模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 映射 EXTI_Line0 到 PA0
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
// 4. 初始化 EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 5. 配置 NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 6. 编写中断服务函数
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 处理中断事件
// ...
// 7. 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
第三章 实例程序
- 感谢你赐予我前进的力量