支持长按,短按,双击,组合键
事件+状态枚举
#define ARR_SIZE(arr) sizeof(arr) / sizeof(arr[0])
#define KEY_COUNT 3 // 按键数量
// 按键ID枚举类型
typedef enum {
KEY_ID_1 = 0,
KEY_ID_2,
KEY_ID_3,
KEY_ID_MAX
} Key_ID;
// 组合键ID枚举类型(每个按键组合为一个独立实体;新增组合只需在此追加一项)
typedef enum {
KEY_COMBO_NONE = 0,
KEY_COMBO_1_2, // KEY1 + KEY2
KEY_COMBO_2_3, // KEY2 + KEY3
KEY_COMBO_1_3, // KEY1 + KEY3
} Key_Combo;
// 按键的状态枚举类型
typedef enum {
KEY_STATE_RELEASED = 0, // 按键释放状态
KEY_STATE_DEBOUNCING, // 按键抖动状态
KEY_STATE_PRESSED, // 按键按下状态
KEY_STATE_LONG_PRESSED, // 按键长按状态
KEY_STATE_WAIT_DOUBLE, // 双击等待:第一次短按释放后,等第二次按下
} Key_State;
// 按键事件类型(通用,不绑定具体按键;新增事件类型只需在此追加一项)
typedef enum {
KEY_EVENT_NONE = 0, // 无事件
KEY_EVENT_PRESSED, // 按下(短按确认,释放时上报)
KEY_EVENT_LONG_PRESSED, // 长按
KEY_EVENT_CLICKED, // 单击(双击等待窗口超时后确认)
KEY_EVENT_DOUBLE_CLICKED, // 双击
} Key_Event;
// 按键的配置结构体
typedef struct {
GPIO_TypeDef *GPIOx; // 按键对应的GPIO端口
uint16_t GPIO_Pin; // 按键对应的GPIO引脚
Key_State state; // 按键的当前状态
uint32_t press_time; // 按键按下的时间
Key_ID id; // 按键的ID
bool support_long_press; // 是否支持长按事件
bool combo_locked; // 被组合消费,屏蔽该键的单键事件(由组合层维护)
bool support_double_click; // 是否支持双击(启用后单击延迟上报为 CLICKED)
uint32_t release_time; // 释放时刻(双击窗口计时用)
bool is_second_click; // 当前是否为双击的第二击(运行时标志)
} Key_Config;
extern Key_Config keys_config[KEY_COUNT]; // 按键配置数组按键驱动状态机实现
// 组合键配置项:每个组合由两个物理键构成
typedef struct {
Key_ID key_a; // 组合的第一个键
Key_ID key_b; // 组合的第二个键
Key_Combo combo; // 该组合对应的ID
bool active; // 该组合是否已激活(边沿触发用)
} Combo_Config;
static Key_Event_Callback key_event_callback = NULL; // 单键事件回调
static Key_Combo_Callback key_combo_callback = NULL; // 组合事件回调
// 组合键配置表:
static Combo_Config combos[] = {
{ KEY_ID_1, KEY_ID_2, KEY_COMBO_1_2, false },
{ KEY_ID_2, KEY_ID_3, KEY_COMBO_2_3, false },
{ KEY_ID_1, KEY_ID_3, KEY_COMBO_1_3, false },
};
void com_key_register_callback(Key_Event_Callback callback)
{
key_event_callback = callback;
}
void com_key_register_combo_callback(Key_Combo_Callback callback)
{
key_combo_callback = callback;
}
// 判断按键是否处于"确认按下"状态(含长按态,均视为按住)
static bool key_is_down(const Key_Config *keys)
{
return keys->state == KEY_STATE_PRESSED || keys->state == KEY_STATE_LONG_PRESSED;
}
// 组合检测:遍历组合表,两键都按下且未被其它组合占用时触发该组合
static void combo_detect(void)
{
for (uint16_t i = 0; i < ARR_SIZE(combos); i++)
{
Combo_Config *c = &combos[i];
Key_Config *a = &keys_config[c->key_a];
Key_Config *b = &keys_config[c->key_b];
bool both_down = key_is_down(a) && key_is_down(b);
if (both_down && !c->active && !a->combo_locked && !b->combo_locked)
{
c->active = true;
a->combo_locked = true; // 锁定两键:屏蔽单键事件,并阻止它们参与其它组合
b->combo_locked = true;
if (key_combo_callback != NULL)
{
key_combo_callback(c->combo); // 上报组合事件
}
}
else if (!both_down && c->active)
{
c->active = false;
// combo_locked 不在此清除,由各键在自身释放时清除(见 key_press_state)
}
}
}
static Key_Event key_press_state(Key_Config *keys)
{
// 检测按键当前是否按下
GPIO_PinState pressed = HAL_GPIO_ReadPin(keys->GPIOx, keys->GPIO_Pin);
// 获取当前时间戳
uint32_t current_time = HAL_GetTick();
// 检测按键状态
switch (keys->state)
{
// 按键释放状态
case KEY_STATE_RELEASED:
if (pressed == GPIO_PIN_RESET)
{
// 按键按下,进入抖动状态
keys->state = KEY_STATE_DEBOUNCING;
keys->press_time = current_time; // 记录按下时间
}
break;
// 防抖操作
case KEY_STATE_DEBOUNCING:
if (pressed == GPIO_PIN_SET)
{
// 抖动状态:未确认就松开,回到释放状态
keys->state = KEY_STATE_RELEASED;
keys->is_second_click = false; // 双击的第二击无效,清除标记
}
else if ((current_time - keys->press_time) >= KEY_DEBOUNCE_TIME)
{
// 按键按下时间超过消抖时间,进入按下状态
keys->state = KEY_STATE_PRESSED;
}
break;
case KEY_STATE_PRESSED:
if (pressed == GPIO_PIN_SET)
{
// 按键已经释放
if (keys->combo_locked)
{
keys->state = KEY_STATE_RELEASED;
keys->combo_locked = false; // 组合消费完毕,解除锁定
return KEY_EVENT_NONE; // 屏蔽单键事件
}
if (keys->support_double_click && !keys->is_second_click)
{
// 第一次短按释放:进入双击等待窗口(延迟判定单击/双击)
keys->state = KEY_STATE_WAIT_DOUBLE;
keys->release_time = current_time;
return KEY_EVENT_NONE;
}
keys->state = KEY_STATE_RELEASED;
if (keys->is_second_click)
{
// 第二次短按释放:确认为双击
keys->is_second_click = false;
return KEY_EVENT_DOUBLE_CLICKED;
}
return KEY_EVENT_PRESSED; // 返回按下事件
}
else if ((current_time - keys->press_time) >= KEY_LONG_PRESS_TIME)
{
// 按键按下时间超过长按时间,进入长按状态
keys->state = KEY_STATE_LONG_PRESSED;
if (keys->combo_locked)
{
return KEY_EVENT_NONE; // 组合中不报长按
}
keys->is_second_click = false; // 长按取消双击判定
if (keys->support_long_press)
{
return KEY_EVENT_LONG_PRESSED; // 返回长按事件
}
else
{
// 不支持长按事件,返回按下事件
return KEY_EVENT_PRESSED;
}
}
break;
case KEY_STATE_WAIT_DOUBLE:
if (pressed == GPIO_PIN_RESET)
{
// 双击窗口内再次按下:进入第二次消抖,并标记为第二击
keys->state = KEY_STATE_DEBOUNCING;
keys->press_time = current_time;
keys->is_second_click = true;
}
else if ((current_time - keys->release_time) >= KEY_DOUBLE_CLICK_GAP)
{
// 等待超时,没有第二次按下:确认为单击
keys->state = KEY_STATE_RELEASED;
return KEY_EVENT_CLICKED;
}
break;
case KEY_STATE_LONG_PRESSED:
if (pressed == GPIO_PIN_SET)
{
// 按键已经释放,回到释放状态
keys->state = KEY_STATE_RELEASED;
if (keys->combo_locked)
{
keys->combo_locked = false; // 组合消费完毕,解除锁定
}
}
break;
}
return KEY_EVENT_NONE; // 没有按键事件
}
void com_key_scan(void)
{
for (uint16_t i = 0; i < KEY_COUNT; i++)
{
Key_Event event = key_press_state(&keys_config[i]);
// combo_locked 的键其单键事件已在 key_press_state 内被屏蔽(返回 NONE),这里无需再判断
if (event != KEY_EVENT_NONE && key_event_callback != NULL)
{
key_event_callback(keys_config[i].id, event);
}
}
// 单键扫描完成后,基于各键当前状态做组合检测(可能触发组合回调)
combo_detect();
}