【說在前面的話】
按鍵作為單片機(jī)的輸入設(shè)備,可以向單片機(jī)輸入數(shù)據(jù)、傳輸命令等,是設(shè)置參數(shù)和控制設(shè)備的常用接口。所以,學(xué)會(huì)按鍵驅(qū)動(dòng)也是初學(xué)者必不可少的能力。說到按鍵驅(qū)動(dòng)程序,大家應(yīng)該也不陌生,而一般的按鍵驅(qū)動(dòng)流程圖如下
這里,可能有人會(huì)問,為什么要延時(shí)10ms啊?
那是因?yàn)榘存I被按下時(shí),不會(huì)像理想的情況非0即1,而是會(huì)有抖動(dòng),如下圖
當(dāng)機(jī)械按鍵被按下或松開時(shí),會(huì)有10ms的抖動(dòng)時(shí)間,所以要延時(shí)10ms來(lái)消去波形抖動(dòng)(* ̄︶ ̄)
知道了這個(gè),一般初學(xué)者編寫的按鍵驅(qū)動(dòng)程序如下:
//延時(shí)1ms
void Delay1ms() { //@12.000MHz
unsigned char i, j;
i = 12;
j = 169;
do{
while (--j);
} while (--i);
}
//ms延時(shí)
void delay_ms(int ms){
char i = 0;
for(i = 0; i < ms; i++){
Delay1ms();
}
}
char get_key(){
//檢測(cè)按鍵是否被按下
if(KEY1 == 0){
//延時(shí)10ms
delay_ms(10);
//再次檢測(cè)按鍵是否被按下
if(KEY1 == 0){
//等待按鍵松開
while(KEY1 == 0){ }
delay_ms(10);
return 1;
}
}
return 0;
}
像這種按鍵驅(qū)動(dòng)程序也很簡(jiǎn)單,作為基礎(chǔ)學(xué)習(xí)和一些簡(jiǎn)單的系統(tǒng)中還可以,但是在很多的產(chǎn)品設(shè)計(jì)中,這種按鍵程序還是有很大的不足和缺陷。因?yàn)樗粌H采用了軟件延時(shí)使單片機(jī)效率降低而且還在那里死等按鍵松開,系統(tǒng)的實(shí)時(shí)性也變得很差。為此,有人提出了 一種基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序 ,很好地解決了上述程序的缺陷。下面我們就簡(jiǎn)單講一下什么是狀態(tài)機(jī)。
【狀態(tài)機(jī)簡(jiǎn)介】
對(duì)于學(xué)電子的同學(xué),首先接觸到的狀態(tài)機(jī)應(yīng)該是在數(shù)字邏輯電路( 簡(jiǎn)稱數(shù)電 )中,狀態(tài)機(jī)的分析方法被應(yīng)用于時(shí)序邏輯電路的設(shè)計(jì)中,其實(shí)狀態(tài)機(jī)的思想對(duì)我們的軟件設(shè)計(jì)也很有用,首先簡(jiǎn)單介紹一下狀態(tài)機(jī),它是由有限的狀態(tài)和相互之間的遷移構(gòu)成的。在任何時(shí)候,只能處于狀態(tài)機(jī)的某一個(gè)狀態(tài),當(dāng)接收到一個(gè)轉(zhuǎn)移事件時(shí),狀態(tài)機(jī)進(jìn)行狀態(tài)的轉(zhuǎn)移。
下面,就以按鍵驅(qū)動(dòng)為例,畫出他的狀態(tài)轉(zhuǎn)移圖,如下
有了狀態(tài)轉(zhuǎn)移圖,那我們就用程序?qū)崿F(xiàn)一下這個(gè)按鍵驅(qū)動(dòng)程序。從圖中我們知道按鍵驅(qū)動(dòng)程序由3個(gè)狀態(tài),剛好可以用C語(yǔ)言的switch case語(yǔ)句來(lái)實(shí)現(xiàn)這3個(gè)狀態(tài),而狀態(tài)間的遷移就可以用if條件判斷語(yǔ)句來(lái)實(shí)現(xiàn)。知道了這個(gè),那我們就動(dòng)手實(shí)現(xiàn)一下。
基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序
首先,打開原理圖,看一下按鍵接到了單片機(jī)的哪個(gè)管腳,如下
我們以按鍵1為例,接到了單片機(jī)的P33腳,當(dāng)按鍵 按下時(shí)為低電平 , 松開為高電平 ,基于此我們的按鍵程序如下:
sbit KEY1 = P3^3;//key1
char get_key(){
//保存按鍵狀態(tài)
static char key_flag = 0;
//軟件延時(shí)計(jì)時(shí)器
static unsigned int s_Counter = 0;
switch(key_flag){
//狀態(tài)0為無(wú)按鍵按下
case 0:
if(KEY1 == 0){
//如果有按鍵按下,轉(zhuǎn)為狀態(tài)1
key_flag = 1;
}
break;
//狀態(tài)1為延時(shí)消抖
case 1:
s_Counter++;
if(s_Counter > 1000){
//延時(shí)10ms,計(jì)時(shí)器清零
s_Counter = 0;
if(KEY1 == 0){
//如果按鍵被按下,轉(zhuǎn)為狀態(tài)2
key_flag = 2;
return 1;
}else{
//如果按鍵未按下,轉(zhuǎn)為狀態(tài)0
key_flag = 0;
}
}
break;
//狀態(tài)2為等待按鍵釋放
case 2:
if(KEY1 == 1){
//如果按鍵松開,轉(zhuǎn)為狀態(tài)0
key_flag = 0;
}
break;
}
return 0;
}
- 注意,每個(gè)case結(jié)束后都有一個(gè)break
- 第18行,s_Counter > 1000相當(dāng)于延時(shí)10ms,當(dāng)然這個(gè)1000是隨便給的值,大家要根據(jù)具體情況設(shè)置此值,如果測(cè)試小于10ms就可以加大此值,我們只是為了說明用s_Counter 可以延時(shí)。
- 第24行,在延時(shí)去抖完成后就返回了1(相當(dāng)于按鍵按下),這樣做的好處就是可以提高按鍵響應(yīng)速度。當(dāng)然也可以在狀態(tài)2按鍵松開后返回1。
基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序我們就簡(jiǎn)單寫完了,相信大家也get到重點(diǎn)了,這個(gè)只是簡(jiǎn)單實(shí)現(xiàn)了按鍵的單擊,當(dāng)然,我們也可以實(shí)現(xiàn)按鍵的雙擊和長(zhǎng)按。
哈哈,在編寫驅(qū)動(dòng)之前,我們先細(xì)化一下需求,首先區(qū)分單擊和長(zhǎng)按,這個(gè)很簡(jiǎn)單,規(guī)定一個(gè)時(shí)間就可以,我們定為1秒鐘。即按下時(shí)間小于1秒為單擊,大于1秒為長(zhǎng)按。
那雙擊怎么辦呢?
我們規(guī)定,當(dāng)?shù)谝淮伟聪鲁掷m(xù)時(shí)間小于500ms內(nèi)松開按鍵,在之后500ms內(nèi)又按下按鍵,此時(shí)為雙擊事件。這里有兩點(diǎn)需要注意,1、第一次按下的時(shí)間不能超過500ms,否則就被判斷為單擊或長(zhǎng)按。2、在第一次按下松開后開始計(jì)時(shí),如果500ms內(nèi)沒有按鍵再次按下則為單擊。按鍵雙擊的原理如下圖所示
按鍵雙擊和長(zhǎng)按的需求我們講完了,接下來(lái)畫出他的狀態(tài)轉(zhuǎn)移圖,如下
哈哈哈,還可以吧,沒那么復(fù)雜。相信大家應(yīng)該能看懂。這里有必要說一下狀態(tài)5,判斷雙擊其實(shí)就是第二次按下延時(shí)10ms消抖,如果確實(shí)按下則為雙擊否則為單擊。好了,看看程序怎么實(shí)現(xiàn)吧,如下
#define DELAY_10ms 500
#define DELAY_500ms 10000
char get_key3(){
static char key_flag = 0;
static unsigned int s_Counter = 0;
switch(key_flag){
case 0://無(wú)按鍵按下
if(KEY1 == 0){
key_flag = 1;
}
break;
case 1://延時(shí)10ms消抖
s_Counter++;
if(s_Counter > DELAY_10ms){
s_Counter = 0;
if(KEY1 == 0){
key_flag = 2;
}else{
key_flag = 0;
}
}
break;
case 2://計(jì)時(shí)500ms,等待按鍵松開
s_Counter++;
if(s_Counter > DELAY_500ms){//500ms內(nèi)按鍵未松開
s_Counter = 0;
key_flag = 6;
}
if(KEY1 == 1){//500ms內(nèi)按鍵松開
s_Counter = 0;
key_flag = 3;
}
break;
case 3://按鍵松開,延時(shí)10ms消抖
s_Counter++;
if(s_Counter > DELAY_10ms){
s_Counter = 0;
key_flag = 4;
}
break;
case 4://等待雙擊
s_Counter++;
if(s_Counter > DELAY_500ms){
s_Counter = 0;
key_flag = 7;//500ms內(nèi)按鍵未按下
}
if(KEY1 == 0){//500ms內(nèi)按鍵被按下
s_Counter = 0;
key_flag = 5;
}
break;
case 5://延時(shí)10ms消抖
s_Counter++;
if(s_Counter > DELAY_10ms){
s_Counter = 0;
if(KEY1 == 0){
key_flag = 8;//等待按鍵松開
return 2;//雙擊
}else{
key_flag = 7;//單擊
}
}
break;
case 6:
s_Counter++;
if(s_Counter > DELAY_500ms){
s_Counter = 0;
key_flag = 8;//等待按鍵松開
return 3;//長(zhǎng)按
}
if(KEY1 == 1){
s_Counter = 0;
key_flag = 7;
}
break;
case 7://單擊
key_flag = 8;
return 1;//單擊
break;
case 8://等待按鍵松開
if(KEY1 == 1){
s_Counter = 0;
key_flag = 0;
}
break;
}
return 0;
}