一、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。
#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 中需要逐个判断:
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(),且只需设置一次。
六、常见误区排查
- 中断不触发:检查
GPIO_EXTILineConfig是否正确映射;检查 NVIC 是否使能;检查引脚是否被复用功能占用。 - 中断误触发频繁:多为毛刺引起,可加硬件 RC 滤波,或改为采集中断后延时去抖(但 ISR 中不要 delay)。
- 优先级配置无效:确认分组设置是否在使用 NVIC 之前完成。
掌握 EXTI 的边沿触发和优先级配置,是写出稳定中断系统的第一步。建议在实际项目中先用按键裸板验证,再逐步扩展到多路信号,方能熟能生巧。