STM32 单片机--中断 NVIC
1、基础概念:
中断系统(NVIC)是 STM32 的关键部分 ,因为 STM32 的高实时性能很大程度上依赖 NVIC(中断控制器)。
NVIC(Nested Vectored Interrupt Controller)
中文名叫:嵌套向量中断控制器。
它是 ARM Cortex-M 内核里一个专门负责管理中断的模块。
STM32 的所有外设(比如 USART、TIM、ADC、EXTI)如果要触发中断,都必须经过 NVIC。
NVIC 的作用(你可以记住4个字)
👉 “管、排、优、嵌”
| 功能 | 说明 |
|---|---|
| 管理中断 | 决定哪个中断能触发、哪个被屏蔽 |
| 排队响应 | 多个中断同时发生时,按优先级处理 |
| 优先级设置 | 每个中断都有优先级,可高可低 |
| 嵌套执行 | 允许高优先级中断打断低优先级中断执行 |
中断分类:
内部中断/内核中断 :
- 芯片内部/内核中断内容,也可以称之为【Exception 异常】。程序运行 正常过程中,出现的非正常情况。例如 RESET 按键,内存溢出,硬件问题。。。内部/内核 中断重点关注【系统嘀嗒 SysTick】。
外部中断:
- 片上外设中断,STM32 片上外设大多数都有对应的中断情况,例如 USART 数据发送完 成中断,数据接收中断,数据总线空闲中断,数据错误中断。。。大多数中断都是片上 外设完成特定任务,指定条件触发... 需要对应外设 CR 和 SR 两个寄存器配合使用。CR 用于控制中断是否开启 IE,SR 是判断触发的中断是哪一个。
- EXTI 外部中断控制线,MCU 提供给引脚连接的外部设备,例如 SR602,外部压力传感 器,烟雾传感器。利用基本的【高低电平切换出现的边沿情况】进行中断触发。扩宽外 部中断控制。
中断触发条件:
STM32 中的中断源主要分为以下三大类:
| 类别 | 来源 | 举例 |
|---|---|---|
| ① 外部中断(EXTI) | 外部引脚电平或边沿变化 |
按键、光敏模块、传感器信号等 上升沿触发(信号从 0 → 1)下降沿触发(信号从 1 → 0)双边沿触发(任意变化都触发) |
| ② 外设中断 | 外设内部事件触发 | USART、TIM、ADC、DMA 等 |
| ③ 系统异常中断 | 内核事件触发 | SysTick、HardFault、NMI 等 |
中断中的常用概念
- 断点:在 MCU 中注册的中断触发位置,一般对应中断触发条件,触发寄存器标志位变化。 需要通过 CR 寄存器对当前模块中断进行使能 IE ==> Interrupt Enable
- 中断源:当前中断触发之后,对应的中断处理函数 IRQHandler
- 压栈:中断触发,此时【中断处理函数 IRQHandler】称为前台函数,中断触发位置函数成为 【后台函数】

中断压栈行为:

IRQn 和 IRQHandler 解释
IRQn 是中断的“编号”(Interrupt Request Number):它是 NVIC(中断控制器)用来识别中断源的编号或枚举名。

