单片机开发时,经常使用按键去执行一些操作,作者写了一个简单的事件驱动函数,来执行按键任务。
1.获取按键状态
按键的状态可以有多种,短按,长按,双击,或者组合键之类。基于按键的状态机算法即可获取这些状态,这里只是简单的实现了长按和短按的状态识别。
static u8 Key_Read(void)
{
if( (KEY_UP == 1 || KEY_DOWN == 0 || KEY_LEFT == 0 || KEY_RIGHT == 0))
{
//延时消抖
delay_ms(10);
if(KEY_UP == 1) return KEY_UP_PRES;
else if(KEY_DOWN == 0) return KEY_DOWN_PRES;
else if(KEY_LEFT == 0) return KEY_LEFT_PRES;
else if(KEY_RIGHT == 0) return KEY_RIGHT_PRES;
}
return KEY_ALL_OFF;
}
这段代码就是读取一次按键值,并进行简单的延时消抖。
#define KEY_LONG_CNT 10
static u8 getKeyState(void)
{
static uint8_t cnt = 0,read_key_state[KEY_LONG_CNT];
uint8_t key_state = 0 , i , key_cnt = 0;
key_state = Key_Read();
if((key_state!=KEY_ALL_OFF)) //有按键按下 开始记录按键 和 计数
{
read_key_state[cnt++] = key_state;
}
else if((key_state == KEY_ALL_OFF) && (cnt != 0)) //在未达到计数次数前按键被松开 则为短按
{
cnt = 0;
key_state = read_key_state[0];
for( i=0 ; i<KEY_LONG_CNT ; i++ )
{
read_key_state[i] = 0;
}
return key_state;
}
if(cnt == KEY_LONG_CNT ) //如果到了长按的计数次数 进行判断为哪个按键的长按
{
key_state = read_key_state[0];
for( i=1 ; i<KEY_LONG_CNT ; i++)
{
if(key_state == read_key_state[i])
{
key_cnt++;
read_key_state[i] = 0;
}
}
cnt = 0;
if(key_cnt > KEY_LONG_CNT -2 ) //最大扫描次数里有 KEY_LONG_CNT -2 次 跟初始按下情况一样 则为此按键的长按
{
while((KEY_UP == 1 || KEY_DOWN == 0 || KEY_LEFT == 0 || KEY_RIGHT == 0)) //等待此按键松开
;
if(key_state == KEY_UP_PRES)
{
return KEY_UP_LONG_PRES;
}
else if(key_state == KEY_DOWN_PRES)
{
return KEY_DOWN_LONG_PRES;
}
else if(key_state == KEY_LEFT_PRES)
{
return KEY_LEFT_LONG_PRES;
}
else if(key_state == KEY_RIGHT_PRES)
{
return KEY_RIGHT_LONG_PRES;
}
}
else
{
return key_state;
}
}
return KEY_ALL_OFF;
}
记录每次按键的状态,然后计算时间,有大于KEY_LONG_CNT -2 次的按键状态一致,即为按键的长按,否则为短按,最后解析出按键的状态,某个按键和按键的状态(长按还是短按).
此时就已经获取到了按键的状态,不过我们再对这个函数进行一层封装。
static keyEventEnum getEnum()
{
KEY_State = getKeyState();
switch(KEY_State)
{
case KEY_UP_PRES : return UP;
case KEY_DOWN_PRES : return DOWN;
case KEY_LEFT_PRES : return LEFT;
case KEY_ALL_OFF : return NONE;
case KEY_RIGHT_PRES : return RIGHT;
case KEY_UP_LONG_PRES : return LONG_UP;
case KEY_LEFT_LONG_PRES : return LONG_LEFT;
case KEY_DOWN_LONG_PRES : return LONG_DOWN;
case KEY_RIGHT_LONG_PRES : return LONG_RIGHT;
default : return NONE;
}
}
根据按键状态返回一个枚举类型的变量
2 构造事件驱动
#define KEY_EVENT_NAME_LENGTH 15 //事件名称最大长度
typedef struct keyEventType
{
struct keyEventType *next;
void (*fun)(void *arg); //处理函数
void *arg; //参数
char name[KEY_EVENT_NAME_LENGTH]; //事件名称
keyEventEnum keyState; //事件
}keyEventType;
以上述结构体构造一个事件处理链表,然后只要根据前文的getEnum和链表中的每个结构体的keystate进行比较,然后执行相应的事件即可。
首先先看初始化代码
//按键事件头指针
keyEventType * currentKeyEvent;
void keyInit(void)
{
/*省略按键io口配置*/
//申请动态内存空间,这个函数可以看我之前写的文章
currentKeyEvent = myMalloc(sizeof(keyEventType));
//指向空
currentKeyEvent->next = NULL;
}
初始化很简单,为根指针分配空间,然后指向空
下面为添加事件代码
//一个按键状态只支持一个事件
#define KEY_UNIQUE_EVENT 0
uint8_t addKeyEvent(char const *name,keyEventEnum state,void (*fun)(void *arg),void *arg)
{
keyEventType *newKeyEvent = NULL;
keyEventType *q = currentKeyEvent;
#if KEY_UNIQUE_EVENT == 1
while(q->next != NULL)
{
//已经存在此按键状态的事件
if(q->next->keyState == state)
{
return False;
}
q = q->next;
}
q = currentKeyEvent;
#endif
newKeyEvent = myMalloc(sizeof(keyEventType));
if(newKeyEvent == NULL)
{
return False;
}
newKeyEvent->arg = arg;
newKeyEvent->fun = fun;
newKeyEvent->keyState = state;
newKeyEvent->next = NULL;
strcpy(newKeyEvent->name,name);
//添加新的按键事件
while(q->next != NULL)
{
q = q->next;
}
q->next = newKeyEvent;
return True;
}
- 首先可以看到一个预编译的代码段,这个宏定义的含义是一个按键状态是否只支持一种状态,如果是,则要对链表进行一个遍历,如果已经存在此按键状态的事件,则返回,事件添加失败。
- 创建一个新的事件结构体,为其分配内存。然后把各个参数赋给它。
- 对链表进行遍历,将新的按键事件结构体添加到尾部。
有添加就有删除,接着看删除事件的代码。
#if KEY_UNIQUE_EVENT == 1
u8 removeKeyEvent(keyEventEnum state)
{
keyEventType *q = currentKeyEvent;
keyEventType *temp;
while(q->next != NULL)
{
//删除事件,回收内存
if(q->next->keyState == state)
{
temp = q->next;
q->next = q->next->next;
myFree(temp);
return True;
}
q = q->next;
}
return False;
}
#else
u8 removeKeyEvent(char const *name)
{
keyEventType *q = currentKeyEvent;
keyEventType *temp;
while(q->next != NULL)
{
//删除事件,回收内存
if(strcmp(q->next->name,(char*)name) == 0)
{
temp = q->next;
q->next = q->next->next;
myFree(temp);
return True;
}
q = q->next;
}
return False;
}
#endif
删除事件的函数很简单,不过通过一个预编译命令写成了两个函数。如果宏定义为1,即一个按键状态只支持一种状态,那么就可以通过按键事件的状态去查找要删除的事件结构体。不然的话就只能通过按键事件名称去删除。
通过对链表遍历,找到对应的结构体,调整链表指向,回收内存。
最后来看事件的执行函数
void runKeyEvent()
{
keyEventEnum state = getEnum();
keyEventType *q = currentKeyEvent;
while(q->next != NULL)
{
if(q->next->keyState == state)
{
q->next->fun(q->next->arg);
#if KEY_UNIQUE_EVENT == 1
break;
#endif
}
q = q->next;
}
}
同样就是对链表进行遍历,然后判断是否触发了此按键事件,如果是,则执行相应的事件回调函数。
宏定义的判断是,如果一个按键状态只支持一种状态,那么执行了一次函数回调就可以返回了,因为后面不会再有与此状态对应的事件了。这个函数100ms调用一次就可以了。
至此完成了一个简单的按键事件驱动。