1. 程式人生 > >[嵌入式開發模組]機械按鈕模組 純C語言 面向物件實現 按鍵消抖、長按、連擊

[嵌入式開發模組]機械按鈕模組 純C語言 面向物件實現 按鍵消抖、長按、連擊

前言

嵌入式開發時,我們經常會用到各種機械按鈕,由於機械按鈕的抖動特性,一般需要保持一個狀態一段時間不變才能認為按鍵真的按下去/擡起來了,不然可能會出現明明只點擊了一下,效果確是連擊了好幾下的情況。另一方面,我們為了最大化的發揮按鈕的作用,還經常需要實現長按,連擊等功能。在網上翻了好多人的按鈕實現,發現都是面向過程寫的。也不是說不行,但是每加個按鈕就得為它重新寫一遍各種處理函式和變數,要是需要改下當前實現加個功能,有N個按鈕就得改N遍,累不累呀。

另外還發現按鍵消抖很多人都是直接在函式中呼叫個for迴圈的延遲函式等個10ms那樣,這樣的話其實有很多問題:一是微控制器在這段時間就完全在進行無意義的迴圈無法處理其他事情;一是如果你只用了一個執行緒來處理鍵盤的話,那就無法同時處理兩個按鍵的事件了,除非你為每個按鍵單獨建立一個執行緒,那花銷也太大了。當然經過精巧的設計還是可以解決這些問題的。

為了方便大家實現各種常用功能(其實主要是給自己以後用吧),我把這些常用的功能進行了封裝。目前實現要求使用者每隔一段時間輪詢一下所有按鈕的狀態,並分別呼叫一次每個按鈕物件的TimeTick方法將其當前狀態傳入,然後TimeTick方法就會把這個按鈕發生了什麼事件(按下、擡起、長按、單連擊)返回給你,然後就可以很方便的按照自己的需要進行處理了。具體的實現細節完全被隱藏。

注1(為什麼使用輪詢):

使用輪詢的方式是權衡後的決定,本來有想過使用非同步中斷的方式來實現,但是那樣就需要其他的方法來計時,增加了設計難度和對使用者的水平要求;另一方面,像那種需要掃描的鍵盤,我暫時沒想到怎麼通過中斷的方式來實現,所以最後還是決定用輪詢的方式。這樣的結果是即使沒有任何按鈕被按下,微控制器還是得每隔一小段時間就遍歷處理遍所有按鈕,增大了微控制器的負擔,但好在這種負擔其實並不大(當然還要取決於你有多少個按鈕,一般不會太多),基本就是每1ms佔用一小個時間片那樣。

注2(為什麼使用同步):

一般來說,事件都是通過非同步的方式通知的(我是這麼覺得的),但考慮到按鈕只是鍵盤的一個最小單元,一般來說還會被進一步封裝,所以為了方便使用者個性化使用,決定直接把事件作為函式返回值。

模組介紹

特性

總結一下這個模組實現的特性:

  1. 記憶體分配:要求使用者自己手動為每個按鈕物件分配記憶體,這是考慮到嵌入式開發一般不動態分配記憶體而作的決定。
  2. 輪詢:要求使用者每隔一段時間為每個按鈕物件呼叫一次TimeTick方法,每次呼叫為一個Tick,也就是模組中使用的時間單位。推薦時間間隔1ms,可以使用軟體/硬體定時器或者其他方法。
  3. 面向物件:內部實現對使用者完全透明,使用者只需要按照要求設定引數,呼叫對應方法即可實現需求,不需要深究具體實現(當然你要看也可以,原始碼都給了)。
  4. 按鍵獨立:任意兩個按鍵間完全獨立,可以輕鬆的實現一個按鈕長按的同時,另一個按鈕在連擊,再加一個按鈕也在長按……
  5. 可定製:可通過在標頭檔案的CONFIGURATION塊中更改引數以裁剪程式碼、控制程式邏輯。
  6. 記憶體佔用:和配置有關,如支援所有功能,程式碼約需佔用380位元組ROM,另外每個按鈕物件佔用3位元組RAM;如不需要連擊和長按功能,則程式碼約需佔用200位元組ROM,另每個按鈕物件則降到只需1位元組RAM。
  7. 時間限制:對於當前實現,消抖時間不超31個tick,連擊不超15次,連擊間隔時間、長按觸發時間、長按重複間隔時間不超4095個tick。也就是說要是一個tick是1ms的話,長按最多支援4秒。

事件

支援以下幾個事件:

  1. down/按下事件: 即按鈕被按下(閉合)了,經過了消抖。
  2. up/擡起事件:即按鈕被鬆開(斷開)了,經過了消抖。
  3. click/點選事件:包括單擊和連擊,如支援連擊,則通過clickCnt欄位提取當前連擊多少次;如在擡起後特定時間內沒有重新按下按鈕,則連擊終止。點選事件肯定伴隨著up事件。
  4. long-press/長按事件:按鈕在特定tick時間內保持按下狀態則會觸發一次長按事件,如果支援長按重複,則會在第一次觸發長按後每隔一段時間再次觸發長按事件。

執行邏輯

模組基本的邏輯如下:一個按鈕物件在初始化後處於鬆開狀態,然後如果在消抖時間內一直保持按下狀態則會變更當前狀態為按下,同時觸發down事件。然後如果在長按事件被觸發之前(經過消抖時間,後面略)擡起了,則觸發click事件與up事件,如果保持按下事件足夠久,就會觸發第一次長按事件,然後就會每間隔一段時間重複觸發長按事件;然後如擡起按鈕,是否觸發click事件取決於配置,但一定會觸發up事件;然後如在特定時間內再次按下,則在再次擡起且判定為click事件時clickCnt值為2即雙擊;否則連擊停止。

這個邏輯受到標頭檔案的CONFIGURATION塊中引數影響,具體邊界條件時的響應和每個引數的定義見對應註釋。

軟體框架

可以按照下述框架使用這個模組,具體使用時根據需要進行修改:

MECBTN_STATE aBtn;  // 為一個按鈕物件分配記憶體
int main(){
  ...
  // initialize the button object  初始化按鈕物件
  MecBtn_Init(&aBtn);
  // start the timer.              啟動軟體定時器,當然你也可以使用定時中斷等方式
  TimerStart();
  ...
}
// 定時器定時呼叫的函式
void Timer_1ms(void){
   MECBTN_EVENTFLAG rst;
   INT8U stateNow,clickCnt;
   stateNow = getBtnState();    // 獲取按鈕當前狀態,記住,TRUE為按下(閉合),FALSE為鬆開(斷開)
   rst = MecBtn_TimeTick(&aBtn,stateNow);
   if(MecBtn_Event_any_happened(rst)){
      if(MecBtn_Event_down_happened(rst)){
          // down event
      }
      if(MecBtn_Event_up_happened(rst)){
          // up event
      }
      if(MecBtn_Event_click_happened(rst)){
          // click event
          clickCnt = MecBtn_clickTimes(rst);
          switch(clickCnt){
             case 1: // single click event
                ...
             break;
             case 2: // double click event
                ...
             break;
             ...
          }
      }
      if(MecBtn_Event_lPress_happened(rst)){
          // long-press event
      }
   }
}

