狀態機按鍵掃描
一般的按鍵輸入軟體介面程式非常簡單,在程式中一旦檢測到按鍵輸入口為低電平(有時可能為高),便採用軟體延時的方 法來進行消抖,然後再次檢測按鍵輸入,如果再次確認為低電平則表示有按鍵按下,轉入執行按鍵處理程式。如果延時後檢測的電平為高電平則放棄本次按鍵檢測, 重新開始一次按鍵檢測過程。在簡單的系統中這種方法比較可以用,但是在複雜的系統實時性要求較高的系統中這種方法的CPU利用率比較低,造成資源的浪費。 另外,由於在不同的產品系統中對按鍵功能的定義和使用方式也會不同,而且是多變的,加上在測試和按鍵處理的同時,MCU還要同時處理其他的任務(如顯示、 計算、計時等),因此編寫鍵盤和按鍵介面的處理程式需要掌握有效的分析方法,具備較高的軟體設計能力和程式編寫的技巧。而採用狀態機的方法是一種比較好的 方法。
何為狀態機
關於狀態機的一個極度確切的描述是它是一個有向圖形,由一組節點和一組相應的轉移函式組成,狀態機通過響應一系列 事件而“執行”。每個事件都在屬於“當前”節點的轉移函式的控制範圍內,其中函式的範圍是節點的一個子集。函式返回“下一個”(也許是同一個)節點。這些 節點中至少有一個必須是終態。當到達終態,狀態機停止。
狀態機是一種概念性機器,它能採取某種操作來響應一個外部事件。具體採取的操作不僅能取決於接收到的事件,還能取決於各個 事件的相對發生順序。之所以能做到這一點,是因為機器能跟蹤一個內部狀態,它會在收到事件後進行更新。為一個事件而響應的行動不僅取決於事件本身,還取決 於機器的內部狀態。另外,採取 的行動還會決定並更新機器的狀態。這樣一來,任何邏輯都可建模成一系列事件/狀態組合。
狀態機是軟體程式設計中的一個重要概念。比如在一個按鍵命令解析程式中,就可以看做狀態機,其過程如下:本來在A狀態下,觸發一個按鍵後切換到B,再觸發另一個鍵後就切換到C狀態,或者返回A狀態。這是最簡單的例子。其他的很多的程式都可以當做狀態機來處理。
狀態機可歸納為4個要素,即現態、條件、動作、次態。這樣的歸納,主要是出於對狀態機內在因果關係的考慮。“現態”和“條件”是因,“動作”和“次態”是果。詳細如下:
現態:是指當前所處的狀態。
條件:又稱為“事件”。當一個條件滿足,將會觸發一個動作,或者執行一次狀態的遷移。
動作:條件滿足後執行動作。動作執行完畢後,可以遷移到新的狀態,也可以仍舊保持原狀態。動作不是必需的,當條件滿足後,也可以不執行任何動作,直接遷移到新狀態。
次態:條件滿足後要遷往的新狀態。“次態”是相對於“現態”而言的,“次態”一旦被啟用,就轉變為新的“現態”了。
按鍵的狀態機實現
一個按鍵從鍵按下到鬆開的過程如下如所示。從圖中可以看出,按鍵的按下和鬆開的過程都有抖動的干擾問題,因此要將它們消除。
可將將按鍵抽象為4個狀態:
(1) 未按下,假定為S0
(2) 確認有鍵按下,假定為S1
(3) 鍵穩定按下狀態,假定為S2
(4) 鍵釋放狀態,假定為S3。
(有時也可以抽象為3個狀態S0,S1,S3)。
在一個系統中按鍵的操作是隨機的,因此係統軟體中要對按鍵進行迴圈查詢。在按鍵檢測過程中需要進行消抖處理,消抖的延時處 理一般要10ms或20ms,因此取狀態機的時間序列為10或20ms,這樣不僅可以跳過按鍵消抖的影響,同事也遠小於按鍵0.3-0.5S的穩定閉合 其,不會將按鍵過程丟失。
假定鍵按下時埠電平為0,未按下時為1(或者相反)。通過狀態機實現按鍵檢測的過程如下:
首先,按鍵的初始態為S0,當檢測到輸入為1時,表示沒有鍵按下,保持S0。當按鍵輸入為0時,則有鍵按下,轉入狀態S1。
在S1狀態時,如果輸入的訊號為1,則表示剛才的按鍵操作為干擾,則狀態跳轉到S0;如果輸入訊號為0,則表示確實有鍵按下,此時可以讀取鍵狀態,產生相應的按鍵標誌或者將該事件存入訊息佇列。同時狀態機切換到S2狀態。
在S2狀態,如果輸入訊號為1,則沒有鍵按下,切換到S3;如果輸入訊號為0,則保持S2狀態,並進行計數。如果計數值超過一定的門限值,則可以認為該按鍵為長按鍵事件或者鍵一直按下狀態,如果未超過門限值,則認為是短按鍵事件,保持S2狀態。
在S3狀態,如果輸入訊號為高電平,則切換到S0.
上面就是採用狀態機進行按鍵檢測的過程。簡單程式如下:
- enum key_states_e{
- KEY_S1,
- KEY_S2,
- KEY_S3,
- KEY_S4
- };
- void key_scan(void)
- {
- static enum key_states_e key_state=KEY_S1;
- static int press=0;
- switch(key_state)
- {
- case KEY_S1:
-
if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1)
- {key_state = KEY_S2;
- }
- else
- key_state = KEY_S2;
- break;
- case KEY_S2:
- if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){
- key_state = KEY_S3;
- trace_notice(MID_KEY,"press\r\n");//相應的鍵操作處理程式
- }else
- key_state = KEY_S1;
- break;
- case KEY_S3:
- if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){
- key_state = KEY_S3;
- press++;
- if(press>20){
- trace_notice(MID_KEY,"press2\r\n");//相應的計數操作,判斷長短按鍵
- }
- }
- else
- key_state = KEY_S4;
- break;
- case KEY_S4:
- if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){
- key_state = KEY_S1;
- press = 0;
- }
- break;
- default:
- key_state = KEY_S1;
- press = 0;
- break;
- }
在定時器中,定時10ms,定時到後在中斷服務程式中呼叫上述函式,每次執行的間隔10ms,可以有效的消除消抖,提高CPU的利用率。
同時可以將狀態機應用於其他的程式中,一個序列通訊的時序(不管它是遵循何種協議,標準串列埠也好、I2C也好;也不管它是 有線的、還是紅外的、無線的)也都可以看做由一系列有限的狀態構成。顯示掃描程式也是狀態機;通訊命令解析程式也是狀態機;甚至連繼電器的吸合/釋放控 制、發光管(LED)的亮/滅控制又何嘗不是個狀態機。