在按下按鍵的時候,在閉合和斷開的瞬間有一連串的抖動。
這樣一次按下的動作可能會觸發很多次。
所以,當檢測到按鍵狀態變化時,不是立即去響應動作,而是先等待閉合或斷開穩定后再進行處理。 按鍵消抖可分為硬件消抖和軟件消抖。
消除抖動有軟件和硬件兩種方法。
通常我們用軟件消抖。
最簡單的消抖原理,就是當檢 測到按鍵狀態變化后,先等待一個 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; } }