一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

晨輝教你輕松學(xué)51--------按鍵篇

 ylw527 2011-01-26
晨輝教你輕松學(xué)51--------按鍵篇
 
    對于一個由單片機(jī)為核心構(gòu)成的系統(tǒng)而言。輸入通道是相當(dāng)重要的??梢钥吹綆缀趺恳粯踊趩纹瑱C(jī)的產(chǎn)品都有人機(jī)交互的部分。如各種儀器設(shè)備上的各種按鈕和開關(guān),以及我們手機(jī)上的鍵盤,MP3上的按鍵等等。最常見的輸入部分,莫非就是按鍵了。對于大多數(shù)初學(xué)者而言,編寫一個好的按鍵程序是一件頗為頭疼的事情。于是乎在網(wǎng)上亂搜一氣,程序倒是找到了不少,但是看了半天依然是不明白。或者在某某論壇上面發(fā)帖“跪求XX按鍵程序,大蝦幫忙……”如果你偶然間進(jìn)了這個論壇,又偶然看到了這個帖子,而且恰好你對按鍵程序的寫法也不是很清楚,那么我希望你能夠靜靜的看完這個帖子。如果你覺得對你很有幫助,那么我希望你能夠在以后的日子中能夠堅(jiān)持到這個論壇來,一起交流學(xué)習(xí),分享自己學(xué)習(xí)過程中的喜悅或者一起探討棘手的問題,這是我寫這個帖子的最大的初衷了。OK,不能再說了,再說就變成水帖了。那么我們開始吧。
按鍵的種類很多。不過原理基本相似。下面我們以一種輕觸開關(guān)為例講解按鍵程序的寫法。

這種輕觸開關(guān)大家不陌生吧^_^

    一般情況下,按鍵與單片機(jī)的連接如下面這幅圖所示。
  

(圖中電阻值一般去4.7k~10k之間,對于內(nèi)部端口有上拉電阻的單片機(jī)則可省略此電阻)
單片機(jī)對于按鍵的按下與否則是通過檢測相應(yīng)引腳上的電平來實(shí)現(xiàn)的。對于上圖而言,當(dāng)P17引腳上面的電平為低時,則表示按鍵已經(jīng)按下。反之,則表明按鍵沒有按下。我們在程序中只要檢測到了P17引腳上面的電平為低了,就可以判斷按鍵按下。呵呵,簡單吧。等會,您先別樂呵,話還沒說完呢。下面我們來看看,當(dāng)按鍵按下時,P17引腳上面的波形是怎么變化的。


上圖是一個理想波形圖,當(dāng)按鍵按下時,P17口的電平馬上被拉低到0V了。當(dāng)然理想的東西都是不現(xiàn)實(shí)的。所以我們還是看看現(xiàn)實(shí)的波形圖吧。


看出什么區(qū)別來了沒。呵呵,只要你不是傻子我相信都能看出其中的區(qū)別。由于按鍵的機(jī)械特性。當(dāng)按鍵閉合時,并不能馬上保存良好的接觸,而是來回彈跳。這個時間很短,我們的手根本感覺不出來。但是對于一秒鐘執(zhí)行百萬條指令的單片機(jī)而言,這個時間是相當(dāng)?shù)拈L了。那么在這段抖動的時間內(nèi),單片機(jī)可能讀到多次高低電平的變化。如果不加任何處理的話,就會認(rèn)為已經(jīng)按下,或者松開很多次了。而事實(shí)上,我們的手一直按在按鍵上,并沒有重復(fù)按動很多次。要想能夠正確的判斷按鍵是否按下就要避開這段抖動的時間。根據(jù)一般按鍵的機(jī)械特點(diǎn),以及按鍵的新舊程度等而言,這段抖動的時間一般在5MS~20MS之間。
看到這里你明白了該如何做了吧。
    看看下面的這個流程圖,你應(yīng)該不陌生吧。

