STM32 外部中断 EXTI 配置实战:边沿触发与中断优先级详解
👁 7 阅读 · 2026-08-14 · STM32 中断
外部中断(EXTI)是 STM32 实现 GPIO 快速响应的核心机制,广泛应用于按键检测、编码器信号、报警输入等场景。本文抛开 HAL 库的封装,从寄存器与 Cortex-M3 NVIC 层面剖析 EXTI 的边沿触发选择、中断/事件区分以及优先级分组策略,并给出基于标准外设库的完整配置示例,帮助你彻底理清外部中断的配置脉络,写出高效可靠的响应代码。
## 一、EXTI 的工作原理与核心寄存器
STM32 的每个 GPIO 引脚均可作为外部中断输入,但同一时刻只能有一个引脚映射到同一条 EXTI 线(EXTI0~EXTI15)。EXTI 的边沿检测电路支持**上升沿触发**、**下降沿触发**和**双边沿触发**,通过以下寄存器配置:
- `EXTI_RTSR`(上升沿触发选择寄存器)
- `EXTI_FTSR`(下降沿触发选择寄存器)
- `EXTI_IMR`(中断屏蔽寄存器)
- `EXTI_PR`(挂起寄存器,写 1 清除中断标志)
注意区分**中断**和**事件**:中断会进入 NVIC 并执行 ISR,而事件仅用于触发其他外设(如 ADC 采样、DMA 传输),不产生中断。本文只讨论中断模式。
## 二、边沿触发选择详解
边沿触发由 `EXTI_RTSR` 和 `EXTI_FTSR` 共同决定:
- 仅上升沿:`RTSR |= (1 << line)`,`FTSR &= ~(1 << line)`
- 仅下降沿:`FTSR |= (1 << line)`,`RTSR &= ~(1 << line)`
- 双边沿:两个寄存器对应位都置 1
实际项目中的选择原则:
- 按键按下为低电平跳变,常用**下降沿**检测;若按键接上拉电阻,按下接地,下降沿触发最直接。
- 编码器信号通常需要双边沿,以便计数方向判断。
- 若信号存在毛刺,必须配合外部滤波电路或开启 STM32 内部施密特触发器(GPIO 配置为输入模式时默认启用)。
## 三、中断优先级:NVIC 配置核心
EXTI 中断线 0~4 分别对应独立的中断通道:`EXTI0_IRQn`、`EXTI1_IRQn`、`EXTI2_IRQn`、`EXTI3_IRQn`、`EXTI4_IRQn`;而线 5~9 共享 `EXTI9_5_IRQn`,线 10~15 共享 `EXTI15_10_IRQn`。共享中断通道的 ISR 内必须读取 `EXTI_PR` 判断具体是哪条线触发。
NVIC 优先级分为**抢占优先级**和**子优先级**,由 `NVIC_PriorityGroup` 设置。例如分组 2 表示 2 位抢占优先级、2 位子优先级(范围均为 0~3)。配置要点:
- 抢占优先级决定中断能否嵌套,数值越小优先级越高。
- 相同抢占优先级下,子优先级高的先响应,但不能嵌套。
- 外部中断若要求实时性高,应设为较低的数值(如 0)。
## 四、完整配置示例(标准外设库)
下面以 STM32F103 为例,配置 PA0 为上升沿触发中断,抢占优先级 1,子优先级 2。
```c
#include "stm32f10x.h"
void EXTI0_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 1. 开启 GPIOA 时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 2. 配置 PA0 为输入模式,浮空输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 外部已有上拉/下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3. 连接 EXTI 线到 PA0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
/* 4. 配置 EXTI 线 0 为上升沿触发、中断模式 */
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,使能 EXTI0 中断 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/* 中断服务函数 */
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
{
/* 用户代码:例如翻转 LED / 设置标志位 */
// GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
/* 清除中断挂起位,必须由软件写 1 清零 */
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
```
若使用共享通道(如 EXTI9_5),ISR 中需要逐个判断:
```c
void EXTI9_5_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line5) != RESET)
{
// 处理线 5
EXTI_ClearITPendingBit(EXTI_Line5);
}
if (EXTI_GetITStatus(EXTI_Line6) != RESET)
{
// 处理线 6
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
```
## 五、编写 ISR 的重要注意事项
- **清除标志**:即使开启了中断,也必须在 ISR 中清除 `EXTI_PR` 对应位,否则中断会不断触发。
- **ISR 尽量精简**:中断上下文中只做标志位赋值、变量计数或关闭外设,耗时操作(如 printf、延时)移入主循环。
- **共享通道的判断顺序**:如果多条线同时挂起,按优先级顺序处理,且每条线都要清标志。
- **GPIO 模式选择**:浮空输入可能因悬空产生误触发,建议使用上拉/下拉输入(`GPIO_Mode_IPU` / `GPIO_Mode_IPD`),或根据外部电路选择。
- **NVIC 分组**:在系统初始化时调用 `NVIC_PriorityGroupConfig()`,且只需设置一次。
## 六、常见误区排查
1. 中断不触发:检查 `GPIO_EXTILineConfig` 是否正确映射;检查 NVIC 是否使能;检查引脚是否被复用功能占用。
2. 中断误触发频繁:多为毛刺引起,可加硬件 RC 滤波,或改为采集中断后延时去抖(但 ISR 中不要 delay)。
3. 优先级配置无效:确认分组设置是否在使用 NVIC 之前完成。
掌握 EXTI 的边沿触发和优先级配置,是写出稳定中断系统的第一步。建议在实际项目中先用按键裸板验证,再逐步扩展到多路信号,方能熟能生巧。