大数跨境

STM32踩坑记:我的按键中断为何无响应?EXTI中断映射的陷阱

STM32踩坑记:我的按键中断为何无响应?EXTI中断映射的陷阱 飞多学堂
2025-12-05
2
导读:深夜调代码,最怕遇到这种“看似正常”的bug:PE0按键响应正常,PC0按键却像睡着了一样毫无反应。
深夜调代码,最怕遇到这种“看似正常”的bug:PE0按键响应正常,PC0按键却像睡着了一样毫无反应。问题出在一个容易被忽视的EXTI映射规则上。

诡异的按键失灵事件

那天晚上,我正在调试一个电源管理面板的前面板按键功能。六个按键整整齐齐排列在原理图上:

  • PC0:增加按键

  • PE0:减小按键

  • 其他四个按键分别对应菜单、输出开关等功能

代码写得很快,EXTI中断配置看起来也很标准。但测试时出现了奇怪的现象:PE0减小按键正常响应,PC0增加按键却完全没反应

更诡异的是,单步调试时发现程序确实进入了EXTI0中断处理函数,但只执行PE0的检测分支。

image-20251205170439845

image-20251205170623029

按键初始化代码如下:

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线

  • 后配置的映射会覆盖先前的映射

这意味着在我的代码中:

  1. 首先将EXTI0映射到PC0

  2. 然后又将EXTI0映射到PE0 → 覆盖了之前的映射

  3. 最终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 按键改为了增加按键。

验总结与预防措施

这次经历让我总结了几个重要经验:

  1. 设计阶段就要规划好EXTI资源:在画原理图时,尽量避免不同端口的相同引脚编号需要使用中断功能

  2. 代码审查时重点关注重复映射:EXTI配置代码需要仔细检查是否有冲突

  3. 使用代码注释明确映射关系

// EXTI0 -> PC0 (增加按键)
  1. 利用STM32CubeMX工具:图形化配置工具能够自动检测并避免这种冲突

结语

这个bug的排查过程再次验证了嵌入式开发中的一个真理:最棘手的问题往往源于对硬件特性的理解不够深入。EXTI映射规则在数据手册中写得明明白白,但在实际开发中很容易因思维惯性而忽略。

现在每次配置EXTI时,我都会格外注意映射冲突问题。希望我的这次踩坑经历能帮助你避免类似的陷阱!

思考题: 在你的项目中,是否遇到过因为硬件特性理解不足而导致的诡异bug?欢迎在评论区分享你的故事!

加作者微信:jiyuyun18, 留言:交流电子技术,加入电子技术交流群。

【声明】内容源于网络
0
0
飞多学堂
1234
内容 157
粉丝 0
飞多学堂 1234
总阅读62
粉丝0
内容157