click here to enlarge
這個流程是好多教科書上的做法??上?,誤導(dǎo)了好多人。為什么呢。因?yàn)樗揪蜎]有考慮實(shí)際情況。我們根據(jù)這幅流程圖來寫它的代碼看看。
        unsigned char v_ReadKey_f( void )
    {
        unsigned char KeyPress ;
        if( P17 == 0)
        {
                Delay(20) ;      //延時20MS
                If( P17 == 0)
               {
                         KeyPress = 1 ;
                         While( !P17) ;  //等待釋放
            }
               else
              KeyPress = 0 ;
            }
    }

     這樣一個程序,相信對很多初學(xué)者而言都不陌生。因?yàn)楹枚鄷匣径际沁@樣的一個流程和寫法。可是當(dāng)有一天,我們想做一個數(shù)碼管加按鍵調(diào)整的時鐘,發(fā)現(xiàn)當(dāng)我們按鍵按下去的時候,數(shù)碼管就不亮了。為什么呢。原因就在這個鍵盤掃描函數(shù)。平常沒有按鍵按下還好。一旦有鍵按下,它先是浪費(fèi)了CPU的大部分時間(就是那個什么事情都沒做的延時20MS函數(shù))然后,又霸占CPU( 就是哪個死死等在那里的while(P17);語句)直到按鍵釋放。對于這種情況我們是忍無可忍的,那么就讓我們徹底的拋棄它吧。那么到底按鍵掃描函數(shù)改如何寫呢……..所謂眾里尋她千百度,驀然回首,那人卻在燈火闌珊處。如果我們把CPU延時的那20MS拿出來去做其它事情,那么不就充分利用CPU的時間了嗎。而一般情況下我們只要前沿去抖動就可以了。也就是說了,我們只需在按鍵按下后去抖就可以了,對于按鍵的釋放抖動可以不必要過于關(guān)注。當(dāng)然這主要和應(yīng)用的場合有關(guān)。一個能有效識別按鍵按下并支持連發(fā)功能的按鍵已經(jīng)能夠應(yīng)用到大多數(shù)的場合了。

     下面以四個獨(dú)立按鍵的處理程序?yàn)槔齺碇v解(支持單擊和連發(fā))
    #include"regx52.h"
                     sbit KeyOne = P1^0 ;
    sbit KeyTwo = P1^1 ;
    sbit KeyThree = P1^2 ;
    sbit KeyFour = P1^3 ;
    #define uint16 unsigned int
    #define uint8 unsigned char
    #define NOKEY  0xff
    #define KEY_WOBBLE_TIME 500          //去抖動時間(待定)
    #define KEY_OVER_TIME 15000      //等待進(jìn)入連擊時間(待定),該常數(shù)要比正常 //按鍵時間要長,防止非目的性進(jìn)入連擊模式
        #define KEY_QUICK_TIME 1000   //等待按鍵抬起的連擊時間(待定)
                    void v_KeyInit_f( void )
    {
        KeyOne = 1 ;    //按鍵初始化(相應(yīng)端口寫1)
        KeyTwo = 1 ;
        KeyThree = 1 ;
        KeyFour = 1 ;
    }
    uint8 u8_ReadKey_f(void)
    {
        static uint8 LastKey = NOKEY ;        //保存上一次的鍵值
        static uint16 KeyCount = 0 ;        //按鍵延時計(jì)數(shù)器
        static uint16 KeyOverTime = KEY_OVER_TIME ; //按鍵抬起時間
            uint8 KeyTemp = NOKEY ;            //臨時保存讀到的鍵值
        KeyTemp = P1 & 0x0f ;                //讀鍵值
        if( KeyTemp == 0x0f )
        {
            KeyCount = 0 ;
            KeyOverTime = KEY_OVER_TIME ;
            return NOKEY ;            //無鍵按下返回NOKEY
        }
        else
        {
                    if( KeyTemp == LastKey )    //是否第一次按下
              {
                                               if( ++KeyCount == KEY_WOBBLE_TIME )    //不是第一次按下,則判斷//抖動是否結(jié)束
            {
                return KeyTemp ;                //去抖動結(jié)束,返回鍵值
            }
            else
            {
                           if( KeyCount > KeyOverTime )
                          {
                KeyCount = 0 ;
                KeyOverTime = KEY_QUICK_TIME ;
                          }
                          return NOKEY ;
            }
                     }
                    else    //是第一次按下則保存鍵值,以便下次執(zhí)行此函數(shù)時與讀到的鍵值作比較
              {
            LastKey = KeyTemp ;            //保存第一次讀到的鍵值
            KeyCount = 0 ;                //延時計(jì)數(shù)器清零
            KeyOverTime = KEY_OVER_TIME ;
            return NOKEY ;
                    }
                    }
    }
  
   下面是我測試用的主程序(相關(guān)頭文件未列出,僅僅作測試演示用)
   void main(void)
{
    uint8 KeyValue ;
    int16 Count ;
    v_LcdInit_f() ;
    v_KeyInit_f() ;
    CLS
    LOCATE(3, 1)
    PRINT("Key Test")
    LOCATE(6, 2)
    SHOW_ICON
    while(1)
    {    
        
        KeyValue = u8_ReadKey_f() ;
        if( KeyValue != NOKEY )
        {    LOCATE(1, 2)
            if( KeyValue == 0x0e )Count++ ;
            if( KeyValue == 0x0d )Count-- ;
            if( KeyValue == 0x0b )Count = 0 ;
            if( KeyValue == 0x07 )Count = 0 ;
            HIDE_ICON
            PRINTD(Count, 5)
            LOCATE(6, 2)
        }
        else
        {
           //SHOW_ICON
         }        
    }        
}

