单片机软件常用设计分享(一)驱动设计之按键设计


前言

  本人从事单片机软件设计工作多年,既从事过裸机系统的设计,也在小型嵌入式实时操作系统下进行过设计。因在工作当中发现好多人对单片机软件的设计非常随意,尤其是在驱动方面,其考虑问题过于单一,并且应用层与底层驱动之间耦合度较大。基于此,本人整理工作当中做过常用的最基本设计分享到这里,希望对需要的人有所帮助或参考。当然,可能我的设计方法并不是很好,所以也算是一种学习交流。
  在整理的过程中,可能会缺乏统一规划,仅先整理一部分常用的驱动设计。至于其它部分的内容,待今后跟进需要再逐一补充。

《驱动设计–按键驱动》

  好多人对单片机的按键设计,往往是只作简单的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灯显示设计
单片机软件常用设计分享(三)驱动设计之数码屏显示设计
单片机软件常用设计分析(四)驱动设计之串口驱动设计
嵌入式开发<串口调试工具>
嵌入式开发<网络调试工具>
嵌入式开发<单片机软件调试>
嵌入式开发<单片机软件升级>

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值