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

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


前言

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

《驱动设计–按键驱动》

  好多人对单片机的按键设计,往往是只作简单的I/O检测或A/D检测,并未考虑到防抖及其它问题,同时也带来可移植性差的问题。一般应该将按键驱动进行分层设计,并具有扫描码与功能码之分,除防抖设计外,同时需要考虑功能码输出时具有按键状态(包括按键按下、长按及长按时间、按键释放等)。甚至,必要的按键处理也应该一并考虑,只是这部分具体应该如何处理,则需要留给上层决定。

一、按键扫描方式

  按键一般有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个bit对应1个按键,这样一个字节可以对应独立的8个按键,同时还可以表示众多的组合按键;另一种是设计为枚举类型,每一个按键或组合按键对应一个枚举值;扫描码的生成由扫描函数完成;
  这里以第一种方式设计扫描码,如有5个按键:上(bit0)、下(bit1)、左(bit2)、右(bit3)、确定(bit4)。具体定义如下(实际可设计为1–4个字节<这里以两个字节举例>);
  扫描码应该由具体的应用去设计。

#define	BitShift(key)		(0x0001<<(key))		//bit表示方法扫描码生成宏定义
#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_UPDN  		(BitShift(KEY_SERIAL_UP)|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
//注:以上还可以定义更多组合按键扫描码,此处不再列出

2.功能码

  功能码可以表示4种状态,按键按下、短按键释放、按键长按、长按键释放,之所以设计多种状态的功能码,是考虑到软件可能会使用不同的按键状态来做处理。以上4种状态的功能码将以基本功能码为基础,使用宏定义生成。基本功能码完全可以使用自然数来定义,如有5个按键如上描述,使用4个字节表示功能码。
  功能码应该由具体的应用去设计。

1)定义基本功能码分别为

#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_MAX				KEY_UPDN

2)功能码的宏定义

  使用功能码的高8位来表示状态,低16位为基本功能码。

#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))//长按键释放

3)功能码定义

  功能码的定义在一个应用中应该是可以统一的。

#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_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_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_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)
//注:以上还可以定义更多组合按键功能码,此处不再列出

4)扫描码与基本功能码默认映射

const uint32_t FunCode[KEY_SCAN_MAX+1]=
{
	KEY_NO_KEY,KEY_UP,KEY_DN,//0x00,0x01,0x02,
	KEY_NO_KEY,KEY_LF,//0x03,0x04,
	KEY_NO_KEY,KEY_NO_KEY,KEY_NO_KEY,KEY_RT,//0x05,0x06,0x07,0x08,
	KEY_NO_KEY,KEY_NO_KEY,KEY_NO_KEY,KEY_NO_KEY,//0x09,0x0a,0x0b,0x0c,
	KEY_NO_KEY,KEY_NO_KEY,KEY_NO_KEY,KEY_EN,//0x0d,0x0e,0x0f,0x10,
};
//定义默认的扫描码映射函数
uint32_t KeyMapDef(uint16_t scanCode)
{
	return	FunCode[scanCode];
}

3.按键驱动数据结构

1)按键值

  按键值包括扫描码与功能码,其定义如下:

typedef struct
{
	uint16_t	 	scan;	//扫描码
	uint32_t 		func;	//功能码
}tKeyValue;//按键值

2)按键消息

  按键消息包括按键长按时间与按键值,其定义如下:

typedef struct
{
	uint16_t		time;	//长按键按下时间
	tKeyValue		value;
}tKeyMsg;//按键信息

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)按键扫描

  按键扫描含多个要素,扫描状态、按键按下计时器、按下抖动计时器、释放抖动计时器、当前扫描码、上次扫描码、扫描参数等。其定义如下:

//设计扫描参数的意义在于,可以使得各个独立的按键驱动有不同的参数数据
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;//按键扫描

5)按键驱动数据结构

  按键驱动包括所有参数、状态、安装的驱动处理函数等,其定义如下:

  以下驱动函数由初始化时外部提供,用以安装

