本文最后更新于 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 004NVIC_PriorityGroup_0
Group 113NVIC_PriorityGroup_1
Group 222NVIC_PriorityGroup_2
Group 331NVIC_PriorityGroup_3
Group 440NVIC_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_0NVIC_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 模块实现了以下功能:

  1. 对输入引脚的上升沿/下降沿进行检测。

  2. 可以通过软件触发中断。

  3. 提供中断屏蔽、事件屏蔽等机制。

  4. 与 NVIC 连接,实现中断请求的发出。

  5. 所有配置都通过 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允许中断)。

  • 返回值SETRESET

  • 使用场景:中断服务函数中判断是哪一条 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);
    }
}

第三章 实例程序