各種程式碼

模組原始碼

以下是.h檔案

/*
*******************************************************************************************
*
*
*                                   MECHANICAL BUTTON MODULE
*                                        機械按鈕模組
*
* File : MecBtn.h
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2017/12/20
* version: V1.0
* History: 2017/12/20  V1.0   the prototype
*
* NOTE(s): 1. the module encapsulates the common functions of mechanical buttons, including
*             jitters elimination, long press, multiple click count.
*             這個模組封裝了機械按鈕常用的幾個功能:按鍵消抖、長按、連擊
*             the module provides convenient judgment of several events,including down event
*             , up event,click event (along with a clickcnt parameter to indicate count of 
*             click, optional), long-press event (support repeat, optional).
*             模組提供了多個按鈕事件的判斷,包括:down即按下(閉合)事件、up即擡起(斷開)事件、
*             click即點選事件(可選的,附帶一個clickcnt引數來獲取連擊了多少次)、long-press即長按
*             事件(可選的,可以重複觸發)。
*          2. user can configure the macro arguments to custom the code and behavior of the 
*             module. For details, refer to the comment of each arguments in CONFIGURATION 
*             block.
*             使用者可以通過配置巨集引數來對模組進行定製。詳見CONFIGURATION塊中各引數的註釋。
*             for current implementation, each button will occupy 3 bytes of RAM; and if you 
*             don't need multi-click and long-press function, memory overhead can reduce to 
*             1 byte for each button.
*             對於當前的實現,每個按鈕要佔用3位元組RAM記憶體,如果你不使用連擊和長按功能的話,記憶體佔用
*             可以減小到1位元組。
*          3. to use the module, user should allocate a MECBTN_STATE struct for each button,
*             and poll the state of each button and pass it to MECBTN_STATE object,and get 
*             the events from value returned. All buttons are mutually independent.
*             為了使用該模組,使用者應該為每個按鈕分配一個MECBTN_STATE結構體,輪詢按鈕的狀態並傳遞給
*             MECBTN_STATE物件,然後從返回值中得到發生的事件。所有的按鈕是相互獨立的。
*             you can use a software/hardware timer to implement polling, each poll is a 'tick',
*             and polling polling interval is recommended to between 1ms and 10ms.
*             可以使用一個定時器來實現輪詢,每次輪詢即為一次tick,輪詢間隔推薦設為1ms到10ms。
*          4. example:
*             MECBTN_STATE aBtn; 
*             int main(){
*               ...
*               // initialize the button object
*               MecBtn_Init(&aBtn);
*               // start the timer.
*               TimerStart();
*               ...
*             }
*             void Timer_1ms(void){
*                MECBTN_EVENTFLAG rst;
*                INT8U stateNow,clickCnt;
*                stateNow = getBtnState();
*                rst = MecBtn_TimeTick(&aBtn,stateNow);
*                if(MecBtn_Event_any_happened(rst)){
*                   if(MecBtn_Event_down_happened(rst)){
*                       // down event
*                   }
*                   if(MecBtn_Event_up_happened(rst)){
*                       // up event
*                   }
*                   if(MecBtn_Event_click_happened(rst)){
*                       // click event
*                       clickCnt = MecBtn_clickTimes(rst);
*                       switch(clickCnt){
*                          case 1: // single click event
*                             ...
*                          break;
*                          case 2: // double click event
*                             ...
*                          break;
*                          ...
*                       }
*                   }
*                   if(MecBtn_Event_lPress_happened(rst)){
*                       // long-press event
*                   }
*                }
*             }
* 
*********************************************************************************************
*/

#ifndef   MECBTN_H
#define   MECBTN_H

/*
********************************************************************************************
*                                   MISCELLANEOUS
********************************************************************************************
*/

#ifndef  FALSE
#define  FALSE    0
#endif

#ifndef  TRUE
#define  TRUE     1
#endif

#ifndef  NULL
#define  NULL      0x00  
#endif


/*
*******************************************************************************************
*                                 CONFIGURATION  配置
*******************************************************************************************
*/
// 配置各種常數(單位:tick)
// 
/*  Ticks count for jitters elimination (0 — MECBTN_JITTERCNT_MAX) 
      Definition: At which tick the press-state will be changed and the up or down event 
      will be triggered after the first tick that changes press-state and states of press
      remain the same. 0 for triggered at first tick.
      消抖時間,單位tick,取值0到MECBTN_JITTERCNT_MAX。
      定義: 從第一次轉變狀態起(不含)一直保持狀態到第幾個tick算作狀態改變,也就是這次tick會
            觸發up或down事件。0表示不消抖,直接觸發。 */
#define  MECBTN_JE_TIME      9
  /*  Minimum interval ticks between multiple clicks. ((MECBTN_JE_TIME+2) — MECBTN_DURCNT_MAX) 
      Definition: At which tick multiple-click will be finished after the last tick that 
      triggered the click event and then no down event happened.
      兩次連擊間的最短間隔時間,單位tick,取值(MECBTN_JE_TIME+2)到MECBTN_DURCNT_MAX。
      定義: 從上次發生單擊事件的Tick起(不含)的到第幾個tick‘前’還沒有觸發下一次down事件
      (注意是down事件而不是click/up事件),則停止連擊。注意:即使第n個tick正好觸發了down
      事件,連擊也會終止。取值範圍不能小於(消抖時間+2)是因為要是小於這個值,根本就觸發不
      了連擊,那你直接關掉連擊呀。
  */
#define  MECBTN_MULTICLICK_INTERVAL_TIME  500
  /*  Ticks count for first long-press event to happen. (1 — MECBTN_DURCNT_MAX)
      Definition: At which tick the long-press event will be triggered after the first tick
      that triggered down event, 1 for trigger at next tick.
      按住多久算髮生長按事件,單位tick,取值1到MECBTN_DURCNT_MAX。
      定義: 從觸發down事件的Tick起(不含),連續到第幾個tick‘前’還沒有觸發任何事件,則觸發第
      一次長按事件。取1即為在下一個tick就觸發。
  */
