单片机软件常用设计分享(一)驱动设计之按键设计
前言
本人从事单片机软件设计工作多年,既从事过裸机系统的设计,也在小型嵌入式实时操作系统下进行过设计。因在工作当中发现好多人对单片机软件的设计非常随意,尤其是在驱动方面,其考虑问题过于单一,并且应用层与底层驱动之间耦合度较大。基于此,本人整理工作当中做过常用的最基本设计分享到这里,希望对需要的人有所帮助或参考。当然,可能我的设计方法并不是很好,所以也算是一种学习交流。
在整理的过程中,可能会缺乏统一规划,仅先整理一部分常用的驱动设计。至于其它部分的内容,待今后跟进需要再逐一补充。
《驱动设计–按键驱动》
好多人对单片机的按键设计,往往是只作简单的I/O检测或A/D检测,并未考虑到防抖及其它更多功能的问题,同时也因为耦合度较高带来可移植性差的问题。
一般来说,应该将按键驱动进行分层设计,包括底层初始化、底层硬件扫描、按键扫描逻辑处理、功能码输出、功能解析等等。并需要设计扫描码与功能码(设计扫描码可以很方便的处理组合按键),除防抖设计外,同时需要考虑功能码输出时具有多种按键类型(包括按键按下、长按及长按时间、按键释放等)。甚至,必要的按键解析处理也应该一并考虑,只是这部分具体应该如何处理,则需要留给应用层决定。
说明:以下在描述时只列出了相关部分代码,但会在最后给出完整代码。
另外,在完成数码屏驱动调试后,对此驱动做了修改,主要是不再依赖应用层必须提供const uint32_t FunCode[]定义。
一、按键扫描方式
按键一般有2种扫描方式,I/O电平检测与AD电压检测,按键的扫描函数应该由具体的应用层去处理(根据具体的硬件电路进行设计),这也是降低耦合度的关键之一。
1.I/O电平检测
I/O电平检测又分为2种。
1)一个按键的一端连接到一个普通I/O口上,另外一端接地。这种方式仅适用于按键较少的情况;
2)一个按键的一端连接到一个行扫描I/O上,另外一端连接到列扫描I/O口上,这样I/O口将分为行扫描组与列扫描组。这种方式适用于按键较多的情况;
2.AD电压检测
这种方式实际上是将一个电压通过多个电阻进行均等分压,每个分压点接入一个按钮的一端,按钮的另一端接地。这种方式在一个AD口不适合连接太多的按钮,因为其受分压间距的影响,如果间距太小,则会出现检测误差或错误,甚至这种方式本身就依赖于电压的稳定性。同时,这种方式对于组合按键的处理一般不会太理想,这需要将硬件分压电阻设计的非常合理。
二、按键驱动数据结构
按键驱动数据结构设计,包括按键数据、按键参数、按键扫描执行状态、按键扫描驱动函数等等,以下分别描述各个部分内容。
1.按键数据
按键数据包括:扫描码、功能码,驱动数据结构设计,以下分别描述各个部分内容;
1)扫描码
扫描码的设计至少可以使用两种方式。一种是设计为1个bit对应1个按键,这样一个字节可以对应独立的8个按键,同时还可以表示众多的组合按键;另一种是设计为枚举类型,每一个按键或组合按键对应一个枚举值;后一种方式在生成的扫描码上不能做组合按键,必须在扫描函数中完成,并且此时的扫描码与功能码区别不是很大(严格来讲这个扫描码已经是功能码了);
这里以第一种方式设计扫描码,如有5个按键:上(bit0)、下(bit1)、左(bit2)、右(bit3)、确定(bit4)。具体定义如下(实际可设计为1–4个字节<这里以4个字节举例>);
注:扫描码应该由具体的应用层去设计,因为每个项目可能存在区别。
//扫描码定义<生成32bit表示的扫描码宏定义> [需要根据项目实际情况修改定义]
#define BitShift(key) (0x00000001<<(key))
//按键序号定义(可定义从0--31)
#define KEY_SERIAL_UP 0
#define KEY_SERIAL_DN 1
#define KEY_SERIAL_LF 2
#define KEY_SERIAL_RT 3
#define KEY_SERIAL_EN 4
//基本扫描码
//#define KEY_SCAN_NO 0x00 //扫描码-无按键
#define KEY_SCAN_UP BitShift(KEY_SERIAL_UP) //扫描码-按键上
#define KEY_SCAN_DN BitShift(KEY_SERIAL_DN) //扫描码-按键下
#define KEY_SCAN_LF BitShift(KEY_SERIAL_LF) //扫描码-按键左
#define KEY_SCAN_RT BitShift(KEY_SERIAL_RT) //扫描码-按键右
#define KEY_SCAN_EN BitShift(KEY_SERIAL_EN) //扫描码-按键确认
#define KEY_SCAN_MAX KEY_SCAN_EN
//组合扫描码(使用位定义扫描码生成组合按键非常容易)
#define KEY_SCAN_UPDN (BitShift(KEY_SERIAL_UP)|BitShift(KEY_SERIAL_DN))//扫描码-按键上+按键下(组合键)
#define KEY_SCAN_LFRT (BitShift(KEY_SERIAL_LF)+BitShift(KEY_SERIAL_RT))//扫描码-按键左+按键右(组合键)
2)功能码
功能码可以表示4种类型,按键按下、短释放、按键长按、长释放,之所以设计这4种类型的功能码,是考虑到软件可能会使用不同的按键状态来做处理。比如,检测按键执行某个功能,是以按下时执行还是按下弹起后执行,或者是长按执行还是长按释放执行,甚至是长按几秒后执行等等,这些都与需求或用户体验为基础进行考虑并设计。以上4种类型的功能码将以基本功能码为基础,使用宏定义生成。基本功能码完全可以使用自然数来定义,如有5个按键如上描述,使用4个字节表示功能码。
注:功能码应该也由具体的应用层去设计。
A,基本功能码定义
//基本功能码
//#define KEY_NO 0x00
#define KEY_UP 0x01 //基本功能码-按键上
#define KEY_DN 0x02 //基本功能码-按键下
#define KEY_LF 0x03 //基本功能码-按键左
#define KEY_RT 0x04 //基本功能码-按键右
#define KEY_EN 0x05 //基本功能码-键按确认
#define KEY_UPDN 0x06 //基本功能码-按键上+按键下(组合键)
#define KEY_LFRT 0x07 //基本功能码-按键左+按键右(组合键)
#define KEY_MAX KEY_LFRT
B,功能码宏定义
使用功能码的高4位来表示类型,低24位为基本功能码。
/*---------------------------------------------------------------------------------------------------
生成功能码宏定义<功能码定义四种类型>
1, 按键按下
2, 按键短释放
3, 按键长按
4, 按键长释放
---------------------------------------------------------------------------------------------------*/
#define SHORT_PRESS 0x00
#define SHORT_BREAK 0x01
#define LONG_PRESS 0x02
#define LONG_BREAK 0x03
#define SHPKEY(key) ((key)+(SHORT_PRESS<<24)) //按键按下
#define SHBKEY(key) ((key)+(SHORT_BREAK<<24)) //按键短释放
#define LGKEY(key) ((key)+(LONG_PRESS<<24)) //按键长按
#define LGBKEY(key) ((key)+(LONG_BREAK<<24)) //按键长释放
C,功能码定义
功能码的定义在一个应用中应该是可以统一的。
#define KEY_NO_KEY 0x00 //无功能码
//按键按下
#define KEY_UP_PRESS SHPKEY(KEY_UP)
#define KEY_DN_PRESS SHPKEY(KEY_DN)
#define KEY_LF_PRESS SHPKEY(KEY_LF)
#define KEY_RT_PRESS SHPKEY(KEY_RT)
#define KEY_EN_PRESS SHPKEY(KEY_EN)
#define KEY_UPDN_PRESS SHPKEY(KEY_UPDN)
#define KEY_LFRT_PRESS SHPKEY(KEY_LFRT)
//按键短释放
#define KEY_UP_BREAK SHBKEY(KEY_UP)
#define KEY_DN_BREAK SHBKEY(KEY_DN)
#define KEY_LF_BREAK SHBKEY(KEY_LF)
#define KEY_RT_BREAK SHBKEY(KEY_RT)
#define KEY_EN_BREAK SHBKEY(KEY_EN)
#define KEY_UPDN_BREAK SHBKEY(KEY_UPDN)
#define KEY_LFRT_BREAK SHBKEY(KEY_LFRT)
//按键长按
#define KEY_UP_LONG LGKEY(KEY_UP)
#define KEY_DN_LONG LGKEY(KEY_DN)
#define KEY_LF_LONG LGKEY(KEY_LF)
#define KEY_RT_LONG LGKEY(KEY_RT)
#define KEY_EN_LONG LGKEY(KEY_EN)
#define KEY_UPDN_LONG LGKEY(KEY_UPDN)
#define KEY_LFRT_LONG LGKEY(KEY_LFRT)
//按键长释放
#define KEY_UP_LONG_BREAK LGBKEY(KEY_UP)
#define KEY_DN_LONG_BREAK LGBKEY(KEY_DN)
#define KEY_LF_LONG_BREAK LGBKEY(KEY_LF)
#define KEY_RT_LONG_BREAK LGBKEY(KEY_RT)
#define KEY_EN_LONG_BREAK LGBKEY(KEY_EN)
#define KEY_UPDN_LONG_BREAK LGBKEY(KEY_UPDN)
#define KEY_LFRT_LONG_BREAK LGBKEY(KEY_LFRT)
2.按键参数
按键参数主要是扫描参数,其涉及到扫描按键时各个状态或阶段的执行时间。
其中有两个参数被放在了tScan结构中。
typedef struct
{
uint16_t scanUnit; //按键扫描时间单位
uint16_t jitterPressCntMax; //按键按下抖动检查时间
uint16_t jitterReleaseCntMax; //按键弹起抖动检查时间
uint16_t keepCntEnsure; //按键按下首次持续时间
uint16_t keepCntLongStart; //按键按下首次判断长按时间
uint16_t keepCntLongKeep; //按键按下持续长按时间间隔
}tScanParam;//扫描参数
typedef struct
{
uint8_t state; //扫描状态
uint8_t pressCnt; //扫描到同时按键个数
uint8_t keepCnt; //按键按下计时器,向下计数,单位为扫描时间,如10ms
uint8_t jitterPressCnt; //按下抖动计时器,向上计数
uint8_t jitterReleaseCnt; //释放抖动计时器,向上计数
uint16_t curKey; //当前扫描码
uint16_t prevKey; //上次扫描码
}tScan;//按键扫描
3.按键扫描执行状态
按键扫描分为几个状态:按键按下、抖动检测、确认按下、长按键、等待释放、按键释放;
按键按下:在按键释放状态,检测到有任意按键按下时进入此状态;
抖动处理:在按键按下或释放时检测到相反状况,进入此状态进行抖动处理(这是一个可并行的状态);
确认按下:按键按下并持续一定时间后,则进入此状态,产生按键按下功能码(基本功能码);
长按键:确认按键已经按下,并在一定时间之后,检测到按键仍然按下,则进入此状态,并产生长按功能码;
等待释放:在检测到按键弹起并执行抖动处理成功后,进入此状态,并在结束此状态时产生短按键释放或长按键释放功能码(这是一个可并行的状态);
按键释放:按键按下时抖动处理失败,或等待释放成功,设置到此状态,并可重新开始检测新的按键按下检测;
各个扫描状态的定义如下:
#define SKEY_STATE_RELEASE 0 //按键释放
#define SKEY_STATE_PUSH 1 //按键按下
#define SKEY_STATE_PRESS 2 //确认按下
#define SKEY_STATE_PRESS_LONG 3 //长按键
#define SKEY_STATE_JITTER 0x40 //抖动处理
#define SKEY_STATE_WAITRELEASE 0x80 //等待释放
#define SKEY_STATE_MASK 0x0f //互斥的状态屏蔽字
4.按键扫描驱动函数
1)扫描码与基本功能码默认映射
扫描码映射到基本功能码,可以使用最简单的查表映射方法,但这个方法在按键个数较多时进行填表就有些繁琐,并且占用FLASH较多。当然,这个方法的优点也显而易见,也就是算法简单的不能再简单了。如果你的应用有好的算法,也可以传递你应用层的映射算法函数给驱动即可。所以,这也是降低耦合度的一个设计。
FunCode同样也应该由应用层定义
const uint32_t FunCode[KEY_SCAN_MAX+1]=
{
//以[0xdd]方式表示的,扫描码对应功能码就存在,反之没有
KEY_NO,KEY_UP,KEY_DN,KEY_UPDN,KEY_LF, //0x00,[0x01],[0x02],[0x03],[0x04]
KEY_NO,KEY_NO,KEY_NO,KEY_RT,KEY_NO, //0x05,0x06,0x07,[0x08],0x09
KEY_NO,KEY_NO,KEY_LFRT,KEY_NO,KEY_NO, //0x0a,0x0b,[0x0c],0x0d,0x0e
KEY_NO,KEY_EN, //0x0f,[0x10]
};
//定义默认的扫描码映射函数
uint32_t KeyMapDef(uint32_t scanCode)
{
return FunCode[scanCode];
}
2)由应用层提供的安装函数(原型)
typedef void(*tKeyInit)(void); //按键底层初始化及去初始化函数原型
/*---------------------------------------------------------------------------------------------------
按键底层初始化及去初始化函数原型
* 摘要: 执行按键的底层硬件初始化功能或去初始化
* 参数: 无
* 返回: 无
* 说明: 如果应用层希望自行控制底层初始化等操作,则将其设置为NULL即可.
---------------------------------------------------------------------------------------------------*/
typedef tKeyMsg*(*tKeyPollFunc)(void); //按键POLL函数原型
/*---------------------------------------------------------------------------------------------------
按键POLL函数原型
* 摘要: 执行按键的POLLING功能,驱动的主要功能均在此完成
* 参数: 无
* 返回: tKeyMsg* 返回的按键消息.
---------------------------------------------------------------------------------------------------*/
*/
typedef uint8_t(*tKeyScanFunc)(uint32_t*); //按键扫描函数原型
/*---------------------------------------------------------------------------------------------------
按键扫描函数原型
* 摘要: 执行按键的硬件扫描功能
* 参数: uint32_t* 接收扫描码结果寄存器指针
* 返回: uint8_t 扫描到的按键数量
* 说明: 此功能应用层必须提供,否则无法执行按键扫描
---------------------------------------------------------------------------------------------------*/
typedef void(*tKeyParseFunc)(tKeyMsg*); //按键解析函数原型
/*---------------------------------------------------------------------------------------------------
按键解析函数原型
* 摘要: 执行按键的功能处理
* 参数: tKeyMsg* 按键消息指针
* 返回: 无
* 说明: 应用层如希望直接在驱动内执行按键处理,则需要将按键处理函数传递给驱动.如其执行时间较长,则最好
* 放在应用层处理.
---------------------------------------------------------------------------------------------------*/
typedef uint32_t(*tKeyMapFunc)(uint32_t); //按键扫描码映射函数原型
/*---------------------------------------------------------------------------------------------------
按键扫描码映射函数原型
* 摘要: 执行从扫描码映射到基本功能码的操作
* 参数: uint32_t 按键扫描码
* 返回: uint32_t 按键基本功能码
* 说明: 应用层如果直接使用驱动的默认映射方式,则只需要将其设置为NULL.如果希望自行编写映射方式,或执行更
* 多的操作,则可以将编写的映射函数设置到此.
---------------------------------------------------------------------------------------------------*/
5.按键驱动数据结构
1)按键值
按键值包括扫描码与功能码,其定义如下:
typedef struct
{
uint32_t scan; //扫描码
uint32_t func; //功能码
}tKeyValue;//按键值
2)按键消息
按键消息包括按键长按时间与按键值,其定义如下:
typedef struct
{
uint32_t time; //长按键按下时间(仅产生按键长按时有效)
tKeyValue value;
}tKeyMsg;//按键信息
3)驱动数据结构
//按键驱动函数(由应用层提供)
typedef struct
{
tKeyScanFunc scan; //安装的按键扫描函数
tKeyParseFunc parse; //安装的按键解析处理函数(可以设置为空)
tKeyMapFunc map; //安装的扫描码映射处理函数(可以设置为空)
tKeyInit init; //安装的底层初始化函数(可以设置为空)
tKeyInit unInit; //安装的底层去初始化函数(可以设置为空)
}tKeyDrvFunc;
//按键驱动安装参数(由应用层提供)
typedef struct
{
tKeyDrvFunc drvFunc; //按键驱动函数
tScanParam param; //扫描参数
}tKeyInitParam;
//按键驱动数据结构
typedef struct
{
bool running; //运行标志
bool parseKeyMsg; //解析按键消息标志
uint16_t longPressStartTime; //长按开始时间(ms)
uint16_t longPressKeepTime; //长按持续时间(ms)
tScanParam param; //扫描参数
tScan scan; //扫描结构
tKeyMsg keyMsg; //按键消息
tKeyDrvFunc drvFunc; //外部安装的驱动函数
}tKeyDriver;
三、按键驱动代码设计
1.参数及变量定义
//以下定义内存申请与释放
#define MALLOC_MEM(memSize) pvPortMalloc(memSize) //内存申请宏
#define FREE_MEM(pMem) vPortFree(pMem) //内存释放宏
//以下定义默认扫描参数值,时间单位定义为ms
#define KEYSCAN_UINT_DEF 10 //按键扫描时间单位
#define JITTERPRESSCNTMAX_DEF 30 //按键按下抖动时间
#define JITTERRELEASECNTMAX_DEF 30 //按键弹起抖动时间
#define KEEPCNT_ENSURE_DEF 30 //按键按下首次持续时间
#define KEEPCNT_LONG_START_DEF 1000 //按键按下首次判断长按时间
#define KEEPCNT_LONG_KEEP_DEF 100 //按键按下持续长按时间间隔
static tKeyDriver *pKeyDrv;
2.驱动处理
以下描述的Key_Poll函数,其执行逻辑如下:
1)初始状态为按键释放,所以首先检测按键按下;
2)在按键按下后,将执行按键功能码确认,并在确认后产生功能码,同时启动长按扫描,并执行长按功能码确认以及重复的长按功能码确认;
3)监测按键释放,并处理短释放或长释放,由此完成整个按键处理;
在以上的处理中,存在按键按下后的抖动处理,或者按键释放时的抖动处理。再配合以上的处理步骤,则完成整个按键驱动处理。以下将列出按键驱动扫描函数。其中涉及到的子模块将不一一列出,稍后将给出完成的驱动代码与测试代码。
//按键驱动轮询函数,包括以下所有被分拆的部分(各个子模块已经被展开)
tKeyMsg* Key_Poll(void)
{
if (pKeyDrv==NULL)
return NULL;//驱动安装失败,拒绝执行
pKeyDrv->running=TRUE;
pKeyDrv->parseKeyMsg=FALSE;
pKeyDrv->keyMsg.value.func=KEY_NO_KEY;
pKeyDrv->keyMsg.value.scan=KEY_SCAN_NO;
//执行按键扫描
pKeyDrv->scan.pressCnt=pKeyDrv->drvFunc.scan(&pKeyDrv->scan.curKey);
//扫描状态为按键释放时,执行按下检测
if (pKeyDrv->scan.state==SKEY_STATE_RELEASE)
Key_PushProcess();// 按键按下模块
else if (pKeyDrv->scan.state&SKEY_STATE_JITTER)//抖动处理状态可以与其它状态同时存在
Key_JitterProcess();// 抖动处理模块
else if (pKeyDrv->scan.state&SKEY_STATE_WAITRELEASE)
Key_ReleaseProcess();// 释放处理模块
else if (pKeyDrv->scan.pressCnt==0||pKeyDrv->scan.curKey!=pKeyDrv->scan.prevKey)
Key_Jitter();// 抖动设置模块
else
Key_FunCodeProcess();// 功能码产生模块
if (pKeyDrv->parseKeyMsg==TRUE&&pKeyDrv->drvFunc.parse!=NULL)
pKeyDrv->drvFunc.parse(&pKeyDrv->keyMsg);//执行按键解析
pKeyDrv->running=FALSE;
return &pKeyDrv->keyMsg;
}
3.按键驱动安装
按键驱动安装主要完成驱动内存申请,驱动函数安装及扫描参数设置等操作。返回tKeyPollFunc类型的按键驱动轮询处理函数指针,用于应用程序定时调用,若返回为NULL,则安装失败。
扫描参数可以由应用层设置,也可以使用驱动默认参数值;
tKeyPollFunc Key_Initial(tKeyInitParam *pInitParam)
{
if (pInitParam==NULL)
return NULL;
//必须提供扫描函数,否则安装失败
if (pInitParam->drvFunc.scan!=NULL)
{
pKeyDrv=MALLOC_MEM(sizeof(tKeyDriver));
//扫描寄存器初始化
pKeyDrv->scan.state=SKEY_STATE_RELEASE;
pKeyDrv->scan.pressCnt=0;
pKeyDrv->scan.keepCnt=0;
pKeyDrv->scan.jitterPressCnt=0;
pKeyDrv->scan.jitterReleaseCnt=0;
pKeyDrv->scan.curKey=KEY_NO;
pKeyDrv->scan.prevKey=KEY_NO;
//安装驱动处理函数及设置扫描参数
pKeyDrv->drvFunc=pInitParam->drvFunc;
pKeyDrv->param=pInitParam->param;
//检测映射函数
if (pKeyDrv->drvFunc.map==NULL)//未指定映射函数,则使用默认映射函数
pKeyDrv->drvFunc.map=KeyMapDef;
//依次检测扫描参数是否定义(如未定义,则使用默认值)
//扫描时间单位
if (pInitParam->param.scanUnit==0)
pKeyDrv->param.scanUnit=KEYSCAN_UINT_DEF;
else
pKeyDrv->param.scanUnit=pInitParam->param.scanUnit;
//按下抖动检测时间
if (pInitParam->param.jitterPressCntMax==0)
pKeyDrv->param.jitterPressCntMax=JITTERPRESSCNTMAX_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.jitterPressCntMax=pInitParam->param.jitterPressCntMax/pKeyDrv->param.scanUnit;
//释放抖动检测时间
if (pInitParam->param.jitterReleaseCntMax==0)
pKeyDrv->param.jitterReleaseCntMax=JITTERRELEASECNTMAX_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.jitterReleaseCntMax=pInitParam->param.jitterReleaseCntMax/pKeyDrv->param.scanUnit;
//按下确认保持时间
if (pInitParam->param.keepCntEnsure==0)
pKeyDrv->param.keepCntEnsure=KEEPCNT_ENSURE_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.keepCntEnsure=pInitParam->param.keepCntEnsure/pKeyDrv->param.scanUnit;
//检测首次长按时间
if (pInitParam->param.keepCntLongStart==0)
pKeyDrv->longPressStartTime=KEEPCNT_LONG_START_DEF;
else
pKeyDrv->longPressStartTime=pInitParam->param.keepCntLongStart;
pKeyDrv->param.keepCntLongStart=pKeyDrv->longPressStartTime/pKeyDrv->param.scanUnit;
//长按保持时间间隔
if (pInitParam->param.keepCntLongKeep==0)
pKeyDrv->longPressKeepTime=KEEPCNT_LONG_KEEP_DEF;
else
pKeyDrv->longPressKeepTime=pInitParam->param.keepCntLongKeep;
pKeyDrv->param.keepCntLongKeep=pKeyDrv->longPressKeepTime/pKeyDrv->param.scanUnit;
//执行低级初始化操作
if (pKeyDrv->drvFunc.init!=NULL)
pKeyDrv->drvFunc.init();
return Key_Poll;
}
else
{
pKeyDrv=NULL;
return NULL;
}
}
4.按键驱动卸载
按键驱动的卸载调用前,最好手动停止按键驱动轮询的调用,此处不对其进行检查,仅对驱动轮询是否正在运行进行检查。
bool Key_UnInitial(void)
{
if (pKeyDrv!=NULL&&pKeyDrv->running==FALSE)
{
//执行低级去初始化操作
if (pKeyDrv->drvFunc.unInit!=NULL)
pKeyDrv->drvFunc.unInit();
FREE_MEM(pKeyDrv);
pKeyDrv=NULL;
return TRUE;
}
else
return FALSE;
}
四、总结
1.使用说明
1)必要的移植修改设计说明
A,需要设计扫描码与功能码及映射函数(map),扫描参数可以根据需要是否使用默认值或者进行单独定义;
B,至少需要设计按键扫描函数(scan);
C,选择按键解析函数(parse)根据需要进行设计;
D,不管是裸机运行还是在操作系统下运行,均需要进行按键驱动安装;
E,设计中将轮询函数提供给应用层去决定如何执行,这样可以进一步降低耦合度以及增强按键驱动使用的灵活性。对于裸机运行可将轮询函数放在硬件定时器产生的POLLING中执行;对于运行在操作系统(如FreeRTOS)下可以将轮询函数放在某个软件定时器的回调函数中执行(或发送事件到某个任务执行);
注意:不管使用硬件定时中断还是软件定时器回调函数执行,最好不要将按键解析函数传递给驱动,除非其执行非常简单快速。
2)外部调用接口
驱动安装函数Key_Initial是唯一直接提供给应用层必须调用的接口;
驱动卸载函数Key_UnInitial是接提供给应用层可选调用的接口;
驱动轮询函数是间接提供给应用层周期性调用的接口;
2.其它说明
以上按键驱动设计,表面上看似乎较为复杂,尤其是觉得应用层的工作较多,但实际上是因为以下原因才如此进行设计。
1)首先,按键驱动的设计考虑到把几乎所有与硬件相关的处理给分离出去,并留给应用层处理。这样设计使得驱动可以适用于同样特性的其它输入设备,如遥控器基本不用做什么修改即可使用;当然,这样就会表面上看起来应用层需要做更多的工作,但其实这些工作都是很简单并且没有逻辑处理的工作。
2)其次,几乎所有参数也被剥离出去,比如扫描码与功能码,但因驱动需要使用部分定义,主要扫描码与功能码表示无的定义。因此,驱动不能再定义这几个数据;
3.按键驱动使用示例
由于本人不知道如何(或是否可以)在这里放文件,只有贴出全部代码,包括驱动文件KeyDriver.c与KeyDriver.h,同时提供一份PannelKey.c文件,这是我用以调测此驱动的全部应用层代码。
以下给出的三份文件,是经过本人测试通过的,原则上可以直接使用。(当然,没有做异常测试,仅正常调用运行)
1)KeyDriver.h文件代码(驱动.h文件)
#ifndef _KEYDRIVER_H
#define _KEYDRIVER_H
#ifndef TRUE
#define TRUE 1
#define FALSE 0
typedef uint8_t bool;
#endif
//============================================================================
//扫描码定义<生成32bit表示的扫描码宏定义> [需要根据项目实际情况修改定义]
#define BitShift(key) (0x00000001<<(key))
#define KEY_SCAN_NO 0x00 //扫描码-无按键
/*注:具体的扫描码留给了应用层去设计,以下给出一个模式
//按键序号定义(可定义从0--31)
#define KEY_SERIAL_UP 0
#define KEY_SERIAL_DN 1
#define KEY_SERIAL_LF 2
#define KEY_SERIAL_RT 3
#define KEY_SERIAL_EN 4
//基本扫描码
#define KEY_SCAN_UP BitShift(KEY_SERIAL_UP) //扫描码-按键上
#define KEY_SCAN_DN BitShift(KEY_SERIAL_DN) //扫描码-按键下
#define KEY_SCAN_LF BitShift(KEY_SERIAL_LF) //扫描码-按键左
#define KEY_SCAN_RT BitShift(KEY_SERIAL_RT) //扫描码-按键右
#define KEY_SCAN_EN BitShift(KEY_SERIAL_EN) //扫描码-按键确认
#define KEY_SCAN_MAX KEY_SCAN_EN
//组合扫描码(使用位定义扫描码生成组合按键非常容易)
#define KEY_SCAN_UPDN (BitShift(KEY_SERIAL_UP)|BitShift(KEY_SERIAL_DN)) //扫描码-按键上+按键下(组合键)
#define KEY_SCAN_LFRT (BitShift(KEY_SERIAL_LF)+BitShift(KEY_SERIAL_RT)) //扫描码-按键左+按键右(组合键)
*/
//============================================================================
//生成功能码宏定义
/*---------------------------------------------------------------------------------------------------
生成功能码宏定义<功能码定义四种类型>
1, 按键按下
2, 按键短释放
3, 按键长按
4, 按键长释放
---------------------------------------------------------------------------------------------------*/
#define SHORT_PRESS 0x00
#define SHORT_BREAK 0x01
#define LONG_PRESS 0x02
#define LONG_BREAK 0x03
#define SHPKEY(key) ((key)+(SHORT_PRESS<<24)) //按键按下
#define SHBKEY(key) ((key)+(SHORT_BREAK<<24)) //按键短释放
#define LGKEY(key) ((key)+(LONG_PRESS<<24)) //按键长按
#define LGBKEY(key) ((key)+(LONG_BREAK<<24)) //按键长释放
//从功能码中获取类型与基本功能码
#define Key_GetFunCodeType(funcKey) ((funcKey)>>24)
#define Key_GetBaseFunCode(funcKey) ((funcKey)&0xff)
//---------------------------------------------------------------------------------------------------
#define KEY_NO 0x00
#define KEY_NO_KEY 0x00 //无功能码
/*注:具体的功能码留给了应用层去设计,以下给出一个模式
//基本功能码
#define KEY_UP 0x01 //基本功能码-按键上
#define KEY_DN 0x02 //基本功能码-按键下
#define KEY_LF 0x03 //基本功能码-按键左
#define KEY_RT 0x04 //基本功能码-按键右
#define KEY_EN 0x05 //基本功能码-键按确认
#define KEY_UPDN 0x06 //基本功能码-按键上+按键下(组合键)
#define KEY_LFRT 0x07 //基本功能码-按键左+按键右(组合键)
#define KEY_MAX KEY_LFRT
//类型功能码
//按键按下
#define KEY_UP_PRESS SHPKEY(KEY_UP)
#define KEY_DN_PRESS SHPKEY(KEY_DN)
#define KEY_LF_PRESS SHPKEY(KEY_LF)
#define KEY_RT_PRESS SHPKEY(KEY_RT)
#define KEY_EN_PRESS SHPKEY(KEY_EN)
#define KEY_UPDN_PRESS SHPKEY(KEY_UPDN)
#define KEY_LFRT_PRESS SHPKEY(KEY_LFRT)
//按键短释放
#define KEY_UP_BREAK SHBKEY(KEY_UP)
#define KEY_DN_BREAK SHBKEY(KEY_DN)
#define KEY_LF_BREAK SHBKEY(KEY_LF)
#define KEY_RT_BREAK SHBKEY(KEY_RT)
#define KEY_EN_BREAK SHBKEY(KEY_EN)
#define KEY_UPDN_BREAK SHBKEY(KEY_UPDN)
#define KEY_LFRT_BREAK SHBKEY(KEY_LFRT)
//按键长按
#define KEY_UP_LONG LGKEY(KEY_UP)
#define KEY_DN_LONG LGKEY(KEY_DN)
#define KEY_LF_LONG LGKEY(KEY_LF)
#define KEY_RT_LONG LGKEY(KEY_RT)
#define KEY_EN_LONG LGKEY(KEY_EN)
#define KEY_UPDN_LONG LGKEY(KEY_UPDN)
#define KEY_LFRT_LONG LGKEY(KEY_LFRT)
//按键长释放
#define KEY_UP_LONG_BREAK LGBKEY(KEY_UP)
#define KEY_DN_LONG_BREAK LGBKEY(KEY_DN)
#define KEY_LF_LONG_BREAK LGBKEY(KEY_LF)
#define KEY_RT_LONG_BREAK LGBKEY(KEY_RT)
#define KEY_EN_LONG_BREAK LGBKEY(KEY_EN)
#define KEY_UPDN_LONG_BREAK LGBKEY(KEY_UPDN)
#define KEY_LFRT_LONG_BREAK LGBKEY(KEY_LFRT)
*/
//============================================================================
//按键驱动数据结构
//按键值
typedef struct
{
uint32_t scan; //扫描码
uint32_t func; //功能码
}tKeyValue;
//按键消息
typedef struct
{
uint32_t time; //长按键按下时间(单位:ms)
tKeyValue value; //按键值
}tKeyMsg;//按键信息
//---------------------------------------------------------------------------------------------------
//按键扫描状态
#define SKEY_STATE_RELEASE 0 //按键释放
#define SKEY_STATE_PUSH 1 //按键按下
#define SKEY_STATE_PRESS 2 //确认按下
#define SKEY_STATE_PRESS_LONG 3 //长按键
#define SKEY_STATE_JITTER 0x40 //抖动处理
#define SKEY_STATE_WAITRELEASE 0x80 //等待释放
#define SKEY_STATE_MASK 0x0f //互斥的状态屏蔽字
//扫描参数数据结构
typedef struct
{
uint16_t scanUnit; //按键扫描时间单位
uint16_t jitterPressCntMax; //按键按下抖动时间
uint16_t jitterReleaseCntMax; //按键弹起抖动时间
uint16_t keepCntEnsure; //按键按下首次持续时间
uint16_t keepCntLongStart; //按键按下首次判断长按时间
uint16_t keepCntLongKeep; //按键按下持续长按时间间隔
}tScanParam;
//按键扫描数据结构
typedef struct
{
uint8_t state; //扫描状态
uint16_t pressCnt; //扫描到同时按键个数
uint16_t keepCnt; //按键按下计时器,自减计数,单位为扫描时间,如10ms
uint16_t jitterPressCnt; //按下抖动计时器,自加计数
uint16_t jitterReleaseCnt; //释放抖动计时器,自加计数
uint32_t curKey; //当前扫描码
uint32_t prevKey; //上次扫描码
}tScan;
//---------------------------------------------------------------------------------------------------
//按键扫描驱动函数原型定义
typedef void(*tKeyInit)(void); //按键底层初始化及去初始化函数原型
/*---------------------------------------------------------------------------------------------------
按键底层初始化及去初始化函数原型
* 摘要: 执行按键的底层硬件初始化功能或去初始化
* 参数: 无
* 返回: 无
* 说明: 如果应用层希望自行控制底层初始化等操作,则将其设置为NULL即可.
---------------------------------------------------------------------------------------------------*/
typedef tKeyMsg*(*tKeyPollFunc)(void); //按键POLL函数原型
/*---------------------------------------------------------------------------------------------------
按键POLL函数原型
* 摘要: 执行按键的POLLING功能,驱动的主要功能均在此完成
* 参数: 无
* 返回: tKeyMsg* 返回的按键消息.
---------------------------------------------------------------------------------------------------*/
typedef uint8_t(*tKeyScanFunc)(uint32_t*); //按键扫描函数原型
/*---------------------------------------------------------------------------------------------------
按键扫描函数原型
* 摘要: 执行按键的硬件扫描功能
* 参数: uint32_t* 接收扫描码结果寄存器指针
* 返回: uint8_t 扫描到的按键数量
* 说明: 此功能应用层必须提供,否则无法执行按键扫描
---------------------------------------------------------------------------------------------------*/
typedef void(*tKeyParseFunc)(tKeyMsg*); //按键解析函数原型
/*---------------------------------------------------------------------------------------------------
按键解析函数原型
* 摘要: 执行按键的功能处理
* 参数: tKeyMsg* 按键消息指针
* 返回: 无
* 说明: 应用层如果希望直接在驱动内执行按键处理,则需要将按键处理函数传递给驱动.如果其执行时间较长, 则最好设置
* 为NULL.
---------------------------------------------------------------------------------------------------*/
typedef uint32_t(*tKeyMapFunc)(uint32_t); //按键扫描码映射函数原型
/*---------------------------------------------------------------------------------------------------
按键扫描码映射函数原型
* 摘要: 执行从扫描码映射到基本功能码的操作
* 参数: uint32_t 按键扫描码
* 返回: uint32_t 按键基本功能码
* 说明: 应用层如果直接使用驱动的默认映射方式,则只需要将其设置为NULL.如果希望自行编写映射方式,或执行更多的操作,
* 则可以将编写的映射函数设置到此.
---------------------------------------------------------------------------------------------------*/
//按键驱动函数(应用层提供)
typedef struct
{
tKeyScanFunc scan; //安装的按键扫描函数
tKeyParseFunc parse; //安装的按键解析处理函数(可以设置为空)
tKeyMapFunc map; //安装的扫描码映射处理函数(可以设置为空)
tKeyInit init; //安装的底层初始化函数(可以设置为空)
tKeyInit unInit; //安装的底层去初始化函数(可以设置为空)
}tKeyDrvFunc;
//按键驱动安装参数(应用层提供)
typedef struct
{
tKeyDrvFunc drvFunc; //按键驱动函数
tScanParam param; //扫描参数
}tKeyInitParam;
//按键驱动数据结构
typedef struct
{
bool running; //运行标志
bool parseKeyMsg; //解析按键消息标志
uint16_t longPressStartTime; //长按开始时间(ms)
uint16_t longPressKeepTime; //长按持续时间(ms)
tScanParam param; //扫描参数
tScan scan; //扫描结构
tKeyMsg keyMsg; //按键消息
tKeyDrvFunc drvFunc; //外部安装的驱动函数
}tKeyDriver;
//============================================================================
//不使用功能码映射,则设置为0
#define USE_FUNCODE_MAP 1
//应用层调用功能
tKeyPollFunc Key_Initial(tKeyInitParam *pInitParam);
bool Key_UnInitial(void);
#endif
2)KeyDriver.c文件代码(驱动.c文件)
/*------------------------------------------说明:------------------------------------------------
此驱动已经被编译并调试验证,具体验证条件将在PannelKey.c详细说明,此处仅对一个设置进行说明:
1,在验证时USE_OS中定义使用了FreeRTOS,因此才有如下定义
#if USE_OS==OS_FREERTOS
#include "FreeRTOS.h"
#endif
2,KEY驱动主要具备以下功能:
1) 通过调用初始化后提供POLL函数指针:
A, 其被应用层在定时扫描位置调用;
B, 在调用完后,会返回当前按键消息;
2) 整个驱动主要实现按键逻辑处理:
A, 扫描按键按下/长按/短释放/长释放;
B, 在长按下时,提供按下时间;
C, 非常容易实现按键组合;
关于按键的实际扫描交给应用层可以降低耦合度.
3) 其它:
A, 在初始化时,还提供了按键解析函数指针,很方便实现简单处理;
B, 扫描码与功能码的映射应用层可以选择时间驱动提供的查表算法还是自定义算法;
--------------------------------------------------------------------------------------------------*/
#define OS_NONE 0
#define OS_FREERTOS 1
#define USE_OS OS_FREERTOS
#if USE_OS==OS_FREERTOS
#include "FreeRTOS.h"
#endif
#include "KeyDriver.h"
//以下定义内存申请与释放
#define MALLOC_MEM(memSize) pvPortMalloc(memSize) //内存申请宏
#define FREE_MEM(pMem) vPortFree(pMem) //内存释放宏
//以下定义默认扫描参数值,时间单位定义为ms
#define KEYSCAN_UINT_DEF 10 //按键扫描时间单位
#define JITTERPRESSCNTMAX_DEF 30 //按键按下抖动时间
#define JITTERRELEASECNTMAX_DEF 30 //按键弹起抖动时间
#define KEEPCNT_ENSURE_DEF 30 //按键按下首次持续时间
#define KEEPCNT_LONG_START_DEF 1000 //按键按下首次判断长按时间
#define KEEPCNT_LONG_KEEP_DEF 100 //按键按下持续长按时间间隔
static tKeyDriver *pKeyDrv;
/*注:具体的映射表留给了应用层去设计,以下给出一个模式
//扫描码与基本功能码默认映射(查表算法)
const uint32_t FunCode[KEY_SCAN_MAX+1]=
{
//以下[0xdd]表示扫描码对应的功能码存在,反之没有
KEY_NO,KEY_UP,KEY_DN,KEY_UPDN,KEY_LF, //0x00,[0x01],[0x02],[0x03],[0x04]
KEY_NO,KEY_NO,KEY_NO,KEY_RT,KEY_NO, //0x05,0x06,0x07,[0x08],0x09
KEY_NO,KEY_NO,KEY_LFRT,KEY_NO,KEY_NO, //0x0a,0x0b,[0x0c],0x0d,0x0e
KEY_NO,KEY_EN, //0x0f,[0x10]
};
*/
#if USE_FUNCODE_MAP==1
extern const uint32_t FunCode[]; //应用层必须定义FunCode,否则编译通不过
static uint32_t KeyMapDef(uint32_t scanCode)
{
return FunCode[scanCode];
}
#endif
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键在释放状态按下处理模块,记录扫描码及设置状态与计数值
* 参数: 无
* 返回: 无
---------------------------------------------------------------------------------------------------*/
static void Key_PushProcess(void)
{
if (pKeyDrv->scan.pressCnt>0)
{
//有按键按下,进入按键按下确认状态
pKeyDrv->scan.prevKey=pKeyDrv->scan.curKey;
pKeyDrv->scan.state=SKEY_STATE_PUSH;
pKeyDrv->scan.keepCnt=pKeyDrv->param.keepCntEnsure;
pKeyDrv->scan.jitterPressCnt=0;
pKeyDrv->scan.jitterReleaseCnt=0;
}
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键抖动处理模块,包括按键按下抖动处理与按键释放抖动处理.如果抖动过程中出现按键更换,则根据当前状态确定
* 是否输出按键释放消息。
* 参数: 无
* 返回: 无
---------------------------------------------------------------------------------------------------*/
static void Key_JitterProcess(void)
{
if (pKeyDrv->scan.pressCnt>0)
{
/*----------------------------------------------------------------------------
抖动处理时,仍然有按键按下
1) 当检测到按键仍然和原来一致时,返回原状态;
2) 当检测到按键确实发生变化,则重新设置新的按键按下;
----------------------------------------------------------------------------*/
pKeyDrv->scan.jitterReleaseCnt=0;
if (pKeyDrv->scan.curKey==pKeyDrv->scan.prevKey)
pKeyDrv->scan.state-=SKEY_STATE_JITTER;//抖动时按键没有变化,维持原按键扫描码
else
{
pKeyDrv->scan.jitterPressCnt++;
if (pKeyDrv->scan.jitterPressCnt>=pKeyDrv->param.jitterPressCntMax)//按键已经发生变化
{
//按键变化前已经产生功能按键(按下/长按),应该输出按键短释放或长释放
if ((pKeyDrv->scan.state&SKEY_STATE_MASK)==SKEY_STATE_PRESS)
{
pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.prevKey;
pKeyDrv->keyMsg.value.func=SHBKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.prevKey));
pKeyDrv->parseKeyMsg=TRUE;
}
else if ((pKeyDrv->scan.state&SKEY_STATE_MASK)==SKEY_STATE_PRESS_LONG)
{
pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.prevKey;
pKeyDrv->keyMsg.value.func=LGBKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.prevKey));
pKeyDrv->parseKeyMsg=TRUE;
}
//重新设置新按键按下状态
pKeyDrv->scan.prevKey=pKeyDrv->scan.curKey;
pKeyDrv->scan.state=SKEY_STATE_PUSH;
pKeyDrv->scan.keepCnt=pKeyDrv->param.keepCntEnsure;
}
}
}
else
{
//抖动处理时,无按键按下,应该处理按键释放
pKeyDrv->scan.jitterPressCnt=0;
pKeyDrv->scan.jitterReleaseCnt++;
if (pKeyDrv->scan.jitterReleaseCnt>=pKeyDrv->param.jitterReleaseCntMax)
{
//按键弹起抖动处理时间到
if ((pKeyDrv->scan.state&SKEY_STATE_MASK)==SKEY_STATE_PUSH)
pKeyDrv->scan.state=SKEY_STATE_RELEASE;//按键按下未被确认时,认为按键无效
else
{
//按键被确认,增加一倍延时检测(增加释放的可靠性)
pKeyDrv->scan.jitterReleaseCnt=0;
pKeyDrv->scan.state=(pKeyDrv->scan.state&SKEY_STATE_MASK)+SKEY_STATE_WAITRELEASE;
}
}
}
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键释放处理模块,属于按键抖动释放的一个延续.主要是增加释放处理的可靠性。
* 参数: 无
* 返回: 无
---------------------------------------------------------------------------------------------------*/
static void Key_ReleaseProcess(void)// 释放处理模块
{
if (pKeyDrv->scan.pressCnt>0)
{
//等待释放时,检测到有按键按下,回到抖动处理
pKeyDrv->scan.jitterReleaseCnt=0;
pKeyDrv->scan.jitterPressCnt=0;
pKeyDrv->scan.state=(pKeyDrv->scan.state&SKEY_STATE_MASK)+SKEY_STATE_JITTER;
}
else
{
//延时处理等待释放
pKeyDrv->scan.jitterReleaseCnt++;
if (pKeyDrv->scan.jitterReleaseCnt>=pKeyDrv->param.jitterReleaseCntMax)
{
pKeyDrv->scan.state&=SKEY_STATE_MASK;
pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.prevKey;
if (pKeyDrv->scan.state==SKEY_STATE_PRESS)
pKeyDrv->keyMsg.value.func=SHBKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.prevKey));//产生短按键释放功能码
else// if (pKeyDrv->scan.state==SKEY_STATE_PRESS_LONG)
pKeyDrv->keyMsg.value.func=LGBKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.prevKey));//产生长按键释放功能码
pKeyDrv->scan.state=SKEY_STATE_RELEASE;
pKeyDrv->parseKeyMsg=TRUE;
pKeyDrv->keyMsg.time=0;
}
}
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键抖动设置模块,即在按键按下后出现当前按键扫描码与前一次的按键扫描码不一致,则设置进入抖动处理。
* 处理的可靠性。
* 参数: 无
* 返回: 无
---------------------------------------------------------------------------------------------------*/
static void Key_Jitter(void)
{
pKeyDrv->scan.state|=SKEY_STATE_JITTER;
pKeyDrv->scan.jitterReleaseCnt=0;
pKeyDrv->scan.jitterPressCnt=0;
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键功能码处理,主要生成按键按下/按键长按及按键时间数据.
* 参数: 无
* 返回: 无
---------------------------------------------------------------------------------------------------*/
static void Key_FunCodeProcess(void)
{
if (pKeyDrv->scan.keepCnt)
pKeyDrv->scan.keepCnt--;
if (pKeyDrv->scan.keepCnt==0)
{
//保持pKeyDrv->scan.keepCnt设置值的时间计数到0时,进行按键功能码产生处理
if (pKeyDrv->scan.state==SKEY_STATE_PUSH)
{
//按键按下处理
pKeyDrv->scan.state=SKEY_STATE_PRESS;
//设置按键信息
pKeyDrv->keyMsg.time=0;
pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.curKey;
pKeyDrv->keyMsg.value.func=SHPKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.curKey));
//设置下次检测时间(长按检测)
pKeyDrv->scan.keepCnt=pKeyDrv->param.keepCntLongStart;
}
else
{
//长按键处理
pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.curKey;
pKeyDrv->keyMsg.value.func=LGKEY(pKeyDrv->drvFunc.map(pKeyDrv->scan.curKey));
//设置下次检测时间(长按间隔)
pKeyDrv->scan.keepCnt=pKeyDrv->param.keepCntLongKeep;
if (pKeyDrv->scan.state==SKEY_STATE_PRESS)
{
//首次长按键
pKeyDrv->scan.state=SKEY_STATE_PRESS_LONG;
pKeyDrv->keyMsg.time=pKeyDrv->longPressStartTime;
}
else
{
//重复的长按键
if (pKeyDrv->keyMsg.time<0xffffffff)
pKeyDrv->keyMsg.time+=pKeyDrv->longPressKeepTime;
}
}
pKeyDrv->parseKeyMsg=TRUE;
}
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 按键POLLING处理,完成整个按键驱动处理。同时,如应用层提供了parse函数,则会执行按键功能解析处理。
* 参数: 无
* 返回: tKeyMsg* 当前按键POLLING结果,即按键消息。
---------------------------------------------------------------------------------------------------*/
static tKeyMsg* Key_Poll(void)
{
if (pKeyDrv==NULL)
return NULL;//驱动安装失败,拒绝执行
pKeyDrv->running=TRUE;
pKeyDrv->parseKeyMsg=FALSE;
pKeyDrv->keyMsg.value.func=KEY_NO_KEY;
pKeyDrv->keyMsg.value.scan=KEY_SCAN_NO;
//执行按键扫描
pKeyDrv->scan.pressCnt=pKeyDrv->drvFunc.scan(&pKeyDrv->scan.curKey);
//扫描状态为按键释放时,执行按下检测
if (pKeyDrv->scan.state==SKEY_STATE_RELEASE)
Key_PushProcess();// 按键按下模块
else if (pKeyDrv->scan.state&SKEY_STATE_JITTER)//抖动处理状态可以与其它状态同时存在
Key_JitterProcess();// 抖动处理模块
else if (pKeyDrv->scan.state&SKEY_STATE_WAITRELEASE)
Key_ReleaseProcess();// 释放处理模块
else if (pKeyDrv->scan.pressCnt==0||pKeyDrv->scan.curKey!=pKeyDrv->scan.prevKey)
Key_Jitter();// 抖动设置模块
else
Key_FunCodeProcess();// 功能码产生模块
if (pKeyDrv->parseKeyMsg==TRUE&&pKeyDrv->drvFunc.parse!=NULL)
pKeyDrv->drvFunc.parse(&pKeyDrv->keyMsg);//执行按键解析
pKeyDrv->running=FALSE;
return &pKeyDrv->keyMsg;
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 提供给外部调用的驱动安装函数。主要完成驱动内存申请、驱动
* 扫描参数初始化等。
* 参数: tKeyInitParam *pInitParam初始化参数
* scanUnit 按键扫描时间,未作限制(可以设置为0,则使用驱动默认参数)
* keepCntEnsure 按键按下确认时间,与scanUnit相同要求
* jitterPressCntMax 按键按下抖动时间,与scanUnit相同要求
* jitterReleaseCntMax 按键释放抖动时间,与scanUnit相同要求
* keepCntLongStart 按键按下后首次确认长按时间,与scanUnit相同要求
* keepCntLongKeep 按键按下后重复确认长按时间,与scanUnit相同要求
* drvFunc 按键驱动函数,包括以下部分
* scan 安装的按键扫描函数(必须设置)
* init 安装的按键底层初始化函数(可以设置为空)
* unInit 安装的按键底层去初始化函数(可以设置为空)
* parse 安装的按键解析函数(可以设置为空)
* map 安装的按键扫描码映射函数(可以设置为空)
* 返回: tKeyPollFunc 按键POLLING函数指针;
* 说明: 这是唯一一个直接提供给外部必须调用的函数。
* 以上设置中可以为0或未NULL的参数,意味着如此设置则会使用驱动提供的默认功能或参数数据。
---------------------------------------------------------------------------------------------------*/
tKeyPollFunc Key_Initial(tKeyInitParam *pInitParam)
{
if (pInitParam==NULL)
return NULL;
//不使用功能码映射,则必须提供映射函数,否则安装失败
#if USE_FUNCODE_MAP==0
if (pInitParam->drvFunc.map==NULL)
return NULL;
#endif
//必须提供扫描函数,否则安装失败
if (pInitParam->drvFunc.scan!=NULL)
{
pKeyDrv=MALLOC_MEM(sizeof(tKeyDriver));
//扫描寄存器初始化
pKeyDrv->scan.state=SKEY_STATE_RELEASE;
pKeyDrv->scan.pressCnt=0;
pKeyDrv->scan.keepCnt=0;
pKeyDrv->scan.jitterPressCnt=0;
pKeyDrv->scan.jitterReleaseCnt=0;
pKeyDrv->scan.curKey=KEY_NO;
pKeyDrv->scan.prevKey=KEY_NO;
//安装驱动处理函数及设置扫描参数
pKeyDrv->drvFunc=pInitParam->drvFunc;
pKeyDrv->param=pInitParam->param;
#if USE_FUNCODE_MAP==1
//检测映射函数
if (pKeyDrv->drvFunc.map==NULL)//未指定映射函数,则使用默认映射函数
pKeyDrv->drvFunc.map=KeyMapDef;
#endif
//依次检测扫描参数是否定义(如未定义,则使用默认值)
//扫描时间单位
if (pInitParam->param.scanUnit==0)
pKeyDrv->param.scanUnit=KEYSCAN_UINT_DEF;
else
pKeyDrv->param.scanUnit=pInitParam->param.scanUnit;
//按下抖动检测时间
if (pInitParam->param.jitterPressCntMax==0)
pKeyDrv->param.jitterPressCntMax=JITTERPRESSCNTMAX_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.jitterPressCntMax=pInitParam->param.jitterPressCntMax/pKeyDrv->param.scanUnit;
//释放抖动检测时间
if (pInitParam->param.jitterReleaseCntMax==0)
pKeyDrv->param.jitterReleaseCntMax=JITTERRELEASECNTMAX_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.jitterReleaseCntMax=pInitParam->param.jitterReleaseCntMax/pKeyDrv->param.scanUnit;
//按下确认保持时间
if (pInitParam->param.keepCntEnsure==0)
pKeyDrv->param.keepCntEnsure=KEEPCNT_ENSURE_DEF/pKeyDrv->param.scanUnit;
else
pKeyDrv->param.keepCntEnsure=pInitParam->param.keepCntEnsure/pKeyDrv->param.scanUnit;
//检测首次长按时间
if (pInitParam->param.keepCntLongStart==0)
pKeyDrv->longPressStartTime=KEEPCNT_LONG_START_DEF;
else
pKeyDrv->longPressStartTime=pInitParam->param.keepCntLongStart;
pKeyDrv->param.keepCntLongStart=pKeyDrv->longPressStartTime/pKeyDrv->param.scanUnit;
//长按保持时间间隔
if (pInitParam->param.keepCntLongKeep==0)
pKeyDrv->longPressKeepTime=KEEPCNT_LONG_KEEP_DEF;
else
pKeyDrv->longPressKeepTime=pInitParam->param.keepCntLongKeep;
pKeyDrv->param.keepCntLongKeep=pKeyDrv->longPressKeepTime/pKeyDrv->param.scanUnit;
//执行低级初始化操作
if (pKeyDrv->drvFunc.init!=NULL)
pKeyDrv->drvFunc.init();
return Key_Poll;
}
else
{
pKeyDrv=NULL;
return NULL;
}
}
/*--------------------------------------------------------------------------------------------------
* 摘要: 提供给外部调用的驱动卸载函数。主要完成驱动内存释放及底层低级去初始化操作(如提供此函数)。
* 参数: 无
* 返回: TRUE 执行卸载成功
* FALSE执行卸载失败
* 说明: 严格来讲,此功能并不是安全的函数.如要执行此功能,最好将POLLING停止后再执行。
---------------------------------------------------------------------------------------------------*/
bool Key_UnInitial(void)
{
if (pKeyDrv!=NULL&&pKeyDrv->running==FALSE)
{
//执行低级去初始化操作
if (pKeyDrv->drvFunc.unInit!=NULL)
pKeyDrv->drvFunc.unInit();
FREE_MEM(pKeyDrv);
pKeyDrv=NULL;
return TRUE;
}
else
return FALSE;
}
//===========================================================================
/*--END--*/
3)PannelKey.c文件代码(测试代码)
/*------------------------------------------说明:------------------------------------------------
此例程用于验证按键驱动,其使用环境如下:
1) MCU使用WZnet芯片W7500;
2) 实时操作系统FreeRTOS V9.0.0;
3) 编译系统使用IAR 8.22.1;
4) 硬件平台没有按键,为此设计了一个模拟机制,其将在后面说明;
5) 除包含"FreeRTOS.H"头文件外,还包括一个串口驱动"Serial.h";
注:因验证比较简单,因此以下功能函数没有做太多详细说明
按键模拟机制说明:
A 利用串口调试,接收产生按键的扫描码及按键时间;
B 调用提供的PannelKey_AttendKey函数,设置模拟键值simulateKey;
C 同时,按照提供的按键时间启动软件定时器;
D 在软件定时器的回调函数中清除模拟键值simulateKey;
--------------------------------------------------------------------------------------------------*/
#include "FreeRTOS.H"
#include "Serial.h"
#include "KeyDriver.h"
//以下扫描码与功能码可以放到应用层.h文件中
//按键序号定义(可定义从0--31)
#define KEY_SERIAL_UP 0
#define KEY_SERIAL_DN 1
#define KEY_SERIAL_LF 2
#define KEY_SERIAL_RT 3
#define KEY_SERIAL_EN 4
//基本扫描码
#define KEY_SCAN_UP BitShift(KEY_SERIAL_UP) //扫描码-按键上
#define KEY_SCAN_DN BitShift(KEY_SERIAL_DN) //扫描码-按键下
#define KEY_SCAN_LF BitShift(KEY_SERIAL_LF) //扫描码-按键左
#define KEY_SCAN_RT BitShift(KEY_SERIAL_RT) //扫描码-按键右
#define KEY_SCAN_EN BitShift(KEY_SERIAL_EN) //扫描码-按键确认
#define KEY_SCAN_MAX KEY_SCAN_EN
//组合扫描码(使用位定义扫描码生成组合按键非常容易)
#define KEY_SCAN_UPDN (BitShift(KEY_SERIAL_UP)|BitShift(KEY_SERIAL_DN)) //扫描码-按键上+按键下(组合键)
#define KEY_SCAN_LFRT (BitShift(KEY_SERIAL_LF)+BitShift(KEY_SERIAL_RT)) //扫描码-按键左+按键右(组合键)
//基本功能码
#define KEY_UP 0x01 //基本功能码-按键上
#define KEY_DN 0x02 //基本功能码-按键下
#define KEY_LF 0x03 //基本功能码-按键左
#define KEY_RT 0x04 //基本功能码-按键右
#define KEY_EN 0x05 //基本功能码-键按确认
#define KEY_UPDN 0x06 //基本功能码-按键上+按键下(组合键)
#define KEY_LFRT 0x07 //基本功能码-按键左+按键右(组合键)
#define KEY_MAX KEY_LFRT
//---------------------------------------------------------------------------------------------------
//类型功能码
//按键按下
#define KEY_UP_PRESS SHPKEY(KEY_UP)
#define KEY_DN_PRESS SHPKEY(KEY_DN)
#define KEY_LF_PRESS SHPKEY(KEY_LF)
#define KEY_RT_PRESS SHPKEY(KEY_RT)
#define KEY_EN_PRESS SHPKEY(KEY_EN)
#define KEY_UPDN_PRESS SHPKEY(KEY_UPDN)
#define KEY_LFRT_PRESS SHPKEY(KEY_LFRT)
//按键短释放
#define KEY_UP_BREAK SHBKEY(KEY_UP)
#define KEY_DN_BREAK SHBKEY(KEY_DN)
#define KEY_LF_BREAK SHBKEY(KEY_LF)
#define KEY_RT_BREAK SHBKEY(KEY_RT)
#define KEY_EN_BREAK SHBKEY(KEY_EN)
#define KEY_UPDN_BREAK SHBKEY(KEY_UPDN)
#define KEY_LFRT_BREAK SHBKEY(KEY_LFRT)
//按键长按
#define KEY_UP_LONG LGKEY(KEY_UP)
#define KEY_DN_LONG LGKEY(KEY_DN)
#define KEY_LF_LONG LGKEY(KEY_LF)
#define KEY_RT_LONG LGKEY(KEY_RT)
#define KEY_EN_LONG LGKEY(KEY_EN)
#define KEY_UPDN_LONG LGKEY(KEY_UPDN)
#define KEY_LFRT_LONG LGKEY(KEY_LFRT)
//按键长释放
#define KEY_UP_LONG_BREAK LGBKEY(KEY_UP)
#define KEY_DN_LONG_BREAK LGBKEY(KEY_DN)
#define KEY_LF_LONG_BREAK LGBKEY(KEY_LF)
#define KEY_RT_LONG_BREAK LGBKEY(KEY_RT)
#define KEY_EN_LONG_BREAK LGBKEY(KEY_EN)
#define KEY_UPDN_LONG_BREAK LGBKEY(KEY_UPDN)
#define KEY_LFRT_LONG_BREAK LGBKEY(KEY_LFRT)
//扫描码与基本功能码默认映射(查表算法)
const uint32_t FunCode[KEY_SCAN_MAX+1]=
{
//以下[0xdd]表示扫描码对应的功能码存在,反之没有
KEY_NO,KEY_UP,KEY_DN,KEY_UPDN,KEY_LF, //0x00,[0x01],[0x02],[0x03],[0x04]
KEY_NO,KEY_NO,KEY_NO,KEY_RT,KEY_NO, //0x05,0x06,0x07,[0x08],0x09
KEY_NO,KEY_NO,KEY_LFRT,KEY_NO,KEY_NO, //0x0a,0x0b,[0x0c],0x0d,0x0e
KEY_NO,KEY_EN, //0x0f,[0x10]
};
typedef struct
{
osTimerId timer; //软件timer用于设计模拟按键触发机制
uint32_t simulateKey; //模拟硬件的I/O键
tKeyPollFunc poll;
}tPannelKey;//应用层中的按键任务资源
#define KEY_POLL_TIME 10 //ms key_poll轮询函数执行周期
#define KEY_JITTERPRESSCNTMAX 30 //按键按下抖动时间
#define KEY_JITTERRELEASECNTMAX 30 //按键弹起抖动时间
#define KEY_KEEPCNT_ENSURE 30 //按键按下首次持续时间
#define KEY_KEEPCNT_LONG_START 1000 //按键按下首次判断长按时间
#define KEY_KEEPCNT_LONG_KEEP 100 //按键按下持续长按时间间隔
//FreeRTOS按键任务栈
#define Key_TaskStackDepth (configMINIMAL_STACK_SIZE)
//模拟的按键I/O个数
#define SIMULATE_IO_MAX 5
//测试的按键字符串
const uint8_t panelKeystr[KEY_MAX][11]=
{
"UP","DOWN","LEFT","RIGHT","ENTER","UP&DOWN","LEFT&RIGHT"
};
static tPannelKey pannelKey;
//===============================================================
//KEY初始化与去初始化模块(可以用实际的KEY驱动I/O设置替代)
//KEY初始化
static void PannelKeyInit(void)
{
Comx_PrintStr(DBG_COM,"pannel key initialize function\r\n");
}
//KEY去初始化
static void PannelKeyUnInit(void)
{
Comx_PrintStr(DBG_COM,"pannel key uninitialize function...\r\n");
}
//按键扫描函数(实际使用可以用硬件扫描替换)
static uint8_t PannelKeyScan(uint32_t* pScanCode)
{
uint8_t loop,scanCnt=0;
uint32_t scanValue;
*pScanCode=0;
for (loop=0;loop<SIMULATE_IO_MAX;loop++)
{
scanValue=BitShift(loop);
if (pannelKey.simulateKey&scanValue)
{
//扫描到一个键值,添加到扫描码中,并执行扫描按键数加一操作
*pScanCode|=scanValue;
scanCnt++;
}
}
return scanCnt;
}
//按键解析函数,仅以输出按键信息模拟按键功能解析
static void PannelKeyParse(tKeyMsg *pKeyMsg)
{
uint8_t funCodeType;
uint8_t baseFunCode;
funCodeType=Key_GetFunCodeType(pKeyMsg->value.func);
baseFunCode=Key_GetBaseFunCode(pKeyMsg->value.func);
switch (funCodeType)
{
case SHORT_PRESS:
Comx_PrintStr(DBG_COM,"按键KEY-%s 短按<扫描码:0x%04x>\r\n",
panelKeystr[baseFunCode-1],pKeyMsg->value.scan);
break;
case SHORT_BREAK:
Comx_PrintStr(DBG_COM,"按键KEY-%s 短释放\r\n",panelKeystr[baseFunCode-1]);
break;
case LONG_PRESS:
Comx_PrintStr(DBG_COM,"按键KEY-%s 长按下<扫描码:0x%04x time:%.1fs>\r\n",
panelKeystr[baseFunCode-1],pKeyMsg->value.scan,pKeyMsg->time/1000.0);
break;
case LONG_BREAK:
Comx_PrintStr(DBG_COM,"按键KEY-%s 长释放\r\n",panelKeystr[baseFunCode-1]);
break;
}
}
//按键模拟机制中的软件定时器回调函数
static void PannelKey_ScanCallback(void const *argument)
{
pannelKey.simulateKey=0;
}
//KEY初始化设置
static void PannelKeySetup(void)
{
tKeyInitParam initParam;
//创建按键模拟机制中的软件定时器
osTimerDef(scan,PannelKey_ScanCallback);
pannelKey.timer=osTimerCreate(osTimer(scan),osTimerOnce,NULL);
initParam.drvFunc.init=PannelKeyInit;
initParam.drvFunc.unInit=PannelKeyUnInit;
initParam.drvFunc.scan=PannelKeyScan;
initParam.drvFunc.parse=PannelKeyParse;
initParam.drvFunc.map=NULL;
initParam.param.scanUnit=KEY_POLL_TIME;
initParam.param.jitterPressCntMax=KEY_JITTERPRESSCNTMAX;
initParam.param.jitterReleaseCntMax=KEY_JITTERRELEASECNTMAX;
initParam.param.keepCntEnsure=KEY_KEEPCNT_ENSURE;
initParam.param.keepCntLongStart=KEY_KEEPCNT_LONG_START;
initParam.param.keepCntLongKeep=KEY_KEEPCNT_LONG_KEEP;
pannelKey.poll=Key_Initial(&initParam);
}
//===============================================================
//KEY任务模块(FreeRTOS任务)
static void PannelKey_Task(void const *pvParameters)
{
/*-----------------------------------------------------------------------------
这里仅仅是用于演示与测试,所以单独创建一个简单任务并且仅执行一个简单的延时操作,然后就调用
驱动提供的poll函数.如果在实际的应用中使用,则不需要单独创建此任务,可通过软件定时器产生回调
时发送消息等通知,由主任务产生调用
-----------------------------------------------------------------------------*/
PannelKeySetup();
for (;;)
{
osDelay(KEY_POLL_TIME);
pannelKey.poll();
}
}
//===============================================================
//外部调用模块(创建FreeRTOS任务)
void PannelKeyTaskCreate(void)
{
osThreadDef(key,PannelKey_Task,osPriorityNormal,0,Key_TaskStackDepth);
osThreadCreate(osThread(key), NULL);
}
//===============================================================
//调测模块(用于调试模块中根据串口输入进行修改)
//添加按下按键操作
void PannelKey_AttendKey(uint32_t keyIO,uint32_t pressTimems)
{
pannelKey.simulateKey=keyIO;
osTimerStart(pannelKey.timer,pdMS_TO_TICKS(pressTimems));
}
//===============================================================
/*--END--*/
五、后续内容
下个分享内容:单片机软件常用设计分享(二)驱动设计之LED灯显示设计
说明
以上设计方法基本为本人在工作中所用,但为了分享,做了一定的整理并进行扩充。另外,在上传后,又对驱动做了优化修改,主要是把扫描码与功能码的定义完全分离到了应用层,本机的代码已经调试通过,但没有完全覆盖上传,而是手动修改。如因此存在以下问题,我会及时修改。
如有错误或BUG,欢迎指正。
附
如果你感兴趣,可查看其它文档.
单片机软件常用设计分享(二)驱动设计之LED灯显示设计
单片机软件常用设计分享(三)驱动设计之数码屏显示设计
单片机软件常用设计分析(四)驱动设计之串口驱动设计
嵌入式开发<串口调试工具>
嵌入式开发<网络调试工具>
嵌入式开发<单片机软件调试>
嵌入式开发<单片机软件升级>