全部例程

外部中断 / 事件控制器

W55MH32

更新于 2026年9月10日

本篇总结

使用外部中断,按键触发中断经消抖后串口打印按键信息。

🔑 KEIL MDK、UART调试、EXTI、NVIC

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示 W55MH32 的 EXTI(外部中断/事件控制器)外设。将板载按键 PG6 接入 EXTI6,配置为下降沿触发中断:按键按下时硬件自动跳转到中断服务函数,经 10ms 消抖确认后通过串口打印按键按下信息。

前置工具准备

开始本例程前,请确认以下工具已安装:

硬件连接

按键 KEY1 接 PG6(按下拉低,内部上拉输入)。USART1 的 TX(PA9)/RX(PA10) 通过板载 USB 转串口连接到 DEBUG USB 口,用于打印中断日志。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC。

引脚功能方向模式
PG6KEY1 → EXTI6输入上拉输入 (IPU)
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\EXTI\,核心文件如下:

EXTI/
├── USER/
│   ├── main.c            ← EXTI/NVIC/LED 初始化 + 中断服务函数 + 主循环
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数(也可放此处)
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

EXTI 简介

EXTI(External interrupt/event controller)— 外部中断/事件控制器,管理 20 根中断/事件线。每根线对应一个边沿检测器,可实现上升沿或下降沿检测。EXTI0~EXTI15 用于 GPIO(如 EXTI0 可选 PA0/PB0/.../PI0,由 AFIO_EXTICR1 选择),EXTI16~19 用于 PVD、RTC 闹钟、USB 唤醒等特定外设事件。

EXTI 有两种工作模式:中断模式(信号进入 NVIC,由 CPU 执行 ISR,软件级)和事件模式(直接输出脉冲触发其他外设如 ADC/TIM,硬件级,CPU 不参与)。本例程使用中断模式。

中断线与 NVIC 向量的对应关系

EXTI0~EXTI4 各有独立中断向量(EXTI0_IRQn ~ EXTI4_IRQn),而 EXTI5~9 共用 EXTI9_5_IRQn,EXTI10~15 共用 EXTI15_10_IRQn——共用向量内需在 ISR 中判断具体哪根线触发。

步骤1:EXTI 初始化

文件:USER/main.c | 函数:GPIO_Configuration() — 完成 GPIO、AFIO 映射、NVIC、EXTI 四部分配置。

void GPIO_Configuration(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    EXTI_InitTypeDef  EXTI_InitStructure;

    /* 1. 开启 GPIOG、AFIO 时钟(AFIO 是 EXTI 线映射必备) */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG | RCC_APB2Periph_AFIO, ENABLE);

    /* 2. PG6: 上拉输入(按键按下拉低) */
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_Init(GPIOG, &GPIO_InitStructure);

    /* 3. 将 PG6 映射到 EXTI6 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource6);

    /* 4. NVIC: 使能 EXTI9_5 中断向量 */
    NVIC_InitStructure.NVIC_IRQChannel                   = EXTI9_5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* 5. 配置 EXTI6:中断模式 / 下降沿触发 / 使能 */
    EXTI_InitStructure.EXTI_Line    = EXTI_Line6;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

五步完成:开时钟(GPIOG + AFIO)→ GPIO 模式(PG6 上拉输入)→ AFIO 映射 PG6 到 EXTI6 → NVIC 使能中断向量 → EXTI 配置(下降沿中断)。

如果你想自定义

  • 换用其他按键引脚 → 例如 PB1:改 GPIO_Pin_1、GPIO_PortSourceGPIOB、EXTI_Line1,对应中断向量改为 EXTI1_IRQn
  • 改为上升沿或双边沿触发 → 将 EXTI_Trigger_Falling 改为 EXTI_Trigger_Rising 或 EXTI_Trigger_Rising_Falling
  • 改为事件模式(触发 ADC/TIM) → 将 EXTI_Mode_Interrupt 改为 EXTI_Mode_Event,此时不进 NVIC

步骤2:NVIC 配置(含于 GPIO_Configuration)

在本例程中,NVIC 配置集成在 GPIO_Configuration() 函数内(见步骤1代码第 4 步)。EXTI6 属于 EXTI9_5 共用中断向量,需使能 EXTI9_5_IRQn,配置抢占优先级 2、子优先级 3。

/* 以下代码位于 GPIO_Configuration() 内 */
NVIC_InitStructure.NVIC_IRQChannel                   = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
NVIC_Init(&NVIC_InitStructure);

优先级分组只设一次

NVIC_PriorityGroupConfig() 在整个程序中只需调用一次。建议放在 main() 开头,不要在每个外设的 NVIC 配置函数里重复调用——否则后设置的分组会覆盖前面的,导致已有外设的优先级被重新解读。

步骤3:中断服务函数

函数:EXTI9_5_IRQHandler() — 名字必须与启动文件中的中断向量名一致。按键按下时先延时 10ms 消抖,再次确认按键仍为低电平后打印按键按下信息,最后清除挂起标志。

