低优先级EXTI15_10中断死循环时高优先级EXTI4无法抢占问题
问题描述
参考《Mastering STM32》中的中断优先级示例代码,将EXTI15_10(对应GPIO_PIN_13)配置为抢占优先级1的低优先级中断,其ISR回调函数包含死循环;理论上抢占优先级0的高优先级EXTI4(对应GPIO_PIN_4)中断应能抢占该低优先级中断,但在NUCLEO-F446RE开发板上多次测试均未实现预期效果。
相关代码如下:
static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* USER CODE BEGIN MX_GPIO_Init_1 */ /* USER CODE END MX_GPIO_Init_1 */ /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET); /*Configure GPIO pin : B1_Pin */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_PULLDOWN; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC,&GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_8; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); HAL_NVIC_SetPriority(EXTI15_10_IRQn,1,0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); HAL_NVIC_SetPriority(EXTI4_IRQn,0,0); HAL_NVIC_EnableIRQ(EXTI4_IRQn); /* USER CODE BEGIN MX_GPIO_Init_2 */ /* USER CODE END MX_GPIO_Init_2 */ } void EXTI15_10_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); } void EXTI4_IRQHandler(void){ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); } /* USER CODE BEGIN 4 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_13) { blink = 1; while (blink) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8); for (volatile int i = 0; i < 100000; i++); // Delay } } else{ blink=0; } }
问题排查与解决方案
1. 显式配置中断优先级分组
STM32的中断优先级依赖优先级分组配置,分组决定了抢占优先级和子优先级的位数分配。若未显式设置,HAL库默认分组可能存在不确定性,导致抢占优先级设置未生效。
在MX_GPIO_Init的USER CODE BEGIN MX_GPIO_Init_1区域添加分组配置:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
该配置为4位抢占优先级,0位子优先级,此时抢占优先级范围为0-15,数值越小优先级越高,完全匹配你设置的EXTI4(0)和EXTI15_10(1)的优先级规则。
2. 验证EXTI4中断触发有效性
单独测试EXTI4中断是否能正常触发:修改HAL_GPIO_EXTI_Callback,在EXTI4分支添加LED闪烁或串口输出,确认按键按下时该分支能被执行,排除硬件连接或触发边沿配置错误。
示例修改:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_13) { blink = 1; while (blink) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8); for (volatile int i = 0; i < 100000; i++); } } else if(GPIO_Pin == GPIO_PIN_4){ blink=0; // 添加测试代码确认中断触发 HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); } }
3. 确认中断标志位清除逻辑
虽然HAL_GPIO_EXTI_IRQHandler会自动清除中断标志位,可手动在EXTI4的IRQHandler中补充清除操作,避免因HAL库逻辑遗漏导致中断无法触发:
void EXTI4_IRQHandler(void){ __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_4); HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); }
4. 检查全局中断状态
确保代码中未调用__disable_irq()关闭全局中断,死循环中的忙等逻辑不会影响中断响应,只要全局中断处于开启状态(默认HAL_Init会开启),高优先级中断就能抢占当前低优先级中断。
原理说明
STM32的NVIC中断抢占规则:
- 仅当新中断的抢占优先级高于当前执行的中断时,才会触发抢占。
- 抢占优先级数值越小,优先级越高(0为最高优先级)。
- 优先级分组必须正确配置,否则抢占优先级的设置无法被NVIC正确识别。
完成上述配置后,按下EXTI4对应的按键,高优先级中断会抢占EXTI15_10的死循环,将blink置0,使死循环退出,恢复正常程序流程。
内容的提问来源于stack exchange,提问作者Lipin Kariappa

