標題: 單片機基于事件的按鍵處理編程思想(原創) [打印本頁]
作者: zyhlove813 時間: 2021-9-25 09:39
標題: 單片機基于事件的按鍵處理編程思想(原創)
本帖最后由 zyhlove813 于 2021-9-25 10:57 編輯
單片機處理按鍵,網上的思路也是五花八門。入門的,可能是直接判斷端口,老手的,可能是通過鍵值計算;不管是誰學單片機,都逃不了做按鍵處理的程序。我在做項目的過程中,參考一些網上的思路,結合自己的算法,通過項目調試和驗證,終于做出了比較優化和滿意的按鍵處理程序,功能有如下幾個方面:
1、多鍵掃描處理,提高處理速度
2、支持長按處理(單次觸發或一直觸發)
3、支持按下、彈起、按住、松開、長按的事件
4、項目中只需要修改掃描鍵值,然后在各事件中判斷對應鍵值(單鍵或多鍵)
主要編程思路如下:
1、變量的說明
- //長按鍵的時長
- #define longkey_times 2000
- //長按單次模式定義,如果要長按時一直執行,請注釋下一行
- #define LONG_PROCESS_ONCE
- uint8_t KEY_PRESS; //當前按下的鍵值
- uint8_t KEY_NOT_PRESS; //當前未被按的鍵值
- uint8_t KEY_LAST; //上一次的鍵值
- uint8_t KEY_LONG; //長按的鍵值
- uint8_t KEY_DOWN; //按下的鍵值
- uint8_t KEY_UP; //彈起的鍵值
- uint8_t KEY_UP_NL; //彈起的鍵值不帶長按鍵
- uint32_t KEY_TICKS; //按鍵時間,用于長按計時
復制代碼
2、按鍵相關函數說明
- //按鍵處理程序
- void JUDGE_KEY(bool SINGLE_KEY); //鍵值掃描及邏輯處理
- void KEY_LONG_PROCESS(void); //長按事件
- void KEY_PRESS_PROCESS(void); //按住狀態事件
- void KEY_NOT_PRESS_PROCESS(void); //松開狀態事件
- void KEY_DOWN_PROCESS(void); //按下事件
- void KEY_UP_PROCESS(void); //彈起事件
復制代碼3、按鍵掃描及邏輯處理思路
- //bool SINGLE_KEY 防抖開關,True時打開
- void JUDGE_KEY(bool SINGLE_KEY)
- {
- uint8_t TEMP_KEY; //臨時的鍵值緩存
- TEMP_KEY = PIND & 0x0C; //批量掃描IO,并生成鍵值,用戶需結合項目自已修改,PIND
- //此處表示PD0-7的端口,不同單片機不一樣,0x0C只取出
- //PD2 PD3的值
- TEMP_KEY ^= 0x0C; //此處主要是把鍵值取反,如果你的按鍵是低電平觸發的話
- //如果你的按鍵是高電平觸發,則刪除此行,不需要取反
- if(TEMP_KEY > 0) //鍵值大于0,表示有按鍵按著
- {
- delay(10); //防抖延時
- //以下再一次批量掃描鍵值
- KEY_PRESS = PIND & 0x0C;
- KEY_PRESS ^= 0x0C;
- //如果防抖開關有效且兩次鍵值不一致,返回不處理
- if(TEMP_KEY!=KEY_PRESS && SINGLE_KEY)
- {
- return;
- }
- }
- else //無按鍵動作,當前按下的鍵值=0
- {
- KEY_PRESS=0;
- }
- //核心按鍵邏輯判斷
- KEY_DOWN=(KEY_LAST^KEY_PRESS) & KEY_PRESS; //按下的鍵值
- KEY_UP=(KEY_LAST^KEY_PRESS) & KEY_LAST;//彈起的鍵值(包含長按鍵)
- KEY_UP_NL=(~KEY_LONG) & KEY_UP; //彈起的鍵值(不包含長按鍵)
- KEY_NOT_PRESS=~KEY_PRESS; //未按的鍵狀態值
- if(KEY_LONG & KEY_UP)
- {
- KEY_LONG=0;
- }
- if(KEY_PRESS > 0) //當前有按鍵值按下
- {
- if(KEY_LAST & KEY_PRESS) //如果當前的值與上次按下的值有相同的地方
- //表示有鍵一直按著,否則可能只是切換了其他按鍵
- {
- //millis()函數是Arduino的開機時間毫秒計數,其他單片機自己實現
- if(millis() - KEY_TICKS > longkey_times) //按鍵時間大于長按時間
- {
- KEY_LONG =KEY_LAST & KEY_PRESS; //長按鍵值等于一直按住的值
- KEY_LONG_PROCESS(); //長按鍵處理
- #ifdef LONG_PROCESS_ONCE //如果是長按單次處理
- KEY_TICKS=millis(); //更新長按時間標記,避免進入長按判斷
- #endif
- }
- }
- else
- {
- KEY_TICKS=millis(); //切換了其他鍵,更新長按時間標記,避免進入長按判斷
- }
- }
- else //當前無按鍵按下
- {
- KEY_TICKS=millis(); //更新長按時間標記,避免進入長按判斷
- }
- if(KEY_UP > 0) //如果有彈起的按鍵值
- {
- KEY_UP_PROCESS(); //按鍵彈起時處理
- KEY_UP = 0; //復位彈起的鍵值
- }
- if(KEY_DOWN > 0)
- {
- KEY_DOWN_PROCESS(); //按鍵按下時處理
- }
- if(KEY_PRESS > 0)
- {
- KEY_PRESS_PROCESS(); //按鍵按著狀態處理
- }
- if(KEY_NOT_PRESS)
- {
- KEY_NOT_PRESS_PROCESS(); //按鍵彈起狀態處理
- }
- KEY_LAST=KEY_PRESS; //更新上一次的鍵值
- }
復制代碼
4、按鍵邏輯處理算法詳解
4.1首次按下的鍵,先用異或^進行上次掃描鍵值和本次掃描鍵值計算,取得不一樣的鍵位,不一樣的鍵位和本次掃描鍵位相同,則表示首次按下。
如 0000 0010表示上次掃描的鍵,第1位是按下的狀態
0000 0110 表示本次掃描的鍵,第1位和第2位是按下的,
我們要算出第2位是首次按下,則
0000 0010 ^ 0000 0110=0000 0100
0000 0100 & 0000 0110=0000 0100
又如 000 0010表示上次掃描的鍵,第1位是按下的
0000 0100表示本次掃描的鍵,第2位是按下的,第1位已經松開
我們要算出第2位是首次按下,則
0000 0010 ^ 0000 0100=0000 0110
0000 0110 & 0000 0100=0000 0100
(所以與本次掃描的鍵值與,可以得到首次按下的鍵值)
KEY_DOWN=(KEY_LAST^KEY_PRESS) & KEY_PRESS; //按下的鍵值
4.2彈起的鍵值
與按下的原理一樣,不同的是要和上次掃描的鍵值相與
如 0000 0010表示上次掃描的鍵,第1位是按下的狀態
0000 0100 表示本次掃描的鍵,第2位是按下的,
我們要算出第1位是彈起,則
0000 0010 ^ 0000 0100=0000 0110
0000 0110 & 0000 0010=0000 0010
KEY_UP=(KEY_LAST^KEY_PRESS) & KEY_LAST;//彈起的鍵值(包含長按鍵)
4.3長按鍵一般單獨處理,彈起時如果要排除,避免多次觸發事件,需要計算出
不包含長按鍵的鍵值,用如下公式
KEY_UP_NL=(~KEY_LONG) & KEY_UP; //彈起的鍵值(不包含長按鍵)
4.4 長按鍵的計算邏輯,見程序注釋
5、如何使用
5.1 設置好長按的時間
#define longkey_times 2000 //這里表示2秒
5.2 修改掃描鍵值
TEMP_KEY = PIND & 0x0C; //批量掃描IO,并生成鍵值,用戶需結合項目自已修改,PIND
//此處表示PD0-7的端口,不同單片機不一樣,0x0C只取出 PD2 PD3的值
TEMP_KEY ^= 0x0C; //此處主要是把鍵值取反,如果你的按鍵是低電平觸發的話
//還有一處地方也要一起改
KEY_PRESS = PIND & 0x0C;
KEY_PRESS ^= 0x0C;
注意:51或其他單片機中,如果按鍵不在同一序列,比如P01 P03 P14 P16,則可以如下設置
TEMP_KEY = P0 & 0x0A; //取出 P01 P03
TEMP_KEY |=(P1 & 0x50); //取出 P14 P16
TEMP_KEY ^= (0x0A|0x50); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發的話,
//如果你的按鍵是高電平觸發,則刪除此行,不需要取反
//還有一處地方也要一起改
KEY_PRESS = P0 & 0x0A; //取出 P01 P03
KEY_PRESS |=(P1 & 0x50); //取出 P14 P16
KEY_PRESS ^= (0x0A|0x50); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發的話,
//如果你的按鍵是高電平觸發,則刪除此行,不需要取反
為了編程方便,盡量使用同一序列的口,如果不同序列的口,那端口號也要能錯開,如用了P01,就不要用P11了。
這樣的話,才能方便計算,提高掃描效率,如果非要用,只能通過移位處理
如51或其他單片機中,想判斷 P01 P02 P12 P13的鍵
TEMP_KEY = P1 & 0x0C; //取出 P12 P13
TEMP_KEY =TEMP_KEY<<1; //左移1位,避開P12和P02交叉重疊
TEMP_KEY |= (P0 & 0x06); //取出 P01 P02
TEMP_KEY ^= (0x18|0x06); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發的話
//如果你的按鍵是高電平觸發,則刪除此行,不需要取反
這樣鍵值里,0x02表示P01,0x04表示P02,0x08表示P12,0x10表示P13
5.3在單片機循環程序或定時器里,周期性調用掃描程序
void loop()
{
JUDGE_KEY(true);
}
5.4在對應事件里進行其他編程
如:
void KEY_NOT_PRESS_PROCESS() //按鍵彈起狀態處理
{
if(KEY_NOT_PRESS & 0x04)
{
//Serial.println("KEY PD2 is NOT PRESSING");
}
}
void KEY_PRESS_PROCESS() //按鍵按著狀態處理
{
if(KEY_PRESS & 0x04)
{
//Serial.println("KEY PD2 is PRESSING");
}
}
void KEY_LONG_PROCESS() //長按鍵處理
{
if(KEY_LONG & 0x04)
{
Serial.println("KEY PD2 is LONG PRESS");
}
if(KEY_LONG & 0x08)
{
Serial.println("KEY PD3 is LONG PRESS");
}
}
void KEY_DOWN_PROCESS() //按鍵按下時處理
{
if(KEY_DOWN & 0x04)
{
Serial.println("KEY PD2 is DOWN NOW");
}
if(KEY_DOWN & 0x08)
{
Serial.println("KEY PD3 is DOWN NOW");
}
Serial.println("---------------------");
}
void KEY_UP_PROCESS() //按鍵彈起時處理
{
if(KEY_UP_NL & 0x04)
{
Serial.println("KEY PD2 is UP_NL NOW");
}
if(KEY_UP_NL & 0x08)
{
Serial.println("KEY PD3 is UP NOW");
}
Serial.println("---------------------");
}
附上Arduino的測試程序,注意Arduino Uno中PD2表示數字腳2,PD3表示數字腳3
作者: ximao 時間: 2021-9-27 04:11
謝了

