按键事件

来源:互联网 发布:上海 儿童编程 编辑:程序博客网 时间:2024/05/16 09:13

单片机开发时,经常使用按键去执行一些操作,作者写了一个简单的事件驱动函数,来执行按键任务。

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        10static 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   0uint8_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;}
  1. 首先可以看到一个预编译的代码段,这个宏定义的含义是一个按键状态是否只支持一种状态,如果是,则要对链表进行一个遍历,如果已经存在此按键状态的事件,则返回,事件添加失败。
  2. 创建一个新的事件结构体,为其分配内存。然后把各个参数赋给它。
  3. 对链表进行遍历,将新的按键事件结构体添加到尾部。

有添加就有删除,接着看删除事件的代码。

#if KEY_UNIQUE_EVENT == 1u8 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;}#elseu8 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调用一次就可以了。

至此完成了一个简单的按键事件驱动。

原创粉丝点击