void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line6) == SET)
    {
        delay_ms(10);                                   /* 1. 软件消抖 */
        if (GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_6) == RESET) /* 2. 确认按键仍按下 */
        {
            printf("The key is pressed\n");             /* 3. 打印按键信息 */
        }
    }
    EXTI_ClearITPendingBit(EXTI_Line6);                 /* 4. 清除挂起标志(必不可少) */
}

三个要点:① ISR 名字必须与启动文件向量表一致(EXTI9_5_IRQHandler);② 进 ISR 先判断标志位、做消抖确认后必须清除挂起标志 EXTI_ClearITPendingBit,否则退出后立即重入;③ 延时消抖在中断中仅为演示,实际项目可用定时器扫描+状态机替代。

共用向量的判断方式

若使用 EXTI5~9(向量 EXTI9_5_IRQHandler),需在 ISR 内用 EXTI_GetITStatus(EXTI_Linex) 逐线判断到底是哪根线触发,再分别处理与清标志。

步骤4:主函数逻辑

文件:USER/main.c | 函数:main() — 初始化延时和串口 → 打印时钟 → 配置 GPIO/EXTI/NVIC → 进入空循环等待中断。

int main(void)
{
    RCC_ClocksTypeDef clocks;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("GPIO IO Input Test: PG6 USER key (active low).\n");

    GPIO_Configuration();

    while (1)
    {
    }
}

主循环为空 while(1),所有工作由 EXTI 中断驱动。按键按下时硬件自动跳转 ISR,执行消抖与打印后返回。

如果你想自定义

  • 在主循环里做其他事 → 去掉 __WFI(),把任务放在 while(1) 中,中断来时自动抢占
  • 软件触发中断 → 调用 EXTI_GenerateSWInterrupt(EXTI_Line6),无需按键也能进 ISR,用于测试或任务通知

步骤5:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。

int fputc(int c, FILE *f)
{
    if (c == '\n')
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    return (SER_PutChar(c));
}

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
    USART_SendData(USART_TEST, (uint8_t)ch);
    return ch;
}

运行验证

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口先打印时钟信息与提示语;随后每按一次 KEY1,串口打印一行按键信息。

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
GPIO IO Input Test: PG6 USER key (active low).
The key is pressed
The key is pressed
The key is pressed

每按一次 KEY1(PG6),EXTI6 下降沿触发中断,ISR 中延时 10ms 消抖并确认按键仍按下后,打印一行 "The key is pressed"。若按一次打印多条,多为按键抖动导致多次触发。

常见问题

常见问题

Q: 按键按下完全没有反应,串口也不打印

按顺序排查:① 是否忘记开 RCC_APB2Periph_AFIO 时钟(EXTI 映射必备);② GPIO_EXTILineConfig 的端口源与引脚源是否匹配实际引脚;③ ISR 函数名是否与启动文件完全一致(如 EXTI9_5_IRQHandler,拼写错则不进中断);④ 是否调用了 NVIC_Init 使能中断向量。

Q: 程序一启动就反复进中断停不下来

ISR 中忘记调用 EXTI_ClearITPendingBit(EXTI_Line6) 清除挂起标志。标志未清除时,退出 ISR 后 NVIC 认为中断仍挂起,会立即重入,导致主循环得不到执行。务必在 ISR 末尾清除对应线的挂起标志。

Q: 按一次键打印好几条(计数跳变)

机械按键存在抖动(bounce),一次按下在几毫秒内产生多次电平跳变,EXTI 下降沿会多次触发。可在 ISR 中加 10~20ms 延时消抖后再次读取引脚电平确认;或改用定时器扫描 + 状态机的方式做消抖,避免在中断里长延时。

Q: 同时用了两个 EXTI 线(如 EXTI0 和 EXTI5),只有一个能进中断

检查 NVIC 是否对两个向量都调用了 NVIC_Init 使能。另外若两条线共用一个向量(如 EXTI5 和 EXTI9 都用 EXTI9_5_IRQHandler),需要在同一个 ISR 内用 EXTI_GetITStatus 分别判断并分别清标志,不能只处理一条线。

Q: 优先级分组设了两次导致中断异常

NVIC_PriorityGroupConfig() 全程序只能调用一次。若在多个外设初始化函数里都调用了,后一次会覆盖前一次的分组设置,导致已配置的抢占/子优先级被重新解读,可能出现高优先级中断被低优先级抢占的反常现象。建议统一放在 main() 开头调用一次。

总结

本文基于 W55MH32 的 EXTI 外设实现按键中断:

  • EXTI 配置四步:开 GPIO + AFIO 时钟 → GPIO 模式 → AFIO 映射引脚到 EXTI 线 → EXTI 初始化(线号/模式/触发沿/使能)
  • NVIC 配置:优先级分组只设一次,再为每个中断向量配置抢占/子优先级并使能
  • ISR 三要点:函数名与启动文件一致 → 判断标志位 → 处理逻辑后必须清除挂起标志
  • EXTI0~4 独立向量,EXTI5~9 共用 EXTI9_5,EXTI10~15 共用 EXTI15_10——共用向量需在 ISR 内逐线判断

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家