Files
DTU-LCD/Drivers/BSP/KEY/key.c

202 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* @file key.c
* @brief 按键驱动模块 - 基于 MultiButton 库的高可移植性实现
* @details 本文件实现了基于 MultiButton 库的按键驱动功能,支持多按键配置、
* 按键状态检测、按键事件回调等功能。采用配置表方式管理按键,
* 便于扩展和维护。支持单次点击事件检测。
* @author 阜阳师范大学物电学院
* @version V0.1
* @date 2026.1.19
* @note 依赖库: MultiButton
* 支持按键: ENTER, UP, DOWN, LEFT, RIGHT, ESC, ADD, DEC, RESET
* 按键模式: 输入模式,无上拉/下拉
* 特殊处理: PB3 需要禁用 JTAG 才能作为普通 GPIO 使用
******************************************************************************/
#include "key.h"
#include "160160D.h"
/* ============================================================================
* 按键配置结构体定义
* ============================================================================ */
/**
* @brief 按键配置结构体
* @details 用于存储每个按键的硬件配置信息,包括 GPIO 端口、引脚和按键类型
*/
typedef struct {
uint16_t pin; /**< GPIO 引脚编号(如 GPIO_PIN_12 */
GPIO_TypeDef* port; /**< GPIO 端口(如 GPIOD */
KEY_TYPE key_type; /**< 按键类型标识(如 KEY_ENTER */
} KeyConfig_t;
/* ============================================================================
* 按键硬件引脚映射表
* ============================================================================ */
/**
* @brief 按键硬件引脚映射表
* @details 详细引脚映射关系如下:
* | 按键名称 | 端口 | 引脚 | 按键类型 | 功能说明 |
* |---------|------|------|----------|----------|
* | KEY_ENTER | GPIOD | PD12 | KEY_ENTER | 确认键 |
* | KEY_UP | GPIOD | PD11 | KEY_UP | 上键 |
* | KEY_DOWN | GPIOD | PD10 | KEY_DOWN | 下键 |
* | KEY_LEFT | GPIOD | PD13 | KEY_LEFT | 左键 |
* | KEY_RIGHT | GPIOD | PD8 | KEY_RIGHT | 右键 |
* | KEY_ESC | GPIOB | PB15 | KEY_ESC | 取消键 |
* | KEY_ADD | GPIOB | PB3 | KEY_ADD | 加键(需禁用 JTAG |
* | KEY_RESET | GPIOD | PD15 | KEY_RESET | 复位键 |
* @note PB3 引脚默认被 JTAG 占用,需要禁用 JTAG 才能作为普通 GPIO 使用
*/
static const KeyConfig_t key_configs[] = {
{GPIO_PIN_12, GPIOD, KEY_ENTER}, /**< 确认键PD12 */
{GPIO_PIN_11, GPIOD, KEY_UP}, /**< 上键PD11 */
{GPIO_PIN_10, GPIOD, KEY_DOWN}, /**< 下键PD10 */
{GPIO_PIN_13, GPIOD, KEY_LEFT}, /**< 左键PD13 */
{GPIO_PIN_8, GPIOD, KEY_RIGHT}, /**< 右键PD8 */
{GPIO_PIN_15, GPIOB, KEY_ESC}, /**< 取消键PB15 */
{GPIO_PIN_3, GPIOB, KEY_ADD}, /**< 加键PB3需禁用 JTAG */
{GPIO_PIN_15, GPIOD, KEY_RESET}, /**< 复位键PD15 */
};
#define KEY_COUNT (sizeof(key_configs) / sizeof(key_configs[0])) /**< 按键总数 */
/* ============================================================================
* 全局变量定义
* ============================================================================ */
static Button btn_handles[KEY_COUNT]; /**< MultiButton 按键句柄数组 */
static KeyCallback key_callback = NULL; /**< 业务逻辑层注册的回调函数指针 */
/* ============================================================================
* 内部函数实现
* ============================================================================ */
/**
* @brief 读取按键 GPIO 电平状态
* @param button_id 按键 ID对应 key_configs 数组索引0 到 KEY_COUNT-1
* @note 该函数由 MultiButton 库调用,用于读取按键的硬件状态
* 返回值0 表示按下低电平1 表示释放(高电平)
* @retval 0: 按键按下(低电平)
* @retval 1: 按键释放(高电平)
*/
static uint8_t button_read_level(uint8_t button_id)
{
uint8_t pin_bit = HAL_GPIO_ReadPin(key_configs[button_id].port, key_configs[button_id].pin);
return pin_bit;
}
/**
* @brief 统一的按键事件回调函数(内部使用)
* @param btn MultiButton 库的按键句柄指针
* @note 当 MultiButton 库检测到按键事件时,会调用此函数
* 此函数将按键事件转换为业务逻辑层的按键类型,并调用注册的回调函数
* 处理流程:
* 1. 检查按键句柄和回调函数是否有效
* 2. 从按键句柄中获取 button_id
* 3. 通过 button_id 查找对应的按键类型
* 4. 调用业务逻辑层注册的回调函数
* @retval 无
*/
static void button_callback(Button* btn)
{
if (btn != NULL && key_callback != NULL)
{
/* 通过 button_id 获取对应的按键类型 */
uint8_t button_id = btn->button_id;
if (button_id < KEY_COUNT)
{
/* 调用业务逻辑层注册的回调函数 */
key_callback(key_configs[button_id].key_type);
}
}
}
/**
* @brief 使能指定 GPIO 端口的时钟
* @param port GPIO 端口指针(如 GPIOA, GPIOB, GPIOD 等)
* @note 使用 switch-case 结构实现,代码简洁清晰
* 支持所有 GPIO 端口A-G
* 在初始化 GPIO 之前必须使能对应的时钟
* @retval 无
*/
static void KEY_GPIO_ClockEnable(GPIO_TypeDef *port)
{
switch ((uint32_t)port) {
case (uint32_t)GPIOA: __HAL_RCC_GPIOA_CLK_ENABLE(); break; /**< 使能 GPIOA 时钟 */
case (uint32_t)GPIOB: __HAL_RCC_GPIOB_CLK_ENABLE(); break; /**< 使能 GPIOB 时钟 */
case (uint32_t)GPIOC: __HAL_RCC_GPIOC_CLK_ENABLE(); break; /**< 使能 GPIOC 时钟 */
case (uint32_t)GPIOD: __HAL_RCC_GPIOD_CLK_ENABLE(); break; /**< 使能 GPIOD 时钟 */
case (uint32_t)GPIOE: __HAL_RCC_GPIOE_CLK_ENABLE(); break; /**< 使能 GPIOE 时钟 */
case (uint32_t)GPIOF: __HAL_RCC_GPIOF_CLK_ENABLE(); break; /**< 使能 GPIOF 时钟 */
case (uint32_t)GPIOG: __HAL_RCC_GPIOG_CLK_ENABLE(); break; /**< 使能 GPIOG 时钟 */
default: break;
}
}
/* ============================================================================
* 外部接口函数实现
* ============================================================================ */
/**
* @brief 按键硬件初始化函数
* @note 初始化流程:
* 1. 使能 AFIO 时钟(修改 AFIO 寄存器前必须)
* 2. 禁用 JTAG保留 SWD释放 PA15、PB3、PB4 作为普通 GPIO
* 3. 遍历按键配置表,对每个按键进行初始化:
* a. 使能对应 GPIO 端口时钟
* b. 配置 GPIO 为输入模式,无上拉/下拉,高速模式
* c. 初始化 MultiButton 按键句柄
* d. 绑定单次点击事件到回调函数
* e. 启动按键检测
* @note 特殊处理:由于使用了 PB3 引脚,需要禁用 JTAG 功能
* JTAG 禁用后PA13、PA14 仍可用于 SWD 调试
* @retval 无
*/
void Key_Init(void)
{
/* ========== AFIO 和 JTAG 配置 ========== */
__HAL_RCC_AFIO_CLK_ENABLE(); /**< 使能 AFIO 时钟(修改 AFIO 寄存器前必须) */
/* 禁用 JTAG保留 SWDPA13、PA14 仍可用于调试) */
/* 这会释放 PA15、PB3、PB4 作为普通 GPIO */
__HAL_AFIO_REMAP_SWJ_NOJTAG();
/* ========== GPIO 和按键初始化 ========== */
GPIO_InitTypeDef gpio_init_struct = {0};
/* 批量初始化所有按键 */
for (uint8_t button_id = 0; button_id < KEY_COUNT; button_id++)
{
/* 使能对应 GPIO 端口时钟 */
KEY_GPIO_ClockEnable(key_configs[button_id].port);
/* 配置 GPIO 为输入模式 */
gpio_init_struct.Pin = key_configs[button_id].pin;
gpio_init_struct.Mode = GPIO_MODE_INPUT; /**< 输入模式 */
gpio_init_struct.Pull = GPIO_NOPULL; /**< 无上拉/下拉(外部电路处理) */
gpio_init_struct.Speed = GPIO_SPEED_HIGH; /**< 高速模式,提高响应速度 */
HAL_GPIO_Init(key_configs[button_id].port, &gpio_init_struct);
/* 初始化 MultiButton 按键句柄 */
button_init(&btn_handles[button_id], button_read_level, 0, button_id);
/* 绑定单次点击事件到回调函数 */
button_attach(&btn_handles[button_id], BTN_SINGLE_CLICK, button_callback);
/* 启动按键检测 */
button_start(&btn_handles[button_id]);
}
}
/**
* @brief 注册按键回调函数(供业务逻辑层调用)
* @param callback 按键回调函数指针,当检测到按键事件时会调用此函数
* @note 业务逻辑层通过此函数注册按键处理回调
* 回调函数参数为 KEY_TYPE 类型,表示被按下的按键类型
* 如果传入 NULL则取消已注册的回调函数
* @retval 无
*/
void Key_RegisterCallback(KeyCallback callback)
{
key_callback = callback;
}