IRQHandler 是中断服务函数(ISR):
IRQHandler= Interrupt Request Handler- 表示对应中断的 中断服务函数(ISR, Interrupt Service Routine)
IRQHandler 和 IRQn 一一对应,当前在 MCU 中注册了 IRQn 中断请求编号,内核在触发中
断之后,会自动调用对应 IRQn 的 IRQHandler 处理函数。当某个中断发生后,NVIC 会自动跳转执行这个函数。IRQn 是中断的 “编号”,IRQHandler 是中断的 “函数入口”。
中断优先级:
在 STM32 中,中断优先级分为两类:
| 类型 | 中文名 | 作用 |
|---|---|---|
| Preemption Priority | 占先优先级(可抢占优先级) | 决定 能不能打断别的中断 |
| Subpriority | 次级优先级(响应优先级) | 决定 相同占先优先级中,谁先执行 |
假设有两个中断:
-
中断A:占先优先级 = 1,次级优先级 = 0
-
中断B:占先优先级 = 2,次级优先级 = 0
👉 当 中断A 正在执行时,中断B 发生了:
由于 A 的占先优先级 比 B 高(1 < 2),
所以 B 不会打断 A,必须等 A 执行完再执行 B。
反过来,如果 中断B 正在执行时,中断A 触发,
A 的占先优先级更高,因此 A 会打断 B 执行(即“抢占”)。
当占先优先级相同,次级优先级决定谁先被响应。次级优先级小的先执行,大的后执行。
优先级分组(非常关键)
STM32 用 NVIC_PriorityGroupConfig() 或 NVIC_SetPriorityGrouping() 来设置优先级分组。
优先级分组决定了 多少位用于占先优先级,多少位用于次级优先级。
| 分组 | 占先优先级位数 | 次级优先级位数 | 说明 |
|---|---|---|---|
| 0 | 0 | 4 | 全是次级优先级,不能抢占 |
| 1 | 1 | 3 | 1位抢占 + 3位次级 |
| 2 | 2 | 2 | 2位抢占 + 2位次级 |
| 3 | 3 | 1 | 3位抢占 + 1位次级 |
| 4 | 4 | 0 | 全是占先优先级,没有次级(最多抢占层次) |
STM32 通常是 4bit 优先级字段,总共 16 级可用。
EXTI 外部中断控制线开发
EXTI = External Interrupt / Event Controller(外部中断/事件控制器)
EXTI 的作用是让 外部引脚(GPIO) 也能触发中断或事件。EXTI 就是让 MCU “听得见” 外部信号变化(比如按钮、传感器信号),
然后触发中断去执行响应动作的模块。


2、相关寄存器编写:
EXTI开发所需的必要内容:
- EXTI 外部中断控制线配置对应的 GPIO 引脚。
- EXTI 边沿检测,上升沿和下降沿控制。
- EXTI 中断使能
开发流程:
🧩 第1步:开启时钟
🧩 第2步:配置 GPIOA0 为输入模式
🧩 第3步:将 GPIOA0 映射到 EXTI0
🧩 第3步:将 GPIOA0 映射到 EXTI0
🧩 第5步:配置 NVIC(中断控制器)
🧩 第6步:编写中断服务函数
AFIO(Alternate Function I/O)复用功能输入输出控制器。
AFIO 是 STM32 芯片里一个专门控制 GPIO 引脚功能映射和复用的外设模块。
| 功能 | 说明 |
|---|---|
| 1️⃣ GPIO 复用控制 | 让一个引脚选择不同的外设功能(如 USART_TX、SPI_MOSI 等) |
| 2️⃣ 外部中断映射 | 让 EXTI0~EXTI15 能和不同端口的 GPIO 引脚绑定(如 PA0、PB0、PC0 等) |
| 3️⃣ JTAG/SWD 配置 | 控制调试接口(JTAG/SWD)的引脚使用情况(比如释放 PA15、PB3、PB4) |

以AFIO_EXTICR1为例:

例如:EXTI1配置0000 == > PA1;EXTI1 配置 0100 ==> PE1;EXTI1 配置 0010 ==> PC1。
AFIO_EXTICR1 控制 EXTI0~EXTI3外部中断控制线,

上升沿/下降沿触发选择寄存器:

以KEY_UP按键为例,希望按下KEY_UP产生外部中断,
按下时,WK_UP 从 0V → 3.3V,电平上升。因此,中断应设置为 上升沿触发(rising edge)。
(如果按键按下之后提供给MCU电平为低电平,GPIO默认为高电平模式,选择下降沿触发。)


EXTI中断使能
需要开启EXTI外部中断线中断触发支持,需要通过两个寄存器配合完成。分别是EXTI_IMR和EXTI_PR。
EXTI_IMR(中断屏蔽寄存器)(IMR = Interrupt Mask Register)是否开发当前对应外部中断线的中断。

EXTI_PR(挂起寄存器)(PR = Pending Register)
中断发生后,该位会被硬件置 1,表示该线路有“中断挂起”。必须手动清零(写 1 清零)。
-
当对应的外部事件发生(选定的上升/下降沿出现)时,硬件会把 EXTI_PR 寄存器对应位写为 1(表示该线有中断“挂起”)。该位不会自动清除,必须由软件清除。
-
必须在中断处理流程中清除该位,否则中断标志一直存在,NVIC 会认为中断仍然挂起,CPU 可能会持续(或再次)进入该中断处理函数,导致“死循环中断”或系统卡住。
-
清除可以在 ISR 开头 也可以在 处理逻辑结束前,两种做法都会被用到

其他EXTI寄存器
EXTI_EMR(事件屏蔽寄存器):控制是否允许产生事件信号(Event,不进入 NVIC,但可触发内部外设反应,比如触发 ADC 转换)。