每次執(zhí)行讀鍵盤函數(shù)時,只是對一些標(biāo)志進(jìn)行判斷,然后退出。因此能夠充分的利用CPU的資源。同時可以處理連發(fā)按鍵。此按鍵掃描按鍵函數(shù)可以直接放在主函數(shù)中。如果感覺按鍵太過靈敏或者遲鈍則改一下相關(guān)消抖動的宏定義即可。此函數(shù)也可以通過中斷標(biāo)志位進(jìn)行定時的掃描。此時,需要添加一個定時標(biāo)志位,并將相關(guān)消抖動的和連擊時間的宏定義改小即可。然后在主程序類似下面這樣寫即可
                if( KeyTime )       //定時掃描時間到
                {
        KeyValue = u8_ReadKey_f() ;
               }
具體的工作就交給您去完成啦。

看看效果:
按鍵單擊

連發(fā)時候的截圖    

至此,關(guān)于單個按鍵的學(xué)習(xí)就告一段落了,您是否已經(jīng)明白了。如果您還不明白,那么把這個程序好好的看看,并畫下流程圖,分析分析。估計(jì)您就會恍然大悟。關(guān)鍵是思路要轉(zhuǎn)換過來。
下面我們來看看多個按鍵的情況吧
一般情況下,如果多個按鍵每個都直接接在單片機(jī)的I/O上的話會占用很多的I/O資源。比較合理的一種做法是,按照行列接成矩陣的形式。按鍵接在每一個的行列的相交處。這樣對于m行n列的矩陣,可以接的按鍵總數(shù)是m*n。這里我們以常見的4*4矩陣鍵盤來講解矩陣鍵盤的編程。

click here to enlarge

上圖就是矩陣鍵盤的一般接法。

這里我們要介紹一種快速的鍵盤掃描法:線反轉(zhuǎn)法(或者稱為行列翻轉(zhuǎn)法)。具體流程如下。首先,讓單片機(jī)的行全部輸出0,列全部輸出1,讀取列的值(假設(shè)行接P3口的高四位,列接低四位)。即P3= 0x0f ; 此時讀列的值,如果有鍵按下,則相應(yīng)的列讀回來的值應(yīng)該為低。譬如此時讀回來的值為 0x0e ; 即按鍵列的位置已經(jīng)確定。這時反過來,把行作為輸入,列作為輸出,即P0 = 0xf0 ;此時再讀行的值,如果按鍵仍然被按下,則相應(yīng)的行的值應(yīng)該為低,如果此時讀回來的值為0xe0,則確定了行的位置 。說到這里,您應(yīng)該笑了,知道了一個按鍵被按下的行和列的位置,那么就可以肯定確定它的位置了。我們把讀回來的行值和列值進(jìn)行或運(yùn)算。即 0xe0 | 0x 0e 即 0xee。那么0xee就是我們按下的按鍵的鍵值了。怎么樣。只需幾步就可以判斷所有的鍵值,簡單吧。下面再結(jié)合一個例子具體看看。
/******************************************
* 此模塊所需相關(guān)支持庫                      *
******************************************/
#include"regx52.h"
#define uint8 unsigned char
#define uint16 unsigned int

