1. 程式人生 > >【按鍵】短按,長按,按鍵釋放,三種模式的按鍵掃描程式(軟體消抖動)

【按鍵】短按,長按,按鍵釋放,三種模式的按鍵掃描程式(軟體消抖動)

先來說一下這三種模式的意思:

1. 短按模式:單擊按鍵時,返回一次有效按鍵值;長按時也只返回一次有效按鍵值。這樣可以有效地排除因不小心長按帶來的返回多次有效按鍵,進而執行多次按鍵處理程式。

2. 長按模式: 單擊按鍵時,返回一次有效按鍵;長按時,返回多次有效按鍵值。這樣可以很快的調節某個較大的引數,比如時間的時分秒引數。

3. 按鍵釋放模式:這個模式與短按模式是相對的。短按模式只要按鍵按下去,立即返回有效鍵值,進而試行按鍵處理程式;二按鍵釋放模式,卻是要等到按下鍵,釋放之後,才會返回有效鍵值,進而執行按鍵處理程式。


接下來說一下掃描程式:
1)採用的是輪詢的方式(非中斷)
2)消抖動的方式:多次掃描,來確定按鍵值。下面的程式的是設定了5次。主要是根據掃描週期來確定,次數的多少。
注:
掃描週期:從進入按鍵掃描程式開始,直到到下一次進入按鍵掃描程式時 結束,之間所用的時間。

下面是整個按鍵掃描程式的原始碼,可以讀一讀,語句都很簡單,而且每一句都有註釋,一步一步看下去,應該能明白。
如果不明白,可以留言談論。
以下是 KeyScan.c 檔案的內容,

//======================================================
//KeyScan.c
//======================================================
//注意:該巨集定義,定義在keyscan.h檔案中
//#define KEYDEBOUNCE 0x05             //消抖動,按鍵掃描次數。如果連續5次都是掃描的都是相同鍵值,則認為是有效鍵值,否則是誤觸發
unsigned int g_uiCurrKey; //當前按鍵值 unsigned int g_uiLastKey; //上次按鍵值 unsigned int g_uiKeyScanCount; //按鍵掃描計數,作用:消抖動 unsigned int g_uiPreKeyValue; // 上一次的有效按鍵值 unsigned int g_uiKeyDown; //鍵被按下,返回的鍵值。 作用:單次按鍵,單次返回有效鍵值;按住不放,也只返回被按下的一個鍵值 unsigned int g_uiKeyRelease; //鍵被釋放後,返回的鍵值。 作用:只有按下的按鍵被釋放後,才返回按下的鍵值
unsigned int g_uiKeyContinue; //鍵連續按鍵,重複返回的鍵值。 作用:只要按住不放,就會重複地返回相同鍵值 //P0口的低八位作為按鍵 //沒有按鍵時,返回的是0xff, void Int_Key_Scan(void) { static unsigned short LastReadKey; //上次從IO口讀取的鍵值 ,注意是靜態變數 unsigned short CurrReadKey; //當前從IO口讀取的鍵值 CurrReadKey = P0 & 0x00ff; //獲取當前的鍵值 if(CurrReadKey == LastReadKey) //如果當前讀取的鍵值與上次從IO口讀取的鍵值相同 { if(g_uiKeyScanCount >= KEYDEBOUNCE) //首先判斷是否大於等於debounce的設定值(即是,是否大於等於設定的取樣次數) { //按住不放,多次響應 g_uiCurrKey = CurrReadKey; //如果是,將當前的讀取值判定為有效按鍵值(如果是,在取樣週期中,都是這個值,則判定為有效按鍵值) g_uiKeyContinue = g_uiCurrKey ; //長按,多次響應 按鍵值 //按住不放只響應一次 if(g_uiPreKeyValue == g_uiCurrKey) { g_uiKeyDown = 0xff; //沒有鍵值 } else { g_uiKeyDown = g_uiCurrKey; //如果不同,按鍵有效,(就是第一次有效值時) } //按鍵釋放時,按鍵值才有效 if(g_uiCurrKey == 0xff) //當有效按鍵值從非0到0的狀態時(即是,從有按鍵到無按鍵,表示已經釋放了),表示之前按鍵已經釋放了 { g_uiKeyRelease = g_uiPreKeyValue; } g_uiLastKey = g_uiCurrKey; //記錄上次有效按鍵值 } else //如果否,則debounce加一(如果否,則繼續取樣鍵值) { g_uiKeyScanCount++; } } else //如果當前讀取的鍵值與上次從IO口讀取的鍵值不同,說明按鍵已經變化 { g_uiKeyDown = 0xff; //放開按鍵後第一次進入掃描程式,清零g_uiKeyDown.作用:消除一個BUG(你猜BUG是什麼?) g_uiKeyScanCount = 0; //清零之前的按鍵的debounce計數 LastReadKey = CurrReadKey; //將當前讀取的鍵值記錄為上次讀取的按鍵值 } }

以下是KeyScan.h檔案內容

//======================================================
//KeyScan.h
//======================================================

//巨集定義
#define KEYDEBOUNCE 0x05             //消抖動,按鍵掃描次數。如果連續5次都是掃描的都是相同鍵值,則認為是有效鍵值,否則是誤觸發

//宣告變數
extern unsigned int g_uiCurrKey;            //當前按鍵值
extern unsigned int g_uiLastKey;            //上次按鍵值
extern unsigned int g_uiKeyScanCount;       //按鍵掃描計數,作用:消抖動

extern unsigned int g_uiPreKeyValue;                //上一次的有效按鍵值
extern unsigned int g_uiKeyDown;            //鍵被按下,返回的鍵值。       作用:單次按鍵,單次返回有效鍵值;按住不放,也只返回被按下的一個鍵值
extern unsigned int g_uiKeyRelease;         //鍵被釋放後,返回的鍵值。     作用:只有按下的按鍵被釋放後,才返回按下的鍵值
extern unsigned int g_uiKeyContinue;        //鍵連續按鍵,重複返回的鍵值。 作用:只要按住不放,就會重複地返回相同鍵值

//函式宣告
void Int_Key_Scan(void);

使用注意:
1.作為按鍵使用的相應IO口,必須設定為輸入模式(如果是51微控制器的話,無需關心)
2.按鍵的硬體連線必須是一端接GND,一端接IO口。

下面介紹一下程式的使用方法:
這裡以51微控制器的 按鍵點亮和熄滅LED燈作為例子。
硬體:
1)按鍵使用微控制器的P0埠
2)LED燈使用P1.0的IO口,低電平點亮

返回的按鍵值:
沒有鍵按下, 返回鍵值是0xFF
如果P0.0按下,返回鍵值是0xFE
如果P0.1按下,返回鍵值是0xFD
如果P0.2按下,返回鍵值是0xFB
如果P0.3按下,返回鍵值是0xF7

如果P0.7按下,返回鍵值是0x7F

下面是例子的參考原始碼:

//======================================================
//main.c
//======================================================
#include "reg51.h"
#include "KeyScan.h"

sbit LED = P1.0;       //定義LEDIO口
char time_10ms_ok;     // 10ms 定時標誌

void init_timer0(void)
{
    //定時器的初始化                          
    TMOD = 0x01;               //選擇定時器的工作模式:定時器0,方式1
    TH0 = (65535 - 10000)/256; //定時器的初值
    TL0 = (65535 - 10000)%256;
    EA = 1;                    //開打總中斷使能
    ET0 = 1;                   //開啟定時器0 的使能
    TR0 = 1;                   //開啟定時器0 ,開始工作
}

void main(void)
{
    P0 = 0xff;
    LED = 0;           //點亮LED

    init_timer0();     //初始化定時器 定時10ms

    while(1)
    {
        if(time_10ms_ok)        //這裡表示10ms掃描一次
        {
            time_10ms_ok = 0;   //清除10ms定時標誌
            Int_Key_Scan();      //按鍵掃描程式
        }

        //第一種:KeyDown的使用
        //單按時和長按時,都只返回一次有效鍵值(無需等到按鍵釋放,就可以返回有效鍵值)
        switch(g_uiKeyDown)
        {
            case 0xFE:
                        //P0.0按鍵程式
                        LED = !LED; 
                        break;

            case 0xFD:
                        //P0.1按鍵程式 
                        //...
                        break;

            case 0xFB:
                        //P0.2按鍵程式 
                        //...
                        break;

            case 0xF7:
                        break;

            case 0xEF:
                        break;

            case 0xDF:
                        break;

            case 0xBF:
                        break;

            case 0x7F:
                        break;      

            case 0xFF:
                        //沒有按鍵程式
                        //...
                        break;
        }

        //第二種:KeyRelease的使用
        //只有當按鍵釋放之後,才返回一次有效鍵值,即是按鍵釋放後,才執行相應的函式
        switch(g_uiKeyRelease)
        {
            case 0xFE:
                         //P0.0按鍵程式
                         LED = !LED; 
                         break;

            case 0xFD:
                        //P0.1按鍵程式 
                        //...
                        break;

            case 0xFB:
                        //P0.2按鍵程式 
                        //...
                        break;

            case 0xF7:
                        break;

            case 0xEF:
                        break;

            case 0xDF:
                        break;

            case 0xBF:
                        break;

            case 0xEF:
                        break;      

            case 0xFF:
                        //沒有按鍵程式
                        //...
                        break;  
        }

        //第三種:KeyContinue的使用
        //1)單次按鍵(非長按),返回一次有效值。
        //2)長按,返回多次相同有效值
        switch(g_uiKeyContinue)
        {
            case 0xFE:
                         //P0.0按鍵程式
                         LED = !LED; 
                         break;

            case 0xFD:
                        //P0.1按鍵程式 
                        //...
                        break;

            case 0xFB:
                        //P0.2按鍵程式 
                        //...
                        break;

            case 0xF7:
                        break;

            case 0xEF:
                        break;

            case 0xDF:
                        break;

            case 0xBF:
                        break;

            case 0xEF:
                        break;      

            case 0xFF:
                        //沒有按鍵程式
                        //...
                        break;  
        }
    }

}


void timer0(void) interrupt 1   //用的是定時器0, 這個“interrupt 1”中的“1”代表1號中斷即是定時器0中斷。如果是“0”就是外部中斷0;“2“=外部中斷1;”3“定時器1中斷;”4“=序列口中斷
{
    TH0 = (65535 - 10000)/256;
    TL0 = (65535 - 10000)%256; //定時器0的方式1,得在中斷程式中重複初值。
    time_10ms_ok = 1;  //定時10MS 的標誌
}

Pillar Peng
2015.5.25 - 18:23

log:
感謝網友“Shiow1984”的提醒,有漏掉和不足的地方,我已經修改。
之前寫的文章意在按鍵程式的思路,就沒有將定時掃描程式新增進去,怕影響按鍵程式的理解,若要穩定地運用於程式中,就要使用定時掃描了,這樣按鍵掃描的時間就可以確定,調節好掃描次數後,幾乎就沒有什麼誤觸了。

我也在上面的程式中添加了定時掃面程式。


擴充套件篇:
該方法的矩陣鍵盤程式