基于STM32L051的按键读取程序

基于STM32L051的按键读取程序

STM32L051按键读取解决方案,包括多种检测方式、按键消抖、长短按识别、多键组合等高级功能。

一、硬件连接与配置

1.1 硬件连接

按键连接示例:
KEY1 → PA0 (外部中断/GPIO输入)
KEY2 → PA1 (GPIO输入)
KEY3 → PA2 (GPIO输入)
KEY4 → PA3 (GPIO输入)
KEY_USER → PA4 (用户按键,LQFP32封装)

内部上拉/下拉配置:
- 按键默认接GND,使用内部上拉
- 按键默认接VDD,使用内部下拉

1.2 时钟配置

/* 系统时钟配置 (16MHz HSI) */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
    
    /* 配置HSI */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    
    /* 配置系统时钟 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
    {
        Error_Handler();
    }
}

二、基础按键读取程序

2.1 基础头文件

/**
 * @file key.h
 * @brief STM32L051 按键驱动
 */

#ifndef __KEY_H
#define __KEY_H

#include "stm32l0xx_hal.h"
#include <stdint.h>
#include <stdbool.h>

/* 按键引脚定义 */
#define KEY1_PIN          GPIO_PIN_0
#define KEY1_PORT         GPIOA
#define KEY2_PIN          GPIO_PIN_1
#define KEY2_PORT         GPIOA
#define KEY3_PIN          GPIO_PIN_2
#define KEY3_PORT         GPIOA
#define KEY4_PIN          GPIO_PIN_3
#define KEY4_PORT         GPIOA
#define KEY_USER_PIN      GPIO_PIN_4
#define KEY_USER_PORT     GPIOA

/* 按键编号 */
typedef enum {
    KEY_NONE = 0,
    KEY_1,
    KEY_2,
    KEY_3,
    KEY_4,
    KEY_USER
} Key_ID;

/* 按键状态 */
typedef enum {
    KEY_STATE_RELEASED = 0,    // 释放
    KEY_STATE_PRESSED,         // 按下
    KEY_STATE_LONG_PRESS,      // 长按
    KEY_STATE_HOLD,            // 保持按下
    KEY_STATE_REPEAT,          // 重复按下
} Key_State;

/* 按键事件 */
typedef enum {
    KEY_EVENT_NONE = 0,        // 无事件
    KEY_EVENT_PRESS,           // 按下事件
    KEY_EVENT_RELEASE,         // 释放事件
    KEY_EVENT_SHORT_PRESS,     // 短按事件
    KEY_EVENT_LONG_PRESS,      // 长按事件
    KEY_EVENT_HOLD,            // 保持事件
    KEY_EVENT_REPEAT,          // 重复事件
} Key_Event;

/* 按键配置结构体 */
typedef struct {
    GPIO_TypeDef* port;        // GPIO端口
    uint16_t pin;              // GPIO引脚
    uint32_t press_time;       // 按下时间(ms)
    uint32_t release_time;     // 释放时间(ms)
    Key_State state;           // 当前状态
    Key_Event event;           // 当前事件
    uint8_t repeat_count;      // 重复次数
    uint8_t debounce_count;    // 消抖计数
} Key_Config;

/* 按键扫描结果结构体 */
typedef struct {
    Key_ID key_id;             // 按键ID
    Key_State state;           // 按键状态
    Key_Event event;           // 按键事件
    uint32_t hold_time;        // 保持时间(ms)
} Key_Result;

/* 函数声明 */
void Key_Init(void);
uint8_t Key_Scan(void);
void Key_Scan_Task(void);
Key_Result Key_Get_Result(void);
uint8_t Key_Is_Pressed(Key_ID key);
uint8_t Key_Is_Long_Press(Key_ID key);
void Key_Enable_Interrupt(void);
void Key_Disable_Interrupt(void);
void Key_Set_LongPress_Time(uint16_t time_ms);
void Key_Set_Repeat_Time(uint16_t time_ms);

/* 按键消抖时间定义 */
#define KEY_DEBOUNCE_TIME     20      // 20ms消抖时间
#define KEY_LONG_PRESS_TIME   1000    // 1秒长按时间
#define KEY_REPEAT_DELAY      500     // 500ms重复延迟
#define KEY_REPEAT_RATE       200     // 200ms重复速率

/* 按键缓冲区大小 */
#define KEY_BUFFER_SIZE       10

#endif /* __KEY_H */

2.2 基础按键驱动

/**
 * @file key.c
 * @brief STM32L051 按键驱动实现
 */

#include "key.h"

/* 全局变量 */
Key_Config keys[] = {
    {KEY1_PORT, KEY1_PIN, 0, 0, KEY_STATE_RELEASED, KEY_EVENT_NONE, 0, 0},
    {KEY2_PORT, KEY2_PIN, 0, 0, KEY_STATE_RELEASED, KEY_EVENT_NONE, 0, 0},
    {KEY3_PORT, KEY3_PIN, 0, 0, KEY_STATE_RELEASED, KEY_EVENT_NONE, 0, 0},
    {KEY4_PORT, KEY4_PIN, 0, 0, KEY_STATE_RELEASED, KEY_EVENT_NONE, 0, 0},
    {KEY_USER_PORT, KEY_USER_PIN, 0, 0, KEY_STATE_RELEASED, KEY_EVENT_NONE, 0, 0}
};

#define KEY_COUNT (sizeof(keys) / sizeof(keys[0]))

/* 按键缓冲区 */
static Key_Result key_buffer[KEY_BUFFER_SIZE];
static uint8_t buffer_head = 0;
static uint8_t buffer_tail = 0;

/* 配置参数 */
static uint16_t long_press_time = KEY_LONG_PRESS_TIME;
static uint16_t repeat_delay = KEY_REPEAT_DELAY;
static uint16_t repeat_rate = KEY_REPEAT_RATE;

/* 按键扫描标志 */
static uint8_t key_scan_enabled = 1;

/**
 * @brief 初始化按键GPIO
 */
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* 使能GPIOA时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* 配置按键引脚为上拉输入 */
    GPIO_InitStruct.Pin = KEY1_PIN | KEY2_PIN | KEY3_PIN | KEY4_PIN | KEY_USER_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;      // 内部上拉,按键接地
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    printf("Keys initialized\n");
}

/**
 * @brief 扫描单个按键
 * @param key_idx 按键索引
 */
static void Scan_Single_Key(uint8_t key_idx)
{
    Key_Config *key = &keys[key_idx];
    uint32_t current_time = HAL_GetTick();
    GPIO_PinState pin_state = HAL_GPIO_ReadPin(key->port, key->pin);
    
    /* 按键按下为低电平(内部上拉) */
    if(pin_state == GPIO_PIN_RESET)  // 按键按下
    {
        switch(key->state)
        {
            case KEY_STATE_RELEASED:
                /* 首次检测到按下 */
                key->debounce_count++;
                if(key->debounce_count * 10 >= KEY_DEBOUNCE_TIME)  // 10ms扫描周期
                {
                    key->state = KEY_STATE_PRESSED;
                    key->event = KEY_EVENT_PRESS;
                    key->press_time = current_time;
                    key->repeat_count = 0;
                    key->debounce_count = 0;
                    
                    /* 存入缓冲区 */
                    Key_Result result = {
                        .key_id = key_idx + 1,
                        .state = key->state,
                        .event = key->event,
                        .hold_time = 0
                    };
                    key_buffer[buffer_head] = result;
                    buffer_head = (buffer_head + 1) % KEY_BUFFER_SIZE;
                }
                break;
                
            case KEY_STATE_PRESSED:
                /* 计算按下时间 */
                uint32_t press_duration = current_time - key->press_time;
                
                if(press_duration > long_press_time)
                {
                    key->state = KEY_STATE_LONG_PRESS;
                    key->event = KEY_EVENT_LONG_PRESS;
                }
                else
                {
                    key->state = KEY_STATE_HOLD;
                    key->event = KEY_EVENT_HOLD;
                }
                
                /* 检查重复按下 */
                if(key->repeat_count == 0 || 
                   (current_time - key->press_time) > 
                   (repeat_delay + key->repeat_count * repeat_rate))
                {
                    key->repeat_count++;
                    key->event = KEY_EVENT_REPEAT;
                }
                break;
                
            case KEY_STATE_LONG_PRESS:
            case KEY_STATE_HOLD:
                /* 保持状态 */
                key->event = KEY_EVENT_HOLD;
                break;
        }
    }
    else  // 按键释放
    {
        switch(key->state)
        {
            case KEY_STATE_PRESSED:
            case KEY_STATE_HOLD:
            case KEY_STATE_LONG_PRESS:
                /* 检查是否为短按 */
                if((current_time - key->press_time) < long_press_time)
                {
                    key->event = KEY_EVENT_SHORT_PRESS;
                }
                else
                {
                    key->event = KEY_EVENT_RELEASE;
                }
                
                key->state = KEY_STATE_RELEASED;
                key->release_time = current_time;
                
                /* 存入缓冲区 */
                Key_Result result = {
                    .key_id = key_idx + 1,
                    .state = key->state,
                    .event = key->event,
                    .hold_time = current_time - key->press_time
                };
                key_buffer[buffer_head] = result;
                buffer_head = (buffer_head + 1) % KEY_BUFFER_SIZE;
                break;
                
            case KEY_STATE_RELEASED:
                /* 空闲状态 */
                key->event = KEY_EVENT_NONE;
                key->debounce_count = 0;
                break;
        }
    }
}

/**
 * @brief 扫描所有按键
 * @return 按下的按键编号 (0表示无按键)
 */
uint8_t Key_Scan(void)
{
    if(!key_scan_enabled) return 0;
    
    /* 扫描每个按键 */
    for(uint8_t i = 0; i < KEY_COUNT; i++)
    {
        if(HAL_GPIO_ReadPin(keys[i].port, keys[i].pin) == GPIO_PIN_RESET)
        {
            /* 简单去抖 */
            HAL_Delay(20);
            if(HAL_GPIO_ReadPin(keys[i].port, keys[i].pin) == GPIO_PIN_RESET)
            {
                return i + 1;  // 返回按键编号 (1-5)
            }
        }
    }
    
    return 0;  // 无按键按下
}

/**
 * @brief 按键扫描任务 (需要定期调用,如10ms)
 */
void Key_Scan_Task(void)
{
    if(!key_scan_enabled) return;
    
    for(uint8_t i = 0; i < KEY_COUNT; i++)
    {
        Scan_Single_Key(i);
    }
}

/**
 * @brief 从缓冲区获取按键结果
 * @return 按键结果
 */
Key_Result Key_Get_Result(void)
{
    Key_Result result = {0};
    
    if(buffer_head != buffer_tail)
    {
        result = key_buffer[buffer_tail];
        buffer_tail = (buffer_tail + 1) % KEY_BUFFER_SIZE;
    }
    
    return result;
}

/**
 * @brief 检查按键是否按下
 * @param key 按键ID
 * @return 1:按下, 0:释放
 */
uint8_t Key_Is_Pressed(Key_ID key)
{
    if(key < 1 || key > KEY_COUNT) return 0;
    
    return (keys[key-1].state == KEY_STATE_PRESSED || 
            keys[key-1].state == KEY_STATE_HOLD || 
            keys[key-1].state == KEY_STATE_LONG_PRESS);
}

/**
 * @brief 检查按键是否为长按
 * @param key 按键ID
 * @return 1:长按, 0:非长按
 */
uint8_t Key_Is_Long_Press(Key_ID key)
{
    if(key < 1 || key > KEY_COUNT) return 0;
    
    return (keys[key-1].state == KEY_STATE_LONG_PRESS);
}

/**
 * @brief 使能按键扫描
 */
void Key_Enable_Interrupt(void)
{
    key_scan_enabled = 1;
}

/**
 * @brief 禁用按键扫描
 */
void Key_Disable_Interrupt(void)
{
    key_scan_enabled = 0;
}

/**
 * @brief 设置长按时间
 * @param time_ms 长按时间(毫秒)
 */
void Key_Set_LongPress_Time(uint16_t time_ms)
{
    long_press_time = time_ms;
}

/**
 * @brief 设置重复按键时间
 * @param time_ms 重复时间(毫秒)
 */
void Key_Set_Repeat_Time(uint16_t time_ms)
{
    repeat_delay = time_ms;
    repeat_rate = time_ms;
}

三、外部中断按键检测

3.1 外部中断配置

/**
 * @file key_exti.c
 * @brief STM32L051 按键外部中断驱动
 */

#include "key.h"

/* 外部中断回调函数指针 */
static void (*key_callback)(Key_ID, Key_Event) = NULL;

/* 按键按下时间戳 */
static uint32_t key_press_time[KEY_COUNT] = {0};

/* 长按标志 */
static uint8_t long_press_flag[KEY_COUNT] = {0};

/**
 * @brief 初始化按键外部中断
 */
void Key_EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* 使能GPIOA时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* 配置按键引脚为中断模式 */
    GPIO_InitStruct.Pin = KEY1_PIN | KEY2_PIN | KEY3_PIN | KEY4_PIN | KEY_USER_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发(按下)
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    /* 设置中断优先级 */
    HAL_NVIC_SetPriority(EXTI0_1_IRQn, 0, 0);
    HAL_NVIC_SetPriority(EXTI2_3_IRQn, 0, 0);
    HAL_NVIC_SetPriority(EXTI4_15_IRQn, 0, 0);
    
    /* 使能中断 */
    HAL_NVIC_EnableIRQ(EXTI0_1_IRQn);
    HAL_NVIC_EnableIRQ(EXTI2_3_IRQn);
    HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
    
    printf("Key EXTI initialized\n");
}

/**
 * @brief 设置按键回调函数
 * @param callback 回调函数指针
 */
void Key_Set_Callback(void (*callback)(Key_ID, Key_Event))
{
    key_callback = callback;
}

/**
 * @brief 处理外部中断
 * @param pin 触发中断的引脚
 */
static void Handle_Key_Interrupt(uint16_t pin)
{
    static uint32_t last_time[5] = {0};
    uint32_t current_time = HAL_GetTick();
    Key_ID key_id = KEY_NONE;
    Key_Event event = KEY_EVENT_NONE;
    
    /* 确定按键ID */
    switch(pin)
    {
        case KEY1_PIN:     key_id = KEY_1; break;
        case KEY2_PIN:     key_id = KEY_2; break;
        case KEY3_PIN:     key_id = KEY_3; break;
        case KEY4_PIN:     key_id = KEY_4; break;
        case KEY_USER_PIN: key_id = KEY_USER; break;
        default: return;
    }
    
    /* 防抖动处理 */
    if(current_time - last_time[key_id-1] < KEY_DEBOUNCE_TIME)
    {
        return;
    }
    last_time[key_id-1] = current_time;
    
    /* 检查是否为长按 */
    if(HAL_GPIO_ReadPin(GPIOA, pin) == GPIO_PIN_RESET)
    {
        /* 按键按下 */
        key_press_time[key_id-1] = current_time;
        long_press_flag[key_id-1] = 0;
        event = KEY_EVENT_PRESS;
    }
    else
    {
        /* 按键释放 */
        if(!long_press_flag[key_id-1])
        {
            /* 短按 */
            event = KEY_EVENT_SHORT_PRESS;
        }
        else
        {
            /* 长按后释放 */
            event = KEY_EVENT_RELEASE;
        }
        long_press_flag[key_id-1] = 0;
    }
    
    /* 调用回调函数 */
    if(key_callback != NULL)
    {
        key_callback(key_id, event);
    }
}

/**
 * @brief 检查长按 (在定时器中调用)
 */
void Key_Check_LongPress(void)
{
    uint32_t current_time = HAL_GetTick();
    
    for(uint8_t i = 0; i < KEY_COUNT; i++)
    {
        if(key_press_time[i] > 0 && !long_press_flag[i])
        {
            if(current_time - key_press_time[i] >= long_press_time)
            {
                long_press_flag[i] = 1;
                
                /* 触发长按事件 */
                if(key_callback != NULL)
                {
                    key_callback(i+1, KEY_EVENT_LONG_PRESS);
                }
            }
        }
    }
}

/* 外部中断处理函数 */
void EXTI0_1_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(KEY1_PIN) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY1_PIN);
        Handle_Key_Interrupt(KEY1_PIN);
    }
    
    if(__HAL_GPIO_EXTI_GET_IT(KEY2_PIN) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY2_PIN);
        Handle_Key_Interrupt(KEY2_PIN);
    }
}

void EXTI2_3_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(KEY3_PIN) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY3_PIN);
        Handle_Key_Interrupt(KEY3_PIN);
    }
    
    if(__HAL_GPIO_EXTI_GET_IT(KEY4_PIN) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY4_PIN);
        Handle_Key_Interrupt(KEY4_PIN);
    }
}

void EXTI4_15_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(KEY_USER_PIN) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(KEY_USER_PIN);
        Handle_Key_Interrupt(KEY_USER_PIN);
    }
}

四、低功耗按键检测

4.1 低功耗模式按键唤醒

/**
 * @file key_lowpower.c
 * @brief STM32L051 低功耗按键唤醒
 */

#include "key.h"

/**
 * @brief 配置按键为唤醒源
 */
void Key_Wakeup_Config(void)
{
    /* 配置PA0(KEY1)为唤醒引脚 (EXTI线0) */
    EXTI_HandleTypeDef hexti = {0};
    
    /* 配置EXTI线0 */
    hexti.Line = EXTI_LINE_0;
    hexti.LineCmd = ENABLE;
    HAL_EXTI_SetConfigLine(&hexti, EXTI_MODE_INTERRUPT, EXTI_TRIGGER_FALLING);
    
    /* 清除EXTI挂起标志 */
    __HAL_GPIO_EXTI_CLEAR_FLAG(KEY1_PIN);
    
    /* 配置WKUP引脚 (PA0) 为唤醒源 */
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
    
    printf("Wakeup key configured on PA0\n");
}

/**
 * @brief 进入STOP模式
 */
void Enter_Stop_Mode(void)
{
    printf("Entering STOP mode...\n");
    
    /* 禁用SysTick中断 */
    HAL_SuspendTick();
    
    /* 进入STOP模式 */
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    /* 退出STOP模式后重新配置时钟 */
    SystemClock_Config();
    
    /* 使能SysTick中断 */
    HAL_ResumeTick();
    
    printf("Woke up from STOP mode\n");
}

/**
 * @brief 进入STANDBY模式
 */
void Enter_Standby_Mode(void)
{
    printf("Entering STANDBY mode...\n");
    
    /* 清除唤醒标志 */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
    
    /* 进入STANDBY模式 */
    HAL_PWR_EnterSTANDBYMode();
    
    /* 唤醒后会从复位开始执行 */
}

/**
 * @brief 低功耗按键扫描
 */
uint8_t Key_LowPower_Scan(void)
{
    /* 使用内部唤醒功能检测按键 */
    if(__HAL_PWR_GET_FLAG(PWR_FLAG_WU) != RESET)
    {
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
        
        /* 简单延时去抖 */
        HAL_Delay(50);
        
        /* 检查按键状态 */
        if(HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == GPIO_PIN_RESET)
        {
            return KEY_1;
        }
    }
    
    return KEY_NONE;
}

五、高级功能:组合键检测

5.1 组合键检测

/**
 * @file key_combo.c
 * @brief 按键组合检测
 */

#include "key.h"

/* 组合键定义 */
#define COMBO_KEY1_KEY2    0x01
#define COMBO_KEY3_KEY4    0x02
#define COMBO_ALL_KEYS     0x03

/* 组合键状态 */
typedef struct {
    uint8_t key_mask;      // 按键掩码
    uint32_t start_time;   // 开始时间
    uint8_t active;        // 激活标志
} Combo_State;

static Combo_State combo_state = {0};
static uint8_t combo_timeout = 1000;  // 1秒超时

/**
 * @brief 检测组合键
 * @return 组合键代码,0表示无组合键
 */
uint8_t Key_Combo_Detect(void)
{
    static uint8_t last_key_mask = 0;
    uint8_t current_key_mask = 0;
    uint32_t current_time = HAL_GetTick();
    
    /* 构建按键掩码 */
    if(HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == GPIO_PIN_RESET)
        current_key_mask |= 0x01;
    if(HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN) == GPIO_PIN_RESET)
        current_key_mask |= 0x02;
    if(HAL_GPIO_ReadPin(KEY3_PORT, KEY3_PIN) == GPIO_PIN_RESET)
        current_key_mask |= 0x04;
    if(HAL_GPIO_ReadPin(KEY4_PORT, KEY4_PIN) == GPIO_PIN_RESET)
        current_key_mask |= 0x08;
    
    /* 检查按键变化 */
    if(current_key_mask != last_key_mask)
    {
        if(current_key_mask == 0)
        {
            /* 所有按键释放,检查是否触发组合键 */
            if(combo_state.active && 
               (current_time - combo_state.start_time) < combo_timeout)
            {
                uint8_t combo = combo_state.key_mask;
                combo_state.active = 0;
                return combo;
            }
        }
        else
        {
            /* 有按键按下 */
            if(combo_state.active == 0)
            {
                /* 开始新的组合 */
                combo_state.key_mask = current_key_mask;
                combo_state.start_time = current_time;
                combo_state.active = 1;
            }
            else
            {
                /* 检查是否超时 */
                if((current_time - combo_state.start_time) >= combo_timeout)
                {
                    combo_state.active = 0;
                }
            }
        }
        
        last_key_mask = current_key_mask;
    }
    
    return 0;
}

/**
 * @brief 处理组合键
 * @param combo 组合键代码
 */
void Handle_Combo_Key(uint8_t combo)
{
    switch(combo)
    {
        case COMBO_KEY1_KEY2:
            printf("Combo: KEY1 + KEY2\n");
            /* 执行组合键1功能 */
            break;
            
        case COMBO_KEY3_KEY4:
            printf("Combo: KEY3 + KEY4\n");
            /* 执行组合键2功能 */
            break;
            
        case COMBO_ALL_KEYS:
            printf("Combo: All Keys\n");
            /* 执行特殊功能 */
            break;
    }
}

六、主程序示例

6.1 完整主程序

/**
 * @file main.c
 * @brief STM32L051 按键读取主程序
 */

#include "main.h"
#include "key.h"
#include "key_exti.h"
#include "key_lowpower.h"
#include "key_combo.h"
#include <stdio.h>

/* 全局变量 */
UART_HandleTypeDef huart2;
TIM_HandleTypeDef htim2;

/* 重定向printf到串口 */
int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart2, (uint8_t*)ptr, len, 1000);
    return len;
}

/**
 * @brief 系统时钟配置
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    /* 配置HSI 16MHz */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    
    /* 配置系统时钟 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}

/**
 * @brief 串口初始化
 */
void MX_USART2_UART_Init(void)
{
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 115200;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&huart2);
}

/**
 * @brief 定时器2初始化 (用于按键扫描)
 */
void MX_TIM2_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 16000 - 1;  // 16MHz/16000 = 1kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10 - 1;       // 1kHz/10 = 100Hz (10ms)
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
    
    /* 使能定时器中断 */
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

/**
 * @brief GPIO初始化
 */
void MX_GPIO_Init(void)
{
    /* GPIO时钟使能 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* LED引脚 */
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_5;  // LED on PA5
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/**
 * @brief 按键回调函数示例
 */
void key_callback_example(Key_ID key_id, Key_Event event)
{
    switch(event)
    {
        case KEY_EVENT_SHORT_PRESS:
            printf("Key %d: Short Press\n", key_id);
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // 切换LED
            break;
            
        case KEY_EVENT_LONG_PRESS:
            printf("Key %d: Long Press\n", key_id);
            /* 执行长按功能 */
            break;
            
        case KEY_EVENT_REPEAT:
            printf("Key %d: Repeat Press\n", key_id);
            break;
    }
}

/**
 * @brief 主函数
 */
int main(void)
{
    /* HAL库初始化 */
    HAL_Init();
    
    /* 系统时钟配置 */
    SystemClock_Config();
    
    /* 外设初始化 */
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_TIM2_Init();
    
    printf("\nSTM32L051 Key Reading Demo\n");
    printf("===========================\n");
    
    /* 按键初始化 */
    Key_Init();                      // 基本GPIO输入
    // Key_EXTI_Init();               // 或使用外部中断
    // Key_Set_Callback(key_callback_example);
    
    /* 配置低功耗唤醒 */
    // Key_Wakeup_Config();
    
    /* 启动定时器 */
    HAL_TIM_Base_Start_IT(&htim2);
    
    printf("System Ready. Press keys to test.\n");
    printf("KEY1: Toggle LED\n");
    printf("KEY2: Enter Low Power\n");
    printf("KEY3+KEY4: Combo Test\n");
    
    uint32_t last_check_time = HAL_GetTick();
    
    while(1)
    {
        /* 方法1: 简单扫描 */
        uint8_t key = Key_Scan();
        if(key != 0)
        {
            printf("Key %d pressed (scan)\n", key);
            
            if(key == KEY_2)
            {
                printf("Entering STOP mode...\n");
                // Enter_Stop_Mode();
            }
        }
        
        /* 方法2: 从缓冲区获取按键事件 */
        Key_Result result = Key_Get_Result();
        if(result.key_id != 0)
        {
            printf("Key %d: Event=%d, Hold=%lums\n", 
                   result.key_id, result.event, result.hold_time);
        }
        
        /* 方法3: 组合键检测 */
        uint8_t combo = Key_Combo_Detect();
        if(combo != 0)
        {
            Handle_Combo_Key(combo);
        }
        
        /* 每100ms检查一次 */
        if(HAL_GetTick() - last_check_time >= 100)
        {
            last_check_time = HAL_GetTick();
            
            /* 可以执行其他周期性任务 */
        }
        
        HAL_Delay(10);  // 10ms延时
    }
}

/**
 * @brief 定时器中断回调 (10ms定时扫描)
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        Key_Scan_Task();      // 扫描按键
        // Key_Check_LongPress();  // 检查长按
    }
}

参考代码 基于STM32L051的按键读取程序 www.youwenfan.com/contentcsv/70752.html

七、测试程序

7.1 按键测试程序

/**
 * @file test_key.c
 * @brief 按键测试程序
 */

#include "key.h"

void Test_All_Keys(void)
{
    printf("\n=== Key Test Program ===\n");
    printf("Press each key to test\n");
    printf("=======================\n");
    
    uint32_t test_start = HAL_GetTick();
    uint8_t key1_pressed = 0;
    uint8_t key2_pressed = 0;
    uint8_t key3_pressed = 0;
    uint8_t key4_pressed = 0;
    uint8_t user_pressed = 0;
    
    while(1)
    {
        /* 测试每个按键 */
        if(HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == GPIO_PIN_RESET)
        {
            if(!key1_pressed)
            {
                key1_pressed = 1;
                printf("KEY1 pressed\n");
            }
        }
        else
        {
            if(key1_pressed)
            {
                key1_pressed = 0;
                printf("KEY1 released\n");
            }
        }
        
        if(HAL_GPIO_ReadPin(KEY2_PORT, KEY2_PIN) == GPIO_PIN_RESET)
        {
            if(!key2_pressed)
            {
                key2_pressed = 1;
                printf("KEY2 pressed\n");
            }
        }
        else
        {
            if(key2_pressed)
            {
                key2_pressed = 0;
                printf("KEY2 released\n");
            }
        }
        
        if(HAL_GPIO_ReadPin(KEY3_PORT, KEY3_PIN) == GPIO_PIN_RESET)
        {
            if(!key3_pressed)
            {
                key3_pressed = 1;
                printf("KEY3 pressed\n");
            }
        }
        else
        {
            if(key3_pressed)
            {
                key3_pressed = 0;
                printf("KEY3 released\n");
            }
        }
        
        if(HAL_GPIO_ReadPin(KEY4_PORT, KEY4_PIN) == GPIO_PIN_RESET)
        {
            if(!key4_pressed)
            {
                key4_pressed = 1;
                printf("KEY4 pressed\n");
            }
        }
        else
        {
            if(key4_pressed)
            {
                key4_pressed = 0;
                printf("KEY4 released\n");
            }
        }
        
        if(HAL_GPIO_ReadPin(KEY_USER_PORT, KEY_USER_PIN) == GPIO_PIN_RESET)
        {
            if(!user_pressed)
            {
                user_pressed = 1;
                printf("USER pressed\n");
            }
        }
        else
        {
            if(user_pressed)
            {
                user_pressed = 0;
                printf("USER released\n");
            }
        }
        
        /* 测试5秒后退出 */
        if(HAL_GetTick() - test_start > 5000)
        {
            printf("\nTest completed\n");
            break;
        }
        
        HAL_Delay(10);
    }
}

void Test_Key_Scan(void)
{
    printf("\n=== Key Scan Test ===\n");
    printf("Press any key. Press USER key to exit.\n");
    
    uint32_t last_key_time = 0;
    
    while(1)
    {
        uint8_t key = Key_Scan();
        
        if(key != 0)
        {
            uint32_t current_time = HAL_GetTick();
            
            if(current_time - last_key_time > 200)  // 200ms防抖
            {
                last_key_time = current_time;
                
                printf("Key %d detected\n", key);
                
                if(key == KEY_USER)
                {
                    printf("Exit test\n");
                    break;
                }
            }
        }
        
        HAL_Delay(10);
    }
}

void Test_Key_Events(void)
{
    printf("\n=== Key Event Test ===\n");
    printf("Test short/long press events\n");
    
    Key_Set_LongPress_Time(1500);  // 设置1.5秒为长按
    
    uint32_t test_time = HAL_GetTick();
    
    while(HAL_GetTick() - test_time < 10000)  // 测试10秒
    {
        Key_Result result = Key_Get_Result();
        
        if(result.key_id != 0)
        {
            switch(result.event)
            {
                case KEY_EVENT_SHORT_PRESS:
                    printf("Key %d: Short Press (%lums)\n", 
                           result.key_id, result.hold_time);
                    break;
                    
                case KEY_EVENT_LONG_PRESS:
                    printf("Key %d: Long Press (%lums)\n", 
                           result.key_id, result.hold_time);
                    break;
                    
                case KEY_EVENT_PRESS:
                    printf("Key %d: Pressed\n", result.key_id);
                    break;
                    
                case KEY_EVENT_RELEASE:
                    printf("Key %d: Released\n", result.key_id);
                    break;
                    
                case KEY_EVENT_HOLD:
                    printf("Key %d: Holding\n", result.key_id);
                    break;
                    
                case KEY_EVENT_REPEAT:
                    printf("Key %d: Repeat\n", result.key_id);
                    break;
            }
        }
        
        HAL_Delay(1);
    }
}

八、优化建议

8.1 降低功耗

/**
 * @brief 优化低功耗按键扫描
 */
void Key_LowPower_Optimize(void)
{
    /* 1. 降低GPIO速度 */
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_LOW;
    
    /* 2. 使用外部中断唤醒替代轮询 */
    /* 3. 在不使用时关闭上拉电阻 */
    // HAL_GPIO_DeInit(GPIOA, KEY1_PIN | KEY2_PIN | KEY3_PIN | KEY4_PIN);
    
    /* 4. 使用RTC唤醒定时扫描 */
}

/**
 * @brief 中断模式配置
 */
void Key_Interrupt_Config(void)
{
    /* 配置上升沿和下降沿都触发 */
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = KEY1_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(KEY1_PORT, &GPIO_InitStruct);
}

九、常见问题解决

问题 可能原因 解决方案
按键无响应 1. GPIO配置错误
2. 上拉电阻未启用
3. 按键损坏
1. 检查GPIO模式
2. 启用内部上拉
3. 检查硬件连接
按键抖动 1. 机械抖动
2. 接触不良
1. 增加消抖时间
2. 使用硬件消抖电路
按键粘连 1. 按键老化
2. 电路问题
1. 更换按键
2. 添加下拉电阻
功耗过高 1. 轮询频率过高
2. 上拉电阻太小
1. 降低扫描频率
2. 增大上拉电阻值

专注于matlab/simulink,电子电路,编程