诡异的按键失灵事件
那天晚上,我正在调试一个电源管理面板的前面板按键功能。六个按键整整齐齐排列在原理图上:
PC0:增加按键
PE0:减小按键
其他四个按键分别对应菜单、输出开关等功能
代码写得很快,EXTI中断配置看起来也很标准。但测试时出现了奇怪的现象:PE0减小按键正常响应,PC0增加按键却完全没反应。
更诡异的是,单步调试时发现程序确实进入了EXTI0中断处理函数,但只执行PE0的检测分支。


按键初始化代码如下:
void key_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
EXTI_InitTypeDef EXTI_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
/* 开启 GPIO 时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOE |
RCC_APB2Periph_AFIO, ENABLE);
/* 配置 PC0, PC1, PC2, PC13, PC14, PE0 为输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 |
GPIO_Pin_13 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 由于硬件已经有上拉电阻,可以选择浮空输入或内部上拉。
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* EXTI 配置 */
/* PC0 -> EXTI0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* PC1 -> EXTI1 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_Init(&EXTI_InitStructure);
/* PC2 -> EXTI2 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource2);
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_Init(&EXTI_InitStructure);
/* PC13 -> EXTI13 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
EXTI_InitStructure.EXTI_Line = EXTI_Line13;
EXTI_Init(&EXTI_InitStructure);
/* PC14 -> EXTI14 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource14);
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_Init(&EXTI_InitStructure);
/* PE0 -> EXTI0 (注意与 PC0 共用 EXTI0,需区分处理) */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_Init(&EXTI_InitStructure);
/* NVIC 配置 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // PC13, PC14
NVIC_Init(&NVIC_InitStructure);
}
中断响应函数如下:
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0) == 0)
{
// PC0 按键:增加
printf("Increase Button Pressed\r\n");
gui_add_voltage_current_value();
}
else if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_0) == 0)
{
// PE0 按键:减小
printf("Decrease Button Pressed\r\n");
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
本文中使用的单片机为 CH32V307, 和 STM32 配置方法和原理是一样的。
问题定位:深夜的“顿悟时刻”
经过反复排查,我的目光停留在了这两行配置代码上:
/* PC0 -> EXTI0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource0);
/* PE0 -> EXTI0 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource0);
突然意识到问题所在:EXTI0线被重复映射了!
EXTI映射规则:一条EXTI线只能连接一个端口
STM32的EXTI(外部中断)控制器有一个重要特性:
每条EXTI线(如EXTI0、EXTI1等)在同一时间只能映射到一个GPIO端口
引脚编号相同的多个引脚共享同一条EXTI线
后配置的映射会覆盖先前的映射
这意味着在我的代码中:
首先将EXTI0映射到PC0
然后又将EXTI0映射到PE0 → 覆盖了之前的映射
最终EXTI0只响应PE0的中断,PC0的中断被“屏蔽”
原因很明确:EXTI 线是按“引脚编号”分配,但每条 EXTI_Line 只能连接到一个 GPIO 端口上的那个编号的引脚。你的代码里把 EXTI_Line0 先映射到 GPIOC(PC0),后来又映射到 GPIOE(PE0)——最后一次的映射生效,所以 EXTI0 实际上绑定的是 PE0,PC0 虽然电平变化但不会触发 EXTI0 中断,因此看不到第 93 行 printf("Increase Button Pressed\r\n"); 的打印。
证据:
如果注释掉绑定 EXTI_Line0 到 PE0 的行,则 PC0 按键可以正常触发中断:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource0);
解决方案:重新分配EXTI线资源
解决方案:重新分配EXTI线资源
需要找一个和已经使用的 EXTI 线不重复的引脚使用,这里作为临时调试手段,我把 MENU 按键改为了增加按键。
验总结与预防措施
这次经历让我总结了几个重要经验:
设计阶段就要规划好EXTI资源:在画原理图时,尽量避免不同端口的相同引脚编号需要使用中断功能
代码审查时重点关注重复映射:EXTI配置代码需要仔细检查是否有冲突
使用代码注释明确映射关系:
// EXTI0 -> PC0 (增加按键)
利用STM32CubeMX工具:图形化配置工具能够自动检测并避免这种冲突
结语
这个bug的排查过程再次验证了嵌入式开发中的一个真理:最棘手的问题往往源于对硬件特性的理解不够深入。EXTI映射规则在数据手册中写得明明白白,但在实际开发中很容易因思维惯性而忽略。
现在每次配置EXTI时,我都会格外注意映射冲突问题。希望我的这次踩坑经历能帮助你避免类似的陷阱!
思考题: 在你的项目中,是否遇到过因为硬件特性理解不足而导致的诡异bug?欢迎在评论区分享你的故事!

