本帖最后由 zyhlove813 于 2021-9-25 10:57 編輯
單片機處理按鍵,網上的思路也是五花八門。入門的,可能是直接判斷端口,老手的,可能是通過鍵值計算;不管是誰學單片機,都逃不了做按鍵處理的程序。我在做項目的過程中,參考一些網上的思路,結合自己的算法,通過項目調試和驗證,終于做出了比較優(yōu)化和滿意的按鍵處理程序,功能有如下幾個方面: 1、多鍵掃描處理,提高處理速度 2、支持長按處理(單次觸發(fā)或一直觸發(fā)) 3、支持按下、彈起、按住、松開、長按的事件 4、項目中只需要修改掃描鍵值,然后在各事件中判斷對應鍵值(單鍵或多鍵) 主要編程思路如下: 1、變量的說明 - //長按鍵的時長
- #define longkey_times 2000
- //長按單次模式定義,如果要長按時一直執(zhí)行,請注釋下一行
- #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); //按住狀態(tài)事件
- void KEY_NOT_PRESS_PROCESS(void); //松開狀態(tài)事件
- 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; //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話
- //如果你的按鍵是高電平觸發(fā),則刪除此行,不需要取反
- 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; //未按的鍵狀態(tài)值
- 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(); //按鍵按著狀態(tài)處理
- }
- if(KEY_NOT_PRESS)
- {
- KEY_NOT_PRESS_PROCESS(); //按鍵彈起狀態(tài)處理
- }
- KEY_LAST=KEY_PRESS; //更新上一次的鍵值
- }
復制代碼
4、按鍵邏輯處理算法詳解 4.1首次按下的鍵,先用異或^進行上次掃描鍵值和本次掃描鍵值計算,取得不一樣的鍵位,不一樣的鍵位和本次掃描鍵位相同,則表示首次按下。 如 0000 0010表示上次掃描的鍵,第1位是按下的狀態(tài) 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位是按下的狀態(tài) 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長按鍵一般單獨處理,彈起時如果要排除,避免多次觸發(fā)事件,需要計算出 不包含長按鍵的鍵值,用如下公式 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; //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話 //還有一處地方也要一起改 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); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話, //如果你的按鍵是高電平觸發(fā),則刪除此行,不需要取反 //還有一處地方也要一起改 KEY_PRESS = P0 & 0x0A; //取出 P01 P03 KEY_PRESS |=(P1 & 0x50); //取出 P14 P16 KEY_PRESS ^= (0x0A|0x50); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話, //如果你的按鍵是高電平觸發(fā),則刪除此行,不需要取反 為了編程方便,盡量使用同一序列的口,如果不同序列的口,那端口號也要能錯開,如用了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); //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話 //如果你的按鍵是高電平觸發(fā),則刪除此行,不需要取反 這樣鍵值里,0x02表示P01,0x04表示P02,0x08表示P12,0x10表示P13
5.3在單片機循環(huán)程序或定時器里,周期性調用掃描程序 void loop() { JUDGE_KEY(true); } 5.4在對應事件里進行其他編程
如: void KEY_NOT_PRESS_PROCESS() //按鍵彈起狀態(tài)處理 { if(KEY_NOT_PRESS & 0x04) { //Serial.println("KEY PD2 is NOT PRESSING"); } } void KEY_PRESS_PROCESS() //按鍵按著狀態(tài)處理 { 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
|