2. 按键输入检测与消抖

任务说明

本案例基于STM32 HAL库,实现一个独立可运行的按键输入检测程序。核心目标为:通过GPIO读取按键原始电平,采用软件延时消抖(按下与释放均消抖),在确认按键有效按下后翻转LED状态。程序需包含完整的按键初始化、电平读取、消抖判定及LED控制逻辑,不依赖外部中断或定时器,仅使用HAL库的延时函数完成消抖。

硬件连接与原理

按键一端连接至MCU的PA0引脚,另一端接地(低电平有效);LED阳极通过限流电阻接至PC13引脚(STM32F103核心板载LED),阴极接地。当按键未按下时PA0被内部上拉电阻拉高,按下时被拉低。程序通过轮询方式读取PA0电平,检测到低电平后启动10ms延时消抖,若延时后仍为低电平则判定为有效按下,随后翻转PC13输出状态。释放时的抖动同样通过延时确认,避免一次按压产生多次翻转。

代码实现

// main.c 核心逻辑(基于STM32CubeMX生成的HAL工程)
#include "main.h"

// 按键与LED引脚定义(根据实际接线修改)
#define KEY_PIN       GPIO_PIN_0
#define KEY_PORT      GPIOA
#define LED_PIN       GPIO_PIN_13
#define LED_PORT      GPIOC

// 消抖延时(毫秒)
#define DEBOUNCE_TIME  10

// 按键状态机
typedef enum {
    KEY_STATE_IDLE,      // 空闲(未按下)
    KEY_STATE_PRESSED,   // 已确认按下
    KEY_STATE_RELEASED   // 已确认释放
} KeyState;

static KeyState keyState = KEY_STATE_IDLE;

// GPIO初始化(由CubeMX生成,此处为必要配置)
static void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();

    // 配置PA0为输入,上拉
    GPIO_InitStruct.Pin = KEY_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY_PORT, &GPIO_InitStruct);

    // 配置PC13为推挽输出
    GPIO_InitStruct.Pin = LED_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);

    // 初始LED熄灭(根据硬件极性调整)
    HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET);
}

// 按键扫描函数:返回1表示检测到一次有效按下,0表示无
uint8_t Key_Scan(void) {
    uint8_t ret = 0;

    switch (keyState) {
        case KEY_STATE_IDLE:
            // 检测到低电平,进入按下消抖
            if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {
                HAL_Delay(DEBOUNCE_TIME);
                // 消抖后仍为低电平,确认按下
                if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {
                    keyState = KEY_STATE_PRESSED;
                    ret = 1;  // 产生一次有效按下事件
                }
            }
            break;

        case KEY_STATE_PRESSED:
            // 等待释放,释放时同样消抖
            if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_SET) {
                HAL_Delay(DEBOUNCE_TIME);
                if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_SET) {
                    keyState = KEY_STATE_RELEASED;
                }
            }
            break;

        case KEY_STATE_RELEASED:
            // 回到空闲,准备下一次检测
            keyState = KEY_STATE_IDLE;
            break;

        default:
            keyState = KEY_STATE_IDLE;
            break;
    }

    return ret;
}

// 主循环
int main(void) {
    HAL_Init();
    MX_GPIO_Init();

    while (1) {
        // 检测到有效按下则翻转LED
        if (Key_Scan()) {
            HAL_GPIO_TogglePin(LED_PORT, LED_PIN);
        }
    }
}

状态机与消抖逻辑

程序采用三态状态机管理按键过程:空闲态等待首次低电平;按下态确认按键已稳定按下,等待释放;释放态确认释放完成后回到空闲。每次状态迁移均经过10ms延时验证,确保电平变化是人为操作而非机械抖动。有效按下事件仅在“空闲→按下”迁移时产生一次,因此每次完整按压(按下并释放)只触发一次LED翻转,不会因抖动产生多次触发。

验证方法

编译与烧录:使用STM32CubeIDE打开工程,编译后将程序下载至开发板。

功能测试:上电后LED保持熄灭(或初始状态)。手动按下按键并保持约0.5秒后松开,观察LED状态是否翻转(亮→灭或灭→亮)。连续快速按压多次,每次按压应只翻转一次,且无漏检或误触发。

判定标准:若每次完整按压均能稳定翻转LED一次,且快速连续按压时无抖动导致的多次翻转,则判定案例通过。若出现一次按压翻转多次或偶尔无响应,需检查消抖延时是否合适(可适当增大至20ms)或硬件连接是否可靠。

按键消抖状态机与信号时序 状态机 空闲 按下确认 释放确认 低电平+10ms 高电平+10ms 自动迁移 有效按下事件在此产生 按键信号与LED响应时序 按键PA0 抖动 抖动 10ms消抖确认 LED PC13 LED翻转(亮) t0 t1 t2 t3 t4

关键说明

本实现采用阻塞式延时消抖,适用于单按键轮询场景。若需同时处理多个按键或要求低功耗,可改用定时器扫描或外部中断+定时器的方式,但核心消抖逻辑(延时确认电平稳定)保持一致。代码中GPIO初始化部分仅列出必要配置,实际工程中由CubeMX生成的MX_GPIO_Init()会自动包含时钟使能与引脚设置,可直接替换使用。