typedef uint8_t(tKeyScanFunc)(uint32_t*);
/*
按键扫描函数原型:uint8_t(tKeyScanFunc)(uint32_t*);
输入:按键扫描码结果存储数据指针;
输出:扫描按键按下个数;
*/
typedef void(tKeyExportFunc)(tKeyMsg*);
/*
按键输出函数原型:void(tKeyExportFunc)(tKeyMsg*);
输入:按键信息指针;
输出:无;
说明:用户应用层需要获取按键时,在安装驱动时需要传递输出函数,且此功能函数在运行于操作系统的情况更合适,只需要设计一个发送队列或邮箱的函数将按键推送到应用层即可;当然,裸机运行情况下也可以设计一个函数完成推送功能;
*/
typedef void(tKeyParseFunc)(tKeyMsg*);
/*
按键解析函数原型:void(tKeyParseFunc)(tKeyMsg*);
输入:按键信息指针;
输出:无;
说明:用户应用层希望扫描到按键时直接解析,则在安装驱动时需要传递解析函数;此功能函数适用于运行于裸机情况,反之可设置为NULL;另外,如无特殊需求,此函数实际可以不使用,仅exportF也可以完成相应功能;
*/
typedef uint32_t(tKeyMapFunc)(uint16_t);
/*
按键扫描码映射函数原型:uint32_t(tKeyMapFunc)(uint16_t);
输入:按键扫描码;
输出:基本功能码;
说明:用户可以指定扫描码与基本功能码的映射函数,也可以不指定,此时驱动将使用默认的方法;
*/
typedef void(tKeyLLInitFunc)(void);
/*
按键低级初始化函数原型:void(tKeyLLInitFunc)(void);
输入:无;
输出:无;
说明:用户指定的低级初始化函数,完成底层硬件初始化;这里没有将其设计为要求直接挂载(因为直接挂载会限制应用程序的设计),是为了进一步降低耦合度与增强灵活性。
*/
typedef void(tKeyLLUnInitFunc)(void);
/*
按键低级去初始化函数原型:void(tKeyLLUnInitFunc)(void);
输入:无;
输出:无;
说明:用户指定的低级去初始化函数,完成底层硬件去初始化;
*/
typedef struct
{
	tKeyScanFunc 	*pScan,			//安装的按键扫描函数
	tKeyExportFunc 	*pExport,		//安装的按键输出处理函数(可以设置为空)
	tKeyParseFunc 	*pParse,		//安装的按键解析处理函数(可以设置为空)
	tKeyMapFunc   	*pMap,			//安装的扫描码映射处理函数(可以设置为空)
	tKeyLLInitFunc	*pInit,			//安装的底层初始化函数(可以设置为空)
	tKeyLLUnInitFunc *pUnInit;		//安装的底层去初始化函数(可以设置为空)
}tKeyDrvFunc;//按键驱动函数
typedef struct
{
	bool			valid;			//驱动安装有效性<TRUE,驱动有效>
	bool			running;		//按键处理正在运行标志<TRUE正在运行>
	tScanParam		param;			//扫描参数
	tScan			scan;			//扫描结构
	tKeyMsg			keyMsg;			//按键消息
	tKeyDrvFunc		func;			//外部安装的驱动函数
}tKeyDriver;//按键驱动数据结构

三、按键驱动代码设计

1.参数及变量定义

//以下定义默认参数值,时间单位定义为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		//按键按下持续长按时间间隔
//宏定义
#define	ScanKeyCnt(time)		((time)/pPkeyDrv->param.scanUnit)//扫描按键时间计数值
#define	FuncLKeyCnt(time)		((time)/pPkeyDrv->param.keepCntLongKeep)//功能码长按键保持时间计数值
#define	MALLOC_MEM(memSize)		malloc(memSize)//内存申请宏
#define	FREE_MEM(pMem)			free(pMem)//内存释放宏

2.驱动处理

//按键驱动轮询函数,包括以下所有被分拆的部分(各个子模块已经被展开)
void Key_Poll(tKeyDriver *pKeyDrv)

1)按键按下检测

  按键在释放状态,则首先检测按键按下

if	(pKeyDrv==NULL||pKeyDrv->valid==FALSE)
return;//驱动安装失败,拒绝执行
pKeyDrv->running=TRUE;//处理开始运行
pKeyDrv->scan.pressCnt=pKeyDrv->func.pScan(&pKeyDrv->scan.curKey);//按键扫描
if	(pKeyDrv->scan.state==SKEY_STATE_RELEASE)//扫描状态为按键释放时,执行按下检测
{
	//void Key_PushProcess(tKeyDriver *pKeyDrv) 按键按下模块
	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;
	}
}

2)抖动处理

  进入抖动处理的条件(按键按下后):
  A 检测到按键释放;
  B 检测按键值发生变化;

