找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

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

單片機基于事件的按鍵處理編程思想(原創(chuàng))

  [復制鏈接]
跳轉到指定樓層
樓主
ID:471574 發(fā)表于 2021-9-25 09:39 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
本帖最后由 zyhlove813 于 2021-9-25 10:57 編輯

單片機處理按鍵,網上的思路也是五花八門。入門的,可能是直接判斷端口,老手的,可能是通過鍵值計算;不管是誰學單片機,都逃不了做按鍵處理的程序。我在做項目的過程中,參考一些網上的思路,結合自己的算法,通過項目調試和驗證,終于做出了比較優(yōu)化和滿意的按鍵處理程序,功能有如下幾個方面:
1、多鍵掃描處理,提高處理速度
2、支持長按處理(單次觸發(fā)或一直觸發(fā))
3、支持按下、彈起、按住、松開、長按的事件
4、項目中只需要修改掃描鍵值,然后在各事件中判斷對應鍵值(單鍵或多鍵)
主要編程思路如下:
1、變量的說明
  1. //長按鍵的時長
  2. #define longkey_times 2000
  3. //長按單次模式定義,如果要長按時一直執(zhí)行,請注釋下一行
  4. #define LONG_PROCESS_ONCE
  5. uint8_t  KEY_PRESS;  //當前按下的鍵值
  6. uint8_t  KEY_NOT_PRESS;  //當前未被按的鍵值
  7. uint8_t  KEY_LAST;  //上一次的鍵值
  8. uint8_t  KEY_LONG;  //長按的鍵值
  9. uint8_t  KEY_DOWN;  //按下的鍵值
  10. uint8_t  KEY_UP; //彈起的鍵值
  11. uint8_t  KEY_UP_NL; //彈起的鍵值不帶長按鍵
  12. uint32_t KEY_TICKS;  //按鍵時間,用于長按計時
復制代碼

2、按鍵相關函數說明
  1. //按鍵處理程序
  2. void JUDGE_KEY(bool SINGLE_KEY); //鍵值掃描及邏輯處理
  3. void KEY_LONG_PROCESS(void);  //長按事件
  4. void KEY_PRESS_PROCESS(void);  //按住狀態(tài)事件
  5. void KEY_NOT_PRESS_PROCESS(void); //松開狀態(tài)事件
  6. void KEY_DOWN_PROCESS(void); //按下事件
  7. void KEY_UP_PROCESS(void);  //彈起事件