#define  MECBTN_LONGPRESS_TIME       800
  /*  Ticks count for repeat long-press event. (1 — MECBTN_DURCNT_MAX)
      Definition: At which tick the long-press event will be triggered after the last tick
      that triggered long-press event, 1 for trigger at next tick.
      重複發生長按事件的時間,單位tick,取值1到MECBTN_DURCNT_MAX。
      定義: 從上一次觸發長按事件的Tick起(不含),如保持pressed狀態到第幾個tick‘前’,則再次
      觸發一次長按事件。取1即為在下一個tick就觸發。
  */
#define  MECBTN_LONGPRESS_REPT_TIME  300



// 是否檢查輸入引數(節省幾行程式碼)
#define MECBTN_SUPPORT_ARGUMENT_CHECK         FALSE

// 是否支援長按?
#define MECBTN_SUPPORT_LONGPRESS              FALSE

// 是否支援多次點選
#define MECBTN_SUPPORT_MULT_CLICK             FALSE

#if (MECBTN_SUPPORT_LONGPRESS == TRUE)

// 是否支援長按後重復發生長按事件(MECBTN_SUPPORT_LONGPRESS需為TRUE)
#define MECBTN_SUPPORT_LONGPRESS_PEPT         TRUE

// 長按後的擡起按鈕還算單擊麼(MECBTN_SUPPORT_LONGPRESS需為TRUE)
#define MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS  FALSE

#endif
/*
*******************************************************************************************
*                                      TYPE DEFINE
*******************************************************************************************
*/
//使用ucos-II時可以直接使用OS的定義
//#include "os_cpu.h"
// 定義變數,根據硬體修改
typedef unsigned short INT16U;
typedef unsigned char INT8U;

// 狀態機結構體,由使用者分配空間,但這些欄位是私有的。使用者不應該直接操作內部欄位以保證行為一致性
// struct for managing the state of a mechanical button.
// user should allocate memory for each button, but should not manipulate the struct directly. 
typedef struct {
  union {
    struct {
      unsigned int jitterCnt:5;          // 消抖計數
      unsigned int pressedLastTick:1;    // 上一Tick的狀態
      unsigned int pressed:1;            // 認為其有沒有按下(因為按鍵抖動的原因,所以兩個不見得一致)
      unsigned int lPressed:1;           // 是否已經發生了長按
    } Bits;
    INT8U Byte;
  } stateRegA;
  // 在即不支援連擊又不支援長按時可以省掉這個位元組。也就是隻提供消抖和單擊功能
#if((MECBTN_SUPPORT_MULT_CLICK == TRUE) || (MECBTN_SUPPORT_LONGPRESS == TRUE))
  union{
    struct {
      unsigned int clickCnt:4;           // 單擊次數計數
      unsigned int durCnt:12;            // 持續時間計數
    } Bits;
    INT16U Word;
  } stateRegB;
#endif
} MECBTN_STATE,* pMECBTN_STATE;
// max value of multi-click count
// 連擊計數的最大值    ((1 << 4) - 1)= 15
#define MECBTN_CLICKCNT_MAX  ((1 << 4) - 1)
// max ticks count value for jitters elimination 
// 消抖時間的最大值    ((1 << 5) - 1) = 31
#define MECBTN_JITTERCNT_MAX ((1 << 5) - 1)
// max value for multi-click interval, long-press ticks count and long-press repeat ticks count
// 連擊間隔時間、長按觸發時間、長按重複間隔時間的最大值  ((1 << 12) - 1) = 4095
#define MECBTN_DURCNT_MAX    ((1 << 12) - 1)

typedef union {
  INT8U Byte;
  struct {
    unsigned int down     :1;            // 閉合按鈕事件
    unsigned int up       :1;            // 斷開按鈕事件
    unsigned int click    :1;            // 單擊事件
    unsigned int lPress   :1;            // 長按事件
    unsigned int clickCnt :4;            // 高4位元組儲存連擊次數(只在發生單擊事件時有效)
  } Flags;
} MECBTN_EVENTFLAG;
#define MECBTN_EVENT_MASK         0x0f    // 提取事件的掩碼
#define MECBTN_EVENT_MASK_LPRESS  0x08
#define MECBTN_EVENT_MASK_CLICK   0x04
#define MECBTN_EVENT_MASK_UP      0x02
#define MECBTN_EVENT_MASK_DOWN    0x01

#define MECBTN_EVENTFLAG_isEqual(eventflag1,eventflag2) ((eventflag1).Byte == (eventflag2).Byte)

/*
******************************************************************************************
*                                    CONSTANT
******************************************************************************************
*/

#define MECBTN_PRESSED      TRUE
#define MECBTN_NONPRESSED   FALSE

/*
******************************************************************************************
*                                     Function  函式
******************************************************************************************
*/
// 返回按鈕是否當前處於按下狀態
// return whether the button is pressed.
#define  MecBtn_State_isPressed(Btn)           ((Btn).stateRegA.Bits.pressed)

// 返回按鈕是否當前正在長按狀態中(它是TRUE的話自然isPressed也是TRUE)
// return whether the button is long-pressed.
#define  MecBtn_State_islPressed(Btn)          ((Btn).stateRegA.Bits.lPressed)

// 返回按鈕當前已經連擊了多少次,最大MECBTN_CLICKCNT_MAX,然後不會再升,0表示還沒單擊過
// 只在MECBTN_SUPPORT_MULT_CLICK為TRUE時有效
// return how many times the button is clicked currently, will not exceed MECBTN_CLICKCNT_MAX.
// only valid when MECBTN_SUPPORT_MULT_CLICK is TRUE.
#define  MecBtn_State_clickCnt(Btn)            ((Btn).stateRegB.Bits.clickCnt)

/*
*********************************************************************************************
*                    MecBtn_Event_XXXX_happen(MECBTN_EVENTFLAG eventFlag)
*
* Description : to determine whether there is a XXXX event  
*               用於判斷是否發生了某事件
*
* Arguments   : eventFlag    value returned by MecBtn_TimeTick()
*                            由MecBtn_TimeTick()返回的值
*                            
* Return      : true     if the event happened
*                            
*               MECBTN_ERR_POINTER_EMPTY   if pBtn or pEvent is NULL.
* Note(s)     : 
*********************************************************************************************
*/

#define MecBtn_Event_any_happened(eventFlag)     ((eventFlag).Byte & MECBTN_EVENT_MASK)
#define MecBtn_Event_down_happened(eventFlag)    ((eventFlag).Flags.down)
#define MecBtn_Event_up_happened(eventFlag)      ((eventFlag).Flags.up)
#define MecBtn_Event_click_happened(eventFlag)   ((eventFlag).Flags.click)
#define MecBtn_Event_lPress_happened(eventFlag)  ((eventFlag).Flags.lPress)
// return : the clicked times up to now
#define MecBtn_clickTimes(eventFlag)             ((eventFlag).Byte >> 4)

