久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 1943|回復: 0
打印 上一主題 下一主題
收起左側

新型按鍵掃描程序分享

[復制鏈接]
跳轉到指定樓層
樓主
ID:235061 發表于 2017-11-7 11:34 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
新型的按鍵掃描程序
不過我在網上游逛了很久,也看過不少源程序了,沒有發現這種按鍵處理辦法的蹤跡,所以,
我將他共享出來,和廣大同僚們共勉。我非常堅信這種按鍵處理辦法的便捷和高效,你可以
移植到任何一種嵌入式處理器上面,因為 C 語言強大的可移植性。
同時,這里面用到了一些分層的思想,在單片機當中也是相當有用的,也是本文的另外一個
重點。
對于老鳥,我建議直接看那兩個表達式,然后自己想想就會懂的了,也不需要聽我后面的自
吹自擂了,我可沒有班門弄斧的意思,hoho~~但是對于新手,我建議將全文看完。因為這
是實際項目中總結出來的經驗,學校里面學不到的東西。
以下假設你懂 C 語言,因為純粹的 C 語言描述,所以和處理器平臺無關,你可以在 MCS-51,
AVR,PIC,甚至是 ARM 平臺上面測試這個程序性能。當然,我自己也是在多個項目用過,
效果非常好的。
好了,工程人員的習慣,廢話就應該少說,開始吧。以下我以 AVR 的 MEGA8 作為平臺講
解,沒有其它原因,因為我手頭上只有 AVR 的板子而已沒有 51 的。用 51 也可以,只是芯
片初始化部分不同,還有寄存器名字不同而已。
核心算法:
unsigned char Trg;
unsigned char Cont;
void KeyRead( void )
{
unsigned char ReadData = PINB^0xff;
// 1
Trg = ReadData & (ReadData ^ Cont); // 2
Cont = ReadData; // 3

}
完了。有沒有一種不可思議的感覺?當然,沒有想懂之前會那樣,想懂之后就會驚嘆于這算
法的精妙!!
下面是程序解釋:
Trg(triger) 代表的是觸發,Cont(continue)代表的是連續按下。
1:讀 PORTB 的端口數據,取反,然后送到 ReadData 臨時變量里面保存起來。
2:算法 1,用來計算觸發變量的。一個位與操作,一個異或操作,我想學過 C 語言都應該
懂吧?Trg 為全局變量,其它程序可以直接引用。
3:算法 2,用來計算連續變量。
看到這里,有種“知其然,不知其所以然”的感覺吧?代碼很簡單,但是它到底是怎么樣實現
我們的目的的呢?好,下面就讓我們繞開云霧看青天吧。
我們最常用的按鍵接法如下:AVR 是有內部上拉功能的,但是為了說明問題,我是特意用
外部上拉電阻。那么,按鍵沒有按下的時候,讀端口數據為 1,如果按鍵按下,那么端口讀
到 0。下面就看看具體幾種情況之下,這算法是怎么一回事。
(1) 沒有按鍵的時候
端口為 0xff,ReadData 讀端口并且取反,很顯然,就是 0x00 了。
Trg = ReadData & (ReadData ^ Cont); (初始狀態下,Cont 也是為 0 的)很簡單的數學計算,
因為 ReadData 為 0,則它和任何數“相與”,結果也是為 0 的。
Cont = ReadData; 保存 Cont 其實就是等于 ReadData,為 0;
結果就是:
ReadData = 0;
Trg = 0;
Cont = 0;
(2) 第一次 PB0 按下的情況
端口數據為 0xfe,ReadData 讀端口并且取反,很顯然,就是 0x01 了。
Trg = ReadData & (ReadData ^ Cont); 因為這是第一次按下,所以 Cont 是上次的值,應為為 0。
那么這個式子的值也不難算,也就是 Trg = 0x01 & (0x01^0x00) = 0x01
Cont = ReadData = 0x01;
結果就是:
ReadData = 0x01;
Trg = 0x01;Trg 只會在這個時候對應位的值為 1,其它時候都為 0
Cont = 0x01;
(3) PB0 按著不松(長按鍵)的情況
端口數據為 0xfe,ReadData 讀端口并且取反是 0x01 了。
Trg = ReadData & (ReadData ^ Cont); 因為這是連續按下,所以Cont 是上次的值,應為為0x01。
那么這個式子就變成了 Trg = 0x01 & (0x01^0x01) = 0x00
Cont = ReadData = 0x01;
結果就是:
ReadData = 0x01;
Trg = 0x00;
Cont = 0x01;
因為現在按鍵是長按著,所以 MCU 會每個一定時間(20ms 左右)不斷的執行這個函數,
那么下次執行的時候情況會是怎么樣的呢?
ReadData = 0x01;這個不會變,因為按鍵沒有松開
Trg = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01) = 0 ,只要按鍵沒有松開,這個
Trg 值永遠為 0 !!!
Cont = 0x01;只要按鍵沒有松開,這個值永遠是 0x01!!
(4) 按鍵松開的情況
端口數據為 0xff,ReadData 讀端口并且取反是 0x00 了。
Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00
Cont = ReadData = 0x00;
結果就是:
ReadData = 0x00;
Trg = 0x00;
Cont = 0x00;
很顯然,這個回到了初始狀態,也就是沒有按鍵按下的狀態。
總結一下,不知道想懂了沒有?其實很簡單,答案如下:
Trg 表示的就是觸發的意思,也就是跳變,只要有按鍵按下(電平從 1 到 0 的跳變),那么
Trg 在對應按鍵的位上面會置一,我們用了 PB0 則 Trg 的值為 0x01,類似,如果我們 PB7
按下的話,Trg 的值就應該為 0x80 ,這個很好理解,還有,最關鍵的地方,Trg 的值每次按
下只會出現一次,然后立刻被清除,完全不需要人工去干預。所以按鍵功能處理程序不會重
復執行,省下了一大堆的條件判斷,這個可是精粹哦!!Cont 代表的是長按鍵,如果 PB0 按
著不放,那么 Cont 的值就為 0x01,相對應,PB7 按著不放,那么 Cont 的值應該為 0x80,
同樣很好理解。
如果還是想不懂的話,可以自己演算一下那兩個表達式,應該不難理解的。
因為有了這個支持,那么按鍵處理就變得很爽了,下面看應用:
應用一:一次觸發的按鍵處理
假設 PB0 為蜂鳴器按鍵,按一下,蜂鳴器 beep 的響一聲。這個很簡單,但是大家以前是怎
么做的呢?對比一下看誰的方便?
#define KEY_BEEP 0x01
void KeyProc(void)
{
if (Trg & KEY_BEEP) // 如果按下的是 KEY_BEEP
{
Beep(); // 執行蜂鳴器處理函數
}
}
怎么樣?夠和諧不?記得前面解釋說 Trg 的精粹是什么?精粹就是只會出現一次。所以你按
下按鍵的話,Trg & KEY_BEEP 為“真”的情況只會出現一次,所以處理起來非常的方便,蜂
鳴器也不會沒事亂叫,hoho~~~
或者你會認為這個處理簡單,沒有問題,我們繼續。
應用 2:長按鍵的處理
項目中經常會遇到一些要求,例如:一個按鍵如果短按一下執行功能 A,如果長按 2 秒不放
的話會執行功能 B,又或者是要求 3 秒按著不放,計數連加什么什么的功能,很實際。不知
道大家以前是怎么做的呢?我承認以前做的很郁悶。
但是看我們這里怎么處理吧,或許你會大吃一驚,原來程序可以這么簡單
這里具個簡單例子,為了只是說明原理,PB0 是模式按鍵,短按則切換模式,PB1 就是加,
如果長按的話則連加(玩過電子表吧?沒錯,就是那個!)
#define KEY_MODE 0x01 // 模式按鍵
#define KEY_PLUS 0x02 // 加
void KeyProc(void)
{
if (Trg & KEY_MODE) // 如果按下的是 KEY_MODE,而且你常按這按鍵也沒有用,
{ //它是不會執行第二次的哦 , 必須先松開再按下
Mode++; // 模式寄存器加 1,當然,這里只是演示,你可以執行你想
// 執行的任何代碼
}
if (Cont & KEY_PLUS) // 如果“加”按鍵被按著不放
{
cnt_plus++; // 計時
if (cnt_plus > 100) // 20ms*100 = 2S 如果時間到
{
Func(); // 你需要的執行的程序
}
}
}
不知道各位感覺如何?我覺得還是挺簡單的完成了任務,當然,作為演示用代碼。
應用 3:點觸型按鍵和開關型按鍵的混合使用
點觸形按鍵估計用的最多,特別是單片機。開關型其實也很常見,例如家里的電燈,那些按
下就不松開,除非關。這是兩種按鍵形式的處理原理也沒啥特別,但是你有沒有想過,如果
一個系統里面這兩種按鍵是怎么處理的?我想起了我以前的處理,分開兩個非常類似的處理
程序,現在看起來真的是笨的不行了,但是也沒有辦法啊,結構決定了程序。不過現在好了,
用上面介紹的辦法,很輕松就可以搞定。
原理么?可能你也會想到,對于點觸開關,按照上面的辦法處理一次按下和長按,對于開關
型,我們只需要處理 Cont 就 OK 了,為什么?很簡單嘛,把它當成是一個長按鍵,這樣就
找到了共同點,屏蔽了所有的細節。程序就不給了,完全就是應用 2 的內容,在這里提為了
就是說明原理~~
好了,這個好用的按鍵處理算是說完了。可能會有朋友會問,為什么不說延時消抖問題?哈
哈,被看穿了。果然不能偷懶。下面談談這個問題,順便也就非常簡單的談談我自己用時間
片輪辦法,以及是如何消抖的。
延時消抖的辦法是非常傳統,也就是 第一次判斷有按鍵,延時一定的時間(一般習慣是 20ms)
再讀端口,如果兩次讀到的數據一樣,說明了是真正的按鍵,而不是抖動,則進入按鍵處理
程序。
當然,不要跟我說你 delay(20)那樣去死循環去,真是那樣的話,我衷心的建議你先放下
手上所有的東西,好好的去了解一下操作系統的分時工作原理,大概知道思想就可以,不需
要詳細看原理,否則你永遠逃不出“菜鳥”這個圈子。當然我也是菜鳥。我的意思是,真正的
單片機入門,是從學會處理多任務開始的,這個也是學校程序跟公司程序的最大差別。當然,
本文不是專門說這個的,所以也不獻丑了。
我的主程序架構是這樣的:
volatile unsigned char Intrcnt;
void InterruptHandle() // 中斷服務程序
{
Intrcnt++; // 1ms 中斷 1 次,可變
}
void main(void)
{
SysInit();
while(1) // 每 20ms 執行一次大循環
{
KeyRead(); // 將每個子程序都掃描一遍
KeyProc();
Func1();
Funt2();


while(1)
{
if (Intrcnt>20) // 一直在等,直到 20ms 時間到
{
Intrcnt="0";
break; // 返回主循環
}
}
}
}
貌似扯遠了,回到我們剛才的問題,也就是怎么做按鍵消抖處理。我們將讀按鍵的程序放在
了主循環,也就是說,每 20ms 我們會執行一次 KeyRead()函數來得到新的 Trg 和 Cont 值。
好了,下面是我的消抖部分:很簡單
基本架構如上,我自己比較喜歡的,一直在用。當然,和這個配合,每個子程序必須執行時
間不長,更加不能死循環,一般采用有限狀態機的辦法來實現,具體參考其它資料咯。
懂得基本原理之后,至于怎么用就大家慢慢思考了,我想也難不到聰明的工程師們。例如還
有一些處理,
怎么判斷按鍵釋放?很簡單,Trg 和 Cont 都為 0 則肯定已經釋放了。  

