[嵌入式開發模塊]4X4矩陣鍵盤掃描 基於MC9S12XEP100

版權聲明:本文爲博主(http://blog.csdn.net/lin_strong)原創文章,轉載請標明出處並事先取得博主同意 https://blog.csdn.net/lin_strong/article/details/78913339

引言

本文基於之前封裝好的 機械按鈕模塊 http://blog.csdn.net/lin_strong/article/details/78897160,示例實現了一個4X4矩陣鍵盤的模塊,簡述了鍵盤掃描的實現,同時示例了怎麼把模塊原先的同步返回的事件轉換爲異步事件通知。

模塊源碼

這次先貼出模塊代碼。
.h文件:

/*
*******************************************************************************************
*
*
*                                   MATRIX KEYBOARD MODULE
*                                        矩陣鍵盤模塊
*
* File : MatKB.h
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2017/12/27
* version: V1.0
* History: 2017/12/27  V1.0   the prototype
*
* NOTE(s): 1. 基於機械按鈕模塊實現對矩陣鍵盤的封裝,實現行列掃描及異步事件通知
*          2. 當前實現使用的矩陣鍵盤的排布如下,如使用其他排布,請自行修改實現
*                         1     2     3     A
*                         4     5     6     B
*                         7     8     9     C
*                         *     0     #     D
* 
*********************************************************************************************
*/

#ifndef  MATKB_H
#define  MATKB_H

#ifdef   MATKB_GLOBALS
#define  MATKB_EXT
#else
#define  MATKB_EXT  extern
#endif

/*
*******************************************************************************************
*                                   INCLUDE FILE
*******************************************************************************************
*/
// 根據實際存放位置修改
#include "../機械按鈕模塊/MecBtn.h"

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

#ifndef  FALSE
#define  FALSE    0
#endif

#ifndef  TRUE
#define  TRUE     1
#endif

#ifndef  NULL
#define  NULL      0x00  
#endif


/*
*******************************************************************************************
*                                 CONFIGURATION  配置
*******************************************************************************************
*/
// 按鈕支持的功能請去MecBtn.h內設置

// 是否支持DOWN事件
#define MATKB_SUPPORT_EVENT_DOWN    TRUE
// 是否支持up事件
#define MATKB_SUPPORT_EVENT_UP      TRUE
// 是否支持click事件
#define MATKB_SUPPORT_EVENT_CLICK   TRUE
// 是否支持long-press事件
#define MATKB_SUPPORT_EVENT_LPRESS  MECBTN_SUPPORT_LONGPRESS


/*
*******************************************************************************************
*                                      TYPE DEFINE
*******************************************************************************************
*/
// ID of each key
enum KEY_ID{
  KEY_1,    KEY_2,    KEY_3,    KEY_A,
  KEY_4,    KEY_5,    KEY_6,    KEY_B,
  KEY_7,    KEY_8,    KEY_9,    KEY_C,
  KEY_STAR, KEY_0,    KEY_HASH, KEY_D,
};

/*
******************************************************************************************
*                                    EVENTS
******************************************************************************************
*/

typedef void (* MatKB_KeyEvent)(INT8U keyID,void *arg);
#if(MATKB_SUPPORT_EVENT_DOWN == TRUE)
// 當有按鍵被按下時被觸發,keyID爲對應按鍵的ID,arg爲NULL
MATKB_EXT MatKB_KeyEvent MatKB_onKeyDown;
#endif
#if(MATKB_SUPPORT_EVENT_UP == TRUE)
// 當有按鍵被擡起時被觸發,keyID爲對應按鍵的ID,arg爲NULL
MATKB_EXT MatKB_KeyEvent MatKB_onKeyUp;
#endif
#if(MATKB_SUPPORT_EVENT_CLICK == TRUE)
// 當有按鍵被擡起時被觸發,keyID爲對應按鍵的ID,((INT16U)arg)爲連擊次數
MATKB_EXT MatKB_KeyEvent MatKB_onKeyClick;
#endif
#if(MATKB_SUPPORT_EVENT_LPRESS == TRUE)
// 當有按鍵發生長按事件時被觸發,keyID爲對應按鍵的ID,arg爲NULL
MATKB_EXT MatKB_KeyEvent MatKB_onKeyLPress;
#endif
/*
******************************************************************************************
*                                     Function  函數
******************************************************************************************
*/

/*
*********************************************************************************************
*                                    MatKB_Init()
*
* Description : To initialize module.
*               用其來初始化模塊
*
* Arguments   : 
*                            
* Return      : TRUE    if success
*               FALSE   if fail
* 
* Note(s)     : 
*********************************************************************************************
*/

INT8U MatKB_Init(void);

/*
*********************************************************************************************
*                                      MatKB_TimeTick()
*
* Description : Indicate a time tick to drive the module. User should use a hardware/software
*               timer to periodically call this routine. The definition of a tick can be 
*               found in MecBtn.h . 
*               通知一次tick以驅動模塊。 用戶應該使用一個軟/硬件定時器來定時調用這個例程。tick
*               的定義詳見MecBtn.h。
*
* Arguments   : 
*
* Return      :  
*
* Note(s)     : 
*********************************************************************************************
*/

void MatKB_TimeTick(void);

/*
*********************************************************************************************
*                                      MatKB_isPressedKey()
*
* Description : To check whether the indicated key is pressed
*
* Arguments   : keyID     the ID of the key(see KEY_ID)
*
* Return      : TRUE      is pressed
*               FALSE     is not pressed.
*
* Note(s)     : 
*********************************************************************************************
*/

INT8U MatKB_isPressedKey(INT8U keyID);

/*
*********************************************************************************************
*                                      MatKB_isLongPressedKey()
*
* Description : To check whether the indicated key is long-pressed
*
* Arguments   : keyID     the ID of the key(see KEY_ID)
*
* Return      : TRUE      is long-pressed
*               FALSE     is not long-pressed.
*
* Note(s)     : 
*********************************************************************************************
*/

INT8U MatKB_isLongPressedKey(INT8U keyID);

/*
************************************************************************************
*                          ERROR CHECK 錯誤檢查
************************************************************************************
*/


#endif  // of  MATKB_H

然後是.c文件

/*
*******************************************************************************************
*
*
*                                   MATRIX KEYBOARD MODULE
*                                        矩陣鍵盤模塊
*
* File : MatKB.c
* By   : Lin Shijun(http://blog.csdn.net/lin_strong)
* Date:  2017/12/27
* version: V1.0
* History: 2017/12/27  V1.0   the prototype
*
* NOTE(s): 1. 基於機械按鈕模塊實現對矩陣鍵盤的封裝,實現行列掃描及異步事件通知
*          2. 當前實現使用的矩陣鍵盤的排布如下,如使用其他排布,請自行修改實現
*                         1     2     3     A
*                         4     5     6     B
*                         7     8     9     C
*                         *     0     #     D
*          3. 當前實現基於HCS12XEP100的,4X4鍵盤的列線0-3接在A0-A3上,行線0-3接在A4-A7上。
*             如改成其他單片機或其他端口,需要進行相應修改
*             
*********************************************************************************************
*/

/*
*********************************************************************************************
*                                       INCLUDES
*********************************************************************************************
*/
#define MATKB_GLOBALS
#include "MatKB.h"
#include <MC9S12XEP100.h>
/*
*********************************************************************************************
*                                   LOCAL VARIABLE 
*********************************************************************************************
*/

static MECBTN_STATE KeysMaxtrix[4][4];
#define MATKB_SIZE (sizeof(KeysMaxtrix)/sizeof(MECBTN_STATE))

/*
*********************************************************************************************
*                                        CONSTANTS
*********************************************************************************************
*/

#define BTN_DDR   DDRA               // PORTA 方向寄存器
#define BTN_STATE PORTA              // PORTA IO寄存器
#define BTN_RDR   RDRIV_RDPA         // PORTA 降驅動寄存器
#define BTN_PUP   PUCR_PUPAE         // PORTA 上拉寄存器

/*
*********************************************************************************************
*                                   LOCAL FUNCTION DECLARE
*********************************************************************************************
*/
// 構造1在第n bit上的掩碼,n >= 0
#define createMask(n)       (1 << (n))

// 掃描第n行 n = 0~3
// void MatKB_ScanRow(INT8U n);
#define MatKB_ScanRow(n)       (BTN_STATE = (~createMask(n)))

// 掃描第n列的是否按下(閉合/連通) n = 0~3
// INT8U MatKB_isPressedCol(INT8U n);
// return: TRUE pressed
#define MatKB_isPressedCol(n)  ((BTN_STATE & createMask((n) + 4)) == 0)      // 因爲使用了上拉電阻,在爲0時是按下

/*
*********************************************************************************************
*                                    MatKB_Init()
*
* Description : To initialize module.
*               用其來初始化模塊
*
* Arguments   : 
*                            
* Return      : TRUE    if success
*               FALSE   if fail
* 
* Note(s)     : 
*********************************************************************************************
*/

INT8U MatKB_Init(void){
  INT8U i;
  pMECBTN_STATE pBtn = &KeysMaxtrix[0][0];
  for(i = 0; i < MATKB_SIZE; i++){
    MecBtn_Init(pBtn++);
  }
  BTN_DDR = 0x0F;     // 列掃描信號設爲輸入,行掃描信號爲輸出
  BTN_RDR = 1;        // 降驅動
  BTN_PUP = 1;        // 使能上拉電阻
  return TRUE;
}

/*
*********************************************************************************************
*                                      MatKB_TimeTick()
*
* Description : Indicate a time tick to drive the module. User should use a hardware/software
*               timer to periodically call this routine. The definition of a tick can be 
*               found in MecBtn.h . 
*               通知一次tick以驅動模塊。 用戶應該使用一個軟/硬件定時器來定時調用這個例程。tick
*               的定義詳見MecBtn.h。
*
* Arguments   : 
*
* Return      :  
*
* Note(s)     : 
*********************************************************************************************
*/

void MatKB_TimeTick(void){
  INT8U i,j,ID_index;
  MECBTN_EVENTFLAG rst;
  ID_index = 0;       // 對應一維數組的索引,和KEY_ID正好是一一對應關係
  for(i = 0; i < 4; i++){
    MatKB_ScanRow(i);           // 依次掃描0-3行
    for(j = 0; j < 4; j++){
      // 驅動i行j列的按鈕
      rst = MecBtn_TimeTick(&KeysMaxtrix[i][j],MatKB_isPressedCol(j));
      if(MecBtn_Event_any_happened(rst)){
#if(MATKB_SUPPORT_EVENT_DOWN == TRUE)
        if(MecBtn_Event_down_happened(rst) && MatKB_onKeyDown != NULL){
           MatKB_onKeyDown(ID_index,NULL);
        }
#endif
#if(MATKB_SUPPORT_EVENT_UP == TRUE)
        if(MecBtn_Event_up_happened(rst) && MatKB_onKeyUp != NULL){
           MatKB_onKeyUp(ID_index,NULL);
        }
#endif
#if(MATKB_SUPPORT_EVENT_CLICK == TRUE)
        if(MecBtn_Event_click_happened(rst) && MatKB_onKeyClick != NULL){
  #if(MECBTN_SUPPORT_MULT_CLICK == TRUE)
          MatKB_onKeyClick(ID_index,(void *)MecBtn_clickTimes(rst));
  #else
          MatKB_onKeyClick(ID_index,(void *)1);
  #endif
        }
#endif
#if(MATKB_SUPPORT_EVENT_LPRESS == TRUE)
        if(MecBtn_Event_lPress_happened(rst) && MatKB_onKeyLPress != NULL){
          MatKB_onKeyLPress(ID_index,NULL);
        }
#endif
      }
      ID_index++;     
    }
  }
}

/*
*********************************************************************************************
*                                      MatKB_isPressedKey()
*
* Description : To check whether the indicated key is pressed
*
* Arguments   : keyID     the ID of the key(see KEY_ID)
*
* Return      : TRUE      is pressed
*               FALSE     is not pressed.
*
* Note(s)     : 
*********************************************************************************************
*/

INT8U MatKB_isPressedKey(INT8U keyID){
  if(keyID >= MATKB_SIZE)
    return FALSE;              // 參數檢查
  return MecBtn_State_isPressed(((MECBTN_STATE *)KeysMaxtrix)[keyID]);
}

/*
*********************************************************************************************
*                                      MatKB_isLongPressedKey()
*
* Description : To check whether the indicated key is long-pressed
*
* Arguments   : keyID     the ID of the key(see KEY_ID)
*
* Return      : TRUE      is long-pressed
*               FALSE     is not long-pressed.
*
* Note(s)     : 
*********************************************************************************************
*/
#if(MATKB_SUPPORT_EVENT_LPRESS == TRUE)
INT8U MatKB_isLongPressedKey(INT8U keyID){
  if(keyID >= MATKB_SIZE)
    return FALSE;              // 參數檢查
  return MecBtn_State_islPressed(((MECBTN_STATE *)KeysMaxtrix)[keyID]);
}
#endif

#undef MATKB_GLOBALS

模塊原理簡介

矩陣鍵盤

矩陣鍵盤是網上買的,爲了避免廣告嫌疑,儘量不出現購買網址等。直接自己重拍了張照片。並標上了實驗出來的行列線排布。

如圖所示,我將8條線直接接到了MC9S12XEP100的PORTA上,A0-A3對應行線,A4-A7對應列線。
然後爲了標識每個按鍵,很簡單的用了個枚舉類型:

enum KEY_ID{
  KEY_1,    KEY_2,    KEY_3,    KEY_A,
  KEY_4,    KEY_5,    KEY_6,    KEY_B,
  KEY_7,    KEY_8,    KEY_9,    KEY_C,
  KEY_STAR, KEY_0,    KEY_HASH, KEY_D,
};

這樣寫之後,KEY_1對應0,KEY_2對應1,……,KEY_4對應4,…… 可以很簡單地建立起行列與ID間的對應關係。
在各事件中,用戶也可以很方便地使用返回的ID值,來實現自己的業務需求。

矩陣鍵盤掃描

爲了獲取每個按鍵的狀態,矩陣鍵盤需要進行逐行掃描。於是我將行線設爲輸出引腳,列線作爲輸入引腳。
MC9S12XEP100的PORTA提供了內部的上拉電阻,我將其使能,這樣,輸入引腳(列線,A4-A7)在懸空的時候就會穩定在高電平也就是讀爲1。

INT8U MatKB_Init(void){
  ……
  BTN_DDR = 0x0F;     // 列掃描信號設爲輸入,行掃描信號爲輸出
  BTN_RDR = 1;        // 降驅動
  BTN_PUP = 1;        // 使能上拉電阻
  ……
}

然後對於行線(A0-A3),需要掃描哪一行的時候我就使其輸出爲0,而其他行的輸出爲1。
這樣噹噹前行上如果有哪個按鍵閉合了,則對應列的引腳讀出來的就會降爲0。也就能確定每行每列的按鍵的狀態了。
也就是實現文件中的local function塊乾的事情。

// 構造1在第n bit上的掩碼,n >= 0
#define createMask(n)       (1 << (n))
// 掃描第n行 n = 0~3
// void MatKB_ScanRow(INT8U n);
#define MatKB_ScanRow(n)       (BTN_STATE = (~createMask(n)))
// 掃描第n列的是否按下(閉合/連通) n = 0~3
// INT8U MatKB_isPressedCol(INT8U n);
// return: TRUE pressed
#define MatKB_isPressedCol(n)  ((BTN_STATE & createMask((n) + 4)) == 0)      // 因爲使用了上拉電阻,在爲0時是按下

按鈕事件判斷

受益於封裝好的機械按鈕模塊,跟獨立按鈕有關的消抖、長按、連擊等的判斷都不需要自己負責了。所以這個模塊只需要爲每個按鍵分別分配一個機械按鈕對象實體:

static MECBTN_STATE KeysMaxtrix[4][4];

並在初始化時調用機械按鈕模塊提供的初始化函數對所有實體進行初始化。

INT8U MatKB_Init(void){
  ……
  pMECBTN_STATE pBtn = &KeysMaxtrix[0][0];
  for(i = 0; i < MATKB_SIZE; i++){
    MecBtn_Init(pBtn++);
  }
  ……
}

這樣,每次Tick的時候只需要輪詢所有按鈕的狀態,分別調用各個對象的TimeTick方法,就能很方便的得到每個按鈕發生了什麼事件。

void MatKB_TimeTick(void){
  INT8U i,j,ID_index;
  MECBTN_EVENTFLAG rst;
  ID_index = 0;       // 對應一維數組的索引,和KEY_ID正好是一一對應關係
  for(i = 0; i < 4; i++){
    MatKB_ScanRow(i);           // 依次掃描0-3行
    for(j = 0; j < 4; j++){
      // 驅動i行j列的按鈕
      rst = MecBtn_TimeTick(&KeysMaxtrix[i][j],MatKB_isPressedCol(j));
      if(MecBtn_Event_any_happened(rst)){
        if(MecBtn_Event_down_happened(rst) && MatKB_onKeyDown != NULL){
           MatKB_onKeyDown(ID_index,NULL);      // 當發生對應事件時使用回調函數進行通知
        }
        ……          // 判斷並通知其他事件
      }
      ID_index++;     
    }
  }
}

這就是基於機械按鈕模塊封裝一個鍵盤的基本方法,有沒有很方便呢。用起來也很方便哦,只需要掛載函數來處理各事件就行了。
下面示例一下這個模塊的使用。

使用示例

使用這個模塊的基本框架如下

#include "MatKB.h"
……
// 有按鈕發生按下事件時的處理函數
void onKeyDown(INT8U keyID,void *arg){
    ……
}
// 有按鈕發生擡起事件時的處理函數
void onKeyUp(INT8U keyID,void *arg){
    ……
}
// 有按鈕發生長按事件的處理函數
void onKeyLPress(INT8U keyID,void *arg){
    ……
}
// 有按鈕發生點擊事件的處理函數
void onKeyClick(INT8U keyID,void *arg){
  // 獲取連擊次數
  // INT8U clickCnt = (INT8U)arg;
  // 比如點擊哪個鍵則printf哪個字符
  switch(keyID){
    case KEY_1:
      printf("1");
      break;
    case KEY_2:
      printf("2");
      break;
    ……
  }
}
// 主函數
int main(){
  ...
  // initialize the MatKB module  初始化
  MatKB_Init();
  // 掛載需要用到的各種事件
  MatKB_onKeyDown = onKeyDown;
  MatKB_onKeyUp = onKeyUp;
  MatKB_onKeyClick = onKeyClick;
  MatKB_onKeyLPress = onKeyLPress;
  // start the timer.              啓動軟件定時器,當然你也可以使用定時中斷等方式
  TimerStart();
  ...
}

// 定時器定時調用的函數
void Timer_1ms(void){
  MatKB_TimeTick();      // 定時驅動
}

再複雜點,比如你想實現按鍵組合,比如,按下“1”的同時按着“D”會有什麼行爲的話,那可以這麼改:

void onKeyDown(INT8U keyID,void *arg){
  if(keyID == KEY_1 && MatKB_isPressedKey(KEY_D)){
    // 按1+D時的處理
    ……
  }
}

後記

好像說的也蠻清楚的了,如果發現了什麼bug或者有什麼意見或建議的話歡迎留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章