1. 程式人生 > >一段比較好的按鍵實現程式碼

一段比較好的按鍵實現程式碼

之前的一個專案按鍵比較多,面板上面有按鍵,遙控器,處理的稍微複雜一點,MCU使用的是STM8S005K6.關於按鍵部分的處理,現在拿處理來和大家分享一下,說的不對的地方還請各位大俠請教,大家共同進步。按鍵通常分有IO口按鍵(BUTTON),AD按鍵(通過AD取樣電壓),IR(遙控器)按按鍵功能分:有短按鍵,長按鍵,連續按鍵。打個比方,遙控電視機,按一下音量鍵,音量增加1,這個就是短按鍵。按住音量鍵不放,音量連續加,這個就是連續按鍵。按住一個按鍵5s,系統會復位,這個是長按鍵。怎麼去處理這些不同的按鍵了,下面我們就細細來說

1,IO口按鍵,就是我們比較常見的一個IO接一個按鍵,或者是一個矩陣鍵盤。很多新人的處理方法可能是取樣延時的方法,當年我也是這樣的,如下

  1.     if(GETIO==low)
  2.     { 
  3.       delay_10ms()
  4.       if(GETIO==low)
  5.       {
  6.         //得到按鍵
  7.       }
  8.     }


   這種方法雖然簡單,但是有很大弊端。


   首先 Delay 浪費很多時間,影響系統。
   第二,無法判斷長短按鍵,連續按鍵
   第三,如果這個按鍵是開關機按鍵系統在低功耗狀態下,需要中斷喚醒,這種方法比較容易出問題,如STM8S系列的 halt 模式。
   
   
所以我們一般在產品開發的過程中,採用掃描的方法,就是每隔10ms 去檢測IO的狀態,看是否有按鍵,然後去抖動,判斷按鍵功能。
   
   
參考程式碼如下,這段程式碼是之前在一個論壇看到的比我自己寫的更加優秀,所以拿出來和大家分享一下,也順便感謝一下作者。

   這段程式碼,容易修改,可以根據自己的時間需要,進行長短按鍵,連續按鍵,還有組合按鍵的判斷。
   
   

  1. /* 按鍵濾波時間50ms, 單位10ms
  2. 只有連續檢測到50ms狀態不變才認為有效,包括彈起和按下兩種事件
  3. */
  4. #define BUTTON_FILTER_TIME         5
  5. #define BUTTON_LONG_TIME         300                /* 持續1秒,認為長按事件 */
  6.  
  7. /*
  8.         每個按鍵對應1個全域性的結構體變數。
  9.         其成員變數是實現濾波和多種按鍵狀態所必須的
  10. */
  11. typedef struct
  12. {
  13.         /* 下面是一個函式指標,指向判斷按鍵手否按下的函式 */
  14.         unsigned char  (*IsKeyDownFunc)(void); /* 按鍵按下的判斷函式,1表示按下 */
  15.  
  16.         unsigned char  Count;                        /* 濾波器計數器 */
  17.         unsigned char  FilterTime;                /* 濾波時間(最大255,表示2550ms) */
  18.         unsigned short LongCount;                /* 長按計數器 */
  19.         unsigned short LongTime;                /* 按鍵按下持續時間, 0表示不檢測長按 */
  20.         unsigned char   State;                        /* 按鍵當前狀態(按下還是彈起) */
  21.         unsigned char  KeyCodeUp;                /* 按鍵彈起的鍵值程式碼, 0表示不檢測按鍵彈起 */
  22.         unsigned char  KeyCodeDown;        /* 按鍵按下的鍵值程式碼, 0表示不檢測按鍵按下 */
  23.         unsigned char  KeyCodeLong;        /* 按鍵長按的鍵值程式碼, 0表示不檢測長按 */
  24.         unsigned char  RepeatSpeed;        /* 連續按鍵週期 */
  25.         unsigned char  RepeatCount;        /* 連續按鍵計數器 */
  26. }BUTTON_T;
  27.  
  28. typedef enum
  29. {
  30.         KEY_NONE = 0,                        /* 0 表示按鍵事件 */
  31.  
  32.         KEY_DOWN_Power,                        /* 按鍵鍵按下 */
  33.         KEY_UP_Power,                        /* 按鍵鍵彈起 */
  34.         KEY_LONG_Power,                        /* 按鍵鍵長按 */
  35.         
  36.         KEY_DOWN_Power_TAMPER        /* 組合鍵,Power鍵和WAKEUP鍵同時按下 */
  37. }KEY_ENUM;
  38.  
  39. BUTTON_T s_Powerkey;                
  40. //是否有按鍵按下介面函式
  41. unsigned char  IsKeyDownUser(void)                 
  42. {if (0==GPIO_ReadInputPin(POWER_KEY_PORT, POWER_KEY_PIN) ) return 1;return 0;}
  43.  
  44.  
  45. void  PanakeyHard_Init(void)
  46. {
  47.    GPIO_Init (POWER_KEY_PORT, POWER_KEY_PIN, GPIO_MODE_IN_FL_NO_IT);//power key
  48. }
  49. void  PanakeyVar_Init(void)
  50. {
  51.         /* 初始化USER按鍵變數,支援按下、彈起、長按 */
  52.         s_Powerkey.IsKeyDownFunc = IsKeyDownUser;                /* 判斷按鍵按下的函式 */
  53.         s_Powerkey.FilterTime = BUTTON_FILTER_TIME;                /* 按鍵濾波時間 */
  54.         s_Powerkey.LongTime = BUTTON_LONG_TIME;                        /* 長按時間 */
  55.         s_Powerkey.Count = s_Powerkey.FilterTime / 2;                /* 計數器設定為濾波時間的一半 */
  56.         s_Powerkey.State = 0;                                                        /* 按鍵預設狀態,0為未按下 */
  57.         s_Powerkey.KeyCodeDown = KEY_DOWN_Power;                        /* 按鍵按下的鍵值程式碼 */
  58.         s_Powerkey.KeyCodeUp =KEY_UP_Power;                                /* 按鍵彈起的鍵值程式碼 */
  59.         s_Powerkey.KeyCodeLong = KEY_LONG_Power;                        /* 按鍵被持續按下的鍵值程式碼 */
  60.         s_Powerkey.RepeatSpeed = 0;                                                /* 按鍵連發的速度,0表示不支援連發 */
  61.         s_Powerkey.RepeatCount = 0;                                                /* 連發計數器 */                
  62. }
  63. void Panakey_Init(void)
  64. {
  65.         PanakeyHard_Init();                /* 初始化按鍵變數 */
  66.         PanakeyVar_Init();                /* 初始化按鍵硬體 */
  67. }

