狀態機按鍵消抖

狀態機按鍵消抖

一般的按鍵輸入軟件接口程序非常簡單,在程序中一旦檢測到按鍵輸入口爲低電平(有時可能爲高),便採用軟件延時的方法來進行消抖,然後再次檢測按鍵輸入,如果再次確認爲低電平則表示有按鍵按下,轉入執行按鍵處理程序。如果延時後檢測的電平爲高電平則放棄本次按鍵檢測,重新開始一次按鍵檢測過程。在簡單的系統中這種方法比較可以用,但是在複雜的系統實時性要求較高的系統中這種方法的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.   
  12.     switch(key_state)  
  13.     {  
  14.         case KEY_S1:  
  15.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1)  
  16.                 {key_state = KEY_S2;  
  17.             }  
  18.             else  
  19.                 key_state = KEY_S2;  
  20.             break;  
  21.   
  22.         case KEY_S2:  
  23.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  24.                 key_state = KEY_S3;  
  25.                 trace_notice(MID_KEY,"press\r\n");//相應的鍵操作處理程序  
  26.             }else  
  27.                 key_state = KEY_S1;  
  28.   
  29.             break;  
  30.   
  31.         case KEY_S3:  
  32.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  33.                 key_state = KEY_S3;  
  34.                 press++;  
  35.                 if(press>20){  
  36.                     trace_notice(MID_KEY,"press2\r\n");//相應的計數操作,判斷長短按鍵  
  37.                 }  
  38.             }  
  39.             else  
  40.                 key_state = KEY_S4;  
  41.   
  42.             break;  
  43.   
  44.         case KEY_S4:  
  45.             if(GPIO_ReadInputDataBit(g_keys[0].port, g_keys[0].gpio)==1){  
  46.                 key_state = KEY_S1;  
  47.                 press = 0;  
  48.             }  
  49.             break;  
  50.   
  51.         default:  
  52.             key_state = KEY_S1;  
  53.             press = 0;  
  54.             break;  
  55.     }  



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

同時可以將狀態機應用於其他的程序中,一個串行通信的時序(不管它是遵循何種協議,標準串口也好、I2C也好;也不管它是有線的、還是紅外的、無線的)也都可以看做由一系列有限的狀態構成。顯示掃描程序也是狀態機;通信命令解析程序也是狀態機;甚至連繼電器的吸合/釋放控制、發光管(LED)的亮/滅控制又何嘗不是個狀態機。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章