引言
本文基于之前封装好的 机械按钮模块 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或者有什么意见或建议的话欢迎留言。