【按鍵】短按,長按,按鍵釋放,三種模式的按鍵掃描程式(軟體消抖動)
先來說一下這三種模式的意思:
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”的提醒,有漏掉和不足的地方,我已經修改。
之前寫的文章意在按鍵程式的思路,就沒有將定時掃描程式新增進去,怕影響按鍵程式的理解,若要穩定地運用於程式中,就要使用定時掃描了,這樣按鍵掃描的時間就可以確定,調節好掃描次數後,幾乎就沒有什麼誤觸了。
我也在上面的程式中添加了定時掃面程式。
擴充套件篇:
該方法的矩陣鍵盤程式