新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 產(chǎn)品級的按鍵輸入系統(tǒng)設(shè)計:去抖、識別與狀態(tài)機實踐

產(chǎn)品級的按鍵輸入系統(tǒng)設(shè)計:去抖、識別與狀態(tài)機實踐

作者:嵌入式芯視野 時間:2025-07-17 來源:今日頭條 收藏

在嵌入式產(chǎn)品開發(fā)中,按鍵輸入看似簡單,但要實現(xiàn)產(chǎn)品級的穩(wěn)定性和交互體驗,需要考慮多個細(xì)節(jié):硬件抖動、長按/短按/連擊的識別、響應(yīng)延遲、誤觸容錯等。尤其在一些工業(yè)控制或消費電子產(chǎn)品中,按鍵響應(yīng)的準(zhǔn)確性與用戶體驗直接相關(guān)。

本文將結(jié)合實際經(jīng)驗,圍繞產(chǎn)品級按鍵系統(tǒng)的核心問題展開,包括:軟件去抖動、按鍵事件識別(單擊、雙擊、長按)、基于狀態(tài)機的設(shè)計思路,并輔以清晰的代碼示例。


一、按鍵抖動的本質(zhì)與去抖方法

機械式按鍵在觸發(fā)時會產(chǎn)生數(shù)十毫秒的抖動信號,如圖所示:

高電平 ——┐    ┌────┐   ┌───┐
           └────┘    └───┘
                ↑抖動階段約5~20ms

若不處理這些抖動,將誤觸發(fā)多次按鍵事件。典型的軟件去抖方法有兩種:

1.1 延時法(簡單粗暴)

#define KEY_PIN   HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)bool read_key(){    static bool key_last = false;    bool key_now = KEY_PIN;    if (key_now != key_last)
    {
        HAL_Delay(20); // 固定延時20ms
        key_now = KEY_PIN;
    }

    key_last = key_now;    return key_now;
}

適用于輕量任務(wù),但阻塞式HAL_Delay()在多任務(wù)或RTOS下不推薦。

1.2 定時器采樣 + 滑動窗口法(推薦)

#define KEY_FILTER_TIME 5typedef struct {
    uint8_t filter_cnt;    uint8_t stable_state;    uint8_t last_state;
} KeyFilter_t;

KeyFilter_t key1 = {0};void key_filter_task() // 每10ms調(diào)用一次{    uint8_t cur = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);    
    if (cur == key1.last_state)
    {        if (key1.filter_cnt < KEY_FILTER_TIME)
            key1.filter_cnt++;        else
            key1.stable_state = cur; // 過濾成功,狀態(tài)更新
    }    else
    {
        key1.filter_cnt = 0;
    }

    key1.last_state = cur;
}

該方案適用于RTOS或主循環(huán)中周期性調(diào)用,無阻塞,去抖效果穩(wěn)定。


二、按鍵事件識別(單擊、雙擊、長按)

產(chǎn)品級系統(tǒng)往往需支持復(fù)雜交互。例如:

  • 短按:執(zhí)行基本操作

  • 長按:進入配置/復(fù)位模式

  • 雙擊/多擊:執(zhí)行特殊功能

關(guān)鍵是精確識別不同的按鍵時序。常用方法是記錄按下/釋放的時間戳,并在定時任務(wù)中分析事件。

2.1 實用結(jié)構(gòu)體定義

typedef enum {
    KEY_IDLE,
    KEY_PRESS,
    KEY_RELEASE,
    KEY_LONG,
    KEY_DOUBLE
} KeyEvent_t;typedef struct {
    uint8_t stable_state;    uint8_t last_state;    uint8_t press_flag;    uint32_t press_time;    uint32_t release_time;    uint8_t click_count;
    KeyEvent_t event;
} KeyCtrl_t;

2.2 狀態(tài)控制邏輯(每10ms調(diào)用)