複製程式碼


*********************************************************************************************************
*        
函 數 名: bsp_DetectButton
*        功能說明: 檢測一個按鍵。非阻塞狀態,必須被週期性的呼叫。
*        形    參:按鍵結構變數指標
*        返 回 值: 無
*********************************************************************************************************

  1. */
  2. void Button_Detect(BUTTON_T *_pBtn)
  3. {
  4.         if (_pBtn->IsKeyDownFunc())
  5.         {
  6.                 if (_pBtn->Count < _pBtn->FilterTime)
  7.                 {
  8.                         _pBtn->Count = _pBtn->FilterTime;
  9.                 }
  10.                 else if(_pBtn->Count < 2 * _pBtn->FilterTime)
  11.                 {
  12.                         _pBtn->Count++;
  13.                 }
  14.                 else
  15.                 {
  16.                         if (_pBtn->State == 0)
  17.                         {
  18.                                 _pBtn->State = 1;
  19.  
  20.                                 /* 傳送按鈕按下的訊息 */
  21.                                 if (_pBtn->KeyCodeDown > 0)
  22.                                 {
  23.                                         /* 鍵值放入按鍵FIFO */
  24.                                         Pannelkey_Put(_pBtn->KeyCodeDown);// 記錄按鍵按下標誌,等待釋放
  25.  
  26.                                 }
  27.                         }
  28.  
  29.                         if (_pBtn->LongTime > 0)
  30.                         {
  31.                                 if (_pBtn->LongCount < _pBtn->LongTime)
  32.                                 {
  33.                                         /* 傳送按鈕持續按下的訊息 */
  34.                                         if (++_pBtn->LongCount == _pBtn->LongTime)
  35.                                         {
  36.                                                 /* 鍵值放入按鍵FIFO */
  37.                                                 Pannelkey_Put(_pBtn->KeyCodeLong);        
  38.                                 
  39.                                         }
  40.                                 }
  41.                                 else
  42.                                 {
  43.                                         if (_pBtn->RepeatSpeed > 0)
  44.                                         {
  45.                                                 if (++_pBtn->RepeatCount >= _pBtn->RepeatSpeed)
  46.                                                 {
  47.                                                         _pBtn->RepeatCount = 0;
  48.                                                         /* 按鍵後,每隔10ms傳送1按鍵 */
  49.                                                         Pannelkey_Put(_pBtn->KeyCodeDown);        
  50.                                 
  51.                                                 }
  52.                                         }
  53.                                 }
  54.                         }
  55.                 }
  56.         }
  57.         else
  58.         {
  59.                 if(_pBtn->Count > _pBtn->FilterTime)
  60.                 {
  61.                         _pBtn->Count = _pBtn->FilterTime;
  62.                 }
  63.                 else if(_pBtn->Count != 0)
  64.                 {
  65.                         _pBtn->Count--;
  66.                 }
  67.                 else
  68.                 {
  69.                         if (_pBtn->State == 1)
  70.                         {
  71.                                 _pBtn->State = 0;
  72.  
  73.                                 /* 傳送按鈕彈起的訊息 */
  74.                                 if (_pBtn->KeyCodeUp > 0) /*按鍵釋放*/
  75.                                 {
  76.                                         /* 鍵值放入按鍵FIFO */
  77.                                 Pannelkey_Put(_pBtn->KeyCodeUp);        
  78.                         
  79.                                 }
  80.                         }
  81.                 }
  82.  
  83.                 _pBtn->LongCount = 0;
  84.                 _pBtn->RepeatCount = 0;
  85.         }
  86. }
  87. //功能說明: 檢測所有按鍵10MS 呼叫一次
  88. void Pannelkey_Polling(void)
  89. {
  90.         Button_Detect(&s_Powerkey);                /* USER */
  91. }
  92. void Pannelkey_Putvoid)
  93. {
  94.         
  95.   // 定義一個佇列 放入按鍵        
  96. }