else if  (pKeyDrv->scan.state&SKEY_STATE_JITTER)//抖动处理状态可以与其它状态同时存在
{
	//void Key_JitterProcess(tKeyDriver *pKeyDrv) 抖动处理模块
	if	(pKeyDrv->scan.pressCnt>0)
	{//抖动处理时,有按键按下且稳定则直接退出
		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>=ScanKeyCnt(pKeyDrv->param.jitterPressCntMax))
			{//抖动处理超时(按键在发生变化),重新设置为按键确认检测
				pKeyDrv->scan.prevKey=pKeyDrv->scan.curKey;
				pKeyDrv->scan.state=SKEY_STATE_PUSH;
				pKeyDrv->scan.keepCnt=pKeyDrv->param.keepCntEnsure;
				pKeyDrv->scan.jitterPressCnt=0;
			}
		}
	}
	else
	{//抖动处理时,无按键按下
		pKeyDrv->scan.jitterPressCnt=0;
		pKeyDrv->scan.jitterReleaseCnt++;
		if	(pKeyDrv->scan.jitterReleaseCnt>=ScanKeyCnt(pKeyDrv->param.jitterReleaseCntMax))
		{//按键弹起抖动处理时间到
			pKeyDrv->scan.jitterReleaseCnt=0;
			if	((pKeyDrv->scan.state&SKEY_STATE_MASK)==SKEY_STATE_PUSH)
			{
				//按键按下未被确认时,认为按键无效
				pKeyDrv->scan.state=SKEY_STATE_RELEASE;
			}
			else
			{
				//按键被确认,进一步确定是短按键还是长按键释放(同时增加一倍延时检测)
				pKeyDrv->scan.state=(pKeyDrv->scan.state&SKEY_STATE_MASK)+SKEY_STATE_WAITRELEASE;
			}
		}
	}
}

3)等待释放

  按键释放时,抖动处理完毕后进入此状态

else if  (pKeyDrv->scan.state&SKEY_STATE_WAITRELEASE)
{
	//void Key_ReleaseProcess(tKeyDriver *pKeyDrv) 释放处理模块
	if	(pKeyDrv->scan.pressCnt>0)
	{//等待释放时,检测到有按键按下,回到抖动处理
		pKeyDrv->scan.state-=SKEY_STATE_WAITRELEASE;
		pKeyDrv->scan.state|=SKEY_STATE_JITTER; 
		pKeyDrv->scan.jitterReleaseCnt=0;
		pKeyDrv->scan.jitterPressCnt=0;
	}
	else
	{//等待释放(延时)
		pKeyDrv->scan.jitterReleaseCnt++;
		if	(pKeyDrv->scan.jitterReleaseCnt>=ScanKeyCnt(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->func.pMap(pKeyDrv->scan.prevKey));
			}
			else// if (pKeyDrv->scan.state==SKEY_STATE_PRESS_LONG)
			{
				//产生长按键释放功能码
				pKeyDrv->keyMsg.value.func=LGBKEY(pKeyDrv->func.pMap(pKeyDrv->scan.prevKey));
			}
			//执行按键驱动中安装的按键输出或按键解析功能
			pKeyDrv->scan.state=SKEY_STATE_RELEASE;
			if	(pKeyDrv->func.pExport!=NULL)
			pKeyDrv->func.pExport(&pKeyDrv->keyMsg);//执行按键输出
			if	(pKeyDrv->func.pParse!=NULL)
			pKeyDrv->func.pParse(&pKeyDrv->keyMsg);//执行按键解析
		}
	}
}

4)按键释放或抖动

  按键按下后,出现按键弹起或按键扫描值变化,将设置进入抖动检测

else if (pKeyDrv->scan.pressCnt==0||pKeyDrv->scan.curKey!=pKeyDrv->scan.prevKey)
{	
	//void Key_Jitter(tKeyDriver *pKeyDrv) 抖动检测模块
	//pressCnt==0,检测到按键释放,则需要进入抖动检测;
	//scan.curKey!=scan.prevKey,检测按键值发生变化,则需要进入抖动检测
	pKeyDrv->scan.state|=SKEY_STATE_JITTER;
	pKeyDrv->scan.jitterReleaseCnt=0;
	pKeyDrv->scan.jitterPressCnt=0;
}

5)按键确认及产生功能码

  按键按下且未出现抖动情况,则检测并产生功能码

