按键事件

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

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

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

#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调用一次就可以了。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值