複製程式碼



   
2,
遙控器按鍵,遙控器解碼的一般就有兩種 脈寬調製和脈衝調製,這裡就不細講解碼的過程了。這裡詳細解碼之後,如何處理遙控器按鍵
   實現遙控器按鍵的長短按功能和連續按鍵功能。
   程式碼裁剪過,大家根據實際需要改動
  其實AD按鍵,通過AD取樣獲得按鍵值之後,可以採取如下面的一樣方法處理,提一個函式介面即可

  1.    typedef struct
  2. {
  3.   unsigned char count;//
  4.   unsigned char LongkeyFlag;/*是否長按鍵1代表是*/
  5.   unsigned char  PreKeyValue;/*按鍵值的一個備份,用於釋放按鍵*/
  6.   
  7. }ScanKeyDef;
  8.  
  9. #define SHORT_PRESS_TIME_IR                16 // 10ms 
  10. #define SERIES_PRESS_TIME_IR            10  
  11. #define LONG_PRESS_TIME_IR                    22
  12. #define KEY_RELEASE_TIME_OUT_IR     12  // 10ms 
  13. //提供5個介面函式,如下。 
  14. unsigned char get_irkey(void);
  15. unsigned char ISSeriesKey(unsigned char temp);//按鍵是否需要連續按鍵
  16. unsigned char changeSeriesKey(unsigned char temp);//轉換連續按鍵
  17. unsigned char ISLongKey(unsigned char temp);//按鍵是否需要連續按鍵
  18. unsigned char changeToLongKey(unsigned char temp);//轉換連續按鍵
  19.  
  20.  
  21. unsigned char KeyScan(void) 
  22. {
  23.     unsigned char  KeyValue = KEY_NONE,
  24.                                 KeyValueTemp = KEY_NONE;
  25.     static   unsigned char KeyReleaseTimeCount =0;
  26.  
  27.     KeyValueTemp = get_irkey();
  28.  
  29.     if(KeyValueTemp != KEY_NONE)
  30.     {
  31.         ScanKeyDef.count++;
  32.         KeyReleaseTimeCount =0;
  33.  
  34.         if(ScanKeyDef.count < LONG_PRESS_TIME_IR )
  35.         {
  36.             ScanKeyDef.LongkeyFlag = 0;
  37.             if((ScanKeyDef.count == SERIES_PRESS_TIME_IR) && ISSeriesKey(KeyValueTemp)) //處理連續按鍵
  38.                 {
  39.                     KeyValue = changeSeriesKey ( KeyValueTemp );
  40.                     ScanKeyDef.PreKeyValue = KEY_NONE;
  41.                 }
  42.             else if ( ScanKeyDef.count  < SHORT_PRESS_TIME_IR )
  43.             {
  44.                 ScanKeyDef.PreKeyValue = KeyValueTemp;
  45.             }
  46.             else
  47.             {
  48.             
  49.                 ScanKeyDef.PreKeyValue  = KEY_NONE; // 無效按鍵
  50.             }
  51.         }
  52.         else if ( ScanKeyDef.count  == LONG_PRESS_TIME_IR )
  53.         {
  54.        
  55.            if (ISLongKey(KeyValueTemp))
  56.             {
  57.                 {
  58.                    ScanKeyDef.LongkeyFlag = 1;
  59.                    KeyValue = changeToLongKey ( KeyValueTemp );
  60.                }
  61.           }
  62.             ScanKeyDef.PreKeyValue = KEY_NONE;
  63.          
  64.         }
  65.         else if (ScanKeyDef.count > LONG_PRESS_TIME_IR )
  66.         {
  67.       
  68.             ScanKeyDef.PreKeyValue  = KEY_NONE; //無效按鍵
  69.         }
  70.     }
  71.     else//release & no press
  72.     {
  73.         KeyReleaseTimeCount ++;
  74.         if(KeyReleaseTimeCount >= KEY_RELEASE_TIME_OUT_IR)
  75.         {
  76.   
  77.             if ( ScanKeyDef.PreKeyValue != KEY_NONE ) //釋放按鍵
  78.             {
  79.                 if ( ScanKeyDef.LongkeyFlag == 0 )
  80.                 {
  81.            
  82.                     KeyValue =ScanKeyDef.PreKeyValue ;
  83.                 }
  84.             }          
  85.             ScanKeyDef.count  = 0;
  86.             ScanKeyDef.LongkeyFlag = 0;
  87.            ScanKeyDef.PreKeyValue = KEY_NONE;
  88.     
  89.         }
  90.     }
  91.     return(KeyValue);
  92. }     

 

通過我的修改支援了AD按鍵

只需在一個任務裡週期呼叫void key_Polling(uint16_t adVal)即可,最終的按鍵處理是在handkeyCode裡

freeRTOS中的呼叫示例如下:

uint16_t keyAdc = 0;
void keyScanTask(void const * argument)
{
  /* USER CODE BEGIN keyScanTask */
    keyVar_Init();
    /* Infinite loop */
    for(;;)
    {
        keyAdc = Get_Adc();
        key_Polling(keyAdc);

        osDelay(10);
    }
  /* USER CODE END keyScanTask */
}
 

電路如此:

 

按鍵程式碼如下:

 

 


#include "FreeRTOS.h"
#include "task.h"
#include "myAdKey.h"

/* 按鍵濾波時間50ms, 單位10ms
只有連續檢測到50ms狀態不變才認為有效,包括彈起和按下兩種事件
*/
#define KEY_FILTER_TIME         5

/*定義多長時間為長按,單位為秒,精度為 portTICK_PERIOD_MS 毫秒*/
#define KEY_LONG_TIME           2.0  

/*
每個按鍵對應1個全域性的結構體變數。
其成員變數是實現濾波和多種按鍵狀態所必須的
*/
typedef struct
{
    unsigned char  Count;                     /* 濾波器計數器 */
    unsigned char  FilterTime;                /* 濾波時間(最大255,表示2550ms) */
    unsigned char  State;                     /* 按鍵當前狀態(按下還是彈起) */
    unsigned char  keyCode;
    TickType_t     keyDownTime;    
    TickType_t     keyUpTime;
} BUTTON_T;


typedef enum
{
    KEY_NONE = 0,                 /* 0 表示按鍵事件 */
    KEY_A,                        /* 按鍵鍵按下 */
    KEY_B,                        /* 按鍵鍵彈起 */
    KEY_CONNECT,                  /* 按鍵鍵長按 */
    KEY_DISCONNECT
} KEY_ENUM;


BUTTON_T key_A, key_B, connect, disconnect ;

volatile char handIsDisconnected = 0, keyAIsShortPushed = 0, keyAIsLongPushed = 0, keyBIsShortPushed = 0, keyBIsLongPushed = 0, handIsConnected = 0;