評分

參與人數 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏2 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 黄a网 | 国产视频线观看永久免费 | 三级成人片 | 国产高清在线观看 | 欧美日韩在线一区二区三区 | 日韩欧美在线不卡 | 日本久久精品 | 欧美在线不卡 | 中文字幕视频一区 | 蜜桃视频在线观看www社区 | 国产精品永久久久久久久www | 欧美9999| 激情小说综合网 | 久久久久久免费毛片精品 | 欧美在线一区二区三区 | 欧美一级在线免费观看 | 成人午夜在线视频 | 亚洲欧洲一区 | 欧美一二三 | 亚洲国产视频一区二区 | 精品久久av | 久久久久久久久久久久91 | 国产精品网址 | 在线观看免费av网 | 一区二区国产精品 | 欧美一区二区三区日韩 | 国产一区二区在线播放 | 成人精品一区二区三区中文字幕 | 久久久久国产一区二区三区 | 一区二区在线不卡 | 亚洲一区二区三区免费视频 | 国产一区二区三区www | 国产黄色大片网站 | 在线资源视频 | 成人av免费 | 成人亚洲精品久久久久软件 | 欧美日韩国产高清 | 亚洲人在线观看视频 | 日韩精品成人 | 天天爽一爽 | 亚洲精品亚洲人成人网 |