/*
*********************************************************************************************
*                                    MecBtn_Init()
*
* Description : make sure that initialize the MECBTN_STATE struct via this function rather 
*               than by yourself.
*               確保用這個函式來初始化狀態機
*
* Arguments   : pBtn         pointer to the MECBTN_STATE object;       
*                            指向MECBTN_STATE物件(結構體實體)的指標;
*                            
* Return      : TRUE    if success
*               FALSE   if pBtn is NULL
* 
*               MECBTN_ERR_POINTER_EMPTY   if pBtn or pEvent is NULL.
* Note(s)     : 
*********************************************************************************************
*/

INT8U MecBtn_Init(pMECBTN_STATE pBtn);

/*
*********************************************************************************************
*                                      MecBtn_TimeTick;
*
* Description : Indicate a time tick and pass the state of button (pressed or not)  
*                通知狀態機一次時間tick並傳遞當前按鈕的狀態
*
* Arguments   : pBtn               pointer to the MECBTN_STATE object;       
*                                  指向MECBTN_STATE物件(結構體實體)的指標;
*               isPressedThisTick  the pressed state, MECBTN_NONPRESSED for not pressed, MECBTN_PRESSED for pressed.
*                                  按鈕是否這次被按下了,0代表當前沒按下(未接通),1代表按下了
*                            
* Return      : eventFlag    return the event happened at this tick(bitMode).
*                            返回此刻發生的事件(位模式,使用提供的函式來獲取資訊)
* 
*               MECBTN_ERR_POINTER_EMPTY   if pBtn or pEvent is NULL.
* Note(s)     : 
*********************************************************************************************
*/

MECBTN_EVENTFLAG MecBtn_TimeTick(pMECBTN_STATE pBtn, INT8U isPressedThisTick);

/*
************************************************************************************
*                          ERROR CHECK 錯誤檢查
************************************************************************************
*/
#if (MECBTN_SUPPORT_LONGPRESS == TRUE)
  #if (MECBTN_LONGPRESS_TIME < 1 || MECBTN_LONGPRESS_TIME > MECBTN_DURCNT_MAX)
    #error "MECBTN_LONGPRESS_TIME is out of range."
  #endif
  #if(MECBTN_SUPPORT_LONGPRESS_PEPT == TRUE)
    #if (MECBTN_LONGPRESS_REPT_TIME < 1 || MECBTN_LONGPRESS_REPT_TIME > MECBTN_DURCNT_MAX)
    #error "MECBTN_LONGPRESS_REPT_TIME is out of range."
    #endif
  #endif

#else
  #undef MECBTN_SUPPORT_LONGPRESS_PEPT
  #undef MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS
  #define MECBTN_SUPPORT_LONGPRESS_PEPT         FALSE
  #define MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS  FALSE
#endif


#if (MECBTN_JE_TIME < 0 || (MECBTN_JE_TIME > MECBTN_JITTERCNT_MAX))
#error "MECBTN_JE_TIME is out of range."
#endif

#if (MECBTN_SUPPORT_MULT_CLICK == TRUE)
  #if ((MECBTN_MULTICLICK_INTERVAL_TIME < (MECBTN_JE_TIME + 2)) || \
       (MECBTN_MULTICLICK_INTERVAL_TIME > MECBTN_DURCNT_MAX))
    #error "MECBTN_MULTICLICK_INTERVAL_TIME is out of range."
  #endif
#endif



#endif  // of  MECBTN_H

以下是.c原始檔

/*
*******************************************************************************************
*
*
*                                   MECHANICAL BUTTON MODULE
*                                        機械按鈕模組
*
* File : MecBtn.c
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2017/12/20
* version: V1.0
* History: 2017/12/20  V1.0   the prototype
*
* NOTE(s): 
*        
*********************************************************************************************
*/

/*
*********************************************************************************************
*                                       INCLUDES
*********************************************************************************************
*/

#include "MecBtn.h"

/*
*********************************************************************************************
*                                        CONSTANTS
*********************************************************************************************
*/
#define  B000  0
#define  B001  1
#define  B010  2
#define  B011  3
#define  B100  4
#define  B101  5
#define  B110  6
#define  B111  7

/*
*********************************************************************************************
*                                   LOCAL FUNCTION DECLARE
*********************************************************************************************
*/

// 是否上次是按下狀態
#define MecBtn_State_isPressedLastTick(Btn) ((Btn).stateRegA.Bits.pressedLastTick)
// 消抖暫存器的值
#define MecBtn_State_jitterCnt(Btn)         ((Btn).stateRegA.Bits.jitterCnt)
// dur暫存器的值
#define MecBtn_State_durCnt(Btn)            ((Btn).stateRegB.Bits.durCnt)
// 是否在多次連擊中,>0表示TRUE,要用於賦值的話只保證可以賦值FALSE
#define MecBtn_State_isMulClicking(Btn)     ((Btn).stateRegB.Bits.clickCnt)
// 重新載入Dur暫存器值,用於第一次長按
#define ReloadDurCnt_First(Btn)             (MecBtn_State_durCnt(Btn) = MECBTN_LONGPRESS_TIME)
// 為支援重複長按重新載入Dur暫存器值
#define ReloadDurCnt_Rep(Btn)               (MecBtn_State_durCnt(Btn) = MECBTN_LONGPRESS_REPT_TIME)
// 為支援連擊中斷重新載入Dur暫存器值
#define ReloadDurCnt_MC(Btn)                (MecBtn_State_durCnt(Btn) = MECBTN_MULTICLICK_INTERVAL_TIME)
// 重新載入jitter暫存器值
#define ReloadJitterCnt(Btn)                (MecBtn_State_jitterCnt(Btn) = MECBTN_JE_TIME)

// 進行一次抖動計數,如到達消抖時間就會復位消抖計數並改變Pressed狀態
// return:0  無變換
//         1  狀態發生了變化(發生了擡起或按下)
static INT8U jitterTick(pMECBTN_STATE pBtn,INT8U isPressedThisTick);
// 進行一次耗時計數,負責消除連擊計數以及判斷是否發生長按
// return:0  未發生長按
//         1  發生了長按事件
static INT8U durTick(pMECBTN_STATE pBtn);
// 在狀態改變時,即jitterTick返回值為1時進一步負責判斷是否發生了單擊事件,如支援連擊,則連擊次數
// 存在MecBtn_clickCnt(Btn)中
// return:0  未發生單擊
//         1  發生了單擊事件
static INT8U isClickEvent(pMECBTN_STATE pBtn,INT8U isPressedThisTick);
/*
*********************************************************************************************
*                                    MecBtn_Init()
*
* Description : make sure that initialize the MECBTN_STATE struct via this function rather 
*               than by yourself.
*               確保用這個函式來初始化狀態機
*
* Arguments   : pBtn         pointer to the MECBTN_STATE object;       
*                            指向MECBTN_STATE物件(結構體實體)的指標;
*                            
* Return      : TRUE    if success
*               FALSE   if pBtn is NULL
* 
*               MECBTN_ERR_POINTER_EMPTY   if pBtn or pEvent is NULL.
* Note(s)     : 
*********************************************************************************************
*/