else
{
	//void Key_FunCodeProcess(tKeyDriver *pKeyDrv) 功能码产生模块
	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.prevKey;
			pKeyDrv->keyMsg.value.func=SHPKEY(pKeyDrv->func.pMap(pKeyDrv->scan.prevKey));
			//设置下次检测时间(长按检测)
			pKeyDrv->scan.keepCnt=ScanKeyCnt(pKeyDrv->param.keepCntLongStart);
		}
		else
		{//长按键处理
			pKeyDrv->keyMsg.value.scan=pKeyDrv->scan.prevKey;
			pKeyDrv->keyMsg.value.func=LGKEY(pKeyDrv->func.pMap(pKeyDrv->scan.prevKey));
			//设置下次检测时间(长按间隔)
			pKeyDrv->scan.keepCnt=ScanKeyCnt(pKeyDrv->param.keepCntLongKeep);
			if	(pPkeyDrv->scan.state==SKEY_STATE_PRESS)
			{//首次长按键
				pKeyDrv->scan.state=SKEY_STATE_PRESS_LONG;
				pKeyDrv->keyMsg.time=FuncLKeyCnt(pKeyDrv->param.keepCntLongStart);
			}
			else
			{//重复的长按键
				if	(pKeyDrv->keyMsg.time<0xffffffffff)
				pKeyDrv->keyMsg.time++;
			}
		}
		//执行按键驱动中安装的按键输出或按键解析功能
		if	(pKeyDrv->func.pExport!=NULL)
		pKeyDrv->func.pExport(&pKeyDrv->keyMsg);//执行按键输出
		if	(pKeyDrv->func.pParse!=NULL)
		pKeyDrv->func.pParse(&pKeyDrv->keyMsg);//执行按键解析
	}
}
pKeyDrv->running=FALSE;//处理运行结束

3.按键驱动安装

  按键驱动安装主要完成驱动内存申请(参数传递了接收申请内存的指针),驱动函数安装及扫描参数设置等操作。返回tKeyPollFunc类型的按键驱动轮询处理指针,用于应用程序定时调用(其参数为:pKeyDrv),若返回为NULL,则安装失败。
  扫描参数可以为每一个需要独立分开的按键驱动设置不同的参数,也可以使用驱动默认参数值;

typdef void (tKeyPollFunc)(tKeyDriver*);
typedef struct
{
	tKeyDrvFunc		func;			//按键驱动函数
	tScanParam		param;			//扫描参数
}tKeyInitParam;//按键驱动安装参数
tKeyPollFunc* Key_Initial(tKeyInitParam *pInitParam,tKeyDriver	 *pKeyDrv)
{//pKeyDrv 接收驱动内存申请的指针
	if	(pInitParam==NULL)
	return	NULL;
	pKeyDrv=MALLOC_MEM(sizeof(tKeyDriver));
	if	(pInitParam->func.pScan!=NULL)//必须提供扫描函数,否则安装失败
	{
		pKeyDrv->valid=TRUE;
		pKeyDrv->running=FALSE;
		//扫描寄存器初始化
		pKeyDrv->scan.state=SKEY_STATE_RELEASE;
		pKeyDrv->scan.keepCnt=0;
		pKeyDrv->scan.jitterPressCnt=0;
		pKeyDrv->scan.jitterReleaseCnt=0;
		pKeyDrv->scan.curKey=KEY_NO;
		pKeyDrv->scan.prevKey=KEY_NO;
		//安装驱动处理函数及设置扫描参数
		pKeyDrv->func=pInitParam->func;
		pKeyDrv->param=pInitParam->param;
		//检测映射函数
		if	(pKeyDrv->func.pMap==NULL)//未指定映射函数,则使用默认映射函数
		pKeyDrv->func.pMap=KeyMapDef;
		//依次检测扫描参数是否需要使用默认值
		if	(pKeyDrv->param.scanUnit==0)//扫描时间单位
		pKeyDrv->param.scanUnit=KEYSCAN_UINT_DEF;
		if	(pKeyDrv->param.jitterPressCntMax==0)//按下抖动检测时间
		pKeyDrv->param.jitterPressCntMax=JITTERPRESSCNTMAX_DEF	;
		if	(pKeyDrv->param.jitterReleaseCntMax==0)//释放抖动检测时间
		pKeyDrv->param.jitterReleaseCntMax=JITTERRELEASECNTMAX_DEF;
		if	(pKeyDrv->param.keepCntEnsure==0)//按下确认保持时间
		pKeyDrv->param.keepCntEnsure=KEEPCNT_ENSURE_DEF;
		if	(pKeyDrv->param.keepCntLongStart==0)//检测首次长按时间
		pKeyDrv->param.keepCntLongStart=KEEPCNT_LONG_START_DEF;
		if	(pKeyDrv->param.keepCntLongKeep==0)//长按保持时间间隔
		pKeyDrv->param.keepCntLongKeep=KEEPCNT_LONG_KEEP_DEF;
		//执行低级初始化操作
		if	(pKeyDrv->func.pInit!=NULL)
		pKeyDrv->func.pInit();
		return	Key_Poll;
	}
	else
	{
		FREE_MEM(pKeyDrv);
		pKeyDrv=NULL;
		return	NULL;
	}
}

