1. 程式人生 > >狀態機按鍵掃描

狀態機按鍵掃描

一般的按鍵輸入軟體介面程式非常簡單,在程式中一旦檢測到按鍵輸入口為低電平(有時可能為高),便採用軟體延時的方 法來進行消抖,然後再次檢測按鍵輸入,如果再次確認為低電平則表示有按鍵按下,轉入執行按鍵處理程式。如果延時後檢測的電平為高電平則放棄本次按鍵檢測, 重新開始一次按鍵檢測過程。在簡單的系統中這種方法比較可以用,但是在複雜的系統實時性要求較高的系統中這種方法的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.

上面就是採用狀態機進行按鍵檢測的過程。簡單程式如下:

  1. enum key_states_e{  
  2.     KEY_S1,  
  3.     KEY_S2,  
  4.     KEY_S3,  
  5.     KEY_S4  
  6. };  
  7. void key_scan(void)  
  8. {  
  9.     static enum key_states_e key_state=KEY_S1;  
  10.     static int press=0;  
  11.     switch(key_state)  
  12.     {  
  13.         case KEY_S1:  
  14.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1)  
  15.                 {key_state = KEY_S2;  
  16.             }  
  17.             else  
  18.                 key_state = KEY_S2;  
  19.             break;  
  20.         case KEY_S2:  
  21.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  22.                 key_state = KEY_S3;  
  23.                 trace_notice(MID_KEY,"press\r\n");//相應的鍵操作處理程式  
  24.             }else  
  25.                 key_state = KEY_S1;  
  26.             break;  
  27.         case KEY_S3:  
  28.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  29.                 key_state = KEY_S3;  
  30.                 press++;  
  31.                 if(press>20){  
  32.                     trace_notice(MID_KEY,"press2\r\n");//相應的計數操作,判斷長短按鍵  
  33.                 }  
  34.             }  
  35.             else  
  36.                 key_state = KEY_S4;  
  37.             break;  
  38.         case KEY_S4:  
  39.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  40.                 key_state = KEY_S1;  
  41.                 press = 0;  
  42.             }  
  43.             break;  
  44.         default:  
  45.             key_state = KEY_S1;  
  46.             press = 0;  
  47.             break;  
  48.     }  




       在定時器中,定時10ms,定時到後在中斷服務程式中呼叫上述函式,每次執行的間隔10ms,可以有效的消除消抖,提高CPU的利用率。

同時可以將狀態機應用於其他的程式中,一個序列通訊的時序(不管它是遵循何種協議,標準串列埠也好、I2C也好;也不管它是 有線的、還是紅外的、無線的)也都可以看做由一系列有限的狀態構成。顯示掃描程式也是狀態機;通訊命令解析程式也是狀態機;甚至連繼電器的吸合/釋放控 制、發光管(LED)的亮/滅控制又何嘗不是個狀態機。