引言
本文基於之前封裝好的 機械按鈕模塊 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或者有什麼意見或建議的話歡迎留言。