深入探究单片机按键应用框架的按键乐章
引言
在嵌入式系统中,按键作为用户与设备交互的重要接口,其稳定可靠的响应是保证用户体验的关键。然而,按键开发面临着诸多挑战,其中包括需求不确定导致的频繁修改和各种按键触发方式的实现。为了解决这些问题,我们提出了一种创新的单片机按键应用框架,旨在简化按键开发流程、提高开发效率,以及增强按键系统的稳定性和灵活性。
按键开发的难点和痛点
在当今的嵌入式系统开发中,按键作为最常用的外设之一,扮演着与用户交互的关键角色。然而,如果在按键的前期开发过程中,框架的设计和实现不合理,往往会导致后续开发阶段面临一系列挑战和痛点。
1. 需求不确定性导致的频繁修改
在嵌入式系统开发中,按键功能的需求经常受到项目进展和用户反馈等因素的影响,这导致按键功能需求具有一定的不确定性。因此,在开发过程中经常需要对按键逻辑进行频繁修改,以满足新的需求和变化的要求。这种频繁修改按键逻辑的现象,不仅增加了开发人员的工作量,还可能导致开发进度的延迟和项目成本的增加。
2. 多样化的按键触发方式需求
在嵌入式系统中,不同的应用场景可能需要实现多种按键触发方式,这些触发方式包括短按、长按、双击等。每种触发方式都有其特定的应用场景和功能需求,因此,为了满足用户的多样化需求,按键应用框架需要具备灵活的触发方式设置功能
框架特性
我们提出的单片机按键应用框架具有以下特性:
1. 多种按键触发方式支持
2. 支持传统按键和AD按键
该框架不仅支持常见的数字按键,还能够兼容模拟量输入,例如通过模拟信号产生的按键操作。这种称为模拟数字(AD)按键的输入方式,在一些场景下非常有用,尤其是需要连续调节、精确控制或模拟信号采集的应用中。
3. 二进制事件映射成按键
除了按键输入外,框架还支持将其他二进制事件映射成按键进行处理,增强了按键系统的灵活性。例如,可以将传感器的触发事件映射为按键事件,从而实现传感器触发时的特定操作。
4. 按键使能功能
框架提供按键使能功能,可以灵活控制按键的使用状态,适用于各种按键需求动态变化的场景。通过按键使能功能,用户可以根据具体需求,在运行时动态地启用或禁用某些按键,以实现灵活的按键管理和控制。
5. 可设置时间
该框架可支持每个按键定制不同的长按时间和持续按时间,使得按键触发的灵敏度和响应速度可以根据具体需求进行灵活调整,进一步提升了按键系统的定制性和适用性。
程序核心代码解析
以下是按键框架的核心代码解析:
// 按键初始化函数
void keyParaInit(keyCategory_t *keys)
{
if (NULL == keys)
return;
if (KEY_NUM >= KEY_MAX_NUMBER)
{
keyNum = KEY_MAX_NUMBER;
}
memcpy(keyTable, keys, sizeof(keyCategory_t) * keyNum);
}
// 按键扫描函数
static bool readKeyStatus(keyFSM_t *Key_Buf)
{
if (!getKeyLevel(Key_Buf))
return false;
switch (Key_Buf->keyStatus)
{
// 状态 0: 未按下按键
case KEY_NULL:
if (Key_Buf->keyLevel == Bit_SET) // 按键按下
{
Key_Buf->keyStatus = KEY_SURE;
}
break;
// 状态 1: 确认按下
case KEY_SURE:
if (Key_Buf->keyLevel == Bit_SET) // 确认按下按键
{
Key_Buf->eventType = DOWN_Event; // 触发按下事件
Key_Buf->keyStatus = KEY_DOWN;
Key_Buf->keyCount = 0; // 重置按键计数值
Key_Buf->keyLongFlag = true;
}
else
{
Key_Buf->keyStatus = KEY_NULL;
}
break;
// 状态 2: 按下按键
case KEY_DOWN:
if (Key_Buf->keyLevel != Bit_SET) // 松开按键
{
if (Key_Buf->keyShortFlag == false) // 按下按键置位标志位
{
Key_Buf->keyShortFlag = true;
Key_Buf->keyFrequency++; // 按键次数加 1
Key_Buf->keyInterval = 0; // 按键间隔清零
}
if (keyClickFrequency(Key_Buf)) // 多击判断
{
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_NULL;
}
if ((Key_Buf->keyCount >= Key_Buf->keyLongTime / DEBOUNCE_TIME) &&
(Key_Buf->keyCount < Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 按键长按释放
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_NULL;
Key_Buf->eventType = RELEASE_Event; // 触发长按释放事件
}
Key_Buf->keyCount = 0; // 重置按键计数器
Key_Buf->keyLongFlag = true;
}
else
{
if ((++Key_Buf->keyCount >= Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 超过设定时间没有释放
Key_Buf->keyCount = 0; // 重置按键计数器
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_LONG;
Key_Buf->eventType = LAST_Event; // 触发持续按事件
Key_Buf->keyLongFlag = true;
}
if ((Key_Buf->keyCount >= Key_Buf->keyLongTime / DEBOUNCE_TIME) &&
(Key_Buf->keyCount < Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 按下按键达到设定时间
Key_Buf->keyFrequency = 0;
if (Key_Buf->keyLongFlag == true)
{
Key_Buf->eventType = LONG_Event;// 触发长按事件
Key_Buf->keyLongFlag = false;
}
}
Key_Buf->keyShortFlag = false; // 按下按键置位标志位
}
break;
// 状态 3: 持续按键长按状态
case KEY_LONG:
if (Key_Buf->keyLevel != Bit_SET) // 松开按键
{
Key_Buf->keyStatus = KEY_NULL;
Key_Buf->eventType = RELEASE_Event; // 触发长按释放事件
}
break;
default:
break;
}
return true;
}
// 按键事件处理函数
void keyHandle(void)
{
for (size_t i = 0; i < keyNum; i++)
{
if (keyTable[i].fsm.eventType == NULL_Event)
continue;
switch (keyTable[i].fsm.eventType)
{
case RELEASE_Event:
if (keyTable[i].func.releasePressCb == NULL)
break;
keyTable[i].func.releasePressCb();
break;
case SHORT_Event:
if (keyTable[i].func.ShortPressCb == NULL)
break;
keyTable[i].func.ShortPressCb();
break;
case DOWN_Event:
if (keyTable[i].func.downPressCb == NULL)
break;
keyTable[i].func.downPressCb();
break;
case LONG_Event:
if (keyTable[i].func.longPressCb == NULL)
break;
keyTable[i].func.longPressCb();
break;
case LAST_Event:
if (keyTable[i].func.lastPressCb == NULL)
break;
keyTable[i].func.lastPressCb();
break;
case DBCL_Event:
if (keyTable[i].func.dbclPressCb == NULL)
break;
keyTable[i].func.dbclPressCb();
break;
default:
break;
}
if (keyTable[i].fsm.eventType != LAST_Event)
{ // 持续按事件需要一直执行不需要复位事件
keyTable[i].fsm.eventType = NULL_Event;
}
}
}
程序解析
以下是对按键框架核心代码的详细解析:
按键参数初始化函数(keyParaInit)
keyParaInit
函数用于初始化按键参数。keys
是一个指向按键配置结构体数组的指针,用于初始化按键的数量和相关配置。memcpy
函数将传入的按键配置结构体数组复制到内部的按键配置表中。按键扫描函数(readKeyStatus)
readKeyStatus
函数用于扫描按键状态,并根据状态变化触发相应的事件。Key_Buf
是一个指向按键状态结构体的指针,用于保存按键的当前状态信息。getKeyLevel
函数获取按键的电平状态,如果按键未按下则返回,否则根据按键状态执行相应的逻辑。按键事件处理函数(keyHandle)
keyHandle
函数用于处理按键事件,根据事件类型调用相应的回调函数进行处理。按键程序框架
key.c
/**
* ***********************************************************
* @file key.c
* @author cyWU
* @brief 按键应用框架
* @version 0.2
* @date 2024-01-29
* @copyright 版权所有 2024
* ***********************************************************
*/
#include "key.h"
#include <math.h>
uint32_t keyCountTime; // 计时器
uint8_t keyNum = KEY_NUM; // 按键数量
keyCategory_t keyTable[KEY_NUM]; // 按键数组
/**
* @brief 判断按键是否被按下
* @param [in] key :按键状态机全局结构指针
* @return : true,按键状态读取成功;
* false,按键未使能;
*/
static bool getKeyLevel(keyFSM_t *key)
{
if (key->keyShield == KEY_DISABLE)
return false;
if (key->keyReadValue() == key->keyDownLevel)
{
key->keyLevel = Bit_SET;
}
else
{
key->keyLevel = Bit_RESET;
}
return true;
}
/**
* @brief 判断按键是否单次或多次被按下
* @param [in] click :按键状态机全局结构指针
* @return : true,如果按键间隔时间超过,返回事件;
* false,如果按键不超过按键间隔,则不返回事件;
*/
static bool keyClickFrequency(keyFSM_t *click)
{
click->keyInterval++;
switch (click->keyFrequency)
{
case CLICK:
if (click->keyInterval >= (KEY_INTERVAL / (DEBOUNCE_TIME * KEY_TIMER_MS)))
{
click->eventType = SHORT_Event; // 单击事件
return true;
}
break;
case DbLCLICK:
if (click->keyInterval >= (KEY_INTERVAL / (DEBOUNCE_TIME * KEY_TIMER_MS)))
{
click->eventType = DBCL_Event; // 双击事件
return true;
}
break;
/*
根据需要添加更多的点击事件
case XXXX:
if (click->keyInterval >= (KEY_INTERVAL / (DEBOUNCE_TIME * KEY_TIMER_MS)))
{
click->eventType = XXXXXX;
return true;
}
break;
*/
default:
return true;
}
return false;
}
/**
* @brief 读取按键值
* @param [in] Key_Buf :按键状态机全局结构指针
* @return : true,按键值获取成功;
* false,按键未使能;
*/
static bool readKeyStatus(keyFSM_t *Key_Buf)
{
if (!getKeyLevel(Key_Buf))
return false;
switch (Key_Buf->keyStatus)
{
// 状态 0: 未按下按键
case KEY_NULL:
if (Key_Buf->keyLevel == Bit_SET) // 按键按下
{
Key_Buf->keyStatus = KEY_SURE;
}
break;
// 状态 1: 确认按下
case KEY_SURE:
if (Key_Buf->keyLevel == Bit_SET) // 确认按下按键
{
Key_Buf->eventType = DOWN_Event; // 触发按下事件
Key_Buf->keyStatus = KEY_DOWN;
Key_Buf->keyCount = 0; // 重置按键计数值
Key_Buf->keyLongFlag = true;
}
else
{
Key_Buf->keyStatus = KEY_NULL;
}
break;
// 状态 2: 按下按键
case KEY_DOWN:
if (Key_Buf->keyLevel != Bit_SET) // 松开按键
{
if (Key_Buf->keyShortFlag == false) // 按下按键置位标志位
{
Key_Buf->keyShortFlag = true;
Key_Buf->keyFrequency++; // 按键次数加 1
Key_Buf->keyInterval = 0; // 按键间隔清零
}
if (keyClickFrequency(Key_Buf)) // 多击判断
{
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_NULL;
}
if ((Key_Buf->keyCount >= Key_Buf->keyLongTime / DEBOUNCE_TIME) &&
(Key_Buf->keyCount < Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 按键长按释放
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_NULL;
Key_Buf->eventType = RELEASE_Event; // 触发长按释放事件
}
Key_Buf->keyCount = 0; // 重置按键计数器
Key_Buf->keyLongFlag = true;
}
else
{
if ((++Key_Buf->keyCount >= Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 超过设定时间没有释放
Key_Buf->keyCount = 0; // 重置按键计数器
Key_Buf->keyFrequency = 0;
Key_Buf->keyStatus = KEY_LONG;
Key_Buf->eventType = LAST_Event; // 触发持续按事件
Key_Buf->keyLongFlag = true;
}
if ((Key_Buf->keyCount >= Key_Buf->keyLongTime / DEBOUNCE_TIME) &&
(Key_Buf->keyCount < Key_Buf->keyLastTime / DEBOUNCE_TIME))
{ // 按下按键达到设定时间
Key_Buf->keyFrequency = 0;
if (Key_Buf->keyLongFlag == true)
{
Key_Buf->eventType = LONG_Event;// 触发长按事件
Key_Buf->keyLongFlag = false;
}
}
Key_Buf->keyShortFlag = false; // 按下按键置位标志位
}
break;
// 状态 3: 持续按键长按状态
case KEY_LONG:
if (Key_Buf->keyLevel != Bit_SET) // 松开按键
{
Key_Buf->keyStatus = KEY_NULL;
Key_Buf->eventType = RELEASE_Event; // 触发长按释放事件
}
break;
default:
break;
}
return true;
}
/**
* @brief 按键事件处理函数
* @param None
* @retval None
*/
void keyEventProcess(void)
{
for (size_t i = 0; i < keyNum; i++)
{
if (!readKeyStatus(&keyTable[i].fsm))
continue;
}
}
/**
* @brief 按键处理函数
* @param None
* @retval None
*/
void keyCheckProcess(void)
{
keyCountTime++;
if (keyCountTime >= (DEBOUNCE_TIME / KEY_TIMER_MS))
{
keyCountTime = 0;
keyEventProcess();
}
}
/**
* @brief 按键参数初始化函数
* @param [in] keys :按键全局结构指针
* @return none
*/
void keyParaInit(keyCategory_t *keys)
{
if (NULL == keys)
{
return;
}
if (KEY_NUM >= KEY_MAX_NUMBER)
{
keyNum = KEY_MAX_NUMBER;
}
memcpy(keyTable, keys, sizeof(keyCategory_t) * keyNum);
}
/**
* @brief 按键处理函数
* @param None
* @retval None
*/
void keyHandle(void)
{
for (size_t i = 0; i < keyNum; i++)
{
if (keyTable[i].fsm.eventType == NULL_Event)
continue;
switch (keyTable[i].fsm.eventType)
{
case RELEASE_Event:
if (keyTable[i].func.releasePressCb == NULL)
break;
keyTable[i].func.releasePressCb();
break;
case SHORT_Event:
if (keyTable[i].func.ShortPressCb == NULL)
break;
keyTable[i].func.ShortPressCb();
break;
case DOWN_Event:
if (keyTable[i].func.downPressCb == NULL)
break;
keyTable[i].func.downPressCb();
break;
case LONG_Event:
if (keyTable[i].func.longPressCb == NULL)
break;
keyTable[i].func.longPressCb();
break;
case LAST_Event:
if (keyTable[i].func.lastPressCb == NULL)
break;
keyTable[i].func.lastPressCb();
break;
case DBCL_Event:
if (keyTable[i].func.dbclPressCb == NULL)
break;
keyTable[i].func.dbclPressCb();
break;
default:
break;
}
if (keyTable[i].fsm.eventType != LAST_Event)
{ // 持续按事件需要一直执行不需要复位事件
keyTable[i].fsm.eventType = NULL_Event;
}
}
}
key.h
/**
* ***********************************************************
* @file key.h
* @author cyWU
* @brief 按键应用框架
* @version 0.2
* @date 2024-01-29
* @copyright Copyright (c) 2024
* ***********************************************************
*/
#ifndef __KEY_H
#define __KEY_H
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define KEY_TIMER_MS 1 // 定时器周期(毫秒)
#define KEY_MAX_NUMBER 12 // 最大支持按键数量
#define DEBOUNCE_TIME 30 // 消抖时间(毫秒)
#define KEY_INTERVAL 160 // 按键间隔时间(毫秒)
#define CLICK 1 // 按键单击
#define DbLCLICK 2 // 按键双击
typedef enum
{
KEY_POWER, // 电源按键
KEY_CHARGE, // 将充电检测口模拟成按键
KEY_NUM, // 按键数量,必须放在注册表的底部
} keyList;
typedef enum
{
Bit_RESET = 0,
Bit_SET
} BitAction_t;
/* 按键状态枚举 */
typedef enum
{
KEY_NULL, // 按键无动作
KEY_RELEASE, // 按键释放
KEY_SURE, // 按键抖动消除
KEY_UP, // 按键释放
KEY_DOWN, // 按键按下
KEY_LONG, // 按键长按
} keyStatus_t;
/* 按键事件枚举 */
typedef enum
{
NULL_Event, // 空事件
DOWN_Event, // 按下事件
SHORT_Event, // 短按事件
LONG_Event, // 长按事件
LAST_Event, // 连按事件
DBCL_Event, // 双击事件
RELEASE_Event, // 释放事件
} keyEvent_t;
typedef enum
{
KEY_DISABLE = 0,
KEY_ENABLE = !KEY_DISABLE
} keyEnable_t;
/* 按键状态机结构体 */
__packed typedef struct
{
bool keyShortFlag; // 按键短按标志
bool keyLongFlag; // 按键长按标志
uint8_t keyInterval; // 按键间隔时间
uint8_t keyFrequency; // 按键按下次数
uint16_t keyLongTime; // 按键长按时间
uint16_t keyLastTime; // 按键连按间隔时间
uint32_t keyCount; // 长按计数
keyEnable_t keyShield; // 按键使能状态
BitAction_t keyLevel; // 判断按键是否按下,按下: 1,松开: 0
BitAction_t keyDownLevel; // 按键按下时 IO 端口的电平状态
keyStatus_t keyStatus; // 按键当前状态
keyEvent_t eventType; // 按键事件类型
uint8_t (*keyReadValue)(void); // 按键读取值函数指针
} keyFSM_t;
/* 不同事件对应的回调函数 */
__packed typedef struct
{
void (*nullPressCb)(void); // 无动作事件回调函数
void (*releasePressCb)(void); // 释放事件回调函数
void (*downPressCb)(void); // 按下事件回调函数
void (*ShortPressCb)(void); // 短按事件回调函数
void (*longPressCb)(void); // 长按事件回调函数
void (*lastPressCb)(void); // 连按事件回调函数
void (*dbclPressCb)(void); // 双击事件回调函数
} keyFunc_t;
/* 按键类结构体 */
__packed typedef struct
{
keyFSM_t fsm; // 按键状态机
keyFunc_t func; // 按键事件回调函数
} keyCategory_t;
void keyParaInit(keyCategory_t *keys); // 按键参数初始化函数
void keyCheckProcess(void); // 按键检测处理函数
void keyHandle(void); // 按键事件处理函数
#endif
使用示例
// 读取电源按键值
uint8_t key_power_read(void)
{ // 返回电源按键的电平值
return HAL_GPIO_ReadPin(KEY_POWER_GPIO_Port, KEY_POWER_Pin);
}
// 读取充电按键值
uint8_t key_charge_read(void)
{ // 返回充电按键的电平值
return HAL_GPIO_ReadPin(CHARGE_GPIO_PORT, CHARGE_PIN);
}
// 电源按键短按回调函数
void key_power_ShortPress(void)
{
printf("key_power_ShortPress");
}
// 电源按键长按回调函数
void key_power_LongPress(void)
{
printf("key_power_LongPress");
}
// 充电按键按下回调函数
void key_charge_InsertPress(void)
{
printf("key_charge_InsertPress");
}
// 充电按键释放回调函数
void key_charge_ExtractPress(void)
{
printf("key_charge_ExtractPress");
}
// 按键初始化
void user_key_Init(void)
{
/* 初始化KEY GPIO */
// 在这里进行按键的GPIO初始化设置
// 初始化KEY EVENT
// 定义一个keyCategory_t类型的数组keys,用于存储每个按键的状态机和回调函数信息
keyCategory_t keys[KEY_NUM] = {
[KEY_POWER] = {
// 电源按键 状态机初始化
.fsm.keyShield = KEY_ENABLE, // 按键使能
.fsm.keyDownLevel = Bit_RESET, // 按键按下时的电平
.fsm.eventType = NULL_Event, // 按键事件类型
.fsm.keyLongTime = 2000, // 长按时间阈值(毫秒)
.fsm.keyLastTime = 5000, // 持续按下时间阈值(毫秒)
.fsm.keyReadValue = key_power_read,// 读取按键值的函数指针
// 电源按键 回调函数初始化
.func.ShortPressCb = key_power_ShortPress, // 短按回调函数
.func.longPressCb = key_power_LongPress, // 长按回调函数
},
[KEY_CHARGE] = {
// 充电 状态机初始化(将充电检测模拟成按键)
.fsm.keyShield = KEY_ENABLE, // 按键使能
.fsm.keyDownLevel = Bit_SET, // 按键按下时的电平
.fsm.eventType = RELEASE_Event, // 按键事件类型
.fsm.keyLongTime = 1000, // 长按时间阈值(毫秒)
.fsm.keyLastTime = 5000, // 持续按下时间阈值(毫秒)
.fsm.keyReadValue = key_charge_read, // 读取按键值的函数指针
// 充电 回调函数初始化
.func.downPressCb = key_charge_InsertPress, // 按键按下回调函数
.func.releasePressCb = key_charge_ExtractPress, // 按键释放回调函数
},
};
// 调用keyParaInit函数进行按键参数初始化
keyParaInit(keys);
}
// 定时器1ms中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{ // 定时器14初始化1ms中断
if (htim->Instance == TIM14)
{ // 在中断中进行按键扫描
keyCheckProcess();
}
}
int main(void)
{
user_key_Init(); // 初始化按键
while(1)
{
keyHandle(); // 按键处理函数
}
}
程序解析
以下是对新增的按键处理函数、按键初始化函数以及主函数的详细解析:
读取电源按键值函数(key_power_read)
key_power_read
函数用于读取电源按键的电平值。HAL_GPIO_ReadPin
函数获取电源按键的电平值,并返回该值。读取充电按键值函数(key_charge_read)
key_charge_read
函数用于读取充电按键的电平值。HAL_GPIO_ReadPin
函数获取充电按键的电平值,并返回该值。电源按键短按回调函数(key_power_ShortPress)
key_power_ShortPress
函数用于处理电源按键的短按事件。电源按键长按回调函数(key_power_LongPress)
key_power_LongPress
函数用于处理电源按键的长按事件。充电按键按下回调函数(key_charge_InsertPress)
key_charge_InsertPress
函数用于处理充电按键按下事件。充电按键释放回调函数(key_charge_ExtractPress)
key_charge_ExtractPress
函数用于处理充电按键释放事件。按键初始化函数(user_key_Init)
user_key_Init
函数用于初始化按键。keys
,并对每个按键进行了状态机参数和回调函数的初始化。keyParaInit
函数进行按键参数的初始化。定时器1ms中断回调函数(HAL_TIM_PeriodElapsedCallback)
HAL_TIM_PeriodElapsedCallback
函数用于定时器1ms中断的回调处理。keyCheckProcess
函数实现。主函数(main)
user_key_Init
函数,初始化按键。keyHandle
函数来处理按键事件。以上程序是一个按键处理的示例程序,包括了按键的初始化、按键状态检测、按键事件回调等功能。用户可以根据自己的需求进行修改和扩展,以实现特定的按键应用逻辑。
结论
通过以上按键处理框架,我们实现了一个可靠、灵活的按键系统,具有以下优势和特点:
-
简化开发流程: 提供了按键初始化函数和事件处理函数,用户只需简单调用这些函数,即可快速搭建起按键系统,无需从头编写复杂的按键处理逻辑,大大节省了开发时间和精力。
-
提高开发效率: 按键框架封装了按键状态机的实现细节,用户只需关注按键的功能和回调函数的实现,无需过多关注底层细节,使得开发过程更加高效。
-
增强系统稳定性: 按键状态机设计合理,采用了状态转换机制和事件驱动模型,能够有效地处理按键的各种状态和事件,提高了系统的稳定性和可靠性。
-
提升系统灵活性: 用户可以根据实际需求自定义按键的状态机参数和回调函数,灵活配置按键的功能和行为,满足不同场景下的按键应用需求,具有较高的灵活性和可定制性。
综上所述,通过以上框架,为用户提供了一个可靠、灵活的按键处理方案,帮助用户快速搭建按键系统,提高了开发效率,增强了系统稳定性和灵活性,为用户提供了更好的按键体验。
作者:阿胡不秃头