按鍵消抖和矩陣鍵盤的掃描進階


在按下按鍵的時候,在閉合和斷開的瞬間有一連串的抖動。

這樣一次按下的動作可能會觸發很多次。

所以,當檢測到按鍵狀態變化時,不是立即去響應動作,而是先等待閉合或斷開穩定后再進行處理。 按鍵消抖可分為硬件消抖和軟件消抖。

消除抖動有軟件和硬件兩種方法。

通常我們用軟件消抖。

最簡單的消抖原理,就是當檢 測到按鍵狀態變化后,先等待一個 10ms 左右的延時時間,讓抖動消失后再進行一次按鍵狀 態檢測,如果與剛才檢測到的狀態相同,就可以確認按鍵已經穩定的動作了。

但是。

當我們的工程龐大的時候,這種的方法就會出現某些問題。

while(1) 這個主循環要不停的掃描各種狀態值是否有發生變化,及時的進行任務調度,如果程序中間 加了這種 delay 延時操作后,很可能某一事件發生了,但是我們程序還在進行 delay 延時操作 中,當這個事件發生完了,程序還在 delay 操作中,當我們 delay 完事再去檢查的時候,已經 晚了,已經檢測不到那個事件了。

就出現了按鍵消抖的優化:

啟用定時器中斷,每 2ms 進一次中斷,掃描 一次按鍵狀態並且存儲起來,連續掃描 8 次后,看看這連續 8 次的按鍵狀態是否是一致的。 8 次按鍵的時間大概是 16ms,這 16ms 內如果按鍵狀態一直保持一致,那就可以確定現在按 鍵處於穩定的階段,而非處於抖動的階段。

這樣就會避免delay占用單片機程序執行時間。

而是變成了按鍵狀態判定,而不是按鍵過程判定。

這只是按鍵消抖算法之一。

 

下面的代碼是按鍵消抖和識別的模塊化代碼:

 

定義部分:

uint8 code KeyCodeMap[4] = { //4位獨立按鍵到標注按鍵的映射表
    0x0d,//回車鍵
    0x26,//上鍵
    0x28,//下鍵
    0x1b //ESC鍵
};

uint8 pdata KeySta[4] = {    //4位獨立按鍵當前狀態
  1, 1, 1, 1
};
code KeyCodeMap是按鍵對應的功能值

 

KeyDriver():
void KeyDriver()
{
    uint8 i;
    static uint8 pdata backup[4] = {    //4位獨立按鍵備份值
          1, 1, 1, 1
    };
    for (i=0; i<4; i++)//循環檢測4個獨立按鍵
    {
        if (backup[i] != KeySta[i])//檢測按鍵
        {
            if(backup[i] != 0) //如果按鍵按下
            {
                KeyAction(KeyCodeMap[i]); //調用按鍵動作函數
            }
            backup[i] =    KeySta[i];//刷新備份值
        }
    }    
}

 

KeyScan():
/* 按鍵掃描函數,需在定時中斷中調用,間隔4ms*/
void KeyScan()
{
    uint8 i;
    static uint8 flag = 0; //消抖計數
    static uint8 keybuf[4] = { //4位獨立按鍵掃描緩沖區
        0xff, 0xff, 0xff, 0xff
    };

    //將4個獨立按鍵值移入緩沖區
    keybuf[0] = (keybuf[0] << 1) | KEY_S2;
    keybuf[1] = (keybuf[1] << 1) | KEY_S3;
    keybuf[2] = (keybuf[2] << 1) | KEY_S4;
    keybuf[3] = (keybuf[3] << 1) | KEY_S5;
    
    flag++;    //間隔5ms掃描一次
    if(flag == 4)//4次就是20ms 完成消抖
    {
        flag = 0;//掃描次數清零
        for (i=0; i<4; i++)  //讀取4個獨立的值
        {
            if ((keybuf[i] & 0x0f) == 0x00)
                {
                    KeySta[i] = 0;//如果4次掃描的值都為0,即按下狀態
                }
                else if    ((keybuf[i] & 0x0f) == 0x0f)
                {
                    KeySta[i] = 1;//如果4次掃描的值都為1,即彈起狀態    
                }
        }    
    }        
}

 

中斷設置:

void Init_Timer0() {  //定時器中斷0
    TMOD = 0x01;
    TH0 = 0xee;
    TL0 = 0x00;    //5ms定時
    ET0 = 1;
    TR0 = 1;
}

void Timer0() interrupt 1  //中斷服務函數
{
    TH0 = 0xee;
    TL0 = 0x00;
    KeyScan();
}

 

主函數:

void main()
{
    EA = 1;
    Init_Timer0();while(1) {
KeyDriver(); } }

 

 

下面是矩陣按鍵的掃描:

 