復制代碼
3、按鍵掃描及邏輯處理思路
  1. //bool SINGLE_KEY 防抖開關,True時打開
  2. void JUDGE_KEY(bool SINGLE_KEY)
  3. {
  4.     uint8_t TEMP_KEY;  //臨時的鍵值緩存
  5.     TEMP_KEY = PIND & 0x0C; //批量掃描IO,并生成鍵值,用戶需結合項目自已修改,PIND
  6.                             //此處表示PD0-7的端口,不同單片機不一樣,0x0C只取出
  7. //PD2 PD3的值  
  8.     TEMP_KEY ^= 0x0C;       //此處主要是把鍵值取反,如果你的按鍵是低電平觸發(fā)的話
  9.                             //如果你的按鍵是高電平觸發(fā),則刪除此行,不需要取反
  10.     if(TEMP_KEY > 0)  //鍵值大于0,表示有按鍵按著
  11.     {
  12.      delay(10); //防抖延時
  13.      //以下再一次批量掃描鍵值
  14.     KEY_PRESS = PIND & 0x0C;         
  15.     KEY_PRESS ^= 0x0C;

  16.     //如果防抖開關有效且兩次鍵值不一致,返回不處理
  17.     if(TEMP_KEY!=KEY_PRESS && SINGLE_KEY)
  18.    {
  19.       return;
  20.     }
  21.     }
  22.     else //無按鍵動作,當前按下的鍵值=0
  23.     {
  24.     KEY_PRESS=0;
  25.     }
  26.    //核心按鍵邏輯判斷
  27.     KEY_DOWN=(KEY_LAST^KEY_PRESS) & KEY_PRESS;   //按下的鍵值
  28.     KEY_UP=(KEY_LAST^KEY_PRESS) & KEY_LAST;//彈起的鍵值(包含長按鍵)
  29.     KEY_UP_NL=(~KEY_LONG) & KEY_UP; //彈起的鍵值(不包含長按鍵)
  30.     KEY_NOT_PRESS=~KEY_PRESS;  //未按的鍵狀態(tài)值
  31.           if(KEY_LONG & KEY_UP)
  32.     {
  33.      KEY_LONG=0;
  34.     }
  35.     if(KEY_PRESS > 0)  //當前有按鍵值按下
  36.     {
  37.        if(KEY_LAST & KEY_PRESS)     //如果當前的值與上次按下的值有相同的地方
  38.                                               //表示有鍵一直按著,否則可能只是切換了其他按鍵
  39.        {
  40.             //millis()函數是Arduino的開機時間毫秒計數,其他單片機自己實現
  41.              if(millis() - KEY_TICKS > longkey_times)   //按鍵時間大于長按時間
  42.             {
  43.                  KEY_LONG =KEY_LAST & KEY_PRESS;    //長按鍵值等于一直按住的值
  44.                  KEY_LONG_PROCESS();   //長按鍵處理
  45.                   #ifdef LONG_PROCESS_ONCE  //如果是長按單次處理
  46.                   KEY_TICKS=millis(); //更新長按時間標記,避免進入長按判斷
  47.                  #endif
  48.              }
  49.         }
  50.        else
  51.        {
  52.             KEY_TICKS=millis();  //切換了其他鍵,更新長按時間標記,避免進入長按判斷
  53.         }
  54.     }
  55.     else   //當前無按鍵按下
  56.     {
  57.       KEY_TICKS=millis();  //更新長按時間標記,避免進入長按判斷
  58.     }
  59.     if(KEY_UP > 0)  //如果有彈起的按鍵值
  60.     {
  61.     KEY_UP_PROCESS();    //按鍵彈起時處理
  62.     KEY_UP = 0;  //復位彈起的鍵值
  63.     }
  64.     if(KEY_DOWN > 0)
  65.     {
  66.     KEY_DOWN_PROCESS();  //按鍵按下時處理
  67.     }
  68.     if(KEY_PRESS > 0)
  69.     {
  70.     KEY_PRESS_PROCESS();  //按鍵按著狀態(tài)處理
  71.     }               
  72.                 if(KEY_NOT_PRESS)
  73.                 {
  74.      KEY_NOT_PRESS_PROCESS();  //按鍵彈起狀態(tài)處理
  75.     }
  76.      KEY_LAST=KEY_PRESS; //更新上一次的鍵值
  77. }
復制代碼


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
KEY_TEST-Arduino.rar (1.35 KB, 下載次數: 135)

評分

參與人數 2黑幣 +62 收起 理由
sadv + 12 好帖
admin + 50 共享資料的黑幣獎勵!

查看全部評分

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

使用道具 舉報

沙發(fā)
ID:968484 發(fā)表于 2021-9-27 04:11 | 只看該作者
謝了好資料,51黑有你更精彩!!!
回復

使用道具 舉報

板凳
ID:471574 發(fā)表于 2021-9-27 10:16 | 只看該作者
ximao 發(fā)表于 2021-9-27 04:11
謝了好資料,51黑有你更精彩!!!

有用就好
回復

使用道具 舉報

地板
ID:194006 發(fā)表于 2021-10-6 17:53 來自手機 | 只看該作者
先收藏一下,以后用得上
回復

使用道具 舉報

5#
ID:971477 發(fā)表于 2021-10-12 16:19 | 只看該作者
好資料,51黑有你更精彩!!!
回復

使用道具 舉報

6#
ID:971822 發(fā)表于 2021-10-13 20:27 | 只看該作者
不錯,值得學習
回復

使用道具 舉報

7#
ID:471574 發(fā)表于 2021-10-25 11:16 | 只看該作者

一起學習,如果你有好的建議也不妨提出,給大家學習一下
回復

使用道具 舉報

8#
ID:941265 發(fā)表于 2021-11-1 17:00 | 只看該作者
好資料,51黑有你更精彩!!!
回復

使用道具 舉報

9#
ID:370415 發(fā)表于 2021-11-9 12:47 | 只看該作者
一起學習,51黑一定輝煌起來
回復

使用道具 舉報

10#
ID:850575 發(fā)表于 2021-11-19 08:18 | 只看該作者
很深奧的內容。
回復

使用道具 舉報

11#
ID:814879 發(fā)表于 2022-1-12 11:01 | 只看該作者
剛好,我這個小白去學習學習!
回復

