全部例程

GPIO输入 - 按键检测

W55MH32

更新于 2026年8月12日

本篇总结

使用 GPIO 外部中断检测 PG6 按键,10ms 消抖后串口打印按键事件。

🔑 KEIL MDK、UART调试、GPIO、EXTI外部中断

整改说明

原例程代码使用 GPIOG Pin_5 / EXTI_Line5,但实际板载 USER 按键连接在 PG6,导致按下按键无任何反应。本次整改将引脚改为 GPIO_Pin_6 / GPIO_PinSource6 / EXTI_Line6,并修正原中断服务函数中错误使用 GPIO_ReadOutputDataBit() 读取输入引脚电平的 bug,改为正确的 GPIO_ReadInputDataBit()。代码已重新验证。

本示例演示 W55MH32 通过 GPIO 外部中断方式检测板载 USER 按键。按键连接 PG6,一端接地,内部上拉,按下时引脚被拉低产生下降沿,触发 EXTI9_5_IRQn 中断,在中断服务函数中经 10ms 软件消抖后打印按键按下提示。

前置工具准备

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

  • WIZ UartTool V1.0:串口收发测试工具:点击下载

硬件连接

本例程使用 PG6 引脚作为板载 USER 按键输入。按键一端接 GND,另一端接 PG6,配置为内部上拉输入模式(GPIO_Mode_IPU),未按下时 PG6 为高电平,按下时被拉低产生下降沿,触发外部中断。USART1(PA9/PA10)用于输出调试信息。

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

工程结构

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

GPIO_Input/
├── USER/
│   ├── main.c            ← GPIO配置 + EXTI外部中断 + 按键消抖
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:GPIO 配置与外部中断按键检测

文件:USER/main.c — 使能 GPIOG 和 AFIO 时钟,配置 PG6 为上拉输入,映射到 EXTI_Line6 下降沿触发,使能 EXTI9_5_IRQn 中断。中断服务函数中 10ms 消抖后用 GPIO_ReadInputDataBit() 确认按键按下。


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

    /* 使能 GPIOG 和 AFIO 时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG | RCC_APB2Periph_AFIO, ENABLE);

    /* 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);

    /* 将 PG6 映射到外部中断线 EXTI6 */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource6);

    /* PG6 -> EXTI6,属于 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);

    /* 配置 EXTI_Line6:中断模式,下降沿触发 */
    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);
}

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)
    {
    }
}

/* EXTI9_5 中断服务函数 */
void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line6) == SET)
    {
        delay_ms(10);  /* 10ms 软件消抖 */
        if (GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_6) == RESET)
        {
            printf("The key is pressed\n");
        }
    }
    EXTI_ClearITPendingBit(EXTI_Line6);
}
  

GPIO 配置:使能 GPIOG 和 AFIO 时钟,配置 PG6 为上拉输入模式(GPIO_Mode_IPU)。未按下时 PG6 为高电平(内部上拉),按下时被拉低。

外部中断线映射:通过 GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource6) 将 PG6 映射到 EXTI_Line6。

NVIC 配置:由于 EXTI_Line6 处于 EXTI5~EXTI9 范围内,共用 EXTI9_5_IRQn 中断通道。设置抢占优先级为 2,子优先级为 3,并使能该中断通道。

EXTI 配置:配置外部中断线 EXTI_Line6 为中断模式,下降沿触发(按键按下时 PG6 由高变低),并使能该中断线。

中断处理流程:检查 EXTI_Line6 中断标志位 → 延时 10ms 消抖 → 使用 GPIO_ReadInputDataBit() 读取 PG6 输入电平,若为低电平(RESET)则确认按键按下,打印 "The key is pressed" → 清除中断标志位。主循环为空,等待外部中断触发。

修正原 bug 说明

原例程代码在中断服务函数中错误使用了 GPIO_ReadOutputDataBit() 读取输入引脚电平——该函数读取的是输出数据寄存器(ODR),而非输入数据寄存器(IDR),用于检测输入引脚电平逻辑上是错误的。本次整改改用正确的 GPIO_ReadInputDataBit(),确保读到的是引脚真实输入电平。

如果你想自定义

  • 换按键引脚 → 修改 GPIO 端口、引脚号和 EXTI 线,如改用 GPIO_Pin_7 / EXTI_Line7(仍属 EXTI9_5)
  • 改消抖时间 → 修改 delay_ms(10) 参数,一般 10~50ms
  • 改触发方式 → 将 EXTI_Trigger_Falling 改为 EXTI_Trigger_Rising(上升沿/释放触发)或 EXTI_Trigger_Rising_Falling(双边沿)
  • 改中断优先级 → 调整 NVIC_IRQChannelPreemptionPriorityNVIC_IRQChannelSubPriority

编译与下载

Keil MDK 中打开 GPIO_Input.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。

运行验证

打开 WIZ UartTool V1.0,波特率 115200 / 8N1。下载固件并按 RESET 后,串口首先打印系统时钟信息。每按一次板载 USER 按键(PG6),串口打印一行 "The key is pressed"。

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 72.0Mhz
GPIO IO Input Test: PG6 USER key (active low).
The key is pressed
The key is pressed
The key is pressed
按键检测串口输出
WIZ UartTool V1.0 显示按键检测:每按一次 USER 键打印一行 "The key is pressed"

常见问题

Q: 按键无反应

确认 PG6 引脚连接了 USER 按键。检查 GPIO 是否配置为上拉输入模式(GPIO_Mode_IPU)。确认 GPIOG 和 AFIO 时钟已使能。检查 EXTI_Line6 是否正确配置为下降沿触发,以及 EXTI9_5_IRQn 中断通道是否已使能。用万用表测量 PG6 电平,未按下应为 3.3V,按下应为 0V。

Q: 串口频繁打印按键事件(抖动)

增加消抖延时时间(如 delay_ms(20))。也可在硬件上加 0.1uF 滤波电容。确认消抖逻辑正确:中断触发后延时 → 再次读取确认电平。

Q: 中断服务函数中读取电平始终为高

检查是否误用了 GPIO_ReadOutputDataBit()(读取输出寄存器 ODR),应使用 GPIO_ReadInputDataBit()(读取输入寄存器 IDR)获取引脚真实输入电平。确认 GPIO 配置为输入模式而非输出模式。

总结

本文基于 W55MH32 的 GPIO 输入功能使用外部中断实现了按键检测:

  • PG6 上拉输入:板载 USER 按键一端接 GND,未按下为高电平,按下为低电平
  • 外部中断 EXTI_Line6:下降沿触发,属于 EXTI9_5_IRQn 中断通道,实时响应按键动作
  • 10ms 软件消抖:中断触发后延时 10ms,再用 GPIO_ReadInputDataBit() 读取确认,消除机械抖动
  • 修正原例程误用 GPIO_ReadOutputDataBit() 的 bug,改为正确的 GPIO_ReadInputDataBit() 读取输入电平

下载本章例程

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

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

Andy

W5500 技术支持专家