/****************************************
* 與硬件連接相關(guān)的定義及宏定義和操作宏  *
*****************************************/
#define KEYBOARD     P3                             //鍵盤連接到單片機(jī)上的端口位置
#define READ_ROW_ENLABLE    KEYBOARD = 0x0f ;     //讀端口之前先把相應(yīng)口置位(由基本51單片機(jī)特性決定的)
#define READ_COL_ENLABLE    KEYBOARD = 0xf0 ;     // 根據(jù)實(shí)際硬件連接情況修改    

/*****************************************
* 模塊內(nèi)相關(guān)的宏定義及常數(shù)宏             *
******************************************/
#define NOKEY         0xff    //定義無鍵按下時的返回值
#define DELAY_COUNT  2        //消抖時間常數(shù)


/*****************************************
* 此模塊所需的全局或者外部變量           *
*****************************************/
bit bdata StartScan = 0 ;//此變量需放在定時中斷中置位

/*****************************************
* 按鍵掃描函數(shù),按下去后經(jīng)去抖,確定按下 *
* 則返回鍵值0~15;無鍵按下則返回0xff ;   *
* 此函數(shù)需要定時器的支持(去抖....)         *
*****************************************/
uint8 u8_KeyBoardScan_f()
{
    static uint8 DelayCount = 0 ;
    uint8 KeyValueRow = 0 ;
    uint8 KeyValueCol = 0 ;
    uint8 KeyValue = 0 ;
    if( StartScan )         //開始掃描,StartScan在定時中斷中置位
    {
        StartScan = 0 ;     //清除開始掃描標(biāo)志位,避免多次重復(fù)執(zhí)行掃描程序
        //讀入按鍵狀態(tài)前先向相應(yīng)端口寫1(由基本51單片機(jī)硬件結(jié)構(gòu)決定)
                        READ_ROW_ENLABLE  
        if( ( KEYBOARD & 0x0f ) != 0x0f ) //判斷是否有鍵按下
        {
            DelayCount++;
             if( DelayCount <= DELAY_COUNT )    //有鍵按下則判斷延時去抖的時間是否達(dá)到
            {
                return NOKEY ;                
            }
            else                 //消除了抖動
            {
                if( ( KEYBOARD & 0x0f ) != 0x0f )    //再次判斷是否按鍵真的按下
                {
                     DelayCount = 0 ;                 //確定按下后,延時去抖計(jì)時器清0
                     KeyValueRow = KEYBOARD & 0x0f ; //取得行碼
                    //準(zhǔn)備讀列,先向相應(yīng)端口寫1(由基本51單片機(jī)硬件結(jié)構(gòu)決定)
                                                                                          READ_COL_ENLABLE    
                     if ( (KEYBOARD & 0xf0) != 0xf0 ) //反轉(zhuǎn),讀列碼
                     {
                         KeyValueCol = KEYBOARD & 0xf0 ;    //取得列碼
                                                               //合并取得的行碼和列碼,即是相應(yīng)按鍵的鍵值
                                                         switch( KeyValueCol | KeyValueRow)    
                        {
                            case 0x77 : KeyValue = 0 ; break ;
                            case 0xb7 : KeyValue = 1 ; break ;
                            case 0xd7 : KeyValue = 2 ; break ;
                            case 0xe7 : KeyValue = 3 ; break ;
                            case 0x7b : KeyValue = 4 ; break ;
                            case 0xbb : KeyValue = 5 ; break ;
                            case 0xdb : KeyValue = 6 ; break ;
                            case 0xeb : KeyValue = 7 ; break ;
                            case 0x7d : KeyValue = 8 ; break ;
                            case 0xbd : KeyValue = 9 ; break ;
                            case 0xdd : KeyValue = 10 ;break ;
                            case 0xed : KeyValue = 11 ;break ;
                            case 0x7e : KeyValue = 12 ;break ;
                            case 0xbe : KeyValue = 13 ;break ;
                            case 0xde : KeyValue = 14 ;break ;
                            case 0xee : KeyValue = 15 ;break ;
                            default :     return NOKEY ;
                        }
                    
                        return KeyValue ;
                     }
                     else
                     {
                         DelayCount = 0 ;
                         return NOKEY ;
                     }
                 }
                 else
                {
                    DelayCount = 0 ;
                    return NOKEY ;
                }
            }
        }
        else
        {
            DelayCount = 0 ;
            return NOKEY ;
        }
    }    
}


