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


前言

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

《驱动设计–按键驱动》

  好多人对单片机的按键设计,往往是只作简单的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;				
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值