在线看毛片网站电影-亚洲国产欧美日韩精品一区二区三区,国产欧美乱夫不卡无乱码,国产精品欧美久久久天天影视,精品一区二区三区视频在线观看,亚洲国产精品人成乱码天天看,日韩久久久一区,91精品国产91免费

<menu id="6qfwx"><li id="6qfwx"></li></menu>
    1. <menu id="6qfwx"><dl id="6qfwx"></dl></menu>

      <label id="6qfwx"><ol id="6qfwx"></ol></label><menu id="6qfwx"></menu><object id="6qfwx"><strike id="6qfwx"><noscript id="6qfwx"></noscript></strike></object>
        1. <center id="6qfwx"><dl id="6qfwx"></dl></center>

            新聞中心

            EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 經(jīng)典按鍵掃描程序

            經(jīng)典按鍵掃描程序

            作者: 時(shí)間:2016-11-21 來源:網(wǎng)絡(luò) 收藏
            核心算法:

            unsigned char Trg;
            unsigned char Cont;
            void KeyRead( void )
            {
            unsigned char ReadData = PINB^0xff; // 1
            Trg = ReadData & (ReadData ^ Cont); // 2
            Cont = ReadData; // 3
            }

            本文引用地址:http://www.biyoush.com/article/201611/319381.htm

            完了。有沒有一種不可思議的感覺?當(dāng)然,沒有想懂之前會(huì)那樣,想懂之后就會(huì)驚嘆于這算法的精妙??!
            下面是程序解釋:
            Trg(triger)代表的是觸發(fā),Cont(continue)代表的是連續(xù)按下。
            1:讀PORTB的端口數(shù)據(jù),取反,然后送到ReadData 臨時(shí)變量里面保存起來。
            2:算法1,用來計(jì)算觸發(fā)變量的。一個(gè)位與操作,一個(gè)異或操作,我想學(xué)過C語言都應(yīng)該懂吧?Trg為全局變量,其它程序可以直接引用。
            3:算法2,用來計(jì)算連續(xù)變量。

            看到這里,有種“知其然,不知其所以然”的感覺吧?代碼很簡單,但是它到底是怎么樣實(shí)現(xiàn)我們的目的的呢?好,下面就讓我們繞開云霧看青天吧。
            我們最常用的按鍵接法如下:AVR是有內(nèi)部上拉功能的,但是為了說明問題,我是特意用外部上拉電阻。那么,按鍵沒有按下的時(shí)候,讀端口數(shù)據(jù)為1,如果按鍵按下,那么端口讀到0。下面就看看具體幾種情況之下,這算法是怎么一回事。

            (1)沒有按鍵的時(shí)候
            端口為0xff,ReadData讀端口并且取反,很顯然,就是 0x00 了。
            Trg = ReadData & (ReadData ^ Cont); (初始狀態(tài)下,Cont也是為0的)很簡單的數(shù)學(xué)計(jì)算,因?yàn)镽eadData為0,則它和任何數(shù)“相與”,結(jié)果也是為0的。
            Cont = ReadData; 保存Cont 其實(shí)就是等于ReadData,為0;
            結(jié)果就是:
            ReadData = 0;
            Trg = 0;
            Cont = 0;

            (2)第一次PB0按下的情況
            端口數(shù)據(jù)為0xfe,ReadData讀端口并且取反,很顯然,就是 0x01 了。
            Trg = ReadData & (ReadData ^ Cont); 因?yàn)檫@是第一次按下,所以Cont是上次的值,應(yīng)為為0。那么這個(gè)式子的值也不難算,也就是 Trg = 0x01 & (0x01^0x00) = 0x01
            Cont = ReadData = 0x01;
            結(jié)果就是:
            ReadData = 0x01;
            Trg = 0x01;Trg只會(huì)在這個(gè)時(shí)候?qū)?yīng)位的值為1,其它時(shí)候都為0
            Cont = 0x01;

            (3)PB0按著不松(長按鍵)的情況
            端口數(shù)據(jù)為0xfe,ReadData讀端口并且取反是 0x01 了。
            Trg = ReadData & (ReadData ^ Cont); 因?yàn)檫@是連續(xù)按下,所以Cont是上次的值,應(yīng)為為0x01。那么這個(gè)式子就變成了 Trg = 0x01 & (0x01^0x01) = 0x00
            Cont = ReadData = 0x01;
            結(jié)果就是:
            ReadData = 0x01;
            Trg = 0x00;
            Cont = 0x01;
            因?yàn)楝F(xiàn)在按鍵是長按著,所以MCU會(huì)每個(gè)一定時(shí)間(20ms左右)不斷的執(zhí)行這個(gè)函數(shù),那么下次執(zhí)行的時(shí)候情況會(huì)是怎么樣的呢?
            ReadData = 0x01;這個(gè)不會(huì)變,因?yàn)榘存I沒有松開
            Trg = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01) = 0 ,只要按鍵沒有松開,這個(gè)Trg值永遠(yuǎn)為 0 !??!
            Cont = 0x01;只要按鍵沒有松開,這個(gè)值永遠(yuǎn)是0x01?。?br />
            (4)按鍵松開的情況
            端口數(shù)據(jù)為0xff,ReadData讀端口并且取反是 0x00 了。
            Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00
            Cont = ReadData = 0x00;
            結(jié)果就是:
            ReadData = 0x00;
            Trg = 0x00;
            Cont = 0x00;

            很顯然,這個(gè)回到了初始狀態(tài),也就是沒有按鍵按下的狀態(tài)。
            總結(jié)一下,不知道想懂了沒有?其實(shí)很簡單,答案如下:
            Trg 表示的就是觸發(fā)的意思,也就是跳變,只要有按鍵按下(電平從1到0的跳變),那么Trg在對(duì)應(yīng)按鍵的位上面會(huì)置一,我們用了PB0則Trg的值為 0x01,類似,如果我們PB7按下的話,Trg 的值就應(yīng)該為 0x80 ,這個(gè)很好理解,還有,最關(guān)鍵的地方,Trg 的值每次按下只會(huì)出現(xiàn)一次,然后立刻被清除,完全不需要人工去干預(yù)。所以按鍵功能處理程序不會(huì)重復(fù)執(zhí)行,省下了一大堆的條件判斷,這個(gè)可是精粹哦?。ont代表的是長按鍵,如果PB0按著不放,那么Cont的值就為 0x01,相對(duì)應(yīng),PB7按著不放,那么Cont的值應(yīng)該為0x80,同樣很好理解。
            如果還是想不懂的話,可以自己演算一下那兩個(gè)表達(dá)式,應(yīng)該不難理解的。因?yàn)橛辛诉@個(gè)支持,那么按鍵處理就變得很爽了,下面看應(yīng)用:

            應(yīng)用一:一次觸發(fā)的按鍵處理
            假設(shè)PB0為蜂鳴器按鍵,按一下,蜂鳴器beep的響一聲。這個(gè)很簡單,但是大家以前是怎么做的呢?對(duì)比一下看誰的方便?
            #define KEY_BEEP 0x01
            void KeyProc(void)
            {
            if (Trg & KEY_BEEP) // 如果按下的是KEY_BEEP
            {
            Beep(); // 執(zhí)行蜂鳴器處理函數(shù)
            }
            }

            怎 么樣?夠和諧不?記得前面解釋說Trg的精粹是什么?精粹就是只會(huì)出現(xiàn)一次。所以你按下按鍵的話,Trg & KEY_BEEP 為“真”的情況只會(huì)出現(xiàn)一次,所以處理起來非常的方便,蜂鳴器也不會(huì)沒事亂叫,hoho~~~或者你會(huì)認(rèn)為這個(gè)處理簡單,沒有問題,我們繼續(xù)。

            應(yīng)用2:長按鍵的處理
            項(xiàng)目中經(jīng)常會(huì)遇到一些要求,例如:一個(gè)按鍵如果短按一下執(zhí)行功能A,如果長按2秒不放的話會(huì)執(zhí)行功能B,又或者是要求3秒按著不放,計(jì)數(shù)連加什么什么的功能,很實(shí)際。不知道大家以前是怎么做的呢?我承認(rèn)以前做的很郁悶。但是看我們這里怎么處理吧,或許你會(huì)大吃一驚,原來程序可以這么簡單,這里舉個(gè)簡單例子,為了只是說明原理,PB0是模式按鍵,短按則切換模式,PB1就是加,如果長按的話則連加(玩過電子表吧?沒錯(cuò),就是那個(gè)?。?br />
            #define KEY_MODE 0x01 // 模式按鍵
            #define KEY_PLUS 0x02 // 加
            void KeyProc(void)
            {
            if (Trg & KEY_MODE) // 如果按下的是KEY_MODE,而且你常按這按鍵也沒有用,
            { //它是不會(huì)執(zhí)行第二次的哦 , 必須先松開再按下
            Mode++; // 模式寄存器加1,當(dāng)然,這里只是演示,你可以執(zhí)行你想
            // 執(zhí)行的任何代碼
            }
            if (Cont & KEY_PLUS) // 如果“加”按鍵被按著不放
            {
            cnt_plus++; // 計(jì)時(shí)
            if (cnt_plus > 100) // 20ms*100 = 2S 如果時(shí)間到
            {
            Func(); // 你需要的執(zhí)行的程序
            }
            }
            }

            不知道各位感覺如何?我覺得還是挺簡單的完成了任務(wù),當(dāng)然,作為演示用代碼。

            應(yīng)用3:點(diǎn)觸型按鍵和開關(guān)型按鍵的混合使用
            點(diǎn)觸形按鍵估計(jì)用的最多,特別是單片機(jī)。開關(guān)型其實(shí)也很常見,例如家里的電燈,那些按下就不松開,除非關(guān)。這是兩種按鍵形式的處理原理也沒啥特別,但是你有沒有想過,如果一個(gè)系統(tǒng)里面這兩種按鍵是怎么處理的?我想起了我以前的處理,分開兩個(gè)非常類似的處理程序,現(xiàn)在看起來真的是笨的不行了,但是也沒有辦法啊,結(jié)構(gòu)決定了程序。不過現(xiàn)在好了,用上面介紹的辦法,很輕松就可以搞定。原理么?可能你也會(huì)想到,對(duì)于點(diǎn)觸開關(guān),按照上面的辦法處理一次按下和長按,對(duì)于開關(guān)型,我們只需要處理Cont就OK了,為什么?很簡單嘛,把它當(dāng)成是一個(gè)長按鍵,這樣就找到了共同點(diǎn),屏蔽了所有的細(xì)節(jié)。程序就不給了,完全就是應(yīng)用2的內(nèi)容,在這里提為了就是說明原理~~

            好了,這個(gè)好用的按鍵處理算是說完了。可能會(huì)有朋友會(huì)問,為什么不說延時(shí)消抖問題?哈哈,被看穿了。果然不能偷懶。下面談?wù)勥@個(gè)問題,順便也就非常簡單的談?wù)勎易约河脮r(shí)間片輪辦法,以及是如何消抖的。延時(shí)消抖的辦法是非常傳統(tǒng),也就是第一次判斷有按鍵,延時(shí)一定的時(shí)間(一般習(xí)慣是20ms)再讀端口,如果兩次讀到的數(shù)據(jù)一樣,說明了是真正的按鍵,而不是抖動(dòng),則進(jìn)入按鍵處理程序。
            當(dāng)然,不要跟我說你delay(20)那樣去死循環(huán)去,真是那樣的話,我衷心的建議你先放下手上所有的東西,好好的去了解一下操作系統(tǒng)的分時(shí)工作原理,大概知道思想就可以,不需要詳細(xì)看原理,否則你永遠(yuǎn)逃不出“菜鳥”這個(gè)圈子。當(dāng)然我也是菜鳥。我的意思是,真正的單片機(jī)入門,是從學(xué)會(huì)處理多任務(wù)開始的,這個(gè)也是學(xué)校程序跟公司程序的最大差別。當(dāng)然,本文不是專門說這個(gè)的,所以也不獻(xiàn)丑了。

            我的主程序架構(gòu)是這樣的:
            volatile unsigned char Intrcnt;
            void InterruptHandle() // 中斷服務(wù)程序
            {
            Intrcnt++; // 1ms 中斷1次,可變
            }
            void main(void)
            {
            SysInit();
            while(1) // 每20ms 執(zhí)行一次大循環(huán)
            {
            KeyRead(); // 將每個(gè)子程序都掃描一遍
            KeyProc();
            Func1();
            Funt2();


            while(1)
            {
            if (Intrcnt>20) // 一直在等,直到20ms時(shí)間到
            {
            Intrcnt="0";
            break; // 返回主循環(huán)
            }
            }
            }
            }

            貌似扯遠(yuǎn)了,回到我們剛才的問題,也就是怎么做按鍵消抖處理。我們將讀按鍵的程序放在了主循環(huán),也就是說,每20ms我們會(huì)執(zhí)行一次KeyRead()函數(shù)來得到新的Trg 和 Cont 值。好了,下面是我的消抖部分,很簡單,基本架構(gòu)如上,我自己比較喜歡的,一直在用。當(dāng)然,和這個(gè)配合,每個(gè)子程序必須執(zhí)行時(shí)間不長,更加不能死循環(huán),一般采用有限狀態(tài)機(jī)的辦法來實(shí)現(xiàn),具體參考其它資料咯。
            懂得基本原理之后,至于怎么用就大家慢慢思考了,我想也難不到聰明的工程師們。例如還有一些處理,怎么判斷按鍵釋放?很簡單,Trg 和Cont都為0 則肯定已經(jīng)釋放了。在這個(gè)基礎(chǔ)上再增加一個(gè)按鍵釋放檢測功能,程序如下:


            volatile unsigned char Trg;
            volatile unsigned char Cont;
            volatile unsigned char Release;
            // 再增加新功能!
            void KeyRead( void )
            {
            unsigned char ReadData = PINB^0xff; // 1 讀鍵值
            Trg = ReadData & (ReadData ^ Cont); // 2 得到按下觸發(fā)值
            Release= (ReadData ^ Trg ^ Cont); // 3 得到釋放觸發(fā)值
            Cont = ReadData; // 4 得到所有未釋放的鍵值
            }



            關(guān)鍵詞: 按鍵掃描程

            評(píng)論


            技術(shù)專區(qū)

            關(guān)閉