使用道具 舉報

12#
ID:8222 發(fā)表于 2022-1-31 09:28 | 只看該作者
很好的資料!
回復

使用道具 舉報

13#
ID:258676 發(fā)表于 2022-3-11 09:54 | 只看該作者
這個模塊很方便
回復

使用道具 舉報

14#
ID:22218 發(fā)表于 2022-3-19 03:39 | 只看該作者
本帖最后由 chinomango 于 2022-3-19 03:41 編輯

這個適合別的uP和C編譯嗎?最好前面有#if以自動適合不同的C編譯。
看到晚了,上個月還自己寫了一個,不過支持連續(xù)按鍵2次,以即時關閉電源;單次則是延遲3分鐘關閉。不知用你這個要如何改動? 先謝過。這個是堵塞式的嗎還是定時中斷掃描鍵盤?
回復

使用道具 舉報

15#
ID:471574 發(fā)表于 2022-3-30 08:10 | 只看該作者
chinomango 發(fā)表于 2022-3-19 03:39
這個適合別的uP和C編譯嗎?最好前面有#if以自動適合不同的C編譯。
看到晚了,上個月還自己寫了一個,不過 ...

這個只是把按鍵值用計算的方式來產生按下、彈長、長按等事件動作,連續(xù)兩次按鍵,你可以在按下或彈起的事件里,用一個值來判斷按了幾次,再加上一個時長,如果超時或者中間按了其他按鍵,這個值就初始化到初值;
這個程序主要作用是產生事件,至少邏輯處理,要做什么事情,是你要在事件里進行編程。我這個程序,如果 JUDGE_KEY(true)在定時器里中斷調用,就變成是中斷式,如果在Loop循環(huán)里調用,就成了堵塞式?茨愕某绦蛐枨
回復

使用道具 舉報

16#
ID:933601 發(fā)表于 2022-5-19 18:29 | 只看該作者
收藏一下
回復

使用道具 舉報

17#
ID:966468 發(fā)表于 2022-6-23 09:35 | 只看該作者
非常好的設計思路,如果能提高實時性就更好了
回復

使用道具 舉報

18#
ID:471574 發(fā)表于 2022-6-25 11:37 | 只看該作者
mrzhou 發(fā)表于 2022-6-23 09:35
非常好的設計思路,如果能提高實時性就更好了

多個項目應用證明,一般的項目都能應對,如果有擔心的話,也可以把調用放在定時中斷里
回復

使用道具 舉報

19#
ID:364137 發(fā)表于 2022-8-19 22:45 | 只看該作者
很好的經驗,謝謝分享。
回復

使用道具 舉報

20#
ID:509408 發(fā)表于 2022-8-20 09:03 | 只看該作者
按鍵IO那里還是要做的更通用些就好了,我自己的項目應用中因為IO口緊張,是不太可能按鍵分布在連續(xù)IO口上的。
回復

使用道具 舉報

21#
ID:137736 發(fā)表于 2022-9-3 17:52 | 只看該作者
太好了,也上個51的源碼好了
回復

使用道具 舉報

22#
ID:1032507 發(fā)表于 2022-11-2 15:48 | 只看該作者
樓主寫的很詳細,謝謝分享。
回復

使用道具 舉報

23#
ID:979603 發(fā)表于 2022-11-10 07:54 | 只看該作者

好資料,51黑有你更精彩!!!
回復

使用道具 舉報

24#
ID:1013961 發(fā)表于 2022-12-3 17:28 | 只看該作者
很不錯的按鍵邏輯處理。收藏了。
回復

使用道具 舉報

25#
ID:27536 發(fā)表于 2023-5-12 16:47 | 只看該作者
既然都用事件了,為什么還要延時防抖呢?
回復

使用道具 舉報

26#
ID:224580 發(fā)表于 2023-8-21 23:26 | 只看該作者
值得慢慢研究,加入到我的代碼上去
回復

使用道具 舉報

27#
ID:471574 發(fā)表于 2024-6-8 08:28 | 只看該作者
likejian 發(fā)表于 2023-8-21 23:26
值得慢慢研究,加入到我的代碼上去

核心思想是把按鍵的IO轉化為數值,然后通過新舊數據的運算,篩選出IO對應的不同狀態(tài)值,然后使用時通過判斷是否為需要的IO值。
回復

使用道具 舉報

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

本版積分規(guī)則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表