EXTI_SWIER(软件中断寄存器)通过软件设置该位,可以人为触发一次中断。用于测试或调试。

3、代码编写:
编写按键中断:
前期分析工作,根据原理图得

代码:
void Key_Init(void)
{
//开启时钟使能
RCC->APB2ENR |= GPIOA_RCC_APB2_CLOCK_ENABLE;
RCC->APB2ENR |= GPIOE_RCC_APB2_CLOCK_ENABLE;
/**
设置为 GPIO 上拉下拉输入模式
*/
// KEY_UP PA0
GPIOA->CRL &= ~(0x0F);
GPIOA->CRL |= PULL_UP_OR_PULL_DOWN;
//利用 ODR 低电平明确限制下拉输入
GPIOA->ODR &= ~(0x01);
// KEY0 PE4 KEY1 PE3 KEY2 PE2
GPIOE->CRL &= ~(0x0FFF << 8);
GPIOE->CRL |= (PULL_UP_OR_PULL_DOWN << 16);
GPIOE->CRL |= (PULL_UP_OR_PULL_DOWN << 12);
GPIOE->CRL |= (PULL_UP_OR_PULL_DOWN << 8);
//利用 ODR 高电平明确限制上拉输入
GPIOE->ODR |= (0x07 << 2);
}
void Key_EXTI_Interrupt_Enable(void)
{
//前面配置过GPIO模式,因此需要 AFIO 对当前引脚进行复用配置
//使能 AFIO
RCC->APB2ENR |= AFIO_RCC_APB2_CLOCK_ENABLE;
/**
* AFIO 外部中断控制线 EXTI 对应 GPIO 引脚配置
* KEY_UP PA0
* KEY0 PE4
* KEY1 PE3
* KEY2 PE2
* 需要配置 EXTI0 EXTI2 EXTI3 EXTI4
* EXTI0 EXTI2 EXTI3 ==> EXTICR1 寄存器
* EXTI4 ==> EXTICR2 寄存器
* EXTI0 ==> PA0 ==> 寄存器对应位存储内容为 0000
* EXTI2 ==> PE2 ==> 寄存器对应位存储内容为 0100
* EXTI3 ==> PE3 ==> 寄存器对应位存储内容为 0100
* EXTI4 ==> PE4 ==> 寄存器对应位存储内容为 0100
*/
AFIO->EXTICR[0] |= EXTI_GPIOEX << 12 | EXTI_GPIOEX << 8 | EXTI_GPIOAX;
AFIO->EXTICR[1] |= EXTI_GPIOEX;
/**
* 配置外部中断控制线 EXTI 对应 上升沿 或者 下降沿
* key_up PA0==> EXTI0 上升沿触发
* KEY0 PE4、KEY1 PE3 、KEY2 PE2 下降沿触发
*/
EXTI->RTSR |= 0x01;
EXTI->FTSR |= (0x07 << 2);
/**
* 中断屏蔽寄存器配置,开启 EXTI 0 2 3 4
*/
EXTI->IMR |= 0x1D;
/**
* 中断注册
* 利用 NVIC 对当前EXTI对应的 EXTIx_IRQn 进行注册,告知 MCU
* 当前程序运行过程中,存在 EXTI 外部中断控制线中断内容
* NVIC_SetPriority(EXTIx_IRQn,优先级)
*/
NVIC_SetPriority(EXTI0_IRQn, 1);
NVIC_SetPriority(EXTI2_IRQn, 1);
NVIC_SetPriority(EXTI3_IRQn, 1);
NVIC_SetPriority(EXTI4_IRQn, 1);
/**
* 中断触发之后处理函数
*/
NVIC_EnableIRQ(EXTI0_IRQn);
NVIC_EnableIRQ(EXTI2_IRQn);
NVIC_EnableIRQ(EXTI3_IRQn);
NVIC_EnableIRQ(EXTI4_IRQn);
/**
* EXTI0_IRQHandler 是 EXTI0_IRQn 的中断处理函数。
*/
}
void EXTI0_IRQHandler(void)
{
/**
*
*/
}
void EXTI2_IRQHandler(void)
{
/**
*
*/
}
void EXTI3_IRQHandler(void)
{
/**
*
*/
}
void EXTI4_IRQHandler(void)
{
/**
*
*/
}
更多推荐


所有评论(0)