按键驱动(状态机)

支持长按,短按,双击,组合键


事件+状态枚举

#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();
}

添加新评论