/****************************************************************************** * @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,保留 SWD(PA13、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; }