|
本帖最后由 Jach_cc 于 2013-9-28 22:22 编辑
按键在单片机系统开发中是一个很常用到的器件。一般情况下按键检测程序显得太长,最糟糕的是缺乏可移植性。这样一来,与按键相关的代码就显得凌乱不堪。苦于这个原因,我写了一个比较通用的按键驱动程序,可以在任何单片机系统中方便移植。有做的不好的地方希望大家指正,谢谢啦。
与一般代码的比较:
一、一般的按键扫描代码特点:
1. 消抖延时浪费CPU资源。
2. 松手检测极度浪费单片机CPU资源。
3. 缺乏封装性:这也是代码凌乱的根源所在。
4. 多个按键不能同时操作。(因为一般我们的代码是单线程的,等待松手会占用100% CPU资源)
二、本键盘扫描代码的特点:
这个程序就是为了解决上面问题而写的,所以特点是针对上面一般按键扫描代码的改进。
1. 不用消抖延时浪费CPU资源,当然也可以用消抖延时,具体视你的调用频率而定。如果你配置为一定频率刷新按键状态就不会因为消抖延时而暂时完全占用CPU资源。
2. 松手检测不占用CPU资源。(这一特色很重要吧?因为你可以去处理别的东西,不用等到松手后)
3. 每一颗按键被完全封装到一个结构体(包括扫描按键状态的函数,这用函数指针实现),不分矩阵键盘和独立按键(它们的不同点仅仅在你的接口函数中有而已,初始化后任何形式的键盘都一样了)。
4. 支持多个按键同时互不影响操作(可以同时按多个按键,支持组合键)。
5. 欢迎大家继续发现它的优异性,或者提出宝贵的意见。
写得很幸苦,有问题谢谢大家一起交流。
这个代码分key.c和key.h,移植时只用将它们复制到你的目录下。稍作配置即可使用。
key.c的移植:
如果你固定频率调用按键刷新函数就完全不用修改这个文件;如果你的调用频率不固定,那么仅仅需要修改void key_delay1ms(uint8_t xms);用来产生一毫的延时。
key.h的移植:key.h只用配置4个宏定义即可:
#define USER_DATA_EN (0) //使能用户数据,每一个按键有一个变量可以供用户任意使用,如果不用这个变量把括号内改成0即可;如果要用就改成1,但是这会浪费一个字节的内存
#define KEY_USE_SEM (0) //1使用按键信号量,0不使用互斥信号量。使用互斥信号量的时候多个地方不能同时访问按键的按下次数变量,这样数据最安全,一般很少人用。
#define KEY_WOBBLE_TIME (10) //按键抖动时间,单位ms。具体大小不同由硬件决定,一般情况下是10ms
//#define KEY_FIXED_PERIOD (10)//固定频率调用按键状态更新函数,括号内为调用周期,周期单位为ms
types.h文件
#ifndef __TYPES_H__
#define __TYPES_H__
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
typedef struct{
uint8_t x;
uint8_t y;
}point_2d;
#endif
本文件讲解:定义类型,定义了8位,16位,32位长度的数据类型,具体视你的编译器修改。还有一个2d图形中的坐标点,这个不用,是我在其它的程序里面用得。
key.h文件
#ifndef __KEY_H__
#define __KEY_H__
#include "types.h"
#define USER_DATA_EN (0)
#define KEY_USE_SEM (0) //1使用按键互斥信号量,0不使用按键互斥信号量
#define KEY_WOBBLE_TIME (10) //按键抖动时间。也就是消抖时间,单位ms
//#define KEY_FIXED_PERIOD (10)//固定频率调用按键状态更新函数,括号内为调用周期,周期单位为ms
#define KEY_TIMES_MAX (0XFF)
typedef enum{
KEY_ACCESS_READ = 0x08,
KEY_ACCESS_WRITE = 0x80,
KEY_ACCESS_CLEAR = 0X40,
KEY_ACCESS_DECREASE= 0X20,
KEY_ACCESS_INCREASE= 0X10
}access_type;
/************以下内容均不需要更改,直接使用即可*******************************/
typedef enum
{
KEY_DOWN = 1,
KEY_UP = 2,
KEY_UP_WOBBLE = 3,//确认弹起消抖状态
KEY_DOWN_WOBBLE = 4 //确认按下消抖状态
}key_state_type;
/************按键信号量,互斥访问时使用*****************/
typedef enum
{
KEY_SEM_USING = 0,
KEY_SEM_FREE = 1
}key_sem_type;
typedef struct
{
uint8_t (*get_state)(void); //用于获取按键状态的函数
#if KEY_USE_SEM==1
key_sem_type sem; //信号量,
#endif
#ifdef KEY_FIXED_PERIOD
uint8_t time_ms; //用于固定周期调用状态更新函数的计时,不能超过类型所能表示的最大值
#endif
key_state_type state;
uint8_t times; //按下并弹出后加一,使用后由应用程序减1
#if (USER_DATA_EN==1)
uint8_t value; //用户变量,可由用户任意使用
#endif
}key_inf_type;
/* 函数声明: */
void Key_Init(key_inf_type* key_this, uint8_t(*getState)(void));
void Key_RefreshState(key_inf_type* theKey);
uint8_t Key_AccessTimes(key_inf_type* theKey, access_type option );
#endif
本文件讲解:access_type这个枚举类型在使用互斥信号量时,对按键状态访问的选择。
key_state_type这个枚举类型用来不由用户使用,是状态机的,正是因为有了这个,才免去的一般按键驱动代码对CPU的100%占用。
key_sem_type这个枚举类型用来记录信号量状态
key_inf_type这个结构体是核心类型了,按键的所有信息都记录在里面。包括按键被按下的次数,和现在按键的状态,以及它的信号量。
key.c文件
#include "key.h"
/****************************************************************
函数说明: 按键专用延时函数,大概一毫秒
移植说明: 需要更改
*****************************************************************/
#ifndef KEY_FIXED_PERIOD
void key_delay1ms(uint8_t xms)
{
unsigned int _1ms;
for( ; xms>0; xms-- )
for( _1ms=400; _1ms>0; _1ms-- );
}
#endif
/* 以下内容不需要更改,直接使用即可 */
/*
函数说明: 初始化一个按键对象
参数说明: key_this:指向按键对象的指针
getState:状态检测函数指针
*/
void Key_Init(key_inf_type* key_this, uint8_t(*getState)(void))
{
key_this->get_state = getState;
key_this->state = KEY_UP;
key_this->times = 0;
#if (USER_DATA_EN==1)
key_this->value = 0;
#endif
#if KEY_USE_SEM==1
key_this->sem = KEY_SEM_FREE;
#endif
#ifdef KEY_FIXED_PERIOD
key_this->time_ms = 0; //用于固定周期调用状态更新函数的计时
#endif
}
/****************************************************************
函数说明: 更新按键状态
要求 :
1、调用频率:满足按键更新最快频率,一般要求调用频率在20HZ以上
*****************************************************************/
void Key_RefreshState(key_inf_type* theKey)
{
#if KEY_USE_SEM==1
if( KEY_SEM_FREE == theKey->sem )
{
theKey->sem = KEY_SEM_USING;
#endif
switch( theKey->state )
{
case KEY_UP:
{
if( (*(theKey->get_state))() )
{
#ifdef KEY_FIXED_PERIOD
theKey->time_ms = 0;
theKey->state = KEY_DOWN_WOBBLE;//进行消抖延时
#else
theKey->state = KEY_DOWN_WOBBLE;
key_delay1ms(KEY_WOBBLE_TIME);
if( (*(theKey->get_state))() )
{
theKey->state = KEY_DOWN;
}
#endif
}
}break;
#ifdef KEY_FIXED_PERIOD
case KEY_DOWN_WOBBLE:
{
theKey->time_ms += KEY_FIXED_PERIOD;
if( theKey->time_ms >=KEY_WOBBLE_TIME )
{
if( (*(theKey->get_state))() )
{
theKey->state = KEY_DOWN;
}else
{
theKey->state = KEY_UP;
}
}
}break;
#endif
case KEY_DOWN:
{
if( (*(theKey->get_state))() == 0 )
{
#ifdef KEY_FIXED_PERIOD
theKey->time_ms = 0;
theKey->state = KEY_UP_WOBBLE;//进行消抖延时
#else
key_delay1ms(KEY_WOBBLE_TIME);
if( (*(theKey->get_state))() == 0 )
{
theKey->state = KEY_UP;
theKey->times++;
if( theKey->times > 250)
theKey->times = 250;//最多允许按下250次没处理
}
#endif
}
}break;
#ifdef KEY_FIXED_PERIOD
case KEY_UP_WOBBLE:
{
theKey->time_ms += KEY_FIXED_PERIOD;
if( theKey->time_ms >= KEY_WOBBLE_TIME )
{
if( 0 == (*(theKey->get_state))() )
{
theKey->state = KEY_UP;
theKey->times++;
if( theKey->times > 250)
theKey->times = 250;//最多允许按下250次没处理
}else
{
theKey->state = KEY_DOWN;
}
}
}break;
#endif
}
#if KEY_USE_SEM==1
theKey->sem = KEY_SEM_FREE;
}
#endif
}
/****************************************************************
函数说明 : 对按键值的访问,主要是封装对信号量的访问
参数 : option -------- 选项
详见access_type定义。主要分read和write两类不可按位或;write有3种情况,不可按位或。
如果只是读取则不用对option进行配置
如果是write类型的访问,则要KEY_ACCESS_WRITE位或上“KEY_ACCESS_CLEAR或者其它两个中的一个”
返回值 : ->times的值。
*****************************************************************/
uint8_t Key_AccessTimes(key_inf_type* theKey, access_type option )
{
uint8_t times_temp;
#if KEY_USE_SEM==1
if( KEY_SEM_FREE == theKey->sem )
{
theKey->sem = KEY_SEM_USING;
#endif
if( (option&KEY_ACCESS_WRITE) == KEY_ACCESS_WRITE )
{
if( (option&KEY_ACCESS_CLEAR) == KEY_ACCESS_CLEAR )
{
theKey->times = 0;
}
if( (option&KEY_ACCESS_DECREASE) == KEY_ACCESS_DECREASE )
{
if( 0 == theKey->times )
{
theKey->times = theKey->times;
}else
{
(theKey->times)--;
}
}
if( (option&KEY_ACCESS_INCREASE) == KEY_ACCESS_INCREASE )
{
if( KEY_TIMES_MAX == theKey->times )
{
theKey->times = theKey->times;
}else
{
(theKey->times)++;
}
}
}
times_temp = theKey->times;
#if KEY_USE_SEM==1
theKey->sem = KEY_SEM_FREE;
}
else
{
return 0xff;//times_temp = 0xff;//表示正在被占用,不能访问times
}
#endif
return times_temp;
}
本文件讲解:void key_delay1ms(uint8_t xms);延时1ms为单位的函数,用来消抖。如果你的程序固定周期调用按键状态刷新函数就不用这个了。
void Key_Init(key_inf_type* key_this, uint8_t(*getState)(void));初始化一颗按键。第一个参数是这一颗按键的结构体,第二个参数是它的状态检测函数,由用户提供。
void Key_RefreshState(key_inf_type* theKey);刷新一颗按键状态。初始化完成后调用它就可以了。
uint8_t Key_AccessTimes(key_inf_type* theKey, access_type option );访问一颗按键的按下次数。
看起来很复杂,新手看起来可能有困难,由于篇幅较多就不一一分析。但是比较容易理解,最重要的是它的通用性,这也是我最喜欢的程序特点。 |
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|