4.按键驱动卸载

  按键驱动的卸载调用前,必须手动停止按键驱动轮询的定时调用,此处不对其进行检查,仅对驱动轮询是否正在运行进行检查。

bool Key_UnInitial(tKeyDriver *pKeyDrv)
{
	if	(pKeyDrv->running==FALSE&&pKeyDrv!=NULL)
	{
		//执行低级去初始化操作
		if	(pKeyDrv->func.pUnInit!=NULL)
		pKeyDrv->func.pUnInit();
		FREE_MEM(pKeyDrv);
		pKeyDrv=NULL;
		return	TRUE;
	}
	else
	return	FALSE;
}

四、总结

1.使用说明

1)必要的移植修改设计说明

  A,需要设计扫描码与功能码及映射函数(pMap),扫描参数可以根据需要是否使用默认值或者进行单独定义;
  B,至少需要设计按键扫描函数(pScan);
  C,选择按键输出函数(pExport)或按键分析函数(pParse)根据需要进行设计;
  D,不管是裸机运行还是在操作系统下运行,均需要进行按键驱动安装;
  E,设计中将轮询函数提供给上层去决定如何执行,这样可以进一步降低耦合度以及对增按键强驱动使用的灵活性。对于裸机运行可将轮询函数放在硬件定时器产生的POLLING中执行;对于运行在操作系统(如FreeRTOS)下可以将轮询函数放在某个软件定时器的回调函数中执行(或发送事件到某个任务执行);

2)外部调用接口

  驱动安装函数Key_Initial是唯一直接提供给应用层必须调用的接口;
  驱动卸载函数Key_UnInitial是接提供给应用层可选调用的接口;
  驱动轮询函数是间接提供给应用层周期性调用的接口;

3)按键驱动使用示例

/*其它头文件包含*/
//#include ......

//按键驱动头文件包含
#include "KeyDriver.h"

/*扫描码与功能码等内容定义*/
//#define ......

//底层初始化及去初始化函数声明
void KeyLLInit(void);
void KeyLLUnInit(void);

//按键扫描函数声明
uint8_t ScanFunc(uint32_t scan);
//按键分析处理函数设计声明
void ParseFunc(tKeyMsg* pMsg);
//主函数设计
void main(void)
{
	//变量定义
	tKeyDriver 		*pKeyDrv;
	tKeyPollFunc	*pKeyPollFunc;
	tKeyInitParam 	init;

	init.func.pScan=&ScanFunc;
	init.func.pParse=&ParseFunc;
	init.func.pInit=KeyLLInit;
	init.func.pUnInit=KeyLLUnInit;
	init.func.pExport=NULL;
	init.param.scanUnit=0;
	init.param.jitterPressCntMax=0;
	init.param.jitterReleaseCntMax=0;
	init.param.keepCntEnsure=0;
	init.param.keepCntLongStart=0;
	init.param.keepCntLongKeep=0;
	pKeyPollFunc=Key_Initial(&init,pKeyDrv);
	for	(;;)
	{
		delayMs(10);
		pKeyPollFunc(pKeyDrv);
		/*其它处理*/
		//......
	}
}

2.其它说明

  以上按键驱动设计,表面上看似乎较为复杂,但实际上是因为两个原因才如此进行设计。
  1)首先,按键驱动的设计考虑到一个应用里面可能存在多个不同的扫描处理方法,可以同时安装多个独立的按键驱动。甚至,设计遥控器驱动也基本可以不做修改而直接使用;
  2)其次,这里已经将按键驱动与应用层尽量做到了较低的耦合度,在移植时仅需要提供一些必要的辅助设计即可,甚至,参数数据均可以使用默认值;

五、后续内容

  下个分享内容:单片机软件常用设计分享(二)驱动设计之LED灯显示设计

说明

  以上设计方法基本为本人在工作中所用,但为了分享,做了一定的整理并进行扩充(扩充后未进行编译运行<抱歉>)。如有错误或BUG,欢迎指正。

CONTACT

  AUTHOR:laijun
   QQ:791662706
  EMail:laijun04@126.com

上一篇:02_漏洞扫描


下一篇:判断一个年月日是当年的第几天