通常我們會先把列線拉高把行線拉低,然后等待按鍵按下判斷那一列被拉低,用switch語句記錄key值。

再把行線拉高把列線拉低,等待行線的值變換,然后記錄key值,兩個key值相加,得到最后的key值。

 

現在,我們每次讓矩 陣按鍵的一個 KeyOut 輸出低電平,其它三個輸出高電平,判斷當前所有 KeyIn 的狀態,下 次中斷時再讓下一個 KeyOut 輸出低電平,其它三個輸出高電平,再次判斷所有 KeyIn,通過 快速的中斷不停的循環進行判斷,就可以最終確定哪個按鍵按下了

 

用 1ms 中斷判斷 4 次采樣值,這樣消抖時間還是 16ms(1*4*4)

 

 

下面是兩種矩陣按鍵的硬件實現形式,他們的實現代碼是通用的。

 

 

 

 

 

 

 實現代碼:

代碼的數碼管為74Hc573驅動。

keyout和keyin的引腳參照圖1修改。

#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};
uchar KeySta[4][4] = { //全部矩陣按鍵的當前狀態
    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
uchar keyvalue = 0;

sbit DU = P2^6;
sbit WE = P2^7; 
sbit KEY_OUT_1 = P3^0;
sbit KEY_OUT_2 = P3^1;
sbit KEY_OUT_3 = P3^2;
sbit KEY_OUT_4 = P3^3;
sbit KEY_IN_1 = P3^4;
sbit KEY_IN_2 = P3^5;
sbit KEY_IN_3 = P3^6;
sbit KEY_IN_4 = P3^7;

/*定時器中斷0初始化函數*/
void timer0Init()
{
    EA = 1;
    TMOD = 0x01; //設置 T0 為模式 1
    TH0 = 0xFC; //為 T0 賦初值 0xFC67,定時 1ms
    TL0 = 0x66;
    ET0 = 1; //使能 T0 中斷
    TR0 = 1; //啟動 T0
}

/*三位數碼管顯示函數*/
void display(uchar i)
{
    static uchar wei;         
    P0 = 0XFF;
    WE = 1;
    P0 = SMGwei[wei];
    WE = 0;
    switch(wei)
    {
        case 0: DU = 1; P0 = SMGduan[i / 100]; DU = 0; break;
        case 1: DU = 1; P0 = SMGduan[i % 100 / 10]; DU = 0; break;    
        case 2: DU = 1; P0 = SMGduan[i % 10]; DU = 0; break;        
    }
    wei++;
    if(wei == 3) {
        wei = 0;
    }
}

void main()
{
    uchar i,j;
    uchar backup[4][4] = { //按鍵值備份,保存前一次的值
        {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
    };
    timer0Init();
    while(1){//循環檢測 4*4 的矩陣按鍵
        for (i=0; i<4; i++){
            for (j=0; j<4; j++){
                if (backup[i][j] != KeySta[i][j]){//檢測按鍵動作
                    if (backup[i][j] != 0){ //按鍵按下時執行動作
                        keyvalue = i * 4 + j;
                    }
                    backup[i][j] = KeySta[i][j]; //更新前一次的備份值
                }
            }
        }
    }
}

/*定時器中斷0服務函數*/
void timer0() interrupt 1
{
    uchar m;
    static uchar keyout = 0; //矩陣按鍵掃描輸出索引
    static uchar flags = 0;
    static uchar keybuf[4][4] = { //矩陣按鍵掃描緩沖區
        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
    };
    
    TH0 = 0xFC; //重新加載初值1ms
    TL0 = 0x66;
    
    /*數碼管*/
    flags++;
    if(flags == 5) {    //5ms
        flags = 0;
        display(keyvalue);    //數碼管動態掃描
    }
    
    /*消抖並更新按鍵狀態*/
    //將一行的 4 個按鍵值移入緩沖區
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按鍵狀態
    for (m = 0; m < 4; m++){ //每行 4 個按鍵,所以循環 4 次
        if ((keybuf[keyout][m] & 0x0F) == 0x00){    //連續 4 次掃描值為 0,即 4*4ms 內都是按下狀態時,可認為按鍵已穩定的按下
            KeySta[keyout][m] = 0;
        }
        else if ((keybuf[keyout][m] & 0x0F) == 0x0F){    //連續 4 次掃描值為 1,即 4*4ms 內都是彈起狀態時,可認為按鍵已穩定的彈起
            KeySta[keyout][m] = 1;
        }
    }
    
    /*進行矩陣按鍵掃描*/
    //執行下一次的掃描輸出
    keyout++; //輸出索引遞增
    keyout = keyout & 0x03; //索引值加到 4 即歸零
    switch (keyout){ //根據索引,釋放當前輸出引腳,拉低下次的輸出引腳
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
 }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM