備抽象到狀態(tài)機(jī)消抖的工程實(shí)踐)
1. 從“按下按鍵”到“程序響應(yīng)”一個(gè)嵌入式工程師的日常作為一名在嵌入式領(lǐng)域摸爬滾打了十多年的老鳥(niǎo)我每天打交道最多的除了各種芯片的數(shù)據(jù)手冊(cè)可能就是“按鍵”了。從最簡(jiǎn)單的復(fù)位鍵到復(fù)雜的矩陣鍵盤(pán)再到如今智能設(shè)備上五花八門(mén)的觸摸感應(yīng)按鍵控制幾乎是所有嵌入式設(shè)備的“標(biāo)配”交互入口。聽(tīng)起來(lái)很簡(jiǎn)單不就是檢測(cè)一個(gè)GPIO通用輸入輸出引腳的高低電平變化嗎但真要把這件事做穩(wěn)、做可靠里面的門(mén)道可不少。新手最容易掉進(jìn)去的坑往往是只看到了“按下燈亮松開(kāi)燈滅”的表象而忽略了防抖、消抖、狀態(tài)機(jī)、中斷與輪詢(xún)權(quán)衡這些底層細(xì)節(jié)。今天我就結(jié)合一個(gè)最經(jīng)典的場(chǎng)景——使用芯片的pin引腳設(shè)備來(lái)實(shí)現(xiàn)按鍵控制來(lái)拆解一下這個(gè)看似基礎(chǔ)實(shí)則考驗(yàn)功力的技術(shù)點(diǎn)。無(wú)論你用的是STM32、瑞芯微RK3568還是其他任何帶GPIO的MCU這套思路都是相通的。2. 核心概念厘清什么是“Pin設(shè)備”在深入代碼之前我們必須先統(tǒng)一語(yǔ)言。當(dāng)我們?cè)谇度胧介_(kāi)發(fā)中談?wù)摗笆褂胮in設(shè)備實(shí)現(xiàn)按鍵控制”時(shí)這里的“pin設(shè)備”并非指一個(gè)獨(dú)立的物理模塊而是一種對(duì)芯片引腳的軟件抽象層。它把硬件上一個(gè)個(gè)分散的、功能可配置的物理引腳封裝成操作系統(tǒng)或驅(qū)動(dòng)框架可以統(tǒng)一管理的邏輯對(duì)象。2.1 物理引腳 vs. 軟件抽象以最常見(jiàn)的STM32為例一塊芯片可能有上百個(gè)物理引腳Pin它們被分組為GPIOA、GPIOB等。每個(gè)引腳可以通過(guò)寄存器配置為輸入、輸出、復(fù)用功能等模式。所謂“pin設(shè)備”就是在芯片原廠提供的標(biāo)準(zhǔn)外設(shè)庫(kù)如HAL庫(kù)或操作系統(tǒng)如RT-Thread、FreeRTOS with ESP-IDF的驅(qū)動(dòng)框架中對(duì)這些物理引腳進(jìn)行的一層封裝。例如在RT-Thread的PIN設(shè)備驅(qū)動(dòng)框架下你可以通過(guò)rt_pin_get(“PE.4”)來(lái)獲取一個(gè)名為”P(pán)E.4”對(duì)應(yīng)GPIOE第4腳的引腳句柄后續(xù)所有的操作讀、寫(xiě)、中斷綁定都通過(guò)這個(gè)句柄進(jìn)行而無(wú)需直接操作底層寄存器。這種抽象的好處顯而易見(jiàn)可移植性你的按鍵控制邏輯不再依賴(lài)于具體的芯片型號(hào)。只要該操作系統(tǒng)或框架支持新的芯片你的代碼幾乎可以無(wú)縫遷移。易用性提供了一套統(tǒng)一的API如pin_readpin_attach_irq降低了直接操作寄存器的復(fù)雜度和出錯(cuò)風(fēng)險(xiǎn)。管理性方便系統(tǒng)統(tǒng)一管理引腳資源避免沖突。2.2 與“設(shè)備樹(shù)”的關(guān)系在Linux嵌入式開(kāi)發(fā)中另一個(gè)高頻詞是“設(shè)備樹(shù)”Device Tree。設(shè)備樹(shù)是一種描述硬件資源配置的數(shù)據(jù)結(jié)構(gòu)。對(duì)于引腳而言設(shè)備樹(shù)文件.dts中會(huì)定義某個(gè)引腳例如gpio1 5被用作按鍵輸入并指定其上下拉電阻、中斷觸發(fā)方式等屬性。驅(qū)動(dòng)加載時(shí)會(huì)解析設(shè)備樹(shù)將對(duì)應(yīng)的引腳初始化為一個(gè)gpio_key類(lèi)型的平臺(tái)設(shè)備。這里的“pin設(shè)備”概念可以理解為設(shè)備樹(shù)描述和驅(qū)動(dòng)代碼共同作用后在內(nèi)核中生成的一個(gè)可供用戶(hù)空間或其它驅(qū)動(dòng)訪問(wèn)的“按鍵輸入設(shè)備”。所以在Linux環(huán)境下“使用pin設(shè)備”往往意味著你要先正確配置設(shè)備樹(shù)然后編寫(xiě)或使用對(duì)應(yīng)的GPIO按鍵驅(qū)動(dòng)。3. 按鍵檢測(cè)的兩種核心模式輪詢(xún)與中斷確定了操作對(duì)象pin設(shè)備后接下來(lái)就是如何檢測(cè)按鍵動(dòng)作。這里有兩個(gè)最根本的策略也是工程師需要做出的第一個(gè)重要設(shè)計(jì)抉擇。3.1 輪詢(xún)模式簡(jiǎn)單粗暴的“不斷詢(xún)問(wèn)”輪詢(xún)Polling是最直觀的方式。你的主程序在一個(gè)循環(huán)里不停地讀取按鍵對(duì)應(yīng)引腳的電平狀態(tài)。// 偽代碼示例輪詢(xún)檢測(cè)按鍵 while (1) { if (pin_read(KEY_PIN) PRESSED_LEVEL) { // 檢測(cè)到低電平假設(shè)按下為低電平 debounce_delay(20); // 延時(shí)消抖 if (pin_read(KEY_PIN) PRESSED_LEVEL) { // 確認(rèn)按下執(zhí)行動(dòng)作 key_action(); } } // 執(zhí)行其他任務(wù) do_other_tasks(); }優(yōu)點(diǎn)實(shí)現(xiàn)簡(jiǎn)單邏輯直白沒(méi)有中斷上下文等復(fù)雜概念。對(duì)時(shí)序要求不嚴(yán)適用于對(duì)按鍵響應(yīng)實(shí)時(shí)性要求不高的場(chǎng)景。缺點(diǎn)CPU資源浪費(fèi)即使沒(méi)有按鍵CPU也在不停地執(zhí)行讀取和判斷指令在低功耗應(yīng)用中這是致命的。響應(yīng)延遲不確定如果do_other_tasks()執(zhí)行時(shí)間很長(zhǎng)按鍵可能已經(jīng)按下并松開(kāi)了程序才檢測(cè)到導(dǎo)致丟失事件。適用場(chǎng)景裸機(jī)小程序、任務(wù)非常簡(jiǎn)單的系統(tǒng)或者作為其他主要檢測(cè)機(jī)制如中斷的補(bǔ)充驗(yàn)證。3.2 中斷模式高效及時(shí)的“事件通知”中斷Interrupt是更專(zhuān)業(yè)、更高效的方式。你將按鍵引腳配置為中斷模式并設(shè)定觸發(fā)條件如下降沿、上升沿、雙邊沿。當(dāng)按鍵按下或松開(kāi)導(dǎo)致電平變化符合觸發(fā)條件時(shí)硬件會(huì)打斷CPU當(dāng)前的工作立即跳轉(zhuǎn)到你預(yù)先設(shè)置好的中斷服務(wù)函數(shù)ISR中執(zhí)行。// 偽代碼示例中斷服務(wù)函數(shù) void key_isr(void *args) { // 通常在此處做標(biāo)記真正的處理放到主循環(huán)或線程中 key_event_flag 1; } // 主循環(huán)中處理事件 while (1) { if (key_event_flag) { key_event_flag 0; // 執(zhí)行消抖和按鍵動(dòng)作處理 key_action_process(); } // 可以安心執(zhí)行其他耗時(shí)任務(wù) do_other_long_tasks(); }優(yōu)點(diǎn)實(shí)時(shí)性高按鍵事件幾乎能被立即響應(yīng)。CPU效率高平時(shí)CPU可以執(zhí)行其他任務(wù)或進(jìn)入休眠只有事件發(fā)生時(shí)才被喚醒。缺點(diǎn)實(shí)現(xiàn)復(fù)雜需要配置中斷控制器編寫(xiě)ISR并注意中斷上下文的要求快進(jìn)快出不能調(diào)用可能阻塞的函數(shù)。容易受干擾硬件上的毛刺可能誤觸發(fā)中斷對(duì)硬件電路和軟件消抖要求更高。適用場(chǎng)景絕大多數(shù)需要高效、實(shí)時(shí)響應(yīng)人機(jī)交互的嵌入式產(chǎn)品。注意在RTOS或Linux等復(fù)雜系統(tǒng)中中斷服務(wù)函數(shù)ISR通常只做最精簡(jiǎn)的操作如發(fā)送一個(gè)信號(hào)量、置位一個(gè)標(biāo)志、向消息隊(duì)列投遞一個(gè)事件將耗時(shí)的處理如消抖、執(zhí)行具體功能交給一個(gè)專(zhuān)用的任務(wù)線程去完成。這是保持系統(tǒng)響應(yīng)性和穩(wěn)定性的關(guān)鍵設(shè)計(jì)模式。4. 跨越“毛刺”的鴻溝按鍵消抖的實(shí)戰(zhàn)藝術(shù)無(wú)論輪詢(xún)還是中斷都無(wú)法回避一個(gè)物理世界的問(wèn)題按鍵抖動(dòng)。機(jī)械按鍵的金屬觸點(diǎn)在閉合或斷開(kāi)的瞬間會(huì)產(chǎn)生一系列快速的、不穩(wěn)定的通斷在電平上表現(xiàn)為一段時(shí)間的毛刺。如果不處理一次按鍵會(huì)被誤判為多次。4.1 硬件消抖從源頭解決問(wèn)題最簡(jiǎn)單的硬件消抖是在按鍵兩端并聯(lián)一個(gè)電容通常0.1uF。電容的充放電特性可以吸收短暫的電壓波動(dòng)使電平變化變得平滑。這是最有效、最可靠的方式強(qiáng)烈推薦在產(chǎn)品硬件設(shè)計(jì)時(shí)就加上。但對(duì)于成本極其敏感或引腳資源緊張的項(xiàng)目可能就需要完全依賴(lài)軟件。4.2 軟件消抖算法與策略軟件消抖的核心思想是忽略短時(shí)間內(nèi)不穩(wěn)定的狀態(tài)變化只確認(rèn)持續(xù)了一段時(shí)間的穩(wěn)定狀態(tài)。1. 簡(jiǎn)單延時(shí)法這是最入門(mén)的方法在檢測(cè)到電平變化后延時(shí)10-20ms抖動(dòng)時(shí)間通常為5-10ms再次讀取引腳如果狀態(tài)一致則確認(rèn)為有效動(dòng)作。// 輪詢(xún)中的簡(jiǎn)單延時(shí)消抖 if (pin_read(KEY_PIN) PRESSED) { rt_thread_mdelay(15); // 延時(shí)15ms if (pin_read(KEY_PIN) PRESSED) { // 確認(rèn)按下 } }缺點(diǎn)在延時(shí)期間CPU被阻塞無(wú)法執(zhí)行其他任務(wù)。在中斷中使用mdelay這類(lèi)阻塞函數(shù)更是大忌。2. 狀態(tài)機(jī)消抖推薦這是更優(yōu)雅、更通用的方法尤其適合在非阻塞的主循環(huán)或任務(wù)中實(shí)現(xiàn)。它把按鍵過(guò)程分為幾個(gè)狀態(tài)如IDLE,DEBOUNCE,PRESSED,RELEASE通過(guò)定時(shí)器或系統(tǒng)節(jié)拍來(lái)推進(jìn)狀態(tài)轉(zhuǎn)移。// 簡(jiǎn)化狀態(tài)機(jī)示例 typedef enum { KEY_STATE_IDLE, KEY_STATE_DEBOUNCE, KEY_STATE_PRESSED, KEY_STATE_RELEASE_DEBOUNCE } key_state_t; void key_scan_task(void) { static key_state_t state KEY_STATE_IDLE; static uint32_t tick 0; switch (state) { case KEY_STATE_IDLE: if (pin_read(KEY_PIN) PRESSED) { state KEY_STATE_DEBOUNCE; tick rt_tick_get(); // 記錄當(dāng)前時(shí)間戳 } break; case KEY_STATE_DEBOUNCE: if ((rt_tick_get() - tick) 15) { // 經(jīng)過(guò)15個(gè)tick if (pin_read(KEY_PIN) PRESSED) { state KEY_STATE_PRESSED; key_action(); // 真正執(zhí)行按鍵動(dòng)作 } else { state KEY_STATE_IDLE; // 抖動(dòng)回到空閑 } } break; case KEY_STATE_PRESSED: if (pin_read(KEY_PIN) RELEASED) { state KEY_STATE_RELEASE_DEBOUNCE; tick rt_tick_get(); } break; case KEY_STATE_RELEASE_DEBOUNCE: if ((rt_tick_get() - tick) 15) { state KEY_STATE_IDLE; } break; } } // 在主循環(huán)中定期調(diào)用 key_scan_task()優(yōu)點(diǎn)非阻塞可以輕松集成到任何任務(wù)系統(tǒng)中能同時(shí)處理多個(gè)按鍵并且可以擴(kuò)展出“長(zhǎng)按”、“連按”等高級(jí)功能。3. 中斷結(jié)合定時(shí)器消抖這是中斷模式下最常用的方式。在中斷服務(wù)函數(shù)中不直接處理按鍵而是啟動(dòng)一個(gè)硬件定時(shí)器例如10ms后超時(shí)。在定時(shí)器中斷里再去讀取引腳狀態(tài)進(jìn)行判斷。這樣既保證了響應(yīng)速度又實(shí)現(xiàn)了消抖且不阻塞主程序。5. 從理論到電路硬件設(shè)計(jì)要點(diǎn)與常見(jiàn)坑位軟件寫(xiě)得再漂亮硬件基礎(chǔ)不牢也是白搭。下面這些硬件設(shè)計(jì)上的細(xì)節(jié)是我用不少“燒板子”的教訓(xùn)換來(lái)的。5.1 上拉/下拉電阻給引腳一個(gè)確定的“默認(rèn)值”MCU的GPIO引腳在配置為輸入時(shí)內(nèi)部可能是高阻態(tài)極易受外界干擾。必須通過(guò)電阻將其鉗位到一個(gè)確定的電平。一般情況按鍵一端接地另一端接GPIO。此時(shí)應(yīng)在GPIO與電源VCC之間連接一個(gè)上拉電阻常用4.7KΩ~10KΩ。按鍵未按下時(shí)GPIO被上拉到高電平按下時(shí)GPIO被拉到低電平。這是最常用的接法。特殊情況如果按鍵一端接電源則GPIO需要連接下拉電阻到地。很多現(xiàn)代MCU的GPIO內(nèi)部集成了可軟件配置的上拉/下拉電阻使用起來(lái)非常方便。在初始化pin設(shè)備時(shí)務(wù)必通過(guò)API或寄存器正確配置這個(gè)選項(xiàng)。這是新手最容易忽略的一步直接導(dǎo)致按鍵狀態(tài)讀取不穩(wěn)定。5.2 干擾與ESD防護(hù)如果你的設(shè)備工作在電機(jī)、繼電器等干擾源附近或者用于工業(yè)環(huán)境必須考慮加強(qiáng)防護(hù)。濾波電容除了消抖電容在按鍵引腳與地之間并聯(lián)一個(gè)幾十pF的小電容可以濾除高頻干擾。ESD二極管對(duì)于裸露在外部的按鍵建議增加ESD保護(hù)二極管防止靜電擊穿IO口。5.3 排查“幽靈按鍵”的步驟當(dāng)你發(fā)現(xiàn)程序偶爾會(huì)收到莫名其妙的按鍵信號(hào)時(shí)可以按以下步驟排查示波器是王道用示波器探頭直接測(cè)量按鍵引腳對(duì)地的電壓波形。這是最直接的方法可以看到是否有毛刺、電平是否干凈、上下拉是否起作用。檢查配置確認(rèn)軟件中引腳模式是否正確配置為輸入上拉/下拉是否使能。檢查硬件用萬(wàn)用表測(cè)量按鍵按下和松開(kāi)時(shí)的電阻值檢查PCB走線是否靠近高頻或大電流線路造成耦合干擾。簡(jiǎn)化代碼寫(xiě)一個(gè)最簡(jiǎn)單的測(cè)試程序只做讀取引腳和打印電平排除復(fù)雜業(yè)務(wù)邏輯的干擾。6. 進(jìn)階實(shí)現(xiàn)長(zhǎng)按、連按與復(fù)合按鍵基本的單次按下檢測(cè)滿足不了產(chǎn)品需求。如何實(shí)現(xiàn)“長(zhǎng)按3秒關(guān)機(jī)”、“快速雙擊切換模式”6.1 長(zhǎng)按檢測(cè)在狀態(tài)機(jī)框架下長(zhǎng)按檢測(cè)非常容易實(shí)現(xiàn)。在KEY_STATE_PRESSED狀態(tài)中開(kāi)始計(jì)時(shí)。case KEY_STATE_PRESSED: if (pin_read(KEY_PIN) PRESSED) { if ((rt_tick_get() - press_tick) 300) { // 按下超過(guò)300個(gè)tick如3秒 long_press_action(); // 執(zhí)行長(zhǎng)按動(dòng)作 state KEY_STATE_WAIT_FOR_RELEASE; // 進(jìn)入等待釋放狀態(tài) } } else { // 提前釋放了則是短按 state KEY_STATE_RELEASE_DEBOUNCE; } break;6.2 連按雙擊、N擊檢測(cè)連按檢測(cè)的關(guān)鍵在于計(jì)時(shí)和計(jì)數(shù)。需要在按鍵釋放后開(kāi)啟一個(gè)“連按有效時(shí)間窗口”。第一次按下釋放后啟動(dòng)一個(gè)定時(shí)器比如500ms。在定時(shí)器超時(shí)前如果再次檢測(cè)到按下則計(jì)為連擊計(jì)數(shù)器加一。定時(shí)器超時(shí)后根據(jù)最終的計(jì)數(shù)器值1, 2, 3…執(zhí)行相應(yīng)動(dòng)作。這個(gè)過(guò)程同樣可以用一個(gè)擴(kuò)展的狀態(tài)機(jī)來(lái)清晰描述。6.3 復(fù)合按鍵組合鍵當(dāng)需要同時(shí)按下兩個(gè)鍵才觸發(fā)某個(gè)功能時(shí)如CtrlC需要注意掃描順序在同一個(gè)掃描任務(wù)中依次讀取所有按鍵的狀態(tài)。狀態(tài)判斷只有當(dāng)所有指定按鍵都處于PRESSED狀態(tài)時(shí)才觸發(fā)組合鍵動(dòng)作。同時(shí)要考慮消抖可以要求組合鍵中的所有按鍵都穩(wěn)定按下一定時(shí)間后才算有效。7. 在不同平臺(tái)上的具體實(shí)現(xiàn)示例理論講完了來(lái)看看如何在具體的開(kāi)發(fā)環(huán)境中操作。7.1 在RT-Thread中使用PIN設(shè)備RT-Thread的PIN設(shè)備驅(qū)動(dòng)框架封裝得非常好使用起來(lái)非常清晰。#include rtdevice.h #define KEY_PIN_NUM GET_PIN(E, 4) // 根據(jù)芯片頭文件定義獲取引腳編號(hào) static void key_scan_entry(void *parameter) { rt_pin_mode(KEY_PIN_NUM, PIN_MODE_INPUT_PULLUP); // 設(shè)置為輸入上拉模式 while (1) { if (rt_pin_read(KEY_PIN_NUM) PIN_LOW) { // 按下為低電平 rt_thread_mdelay(10); if (rt_pin_read(KEY_PIN_NUM) PIN_LOW) { rt_kprintf(“Key Pressed!n”); // 等待按鍵釋放 while (rt_pin_read(KEY_PIN_NUM) PIN_LOW) { rt_thread_mdelay(10); } } } rt_thread_mdelay(10); // 掃描間隔 } } // 在合適的地方創(chuàng)建線程運(yùn)行 key_scan_entry對(duì)于中斷模式可以使用rt_pin_attach_irq,rt_pin_irq_enable等API。7.2 在STM32 HAL庫(kù)中操作如果使用STM32CubeMX生成代碼配合HAL庫(kù)操作更偏向底層寄存器一些。// CubeMX配置將PE4配置為GPIO_Input并開(kāi)啟上拉。 // 生成的代碼中會(huì)自動(dòng)完成GPIO初始化。 // 在主循環(huán)中輪詢(xún) while (1) { if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { HAL_Delay(20); if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { // 按鍵按下處理 while (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET); // 等待釋放 } } // ... 其他任務(wù) }使用中斷則需要配置NVIC并在stm32fxx_it.c中實(shí)現(xiàn)對(duì)應(yīng)的外部中斷服務(wù)函數(shù)EXTIx_IRQHandler并在其中調(diào)用HAL_GPIO_EXTI_IRQHandler。7.3 在嵌入式Linux中使用GPIO Sysfs或Libgpiod對(duì)于運(yùn)行Linux的系統(tǒng)如RK3568用戶(hù)空間程序可以通過(guò)文件系統(tǒng)或庫(kù)來(lái)訪問(wèn)GPIO。傳統(tǒng)Sysfs接口已逐漸淘汰echo 4 /sys/class/gpio/export echo in /sys/class/gpio/gpio4/direction cat /sys/class/gpio/gpio4/value現(xiàn)代libgpiod庫(kù)推薦#include gpiod.h struct gpiod_chip *chip; struct gpiod_line *line; chip gpiod_chip_open_by_name(“gpiochip0”); line gpiod_chip_get_line(chip, 4); // 使用GPIO4 gpiod_line_request_input(line, “my_key”); while (1) { int val gpiod_line_get_value(line); if (val 0) { /* 按下 */ } usleep(10000); }更高效的方式是使用gpiod_line_event_wait等待事件。8. 調(diào)試技巧與性能優(yōu)化心得最后分享幾個(gè)我壓箱底的調(diào)試和優(yōu)化經(jīng)驗(yàn)。1. 活用調(diào)試工具邏輯分析儀比示波器更擅長(zhǎng)看數(shù)字時(shí)序??梢酝瑫r(shí)抓取多個(gè)按鍵引腳、甚至加上串口數(shù)據(jù)的波形清晰看到按鍵按下、消抖、程序響應(yīng)的完整時(shí)間線是分析復(fù)雜按鍵邏輯如連按的利器。IO翻轉(zhuǎn)調(diào)試法在按鍵處理函數(shù)的關(guān)鍵位置如進(jìn)入中斷、確認(rèn)按下增加一條翻轉(zhuǎn)某個(gè)空閑IO口的語(yǔ)句用示波器測(cè)量這個(gè)IO的波形就能直觀看到代碼執(zhí)行到該處的時(shí)間點(diǎn)和耗時(shí)。2. 低功耗設(shè)計(jì)下的按鍵喚醒 對(duì)于電池供電的設(shè)備MCU大部分時(shí)間在休眠。此時(shí)必須將按鍵引腳配置為“外部中斷喚醒”模式。當(dāng)按鍵按下產(chǎn)生中斷時(shí)MCU從深眠中被喚醒執(zhí)行處理后再進(jìn)入休眠。這里要特別注意中斷觸發(fā)邊沿的選擇和喚醒后的去抖處理。3. 軟件濾波增強(qiáng)抗干擾 在極端嘈雜的環(huán)境簡(jiǎn)單的延時(shí)消抖可能不夠??梢圆捎谩岸啻尾蓸颖頉Q法”在消抖窗口時(shí)間內(nèi)以1ms間隔連續(xù)采樣10次如果其中7次以上是按下?tīng)顟B(tài)才判定為有效按下。這能有效抵抗偶發(fā)的尖峰脈沖干擾。4. 代碼結(jié)構(gòu)優(yōu)化 將按鍵掃描和處理分離。用一個(gè)高優(yōu)先級(jí)的定時(shí)器中斷如1ms來(lái)采樣所有按鍵的原始狀態(tài)存入一個(gè)緩沖區(qū)。另一個(gè)低優(yōu)先級(jí)的任務(wù)從緩沖區(qū)讀取已消抖處理后的穩(wěn)定狀態(tài)進(jìn)行事件識(shí)別單擊、長(zhǎng)按等并分發(fā)給應(yīng)用。這種“生產(chǎn)者-消費(fèi)者”模型結(jié)構(gòu)清晰易于擴(kuò)展且能保證實(shí)時(shí)性。按鍵控制這個(gè)嵌入式世界的“Hello World”真正要做好需要考慮硬件、底層驅(qū)動(dòng)、操作系統(tǒng)、應(yīng)用邏輯等多個(gè)層面的協(xié)同。它像一面鏡子能清晰地反映出一個(gè)嵌入式工程師對(duì)系統(tǒng)理解的深度和代碼設(shè)計(jì)的功底。希望這次從pin設(shè)備開(kāi)始的深度拆解能幫你把這塊基礎(chǔ)的基石打得更牢。下次當(dāng)你再面對(duì)一個(gè)需要按鍵的產(chǎn)品需求時(shí)不妨先問(wèn)問(wèn)自己我的消抖策略夠健壯嗎我的中斷服務(wù)函數(shù)夠輕量嗎我的狀態(tài)機(jī)設(shè)計(jì)能方便地?cái)U(kuò)展新功能嗎想清楚這些寫(xiě)出來(lái)的代碼自然會(huì)大不一樣。