好資料,51黑有你更精彩!!!
作者: zyhlove813 時間: 2021-9-27 10:16
有用就好
作者: kavin21513 時間: 2021-10-6 17:53
先收藏一下,以后用得上
作者: 1009214562 時間: 2021-10-12 16:19
好資料,51黑有你更精彩!!!
作者: 窮書生 時間: 2021-10-13 20:27
不錯,值得學習
作者: zyhlove813 時間: 2021-10-25 11:16
一起學習,如果你有好的建議也不妨提出,給大家學習一下
作者: adad2 時間: 2021-11-1 17:00
好資料,51黑有你更精彩!!!
作者: linyin 時間: 2021-11-9 12:47
一起學習,51黑一定輝煌起來
作者: lddy123456 時間: 2021-11-19 08:18
很深奧的內容。
作者: 小送送 時間: 2022-1-12 11:01
剛好,我這個小白去學習學習!
作者: 溫xyz 時間: 2022-1-31 09:28
很好的資料!
作者: qidiao007 時間: 2022-3-11 09:54
這個模塊很方便
作者: chinomango 時間: 2022-3-19 03:39
本帖最后由 chinomango 于 2022-3-19 03:41 編輯
這個適合別的uP和C編譯嗎?最好前面有#if以自動適合不同的C編譯。
看到晚了,上個月還自己寫了一個,不過支持連續按鍵2次,以即時關閉電源;單次則是延遲3分鐘關閉。不知用你這個要如何改動? 先謝過。這個是堵塞式的嗎還是定時中斷掃描鍵盤?
作者: zyhlove813 時間: 2022-3-30 08:10
這個只是把按鍵值用計算的方式來產生按下、彈長、長按等事件動作,連續兩次按鍵,你可以在按下或彈起的事件里,用一個值來判斷按了幾次,再加上一個時長,如果超時或者中間按了其他按鍵,這個值就初始化到初值;
這個程序主要作用是產生事件,至少邏輯處理,要做什么事情,是你要在事件里進行編程。我這個程序,如果 JUDGE_KEY(true)在定時器里中斷調用,就變成是中斷式,如果在Loop循環里調用,就成了堵塞式?茨愕某绦蛐枨
作者: lhtlhtl 時間: 2022-5-19 18:29
收藏一下
作者: mrzhou 時間: 2022-6-23 09:35
非常好的設計思路,如果能提高實時性就更好了
作者: zyhlove813 時間: 2022-6-25 11:37
多個項目應用證明,一般的項目都能應對,如果有擔心的話,也可以把調用放在定時中斷里
作者: y1z2f4 時間: 2022-8-19 22:45
很好的經驗,謝謝分享。
作者: tzs233 時間: 2022-8-20 09:03
按鍵IO那里還是要做的更通用些就好了,我自己的項目應用中因為IO口緊張,是不太可能按鍵分布在連續IO口上的。
作者: zmc419 時間: 2022-9-3 17:52
太好了,也上個51的源碼好了
作者: nanjingcxy2008 時間: 2022-11-2 15:48
樓主寫的很詳細,謝謝分享。
作者: xiaoshongdh009 時間: 2022-11-10 07:54
好資料,51黑有你更精彩!!!
作者: cooltao2008 時間: 2022-12-3 17:28
很不錯的按鍵邏輯處理。收藏了。
作者: qhp777 時間: 2023-5-12 16:47
既然都用事件了,為什么還要延時防抖呢?
作者: likejian 時間: 2023-8-21 23:26
值得慢慢研究,加入到我的代碼上去
作者: zyhlove813 時間: 2024-6-8 08:28
核心思想是把按鍵的IO轉化為數值,然后通過新舊數據的運算,篩選出IO對應的不同狀態值,然后使用時通過判斷是否為需要的IO值。
| 歡迎光臨 (http://www.raoushi.com/bbs/) |
Powered by Discuz! X3.1 |