void v_T0_Isr_f( void ) interrupt INTERRUPT_TIMER2_OVERFLOW
{

    StartScan = 1 ;
}

/***************************************************
*模塊調(diào)試                                           *
***************************************************/

//主函數(shù)僅作演示用,主函數(shù)除按鍵掃描外的函數(shù)并沒在這里給出
void v_Init_T2_f( void )
{
    T2CON    = 0x04 ;
    T2MOD     = 0x00 ;
    TH2      = 0xd8 ;
    RCAP2H     = 0xd8 ;
    TL2       = 0xf0 ;
    RCAP2L    = 0xf0 ;
    ET2     = 1 ;
    TR2    = 1 ;
}



void main( void )
{
    uint8 readkey = 0 ;
    v_Init_T2_f( ) ;
    v_LcdInit_f( );
    LOCATE( 1, 1)
    PRINT("4*4KeyBoard Test")
    EA = 1 ;

    
    
       LOCATE( 3, 2)
    while( 1 )
    {    
        
        SHOW_ICON
        readkey =  u8_KeyBoardScan_f()    ;
        if( readkey != NOKEY)
        {    
            PRINTN( readkey , 2)
            
            LOCATE( 3, 2)
            continue ;
        }
        else
        {
            continue ;
        }
    }
}




呵呵,按鍵掃描程序已經(jīng)注釋的很詳細(xì)了。我就不多費(fèi)嘴舌了。如果有不清楚的地方,歡迎跟帖討論。
下面是按鍵測試的截圖




我的自己搭建的實(shí)驗(yàn)板


OK,Enioy it !自此按鍵檢測告一段落。下次如果再講按鍵。將會討論另外一種按鍵的寫法:基于狀態(tài)機(jī)的按鍵程序設(shè)計(jì)。歡迎討論。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    老司机这里只有精品视频| 国产丝袜极品黑色高跟鞋| 老司机这里只有精品视频| 区一区二区三中文字幕| 精品人妻少妇二区三区| 久久老熟女一区二区三区福利| 日韩一区中文免费视频| 五月激情婷婷丁香六月网| 九九热这里只有精品哦| 欧美日韩国内一区二区| 91在线爽的少妇嗷嗷叫| 欧美日韩精品视频在线| 亚洲精品成人综合色在线| 夫妻性生活真人动作视频| 丰满少妇高潮一区二区| 亚洲一区二区精品免费| 2019年国产最新视频| 欧美色婷婷综合狠狠爱| 91天堂免费在线观看| 国产日韩久久精品一区| 婷婷色香五月综合激激情| 久久精品视频就在久久| 自拍偷女厕所拍偷区亚洲综合| 精品国产亚洲av成人一区| 亚洲熟妇中文字幕五十路| 在线免费不卡亚洲国产| 日韩精品视频免费观看| 中文字幕一区久久综合| 九九热这里只有免费精品| 国产一区一一一区麻豆| 国产麻豆一区二区三区在| 日韩人妻少妇一区二区| av在线免费观看在线免费观看| 一区二区福利在线视频| 亚洲一区二区三区日韩91| 国产美女网红精品演绎| 国产成人一区二区三区久久| 日本人妻的诱惑在线观看| 午夜小视频成人免费看| 99热九九在线中文字幕| 韩日黄片在线免费观看|