STM32L072CZ双EXTI中断异常排查:操作Switch-1时Switch-2中断ISR误触发问题分析
开发背景
我基于STM32L072CZ微控制器做项目开发,集成了LoRa通信功能,是在LoRa LSN50-V2(集成STM32与LoRa芯片的节点)的底层固件基础上编写应用层程序。
配置说明
我把PB14(对应Switch-1)和PB15(对应Switch-2)两个引脚配置成上升沿+下降沿触发的EXTI中断,用宏定义来管理引脚、时钟及端口的初始化、配置和反初始化,相关代码如下:
void mainInit( void ) { GPIO_InitTypeDef GPIO_InitStruct={0}; GPIO_MAIN_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_MAIN_PIN; GPIO_InitStruct.Mode =GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed=GPIO_SPEED_HIGH; HW_GPIO_Init( GPIO_MAIN_PORT, GPIO_MAIN_PIN, &GPIO_InitStruct ); /* Enable and set EXTI lines 4 to 15 Interrupt to the lowest priority */ HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI4_15_IRQn); } void dgInit( void ) { GPIO_InitTypeDef GPIO_InitStruct={0}; GPIO_DG_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_DG_PIN; GPIO_InitStruct.Mode =GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed=GPIO_SPEED_HIGH; HW_GPIO_Init( GPIO_DG_PORT, GPIO_DG_PIN, &GPIO_InitStruct ); /* Enable and set EXTI lines 4 to 15 Interrupt to the lowest priority */ HAL_NVIC_SetPriority(EXTI4_15_IRQn, 1, 0); HAL_NVIC_EnableIRQ(EXTI4_15_IRQn); } void mainIoDeInit( void ) { GPIO_InitTypeDef GPIO_InitStruct={0}; GPIO_InitStruct.Pull = GPIO_PULLDOWN; GPIO_InitStruct.Mode =GPIO_MODE_IT_RISING_FALLING; HW_GPIO_Init( GPIO_MAIN_PORT, GPIO_MAIN_PIN, &GPIO_InitStruct ); } void dgIoDeInit( void ) { GPIO_InitTypeDef GPIO_InitStruct={0}; GPIO_InitStruct.Pull = GPIO_PULLDOWN; GPIO_InitStruct.Mode =GPIO_MODE_IT_RISING_FALLING; HW_GPIO_Init( GPIO_DG_PORT, GPIO_DG_PIN, &GPIO_InitStruct ); } /* IRQ handler functionality*/ //HAL_GPIO_EXTI_IRQHandler( GPIO_PIN_14 ); if (joined_flags==1) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_DG_PIN)!=RESET) { HAL_Delay(30); mainStreamer=0; dgStreamer=1; __HAL_GPIO_EXTI_CLEAR_IT(GPIO_DG_PIN); HAL_GPIO_EXTI_IRQHandler(GPIO_DG_PIN); } //HAL_GPIO_EXTI_IRQHandler( GPIO_PIN_15 ); if(__HAL_GPIO_EXTI_GET_IT(GPIO_MAIN_PIN)!=RESET) { HAL_Delay(30); mainStreamer=1; dgStreamer=0; __HAL_GPIO_EXTI_CLEAR_IT(GPIO_MAIN_PIN); HAL_GPIO_EXTI_IRQHandler(GPIO_MAIN_PIN); } }
问题现象
大部分时候中断都能正常触发并更新开关状态,但偶尔会出问题:当Switch-2处于空闲状态时,我只操作Switch-1,却触发了Switch-2的中断服务程序(ISR)。
问题原因分析
结合STM32 EXTI的工作机制和你的代码实现,我梳理了几个可能的诱因:
1. EXTI中断组的优先级配置逻辑有误
你要注意,PB14和PB15都属于EXTI4-15中断组,它们共享同一个EXTI4_15_IRQn中断向量。你在mainInit()里给这个中断向量设了优先级0,又在dgInit()里改成了优先级1——但STM32的硬件逻辑里,同一个中断向量下的多个EXTI线,优先级配置是全局生效的,不会单独区分引脚。而且当多个EXTI线同时触发时,硬件不会帮你排序,得靠ISR里的代码逐个判断触发源,这就给误判留了空间。
2. 中断标志位的处理顺序混乱
看你的ISR代码,你先手动调用__HAL_GPIO_EXTI_CLEAR_IT()清除标志,然后又调用HAL_GPIO_EXTI_IRQHandler()——这个HAL函数内部还会再执行一次标志位清除,同时还会触发HAL_GPIO_EXTI_Callback()回调。如果此时刚好有电磁干扰导致另一个引脚的标志位被意外置位,就很容易被误当成有效触发来处理。
另外,绝对不要在ISR里用HAL_Delay()!HAL_Delay依赖SysTick定时器计时,而中断上下文会打断SysTick的计数,不仅延时不准,还会拉长ISR的执行时间,让其他中断的标志位累积,增加误触发的概率。
3. 硬件干扰或开关抖动的影响
虽然你配置了上拉电阻,但LoRa节点工作时的射频信号可能会产生电磁干扰,给引脚带来毛刺信号,误触发EXTI中断。你加的30ms延时本来是用来防抖的,但放在ISR里执行反而会让问题恶化——这段时间里如果有干扰信号触发了另一个引脚的标志,就会被当成有效中断处理。
4. 反初始化函数的逻辑不符合预期
你的mainIoDeInit()和dgIoDeInit()里,只是把上拉改成了下拉,但引脚仍然保持GPIO_MODE_IT_RISING_FALLING的中断模式。如果某些场景下调用了这些反初始化函数,引脚还是会响应电平变化,要是此时下拉后的引脚出现电平波动,就可能误触发中断,而且此时的处理逻辑和正常初始化后的逻辑不一致,更容易出问题。
建议的修复方向
- 调整ISR的标志位处理流程:先判断中断标志,然后把防抖逻辑移到主循环(用定时器记录触发时间,主循环判断是否达到防抖时长再更新状态),最后只清除当前触发的标志位,不要再调用
HAL_GPIO_EXTI_IRQHandler(),避免重复操作标志位。 - 移除ISR中的延时函数:改用定时器防抖,比如在ISR里记录当前的系统tick值,然后在主循环中检查当前tick和记录的tick差值是否大于防抖时间,再执行状态更新。
- 简化中断优先级配置:因为两个引脚共享同一个中断向量,只需要在初始化时设置一次优先级即可,不需要重复设置。
- 优化硬件抗干扰:给开关引脚并接100nF的陶瓷滤波电容,布线时远离LoRa射频电路,减少干扰信号的影响。
- 修正反初始化函数:如果反初始化是为了关闭中断,应该把引脚模式改成
GPIO_MODE_INPUT或者GPIO_MODE_ANALOG,同时关闭对应的EXTI中断线,彻底禁用中断触发。
内容的提问来源于stack exchange,提问作者Annakkili Arumugam

