基本的按鍵程序結構分析:
1 void key_scan(void) 2 { 3 if (!key) //檢測按鍵是否按下 4 { 5 delay_ms(20); //延時去抖,一般20ms 6 if(!key) 7 { 8 ...... 9 } 10 while (!key); //等待按鍵釋放 11 } 12 }
注意:以上基本按鍵程序中,在按鍵執行之后必須要加上等待按鍵釋放,否則程序會出現一些奇怪的問題,比如說按鍵累加時按鍵一次,卻累加了多次。
可識別長擊和短擊按鍵程序(有限狀態機):
主函數文件:
main.c
1 #include "key.h" 2 3 sbit LED = P2^0; 4 5 u8 timer0_flag; //定時器10ms計時標記 6 7 8 void timer0_init(void) 9 { 10 TMOD |= 0x01; //定時器0的方式1(16位定時器) 11 TH0 = 0xDC; //定時10ms初值 12 TL0 = 0x00; 13 14 ET0 = 1; 15 TR0 = 1; 16 EA = 1; 17 } 18 19 void main(void) 20 { 21 key_init(); //按鍵初始化,51單片機在讀取某個端口的值時,先拉高 22 timer0_init(); //定時器初始化 23 24 for (;;) 25 { 26 if (timer0_flag) 27 { 28 timer0_flag = 0; 29 30 switch (key_driver()) 31 { 32 case KEY_SHORT: LED = 0; break; //短擊點亮LED燈 33 case KEY_LONG : LED = 1; break; //長擊熄滅LED燈 34 } 35 } 36 } 37 } 38 39 void timer0_int(void) interrupt 1 //中斷處理函數 40 { 41 TH0 = 0xDC; 42 TL0 = 0x00; 43 timer0_flag = 1; 44 }
主文件里非常重要的有兩處:
1、時間粒度控制:本程序以10ms做時間單位,類似於時間片輪詢的方式,每隔10ms對按鍵狀態掃描一次,對應代碼為:
void timer0_int(void) interrupt 1 //中斷處理函數
{
TH0 = 0xDC;
TL0 = 0x00;
timer0_flag = 1; //10ms時間
}
2、對按鍵驅動函數返回值的判斷,根據按鍵返回值,識別按鍵操作是短擊還是長擊后,執行相應的動作,對應代碼為:
switch (key_driver())
{ case KEY_SHORT: LED = 0; break; //短擊點亮LED燈
case KEY_LONG : LED = 1; break; //長擊熄滅LED燈
}
按鍵驅動文件:
key.c
1 #include "key.h" 2 3 sbit KEY_INPUT = P3^3; //獨立按鍵 4 5 key custom_key; //按鍵的數據結構體 6 7 void key_init(void) //按鍵初始化 8 { 9 KEY_INPUT = 1; 10 } 11 12 u8 key_driver(void) //按鍵驅動函數 13 { 14 custom_key.press = KEY_INPUT; //讀取按鍵接口值 15 custom_key.value = KEY_NONE; //返回值初始化為無值 16 17 switch(custom_key.state) //按鍵狀態判斷 18 { 19 case KEY_STATE_JUDGE: //判斷有無按鍵按下狀態 20 if (!custom_key.press) //有按鍵按下 21 { 22 custom_key.state = KEY_STATE_DEBOUNCE; //轉入消抖狀態 23 custom_key.count = 0; //計數器清零 24 } 25 break; 26 27 case KEY_STATE_DEBOUNCE: //消抖狀態 28 if (!custom_key.press) 29 { 30 custom_key.count ++; 31 if (custom_key.count >= SINGLE_KEY_TIME) 32 { 33 custom_key.state = KEY_STATE_SPAN; //消抖確認是有效按鍵,轉入短擊和長擊判斷狀態 34 } 35 } 36 else 37 custom_key.state = KEY_STATE_JUDGE; //按鍵誤動作,返回判斷有無按鍵按下狀態 38 break; 39 40 case KEY_STATE_SPAN: //短擊和長擊判斷狀態 41 if (custom_key.press) //在長擊臨界值之前釋放按鍵,判斷為短擊 42 { 43 custom_key.value = KEY_SHORT; 44 custom_key.state = KEY_STATE_JUDGE; //返回判斷有無按鍵按下狀態 45 } 46 else //計數器值超過長擊臨界值,判斷為長擊 47 { 48 custom_key.count ++; 49 if (custom_key.count >= LONG_KEY_TIME) 50 { 51 custom_key.value = KEY_LONG; 52 custom_key.state = KEY_STATE_RELEASE; //進入按鍵釋放狀態 53 } 54 } 55 break; 56 57 case KEY_STATE_RELEASE: //按鍵釋放狀態 58 if (custom_key.press) 59 { 60 custom_key.state = KEY_STATE_JUDGE; //返回判斷有無按鍵按下狀態 61 } 62 break; 63 64 default: //默認返回判斷有無按鍵按下狀態 65 custom_key.state = KEY_STATE_JUDGE; 66 break; 67 } 68 return custom_key.value; //返回按鍵值 69 }
代碼中做了詳細的注釋,需要說明的是按鍵的四種狀態:
KEY_STATE_JUDGE:用來檢測是否有按鍵按下, 當有按鍵按下后,轉移到消抖狀態,否則每次時間片掃描時都處於此狀態 KEY_STATE_DEBOUNCE:消抖狀態,用來檢測按鍵有效還是誤觸發,假如只是誤觸發,則返回到按鍵等待狀態 KEY_STATE_SPAN:判斷按鍵是長擊還是短擊,如果在延時消抖后,按鍵在長按的臨界值之前釋放,則判斷為短擊,否則判斷為長擊,此處的臨界值為2s KEY_STATE_RELEASE:按鍵釋放狀態,擺脫用while循環等待按鍵釋放,當判斷為長擊以后,程序將進入此狀態,在此之后只需在每次時間片到了以后判斷是否釋放即可
此處需要理解的是“並行”的思想:主程序一直在for(;;)循環中運行,同時定時器也在不斷累加計數,當達到定時器中斷觸發條件后,定時器中斷當前的循環,進入定時器服務程序。因此,
這四種狀態在每次定時器中斷觸發后就會檢測判斷一次,相隔時間為10ms。
頭文件:
key.h
1 #ifndef _KEY_H_ 2 #define _KEY_H_ 3 4 #include "reg52.h" 5 6 typedef unsigned char u8; 7 typedef unsigned int u16; 8 9 #define KEY_STATE_JUDGE 0 //判斷有無按鍵按下狀態 10 #define KEY_STATE_DEBOUNCE 1 //消抖狀態 11 #define KEY_STATE_SPAN 2 //判斷是短按還是長按 12 #define KEY_STATE_RELEASE 3 //按鍵釋放 13 14 #define LONG_KEY_TIME 200 //按鍵持續超過2s,判斷為長擊 15 #define SINGLE_KEY_TIME 2 //消抖 16 17 #define KEY_NONE 0 //沒有按下按鍵 18 #define KEY_SHORT 1 //短擊 19 #define KEY_LONG 2 //長擊 20 21 typedef struct 22 { 23 u8 press; //讀取按鍵接口 24 u8 state; //按鍵狀態 25 u8 value; //按鍵返回值 26 u16 count; //按鍵時間計數器 27 }key; 28 29 //extern key custom_key; 30 31 void key_init(void); //按鍵初始化函數 32 u8 key_driver(void); //按鍵驅動函數 33 34 #endif
文件中定義了需要的變量、數據結構,以及函數聲明。