INT8U MecBtn_Init(pMECBTN_STATE pBtn){
#if(MECBTN_SUPPORT_ARGUMENT_CHECK == TRUE)
  if(pBtn == NULL)
    return FALSE;
#endif
  pBtn->stateRegA.Bits.jitterCnt = MECBTN_JE_TIME;
  pBtn->stateRegA.Bits.lPressed = FALSE;
  pBtn->stateRegA.Bits.pressed = FALSE;
  pBtn->stateRegA.Bits.pressedLastTick = FALSE;
#if ((MECBTN_SUPPORT_MULT_CLICK == TRUE) || (MECBTN_SUPPORT_LONGPRESS == TRUE))
#if(MECBTN_SUPPORT_MULT_CLICK == TRUE)
  pBtn->stateRegB.Bits.clickCnt = 0;
#else
  pBtn->stateRegB.Bits.clickCnt = 1;
#endif
  pBtn->stateRegB.Bits.durCnt = 0;
#endif
  return TRUE;
}

/*
*********************************************************************************************
*                                    MecBtn_TimeTick()
*
* Description : Indicate a time tick and pass the state of button (pressed or not)  
*                通知狀態機一次時間tick並傳遞當前按鈕的狀態
*
* Arguments   : pBtn         pointer to the MECBTN_STATE object;       
*                            指向MECBTN_STATE物件(結構體實體)的指標;
*               isPressed    the press-state this tick, MECBTN_NONPRESSED for not pressed,
*                            MECBTN_PRESSED for pressed.
*                            這次tick中按鈕是否被按下了,MECBTN_NONPRESSED代表當前沒按下(未接通),
*                            MECBTN_PRESSED代表按下了
*                            
* Return      : eventFlag    return the event happened at this tick(bitMode).
*                            返回此刻發生的事件(位模式,使用提供的函式來獲取資訊)
* 
*               MECBTN_ERR_POINTER_EMPTY   if pBtn or pEvent is NULL.
* Note(s)     : 
*
*********************************************************************************************
*/

MECBTN_EVENTFLAG MecBtn_TimeTick(pMECBTN_STATE pBtn, INT8U isPressedThisTick){
#define Btn (*pBtn)
  MECBTN_EVENTFLAG rst;
  INT8U bEvent;
  rst.Byte = 0;
#if(MECBTN_SUPPORT_ARGUMENT_CHECK == TRUE)
  if(pBtn == NULL)
    return rst;
  isPressedThisTick = !!isPressedThisTick;     // 標準化BOOL值
#endif
#if((MECBTN_SUPPORT_MULT_CLICK == TRUE) || (MECBTN_SUPPORT_LONGPRESS == TRUE))
  // 先Tick一下延續時間,看有沒有長按事件
  if(durTick(pBtn))
    rst.Flags.lPress = TRUE;
#endif
  // 然後Tick消抖看有沒按鈕變化事件
  if(jitterTick(pBtn,isPressedThisTick)){  // !!isPressedThisTick for change 1..N to 1
    if(isPressedThisTick)
      rst.Flags.down = TRUE;
    else
      rst.Flags.up = TRUE;
    if(isClickEvent(pBtn,isPressedThisTick)){
      rst.Flags.click = TRUE;
#if(MECBTN_SUPPORT_MULT_CLICK == TRUE)
      rst.Flags.clickCnt = MecBtn_State_clickCnt(Btn);
#endif
    }
  }
#if(MECBTN_SUPPORT_LONGPRESS == TRUE)
#endif
  return rst;
#undef Btn
}
/*
*********************************************************************************************
*                                    LOCAL FUNCTION 
*********************************************************************************************
*/
#if((MECBTN_SUPPORT_MULT_CLICK == TRUE) || (MECBTN_SUPPORT_LONGPRESS == TRUE))
// 進行一次耗時計數,只負責處理是否發生長按事件及相關暫存器
// return:FALSE 未發生長按
//         TRUE  發生了長按事件
static INT8U durTick(pMECBTN_STATE pBtn){
#define Btn (*pBtn)
  if(MecBtn_State_isPressed(Btn)){
#if(MECBTN_SUPPORT_LONGPRESS == TRUE)
  #if(MECBTN_SUPPORT_LONGPRESS_PEPT == FALSE)
    if(MecBtn_State_islPressed(Btn) == FALSE)  // 不支援長按重複時就可以在已經長按時跳過後續操作了
  #endif
    if(--MecBtn_State_durCnt(Btn) == 0){       // 按下時dur內的值減到0意味著觸發長按
      MecBtn_State_islPressed(Btn) = TRUE;
  #if(MECBTN_SUPPORT_LONGPRESS_PEPT == TRUE)
      ReloadDurCnt_Rep(Btn);                   // 支援長按重複時暫存器載入下次觸發的計數值
  #endif
      return TRUE;
    }
#endif // of (MECBTN_SUPPORT_LONGPRESS == TRUE)
  }
#if(MECBTN_SUPPORT_MULT_CLICK == TRUE)
  else{
    if(MecBtn_State_isMulClicking(Btn)){        // 未按下時dur內的值減到0意味著停止連擊
      if(--MecBtn_State_durCnt(Btn) == 0)
        // 連擊停止事件
          MecBtn_State_isMulClicking(Btn) = FALSE;
    }
  }
#endif
  return FALSE;
#undef Btn
}
#endif
/* 只負責處理當前Tick是否導致閉合狀態的改變事件及相關暫存器,不處理單擊事件
  *  程式的處理過程基本按照下表
  *    the process follow the table, where X for no action, R for refresh, T for tick ,
  *     C for state converted :
  *
  *    | isPressed | isPressedLastTick | isPressedThisTick | jitter Count |
  *    |     0     |         0         |         0         |       X      | 
  *    |     0     |         0         |         1         |       R      | 
  *    |     0     |         1         |         0         |       R      | 
  *    |     0     |         1         |         1         |       T      |
  *    |     1     |         0         |         0         |       T      |
  *    |     1     |         0         |         1         |       R      |
  *    |     1     |         1         |         0         |       R      |
  *    |     1     |         1         |         1         |       X      |
  */
static INT8U jitterTick(pMECBTN_STATE pBtn,INT8U isPressedThisTick){
#define Btn (*pBtn)
  INT8U PressState;
  // 將三個量按位模式組合
  PressState = ((INT8U)MecBtn_State_isPressed(Btn) << 2) | ((INT8U)MecBtn_State_isPressedLastTick(Btn) << 1) 
    | isPressedThisTick;
  switch(PressState){
  default:            // 0b001 , 0b010 , 0b101 , 0b110
    MecBtn_State_isPressedLastTick(Btn) = isPressedThisTick;
    ReloadJitterCnt(Btn);
    // 故意不break,這樣就相容了MECBTN_JE_TIME為0的情況
  case B011:case B100: 
    // 如果計數器減到0說明發生了狀態改變事件
    if(MecBtn_State_jitterCnt(Btn)-- == 0){         
      ReloadJitterCnt(Btn);
      MecBtn_State_isPressed(Btn) = isPressedThisTick;
      return TRUE;
    }
    break;
  case B000:case B111: 
    break;
  }
  return FALSE;
#undef Btn
}

// 在狀態改變時,即jitterTick返回值為TRUE時進一步負責判斷是否發生了單擊事件,並處理相關欄位變化。
// 如支援連擊,則連擊次數存在MecBtn_clickCnt(Btn)中,負責與單擊有關的暫存器
// return:0  未發生單擊
//         1  發生了單擊事件
static INT8U isClickEvent(pMECBTN_STATE pBtn,INT8U isPressedThisTick){
#define Btn (*pBtn)
  INT8U islPressed;
  if(!isPressedThisTick){   // 擡起時清零lPress標誌位,載入Dur暫存器,根據配置決定算不算一次單擊事件

#if(MECBTN_SUPPORT_LONGPRESS == TRUE )
  #if (MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS != TRUE)
    islPressed = MecBtn_State_islPressed(Btn);
  #endif
    MecBtn_State_islPressed(Btn) = FALSE;
  #if (MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS != TRUE)
    if(!islPressed){
  #endif
#endif
#if(MECBTN_SUPPORT_MULT_CLICK == TRUE)  // 支援連擊時才有clickCnt的事,而且需要載入Dur暫存器以支援連擊中斷
      if(MecBtn_State_clickCnt(Btn)++ >=  MECBTN_CLICKCNT_MAX)
        MecBtn_State_clickCnt(Btn) = MECBTN_CLICKCNT_MAX;
      ReloadDurCnt_MC(Btn);
#endif
      return TRUE;
#if(MECBTN_SUPPORT_LONGPRESS == TRUE && MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS != TRUE)
    }
#endif
  }
#if(MECBTN_SUPPORT_LONGPRESS == TRUE)
  // 按下的時候需要重新載入下dur計數器
  else
    ReloadDurCnt_First(Btn);
#endif // of (MECBTN_SUPPORT_LONGPRESS == TRUE)
  return FALSE;
#undef Btn
}
#undef  MecBtn_State_isPressedLastTick
#undef  MecBtn_State_jitterCnt
#undef  MecBtn_State_durCnt
#undef  MecBtn_State_isMulClicking
#undef  ReloadDurCnt_First
#undef  ReloadDurCnt_Rep
#undef  ReloadJitterCnt
#undef  B000  0
#undef  B001  1
#undef  B010  2
#undef  B011  3
#undef  B100  4
#undef  B101  5
#undef  B110  6
#undef  B111  7

使用示例

下面簡單示範下在uCos-II,MC9S12XEP100環境下,使用兩個按鈕分別控制8個LED燈(在PORTB上)的前後4個的閃爍的程式碼。只展示main檔案中相關內容,無關內容已全部略去。

#include "includes.h"
#include "MecBtn.h"
#include "LED.h"
#define  KEY_dir  DDRH
#define  F1       PTIH_PTIH4
#define  F1_dir   DDRH_DDRH4
#define  F2       PTIH_PTIH7
#define  F2_dir   DDRH_DDRH7
/*
*********************************************************************************************************
*                                      STACK SPACE DECLARATION
*********************************************************************************************************
*/
static  OS_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];
static  OS_STK  AppTaskScanBtnStk[APP_TASK_SCANBTN_STK_SIZE];

/*
*********************************************************************************************************
*                                      TASK FUNCTION DECLARATION
*********************************************************************************************************
*/

static  void    AppTaskStart(void *p_arg);
static  void    AppTaskScanBtn(void *p_arg);
/*
*********************************************************************************************************
*                                      LOCAL  VARIABLE  DECLARE
*********************************************************************************************************
*/
static MECBTN_STATE Btn_F1;      // 按鈕F1物件
static MECBTN_STATE Btn_F2;      // 按鈕F2物件
/*
*********************************************************************************************************
*                                      LOCAL  FUNCTION  DECLARE
*********************************************************************************************************
*/

// 初始化看門狗
static void INIT_COP(void);
// 初始化燈和按鍵 
static void Btn_Init(void) 
{
  F1_dir=0;           // 設定F1鍵為輸入
  F2_dir=0;           // 設定F2鍵為輸入
  MecBtn_Init(&Btn_F1); // 初始化對應的機械按鈕物件
  MecBtn_Init(&Btn_F2); // 初始化對應的機械按鈕物件
}
/*
*********************************************************************************************************
*                                           MAIN FUNCTION
*********************************************************************************************************
*/
void main(void) {
    INT8U  err;    
    BSP_IntDisAll();                                                    /* Disable ALL interrupts to the interrupt controller       */
    OSInit();                                                           /* Initialize uC/OS-II                                      */

    err = OSTaskCreate(AppTaskStart,
                          NULL,
                          (OS_STK *)&AppTaskStartStk[APP_TASK_START_STK_SIZE - 1],
                          APP_TASK_START_PRIO);                     
    OSStart();
}

