3. EXTI外部中断控制器:从原理到实战

好,咱们今天来聊聊EXTI。说实话,外部中断是嵌入式开发里最常用的功能之一。按键响应、传感器信号跳变、编码器脉冲计数……哪样都离不开它。我刚开始用STM32时,总觉得EXTI配置挺绕的,后来摸透了它的设计思路,才发现其实很清晰。

3.1 EXTI功能概述

EXTI的全称是External Interrupt/Event Controller,也就是外部中断/事件控制器。它的核心任务就一个:检测外部引脚上的信号变化,然后产生中断请求或事件脉冲。

你想想看,CPU不可能一直轮询某个引脚的电平变化吧?那太浪费算力了。EXTI就是帮我们做这个监控工作的硬件模块。一旦检测到我们设定的边沿条件,它就会通知CPU去处理。

核心要点:EXTI可以工作在两种模式下——中断模式和事件模式。中断模式会触发CPU执行中断服务函数;事件模式只产生一个硬件脉冲,用于触发其他外设(比如DMA、定时器),不打断CPU。

3.2 EXTI线:16条GPIO线 + 7条专用线

EXTI最让人困惑的地方,可能就是它的“线”的概念了。我当年第一次看参考手册时,也被这23条线搞得有点懵。

简单来说,EXTI有23条检测线,每条线对应一个独立的检测通道:

线编号 来源 说明
EXTI0 ~ EXTI15 GPIO引脚 16条GPIO线,每条对应一组GPIO端口的同号引脚(如PA0、PB0、PC0共用EXTI0)
EXTI16 PVD输出 可编程电压检测器
EXTI17 RTC闹钟事件 RTC闹钟触发
EXTI18 RTC唤醒事件 RTC唤醒定时器触发
EXTI19 USB唤醒事件 USB从挂起模式唤醒
EXTI20 以太网唤醒事件 部分型号支持
EXTI21 RTC入侵事件 RTC tamper检测
EXTI22 RTC时间戳事件 RTC时间戳触发

这里有个关键点:EXTI0~EXTI15这16条线,每条线只能同时连接一个GPIO引脚。什么意思呢?比如EXTI0线,你可以选择PA0、PB0、PC0、PD0……中的任意一个,但同一时刻只能选一个。不能同时让PA0和PB0都挂在EXTI0上。

我曾经踩过的坑:有一次项目里要用两个按键,我图省事把PA0和PB0都配置成EXTI0中断,结果发现只有一个能触发。查了半天手册才意识到——EXTI0线只能选一个引脚源。后来老老实实把第二个按键换到EXTI1线上,问题解决。

3.3 EXTI触发方式

EXTI支持三种触发方式,说白了就是告诉硬件:什么样的信号变化算“有效事件”。

  • 上升沿触发:信号从低电平跳变到高电平时触发。适合检测按键松开、脉冲上升段。
  • 下降沿触发:信号从高电平跳变到低电平时触发。适合检测按键按下、脉冲下降段。
  • 双边沿触发:上升沿和下降沿都触发。适合编码器信号、频率测量等场景。

我个人习惯,按键中断一般用下降沿触发,配合硬件消抖电路效果最好。如果是检测脉冲宽度,那就用双边沿,分别记录上升和下降的时间戳。

3.4 EXTI中断模式与事件模式

这两个模式的区别,我打个比方你就明白了:

  • 中断模式:就像有人敲门,你必须停下来去开门(执行ISR)。CPU会被打断,保存现场,执行中断服务函数,再恢复现场。
  • 事件模式:就像门铃响了之后自动触发了一个机关(比如启动DMA传输),但你不必亲自去开门。CPU继续干自己的事,事件信号直接送给其他外设。

什么时候用事件模式?我举个例子:ADC连续采样时,你可以用定时器事件来触发ADC转换,而不需要CPU每次去启动转换。这样CPU就能腾出手来处理更重要的任务。

小技巧:如果你只需要唤醒CPU而不需要执行复杂的ISR逻辑,可以考虑用事件模式配合WFI指令。这样功耗更低,响应也够快。

3.5 EXTI寄存器配置

配置EXTI,说白了就是操作几个寄存器。STM32的HAL库封装得很好,但理解底层寄存器对调试很有帮助。我一般先看寄存器,再用HAL库,这样出问题时能快速定位。

核心寄存器就这几个:

寄存器 功能 关键位
IMR 中断屏蔽寄存器 对应位写1使能中断请求
EMR 事件屏蔽寄存器 对应位写1使能事件请求
RTSR 上升沿触发选择寄存器 对应位写1使能上升沿触发
FTSR 下降沿触发选择寄存器 对应位写1使能下降沿触发
SWIER 软件中断事件寄存器 写1可软件触发中断/事件(用于调试)
PR 挂起寄存器 硬件置1表示有中断挂起,写1清除

配置流程其实就三步:

  1. 选择GPIO引脚源:通过SYSCFG_EXTICRx寄存器,把某个GPIO引脚映射到对应的EXTI线上。
  2. 配置触发方式和模式:设置RTSR/FTSR选择边沿,设置IMR/EMR选择中断还是事件。
  3. 配置NVIC:使能对应的中断通道,设置优先级。

下面是一个用寄存器方式配置PA0下降沿中断的示例:

// 1. 使能GPIOA和SYSCFG时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

// 2. 将PA0连接到EXTI0线
SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;
// 默认EXTI0对应PA0,这里保持默认即可

// 3. 配置下降沿触发
EXTI->FTSR |= EXTI_FTSR_TR0;

// 4. 使能中断模式(不使能事件)
EXTI->IMR |= EXTI_IMR_MR0;
EXTI->EMR &= ~EXTI_EMR_MR0;

// 5. 配置NVIC
NVIC_EnableIRQ(EXTI0_IRQn);
NVIC_SetPriority(EXTI0_IRQn, 2);

注意:中断服务函数里一定要记得清除挂起位!否则会一直进中断。清除方法是在PR寄存器的对应位写1:EXTI->PR |= EXTI_PR_PR0;

3.6 EXTI知识体系总览

下面这张图把EXTI的整体结构梳理了一遍,建议你保存下来,配置时对照着看:

EXTI外部中断控制器知识体系 输入源 16条GPIO线 (EXTI0~15) 7条专用线 (EXTI16~22) 触发方式 上升沿 / 下降沿 / 双边沿 RTSR / FTSR 寄存器 工作模式 中断模式 (IMR) 事件模式 (EMR) 核心寄存器组 IMR | EMR | RTSR | FTSR | SWIER | PR SYSCFG_EXTICRx (GPIO引脚映射) 中断路径 → NVIC → CPU执行ISR 事件路径 → 脉冲输出 → 触发其他外设 配置流程:选择引脚源 → 配置触发边沿 → 选择中断/事件 → 使能NVIC → 编写ISR 注意:中断服务函数中必须清除PR挂起位,否则会反复触发