新聞中心

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

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

        作者:嵌入式芯視野 時(shí)間:2025-07-17 來(lái)源:今日頭條 收藏

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

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


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

        機(jī)械式按鍵在觸發(fā)時(shí)會(huì)產(chǎn)生數(shù)十毫秒的抖動(dòng)信號(hào),如圖所示:

        高電平 ——┐    ┌────┐   ┌───┐
                   └────┘    └───┘
                        ↑抖動(dòng)階段約5~20ms

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

        1.1 延時(shí)法(簡(jiǎn)單粗暴)

        #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); // 固定延時(shí)20ms
                key_now = KEY_PIN;
            }

            key_last = key_now;    return key_now;
        }

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

        1.2 定時(shí)器采樣 + 滑動(dòng)窗口法(推薦)

        #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; // 過(guò)濾成功,狀態(tài)更新
            }    else
            {
                key1.filter_cnt = 0;
            }

            key1.last_state = cur;
        }

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


        二、按鍵事件識(shí)別(單擊、雙擊、長(zhǎng)按)

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

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

        • 長(zhǎng)按:進(jìn)入配置/復(fù)位模式

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

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

        2.1 實(shí)用結(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)變化檢測(cè)
            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++;  // 累積點(diǎn)擊次數(shù)
                    key->press_flag = 0;
                }
            }    // 長(zhǎng)按識(shí)別
            if (key->press_flag)
            {
                key->press_time++;        if (key->press_time == LONG_PRESS_TIME)
                    key->event = KEY_LONG;
            }    // 點(diǎn)擊識(shí)別(釋放后計(jì)時(shí))
            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)中讀取:

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

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

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

        在產(chǎn)品級(jí)設(shè)計(jì)中,代碼清晰度和可維護(hù)性極其重要。直接用變量堆疊判斷邏輯容易混亂。狀態(tài)機(jī)設(shè)計(jì)是一種簡(jiǎn)潔的思路:每種按鍵狀態(tài)對(duì)應(yīng)一個(gè)具體行為轉(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)切換實(shí)現(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ǔ):推薦使用定時(shí)采樣 + 滑動(dòng)濾波方式,兼顧實(shí)時(shí)性和準(zhǔn)確性。

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

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

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

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

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


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

        評(píng)論


        相關(guān)推薦

        技術(shù)專(zhuān)區(qū)

        關(guān)閉
        主站蜘蛛池模板: 凯里市| 信宜市| 同仁县| 临泉县| 玛曲县| 高要市| 仙桃市| 庆云县| 许昌县| 正蓝旗| 天气| 象山县| 广西| 丹凤县| 沂南县| 虹口区| 鸡泽县| 广丰县| 南皮县| 北海市| 靖州| 富阳市| 聂荣县| 商都县| 双桥区| 娱乐| 龙南县| 桂平市| 洛隆县| 称多县| 旌德县| 城口县| 丹棱县| 临武县| 米脂县| 长宁县| 平原县| 康平县| 辽阳市| 新晃| 嘉禾县|