static  void  AppTaskStart (void *p_arg)
{
    INT8U  err;
    (void)p_arg;                                            /* Prevent compiler warning    */
    BSP_Init(); 
    LED_Init(LED_ALL);
    Btn_Init();
    err = OSTaskCreate(AppTaskScanBtn,
                   NULL,
                   (OS_STK *)&AppTaskScanBtnStk[APP_TASK_SCANBTN_STK_SIZE - 1],
                   APP_TASK_SCANBTN_PRIO);
    while (DEF_TRUE) 
    {
       OSTimeDlyHMSM(1,0,0,0);
    }

}
static void AppTaskScanBtn(void *p_arg){
  MECBTN_EVENTFLAG rst;
  INT8U clickCnt;
  while (DEF_TRUE) {
    rst = MecBtn_TimeTick(&Btn_F1,!F1);   // 這塊開發板上,低電平時為按下
      if(MecBtn_Event_click_happened(rst)){
         // click event
        clickCnt = MecBtn_clickTimes(rst);
        switch(clickCnt){
          case 0:                           // 當前實現中,如果不支援連擊,這個值是0
          LED_Toggle(LED_1);
          break;
          case 4:case 3:case 2:case 1:      // F1鍵控制低4個LED
          LED_Toggle(NumToLED(clickCnt));
          break;
          default:
          break;
        }
       }
       if(MecBtn_Event_lPress_happened(rst)){
        // long-press event
         LED_Toggle(LED_1|LED_2|LED_3|LED_4);      // 每次長按事件時反轉LED1-4
       }
    rst = MecBtn_TimeTick(&Btn_F2,!F2);   // 這塊開發板上,低電平時為按下
      if(MecBtn_Event_click_happened(rst)){
         // click event
        clickCnt = MecBtn_clickTimes(rst);
        switch(clickCnt){
          case 0:
          LED_Toggle(LED_5);
          break;
          case 4:case 3:case 2:case 1:      // F2鍵控制高4個LED
          LED_Toggle(NumToLED(clickCnt + 4));
          break;
          default:
          break;
        }
       }
       if(MecBtn_Event_lPress_happened(rst)){
        // long-press event
         LED_Toggle(LED_5|LED_6|LED_7|LED_8);      // 每次長按事件時反轉LED5-8
       }
    OSTimeDlyHMSM(0,0,0,1);                    // 延遲1ms再次輪詢
  }
}

測試用例

這是我自己寫的測試,蠻放出來。

/*
*******************************************************************************************
*
*
*                                 MECHANICAL BUTTON MODULE TEST
*                                        機械按鈕模組配套測試
*
* File : MecBtn.c
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2017/12/20
* version: V1.0
* History: 2017/04/24  V1.0   the prototype
*
* NOTE(s): 
*        
*********************************************************************************************
*/
#include <stdio.h>
#include "../MecBtn.h"

static MECBTN_STATE aBtn;
// 迴圈times次Tick的pressed狀態中是否發生了事件
static INT8U MecBtnTest_loop_hasEvent(int times,INT8U pressed){
  int i;
  for(i = 0; i < times; i++)
    if(MecBtn_Event_any_happened(MecBtn_TimeTick(&aBtn,pressed)))
      return TRUE;
  return FALSE;
}
// 迴圈times次Tick的未按下狀態中是否發生了事件
static INT8U MecBtnTest_up_hasEvent(int times){
  return MecBtnTest_loop_hasEvent(times,MECBTN_NONPRESSED);
}
static INT8U MecBtnTest_down_hasEvent(int times){
  return MecBtnTest_loop_hasEvent(times,MECBTN_PRESSED);
}

// 迴圈times次Tick的同個狀態,返回最後一次的eventFlag
static MECBTN_EVENTFLAG MecBtnTest_loop(int times,INT8U pressed){
  int i;
  static INT8U emptyEvent = 0;
  if(times == 0)
    return *(MECBTN_EVENTFLAG *)(&emptyEvent);   // 空MECBTN_EVENTFLAG結構的表示方法
  for(i = 0; i < times - 1; i++)
     MecBtn_TimeTick(&aBtn,pressed);
  return MecBtn_TimeTick(&aBtn,pressed);
}
// 保持times次的擡起(斷開),返回最後一次的狀態
static MECBTN_EVENTFLAG MecBtnTest_up(int times){
  return MecBtnTest_loop(times, MECBTN_NONPRESSED);
}
// 保持times次的按下(閉合),返回最後一次的狀態
static MECBTN_EVENTFLAG MecBtnTest_down(int times){
  return MecBtnTest_loop(times, MECBTN_PRESSED);
}
static void MecBtnTest_press(void){
  MecBtnTest_down(MECBTN_JE_TIME + 1);
  return;
}
// 測試消抖
static INT8U JE_test(){
  MecBtn_Init(&aBtn);
  if(MecBtnTest_down_hasEvent(MECBTN_JE_TIME)) return FALSE;
  if(MecBtnTest_up_hasEvent(2)) return FALSE;
  return TRUE;
}
static INT8U Down_test(){
  MecBtn_Init(&aBtn);
  if(MecBtnTest_down_hasEvent(MECBTN_JE_TIME)) return FALSE;
  if(MecBtnTest_down(1).Byte != MECBTN_EVENT_MASK_DOWN) return FALSE;
  return TRUE;
}
static INT8U Up_test(){
  MECBTN_EVENTFLAG flagTmp,reg;
  MecBtn_Init(&aBtn);
  // 這個測試的預設條件:起碼MECBTN_LONGPRESS_TIME比MECBTN_JE_TIME大2
  if(MECBTN_LONGPRESS_TIME < MECBTN_JE_TIME + 2)
    return TRUE;
  reg.Byte = 0;
  MecBtnTest_press();       // 按下按鈕
  // up+click事件
  if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_TIME - MECBTN_JE_TIME - 2)) return FALSE;
  if(MecBtnTest_up_hasEvent(MECBTN_JE_TIME)) return FALSE;
  flagTmp = MecBtnTest_up(1);
  // 構造應該有的結果
  reg.Byte = MECBTN_EVENT_MASK_UP | MECBTN_EVENT_MASK_CLICK;
#if (MECBTN_SUPPORT_MULT_CLICK == TRUE)
  reg.Flags.clickCnt = 1;
#endif
#if ((MECBTN_SUPPORT_LONGPRESS == TRUE))
  if( MECBTN_LONGPRESS_TIME == 1){                // MECBTN_LONGPRESS_TIME == 1的邊際條件要特殊處理。。
  reg.Flags.lPress = TRUE;
  #if (MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS == FALSE)
  reg.Flags.click = FALSE;
  reg.Flags.clickCnt = 0;
  #endif
  }
#else
  // 不支援長按時up肯定會伴隨click事件
  reg.Flags.click = TRUE;
#endif
  if(!MECBTN_EVENTFLAG_isEqual(flagTmp,reg)) // 只應該發生單擊和擡起事件,且單擊計數為1
    return FALSE;
  // up + long-press (/+ click) 事件
  MecBtnTest_press();  
  if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_TIME - MECBTN_JE_TIME - 1)) return FALSE;
  if(MecBtnTest_up_hasEvent(MECBTN_JE_TIME)) return FALSE;
  flagTmp = MecBtnTest_up(1); 
  reg.Byte = MECBTN_EVENT_MASK_UP;
#if (MECBTN_SUPPORT_LONGPRESS == TRUE)
  reg.Flags.lPress = TRUE;
  #if (MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS == TRUE)
  reg.Flags.click = TRUE;
  #endif