#define KEY_DOWN_LEVEL     0#define LONG_PRESS_TIME    100   // 100 * 10ms = 1s#define DOUBLE_CLICK_TIME  30    // 300msvoid key_scan(KeyCtrl_t *key, uint8_t read_level)
{    // 狀態(tài)變化檢測
    if (read_level != key->last_state)
    {
        key->last_state = read_level;        if (read_level == KEY_DOWN_LEVEL)
        {
            key->press_time = 0;
            key->press_flag = 1;
        }        else
        {
            key->release_time = 0;            if (key->press_time < LONG_PRESS_TIME)
                key->click_count++;  // 累積點擊次數(shù)
            key->press_flag = 0;
        }
    }    // 長按識別
    if (key->press_flag)
    {
        key->press_time++;        if (key->press_time == LONG_PRESS_TIME)
            key->event = KEY_LONG;
    }    // 點擊識別(釋放后計時)
    if (!key->press_flag && key->click_count > 0)
    {
        key->release_time++;        if (key->release_time > DOUBLE_CLICK_TIME)
        {            if (key->click_count == 1)
                key->event = KEY_PRESS;            else if (key->click_count == 2)
                key->event = KEY_DOUBLE;

            key->click_count = 0;
            key->release_time = 0;
        }
    }
}

調(diào)用方式:

KeyCtrl_t key1;void SysTick_Handler() // 每10ms調(diào)用{    uint8_t key_level = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    key_scan(&key1, key_level);
}

2.3 事件處理

在主循環(huán)中讀?。?/span>

switch (key1.event)
{    case KEY_PRESS:        // 短按操作
        do_action();        break;    case KEY_LONG:        // 長按復(fù)位
        system_reset();        break;    case KEY_DOUBLE:        // 雙擊切換模式
        toggle_mode();        break;    default:        break;
}

key1.event = KEY_IDLE; // 清除事件

三、引入狀態(tài)機的設(shè)計優(yōu)勢

在產(chǎn)品級設(shè)計中,代碼清晰度和可維護性極其重要。直接用變量堆疊判斷邏輯容易混亂。狀態(tài)機設(shè)計是一種簡潔的思路:每種按鍵狀態(tài)對應(yīng)一個具體行為轉(zhuǎn)換條件。

3.1 狀態(tài)枚舉

typedef enum {
    ST_IDLE,
    ST_WAIT_RELEASE,
    ST_WAIT_SECOND_PRESS,
    ST_LONG_PRESS
} KeyState_t;

3.2 狀態(tài)切換實現(xiàn)

void key_state_machine(KeyCtrl_t *key)
{    static KeyState_t state = ST_IDLE;    switch (state)
    {        case ST_IDLE:            if (key->stable_state == KEY_DOWN_LEVEL)
            {
                key->press_time = 0;
                state = ST_WAIT_RELEASE;
            }            break;        case ST_WAIT_RELEASE:            if (key->stable_state != KEY_DOWN_LEVEL)
            {                if (key->press_time < LONG_PRESS_TIME)
                    state = ST_WAIT_SECOND_PRESS;                else
                    key->event = KEY_LONG, state = ST_IDLE;
            }            else
            {
                key->press_time++;                if (key->press_time >= LONG_PRESS_TIME)
                    key->event = KEY_LONG, state = ST_IDLE;
            }            break;        case ST_WAIT_SECOND_PRESS:
            key->release_time++;            if (key->stable_state == KEY_DOWN_LEVEL)
            {
                key->event = KEY_DOUBLE;
                state = ST_IDLE;
            }            else if (key->release_time > DOUBLE_CLICK_TIME)
            {
                key->event = KEY_PRESS;
                state = ST_IDLE;
            }            break;        default:
            state = ST_IDLE;            break;
    }
}

四、總結(jié)與建議

  1. 去抖是基礎(chǔ):推薦使用定時采樣 + 滑動濾波方式,兼顧實時性和準(zhǔn)確性。

  2. 事件識別需明確時序:長按、雙擊等需合理時間窗口與狀態(tài)標(biāo)記。

  3. 狀態(tài)機利于擴展:可讀性好,便于多鍵支持、增加按鍵組合等高級功能。

  4. 避免阻塞邏輯:無論是delay或while等待,都應(yīng)盡量避免使用在中斷或主循環(huán)中。

按鍵雖然是最基礎(chǔ)的輸入方式之一,但在產(chǎn)品級別的設(shè)計中,它體現(xiàn)的是系統(tǒng)響應(yīng)能力、用戶體驗和設(shè)計規(guī)范的綜合考量。

當(dāng)然也參考一個開源按鍵網(wǎng)站:
https://github.com/murphyzhao/FlexibleButton


關(guān)鍵詞: 嵌入式開發(fā)

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