## 一、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 的边沿触发和优先级配置,是写出稳定中断系统的第一步。建议在实际项目中先用按键裸板验证,再逐步扩展到多路信号,方能熟能生巧。