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清除 |
配置流程其实就三步:
- 选择GPIO引脚源:通过SYSCFG_EXTICRx寄存器,把某个GPIO引脚映射到对应的EXTI线上。
- 配置触发方式和模式:设置RTSR/FTSR选择边沿,设置IMR/EMR选择中断还是事件。
- 配置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的整体结构梳理了一遍,建议你保存下来,配置时对照着看: