基于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. 增大上拉电阻值 |