#else
  // 不支援長按時up肯定會伴隨click事件
  reg.Flags.click = TRUE;
#endif
  if((flagTmp.Byte & MECBTN_EVENT_MASK) != (reg.Byte & MECBTN_EVENT_MASK)) 
    return FALSE;
  return TRUE;
}
// 主要測試連擊計數功能,click功能實際已經在up測試中測試了
static INT8U Click_test(){
  MECBTN_EVENTFLAG reg;
  INT8U i,max;
  // 這個測試的預設條件:起碼MECBTN_LONGPRESS_TIME比MECBTN_JE_TIME大2
  if(MECBTN_LONGPRESS_TIME < MECBTN_JE_TIME + 2)
    return TRUE;
#if (MECBTN_SUPPORT_MULT_CLICK == TRUE)
  max = MECBTN_CLICKCNT_MAX;          // 支援連擊要測試到最大值都正常
#else
  max = 1;                            // 不支援連擊時只用迴圈1次
#endif
  MecBtn_Init(&aBtn);
  // 連擊計數測試
  reg.Byte = 0;
  reg.Flags.click = TRUE;
  reg.Flags.up = TRUE;
  for(i = 0; i < max; i++){
    MecBtnTest_press();       // 按下按鈕
    MecBtnTest_up(MECBTN_JE_TIME);
#if (MECBTN_SUPPORT_MULT_CLICK == TRUE)
    reg.Flags.clickCnt++;
#endif
    if(!MECBTN_EVENTFLAG_isEqual(MecBtnTest_up(1),reg))
      return FALSE;
  }
  // 再點選一次計數應該不變
  MecBtnTest_press();       // 按下按鈕
  MecBtnTest_up(MECBTN_JE_TIME);
  if(!MECBTN_EVENTFLAG_isEqual(MecBtnTest_up(1),reg))
    return FALSE;
#if (MECBTN_SUPPORT_MULT_CLICK == TRUE)      // 如支援連擊,需再測試下連擊中斷
  if(MecBtnTest_up_hasEvent(MECBTN_MULTICLICK_INTERVAL_TIME - 1)) return FALSE;
  // 這邊出問題說明連擊中斷比規定提前了
  if(MecBtn_State_clickCnt(aBtn) != MECBTN_CLICKCNT_MAX) return FALSE;
  if(MecBtnTest_up_hasEvent(1)) return FALSE;
  // 這邊出問題說明連擊沒有按照要求中斷
  if(MecBtn_State_clickCnt(aBtn) != 0) return FALSE;
#endif
  return TRUE;
}
// 主要測試重複長按功能,首次長按功實際已經在up測試中測試了
static INT8U lPress_test(){
#if(MECBTN_SUPPORT_LONGPRESS == TRUE)
  MECBTN_EVENTFLAG flagTmp,reg;
  INT8U i;
  MecBtn_Init(&aBtn);
  // 連擊計數測試
  reg.Byte = 0;
  reg.Flags.lPress = TRUE;
  MecBtnTest_press();       // 按下按鈕
  // 第一次的觸發長按的時長是MECBTN_LONGPRESS_TIME
  if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_TIME - 1)) return FALSE;
  if(!MECBTN_EVENTFLAG_isEqual(MecBtnTest_down(1),reg)) return FALSE;
  #if(MECBTN_SUPPORT_LONGPRESS_PEPT == TRUE)
  // 測試10次重複長按
  for(i = 0; i < 9; i++){
    if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_REPT_TIME - 1)) return FALSE;
    if(!MECBTN_EVENTFLAG_isEqual(MecBtnTest_down(1),reg)) return FALSE;
  }
  #endif
  // 最後一次可能會有click事件
  reg.Flags.up = TRUE;
  #if (MECBTN_SUPPORT_CLICK_AFTER_LONGPRESS == TRUE)
  reg.Flags.click = TRUE;
    #if (MECBTN_SUPPORT_MULT_CLICK == TRUE)
  reg.Flags.clickCnt = 1;
    #endif
  #endif
  #if(MECBTN_SUPPORT_LONGPRESS_PEPT == TRUE)
  if(MECBTN_LONGPRESS_REPT_TIME < MECBTN_JE_TIME + 2) return TRUE; // 邊際條件暫不支援
  if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_REPT_TIME - MECBTN_JE_TIME - 1)) return FALSE;
  #else
  if(MecBtnTest_down_hasEvent(MECBTN_LONGPRESS_TIME + MECBTN_JE_TIME + 100)) return FALSE;
  // 不支援連續長按時擡起不應該觸發長按事件
  reg.Flags.lPress = FALSE;
  #endif
  if(!MECBTN_EVENTFLAG_isEqual(MecBtnTest_up(MECBTN_JE_TIME+1),reg)) return FALSE;
#endif // of (MECBTN_SUPPORT_LONGPRESS == TRUE)
  return TRUE;
}
INT8U MecBtnTest_TimeTick(void){
  MECBTN_EVENTFLAG flagTmp;
  // 測試消抖
  if(!JE_test()) return FALSE;
  // 測試按下事件
  if(!Down_test()) return FALSE;
  // 測試擡起事件
  if(!Up_test()) return FALSE;
  // 測試連擊計數
  if(!Click_test()) return FALSE;
  // 長按測試
  if(!lPress_test()) return FALSE;
 //*/
  return TRUE;
}

int main(){
  if(MecBtnTest_TimeTick())
    printf("MecBtn模組通過測試。");
  else
    printf("MecBtn模組未通過測試。");
  return 0;
}

後記

1.這個模組放到微控制器上執行時發現

  union {
    struct {
      unsigned int pressedLastTick:1;    // 上一Tick的狀態
      unsigned int pressed:1;            // 認為其有沒有按下(因為按鍵抖動的原因,所以兩個不見得一致)
      unsigned int lPressed:1;           // 是否已經發生了長按
      unsigned int jitterCnt:5;          // 消抖計數
    } Bits;
    INT8U Byte;
  } stateRegA;

這樣的順序放欄位時,執行XXXXX.jitterCnt- - 時會出錯,jitterCnt數值直接變為了15,在windows上測試時卻沒問題,然後我把jitterCnt放到了最低位就沒有問題了,這個事情蠻困擾我的,希望有大神解惑。

2.對於特定場合,如只需要判斷16個按鍵的單擊,甚至消抖都不用的情況,這個模組的效率確實遠不如那些專用的演算法,面向物件的威力只有在需要實現很複雜的邏輯時才能體現出來。

--------------------- 本文來自 夏日白雲 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/lin_strong/article/details/78897160?utm_source=copy