3. EXTI(外部中断/事件控制器)详解
好,咱们今天来聊聊EXTI。说实话,这个外设是STM32中断系统里最常用、也最容易出问题的一个模块。我刚开始做项目那会儿,就因为在EXTI配置上踩了个坑,导致产品在强电磁干扰环境下疯狂误触发。从那以后,我对EXTI的理解就深刻多了。
EXTI的全称是External Interrupt/Event Controller,也就是外部中断/事件控制器。它负责检测GPIO引脚上的电平变化,然后产生中断请求或者事件信号。你想想看,按键按下、传感器输出跳变、通信协议中的边沿检测……这些场景都离不开EXTI。
3.1 EXTI结构框图
我们先看EXTI的内部结构。我习惯把EXTI分成三个部分:输入级、边沿检测电路、以及输出级。
核心要点:EXTI的输入信号可以来自GPIO引脚,也可以来自软件触发。输出信号可以走中断通道,也可以走事件通道。两者互不干扰。
从框图可以看到,EXTI的核心寄存器包括:
- 中断屏蔽寄存器 (IMR):控制是否允许中断通过。我习惯叫它「总闸」。
- 上升沿触发选择寄存器 (RTSR):使能上升沿触发。
- 下降沿触发选择寄存器 (FTSR):使能下降沿触发。
- 软件中断事件寄存器 (SWIER):通过软件写1来模拟外部中断。
- 挂起寄存器 (PR):中断发生后硬件自动置1,软件读1后写1清除。
个人经验:我在调试时经常用PR寄存器来判断中断是否真的触发了。如果PR位为1但中断没响应,那问题大概率出在NVIC配置上,而不是EXTI本身。
3.2 EXTI触发方式
EXTI支持三种触发方式:
| 触发方式 | 配置寄存器 | 典型应用场景 |
|---|---|---|
| 上升沿触发 | RTSR对应位置1 | 按键释放检测、脉冲计数 |
| 下降沿触发 | FTSR对应位置1 | 按键按下检测、下降沿中断 |
| 双边沿触发 | RTSR和FTSR同时置1 | 编码器信号、电平变化检测 |
这里有个细节要注意:边沿检测电路只检测电平变化,不检测电平状态。也就是说,如果你配置了上升沿触发,那么只有从低到高的跳变才会产生中断。持续的高电平不会触发。
避坑指南:我曾经在一个项目中,用EXTI检测一个传感器的输出。传感器上电后默认输出高电平,我配置了上升沿触发。结果传感器第一次输出高电平时,因为是从低到高跳变,触发了中断。但后续传感器一直保持高电平,再也没有中断产生。后来我改成双边沿触发才解决问题。所以,一定要根据信号的实际波形来选择触发方式。
3.3 EXTI与GPIO的映射关系
这是很多新手容易搞混的地方。STM32的EXTI有16条中断线(EXTI0~EXTI15),分别对应GPIO的16个引脚。但注意:同一时刻,一条中断线只能连接一个GPIO引脚。
举个例子:
- EXTI0 可以连接 PA0、PB0、PC0……但只能选一个。
- EXTI1 可以连接 PA1、PB1、PC1……同样只能选一个。
这个映射关系是通过SYSCFG_EXTICR寄存器来配置的。每个EXTI线对应一个4位的配置域,选择对应的GPIO端口。
核心原则:如果你要用PA0和PB0同时作为外部中断输入,那是不可能的!因为它们都映射到EXTI0。你必须把其中一个换到其他引脚,比如PA0和PB1。
我见过有人这样写代码:
// 错误示例:PA0和PB0同时配置为EXTI中断
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 这样配置后,只有最后一个配置生效!
正确的做法是:
// 正确示例:使用PA0和PB1
// PA0 -> EXTI0
SYSCFG_EXTILineConfig(EXTI_PortSource_GPIOA, EXTI_PinSource0);
// PB1 -> EXTI1
SYSCFG_EXTILineConfig(EXTI_PortSource_GPIOB, EXTI_PinSource1);
3.4 软件中断
软件中断是个很实用的功能。说白了,就是通过写寄存器来「伪造」一个外部中断。这在测试和调试时特别有用。
具体做法是:
- 配置好EXTI的触发方式和中断屏蔽。
- 向SWIER寄存器的对应位写1。
- 硬件会自动产生一个中断请求,就像真的外部信号触发了一样。
// 软件触发EXTI0中断
EXTI->SWIER |= EXTI_SWIER_SWI0; // 写1触发
// 注意:触发后需要清除挂起位
EXTI->PR = EXTI_PR_PR0; // 写1清除
我的习惯:在写驱动代码时,我会先用软件中断测试中断服务函数是否正常。这样就不用每次都拿信号发生器去戳引脚了。等软件中断跑通了,再接外部信号。能省不少调试时间。
软件中断还有一个妙用:在低功耗模式下唤醒MCU。比如你可以在进入休眠前,通过软件中断设置一个定时唤醒。虽然这个功能通常用RTC或定时器来实现,但EXTI的软件中断在某些场景下也是个轻量级的选择。
嗯,EXTI的内容差不多就这些。结构不复杂,但细节很多。尤其是GPIO映射那块,我建议你画个表格记一下,或者直接看参考手册的EXTI章节。下次咱们聊NVIC的时候,会看到EXTI和NVIC是怎么配合工作的。
公众号:蓝海资料掘金营,微信deep3321