void  keyVar_Init(void)
{
    key_A.FilterTime    = KEY_FILTER_TIME;                /* 按鍵濾波時間 */
    key_A.Count         = key_A.FilterTime / 2;         /* 計數器設定為濾波時間的一半 */
    key_A.State         = 0;                                 /* 按鍵預設狀態,0為未按下 */
    key_A.keyCode       = KEY_A;

///////////////////////////////////
    key_B.FilterTime    = KEY_FILTER_TIME;                /* 按鍵濾波時間 */
    key_B.Count         = key_B.FilterTime / 2;         /* 計數器設定為濾波時間的一半 */
    key_B.State         = 0;                                 /* 按鍵預設狀態,0為未按下 */
    key_B.keyCode       = KEY_B;
    
//////////////////////////////////
    connect.FilterTime    = 100;                                /* 接入檢測濾波時間 100x10ms=1S */
    connect.Count         = connect.FilterTime / 2;         /* 計數器設定為濾波時間的一半 */
    connect.State         = 0;                                 /* 按鍵預設狀態,0為未按下 */
    connect.keyCode       = KEY_CONNECT;
        
//////////////////////////////////
    disconnect.FilterTime    = 100;                               /* 斷開檢測濾波時間 100x10ms=1S */
    disconnect.Count         = disconnect.FilterTime / 2;         /* 計數器設定為濾波時間的一半 */
    disconnect.State         = 0;                                 /* 按鍵預設狀態,0為未按下 */
    disconnect.keyCode       = KEY_DISCONNECT;
        
    
}


void handkeyCode(BUTTON_T *_pBtn)
{
    TickType_t timeInterval;
    
    timeInterval = _pBtn->keyUpTime - _pBtn->keyDownTime;
    
    switch(_pBtn->keyCode)
    {
        case KEY_A:
        {
            if(timeInterval > (TickType_t)(KEY_LONG_TIME * configTICK_RATE_HZ )) // 按鍵A長按
            {
                keyAIsLongPushed = 1;
            }
            else //按鍵A短按
            {
                keyAIsShortPushed = 1;
            }
            break;
        }
        case KEY_B:
        {
            if(timeInterval > (TickType_t)(KEY_LONG_TIME * configTICK_RATE_HZ )) // 按鍵B長按
            {
                keyBIsLongPushed = 1;
            }
            else //按鍵B短按
            {
                keyBIsShortPushed = 1;
            }            
            break;
        }
        case KEY_CONNECT:
        {
            handIsConnected = 1;
            break;
        }
        case KEY_DISCONNECT:
        {
            handIsDisconnected = 1;
            break;
        }
    }

}

void keyDownDetect(BUTTON_T *_pBtn)
{
    if (_pBtn->Count < _pBtn->FilterTime)  // 2   5
    {
        _pBtn->Count = _pBtn->FilterTime;
    }
    else if(_pBtn->Count < 2 * _pBtn->FilterTime)
    {
        _pBtn->Count++;
    }
    else
    {
        if (_pBtn->State == 0)
        {
            _pBtn->State = 1;

            /* 傳送按鈕按下的訊息 */
            _pBtn->keyDownTime = xTaskGetTickCount();
            
        }
    }    
}

void keyUpDetect(BUTTON_T *_pBtn)
{
    if(_pBtn->Count > _pBtn->FilterTime)
    {
        _pBtn->Count = _pBtn->FilterTime;
    }
    else if(_pBtn->Count != 0)
    {
        _pBtn->Count--;
    }
    else
    {
        if (_pBtn->State == 1)
        {
            _pBtn->State = 0;

            /* 傳送按鈕彈起的訊息 */
            _pBtn->keyUpTime = xTaskGetTickCount();
            handkeyCode(_pBtn);
        }
    }
}

/*
非阻塞狀態,必須被週期性的呼叫。
*/

void key_Polling(uint16_t adVal)
{
    
    
    if(adVal > 3584 && adVal < 4095) // 探頭斷開
    {
        keyDownDetect(&connect);
        keyUpDetect(&disconnect);
        
    }
    else if(adVal > 2560 && adVal < 3584) // 接入,注意這裡既包括手柄第一次插入的情況又包括A鍵或B鍵由按下到釋放的情況
    {
        keyUpDetect(&connect);
        keyDownDetect(&disconnect);
        
        keyUpDetect(&key_A);
        keyUpDetect(&key_B);
    }        
    else if(adVal > 1536 && adVal < 2560) // 按鍵A按下
    {
        keyDownDetect(&key_A);
    }

    else if(adVal > 512 && adVal < 1536)  //按鍵B按下
    {
        keyDownDetect(&key_B);
    }    

}