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