Jach_cc 发表于 2013-9-28 21:22:38

分享一下我的通用按键驱动代码

本帖最后由 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

        #ifdefKEY_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"

/****************************************************************
函数说明:        按键专用延时函数,大概一毫秒
移植说明:        需要更改
*****************************************************************/
#ifndefKEY_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
        #ifdefKEY_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))() )
                                {
                                        #ifdefKEY_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;
                       
                        #ifdefKEY_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 )
                                {
                                        #ifdefKEY_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;

                        #ifdefKEY_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 );访问一颗按键的按下次数。
看起来很复杂,新手看起来可能有困难,由于篇幅较多就不一一分析。但是比较容易理解,最重要的是它的通用性,这也是我最喜欢的程序特点。

Jach_cc 发表于 2013-9-28 22:24:57

忘了上次代码。补上

liliuqun 发表于 2013-9-28 22:31:04

太强了,顶下,学习

高灿健 发表于 2013-9-28 22:34:33

学长,怎么使用这个啊?

shotstar 发表于 2013-9-28 22:36:44

学习一下

Jach_cc 发表于 2013-9-28 22:45:36

高灿健 发表于 2013-9-28 22:34 static/image/common/back.gif
学长,怎么使用这个啊?

对了啊,这个忘了说,使用如下(avr例,矩阵键盘):

key_inf_type key_up; //定义上键

uint8_t KeyUp_state(void)
{
        PORTD |= ( (1u<<PORTD4)|(1u<<PORTD5) );
        PORTD &= ( ~(1u<<PORTD3) );
        asm("nop");//等待电平稳定
        if( (PINB&0x02)!=0x02 )
        {
                return 1;
        }else
        {
                return 0;
        }
}
void main(void)
{
Key_Init( &key_up, KeyUp_state );//初始化按键,将上键和它的检测函数联系起来。
while(1)
{
      Key_RefreshState( &key_up );//刷新按键状态
      if( Key_AccessTimes(&key_up, KEY_ACCESS_READ ) != 0 )//按键被按下过
        {
                Key_AccessTimes(&key_up, KEY_ACCESS_WRITE|KEY_ACCESS_CLEAR ) ;//清除按键状态
                state = 6;
        }

      display( state );
}

简单吧?独立按键一样,只是它们的状态检测函数不一样而已。
这里只是简单的使用,还没有涉及综合应用和组合键。

vc8fans 发表于 2013-9-28 23:04:09

不错,值得学习,,,

高灿健 发表于 2013-9-28 23:04:57

学长,写的比我的好多了。{:biggrin:}

二进制 发表于 2013-9-28 23:06:35

按键应该用 状态机               

Jach_cc 发表于 2013-9-28 23:09:33

二进制 发表于 2013-9-28 23:06 static/image/common/back.gif
按键应该用 状态机

就是用的状态机
typedef struct
{
        uint8_t (*get_state)(void);        //用于获取按键状态的函数

        #if KEY_USE_SEM==1
        key_sem_type                sem;                                //信号量,
        #endif

        #ifdefKEY_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;
这个里面的state就是。。状态有下面这些东西:
typedef enum
{
        KEY_DOWN                                 = 1,
        KEY_UP                                       = 2,
        KEY_UP_WOBBLE         = 3,//确认弹起消抖状态
        KEY_DOWN_WOBBLE = 4 //确认按下消抖状态
}key_state_type;

zenghl 发表于 2013-9-28 23:12:44

值得学习

Gallen.Zhang 发表于 2013-9-28 23:17:26

确实比较高深,对我等新手来说比较难懂,顶一顶!

djkc 发表于 2013-9-28 23:47:40

mark 按键 学习了

ijlc1314 发表于 2013-9-28 23:51:15

按键一所般都是使用一个通用模块,方便移植,每次只需要编写读键值部分即可。

zheng_pf 发表于 2013-9-29 00:26:51

做个额记号,好学习。

fengyunyu 发表于 2013-9-29 07:12:06

先学习   

cc1989summer 发表于 2013-9-29 08:45:35

收藏了细看

dongfo 发表于 2013-9-29 09:24:18

写的不错,遇到键盘就可以不用再慢慢扣了,只是对于没怎么接触过状态机的初学者可能需要花点时间研究一下

kxb 发表于 2013-9-29 09:42:17

不错,很好的思路。

qufuta 发表于 2013-9-29 10:40:59

楼主,我想问下,既然你说了延时消抖会浪费CPU时间,但是在你的程序中还是有key_delay1ms这个函数的调用,这函数,不同样也会是程序停在这个地方,一样也是会浪费CPU时间啊???

Jach_cc 发表于 2013-9-29 14:13:42

qufuta 发表于 2013-9-29 10:40 static/image/common/back.gif
楼主,我想问下,既然你说了延时消抖会浪费CPU时间,但是在你的程序中还是有key_delay1ms这个函数的调用, ...

这个函数不一定用得着,你如果固定频率调用刷新函数这个就完全不用。如果你自己也不知道多长时间调用一次刷新,那就需要用这个来消抖。

tjCFeng 发表于 2013-9-29 14:14:50

这个要顶。。。。。。。。。

csb1030 发表于 2013-9-29 14:22:33

顶一下,不错

whatcanitbe 发表于 2013-9-29 15:16:33

还没仔细看,不过将三个文件打包一下方便大家下载

皮爱了西 发表于 2013-9-29 15:35:31

好像没法处理两个按钮同时按下的情况。(例如,两个按键,需要处理,每个按键短按,长按,以及两个按键同时按下的情况),代码是自己写的,特繁琐,测试了好用。/*---------define key's STATE-------------*/
#define KEY_NORMAL                        0
#define KEYA_PRESS                        1
#define        KEYA_RELEASE                2
#define KEYB_PRESS                        3
#define KEYB_RELEASE                4
#define KEYAB_PRESS                        5
#define KEYA_SHORTPRESS                6
#define KEYA_LONGPRESS                7
#define KEYB_SHORTPRESS                8
#define KEYB_LONGPRESS                9


/*-----------define IO input state----*/
#define KeyARelease                (P1IN & 0x01) == 0x01
#define KeyBRelease                (P1IN & 0x04) == 0x04
#define KeyAPress                (P1IN & 0x01) == 0
#define KeyBPress                (P1IN & 0x04) == 0

unsigned char KeyAction;        //
unsigned char KeyState;

/*===================键盘状态机函数=======================================*/
void ScanKeyState(void)
{
        static unsigned char DelayKeyAShortPress = 0;
        static unsigned char DelayKeyALongPress = 0;
        static unsigned char DelayKeyBShortPress = 0;
        static unsigned char DelayKeyBLongPress = 0;

        switch(KeyState)
        {
        case KEY_NORMAL:                //there are three state to go
                if(KeyAPress && KeyBRelease)
                {
                        DelayKeyAShortPress++;
                        if(DelayKeyAShortPress>1)
                        {
                                DelayKeyAShortPress = 0;
                                KeyState = KEYA_PRESS;
                                break;
                        }
                }
                else
                {
                        DelayKeyAShortPress = 0;
                }

                if(KeyBPress && KeyARelease)
                {
                        DelayKeyBShortPress++;
                        if(DelayKeyBShortPress>1)
                        {
                                DelayKeyBShortPress = 0;
                                KeyState = KEYB_PRESS;
                                break;
                        }
                }
                else
                {
                        DelayKeyBShortPress = 0;
                }

                if(KeyAPress && KeyBPress)
                {
                        DelayKeyAShortPress++;
                        DelayKeyBShortPress++;
                        if(DelayKeyAShortPress>1 && DelayKeyBShortPress>1)
                        {
                                DelayKeyAShortPress = 0;
                                DelayKeyBShortPress = 0;
                                KeyState = KEYAB_PRESS;
                                break;
                        }
                }
                break;

        case KEYA_PRESS:                                //need to check three condition:KeyA release;KeyA Press for T1;KeyB press for t2
                if(KeyARelease)                                //if the condition is false, then goes to next "if"
                {
                        //DelayKeyALongPress = 0;
                        DelayKeyAShortPress++;
                        if(DelayKeyAShortPress>1)
                        {
                                DelayKeyAShortPress = 0;
                                KeyState = KEYA_SHORTPRESS;
                                KeyAction=1;                //KeyA short press
                                break;
                        }
                }
                else if(KeyAPress && KeyBRelease)        //if the condition is false, then
                {
                        DelayKeyALongPress++;
                        if(DelayKeyALongPress>100)
                        {
                                DelayKeyALongPress = 0;
                                DelayKeyAShortPress = 0;
                                KeyState = KEYA_LONGPRESS;
                                KeyAction=2;                        //KeyA Long Press
                                break;
                        }
                        //if(DelayKeyALongPress>)
                }
                else if(KeyAPress && KeyBPress)
                {
                        DelayKeyALongPress++;
                        DelayKeyBShortPress++;
                        if(DelayKeyALongPress > 1 && DelayKeyBShortPress > 1)
                        {
                                DelayKeyALongPress= 0;
                                DelayKeyBShortPress = 0;
                                DelayKeyAShortPress = 0;
                                KeyState = KEYAB_PRESS;
                                KeyAction=5;                        //KeyA and KeyB are all Press in the same time
                        }
                }
                break;
        case KEYB_PRESS:
                if(KeyBRelease)
                {
                        DelayKeyBShortPress++;
                        if(DelayKeyBShortPress>1)
                        {
                                DelayKeyBShortPress = 0;
                                KeyState = KEYB_SHORTPRESS;
                                KeyAction=3;                        //KeyB short press
                                break;
                        }
                }
                else if(KeyBPress && KeyARelease)
                {
                        DelayKeyBLongPress++;
                        if(DelayKeyBLongPress>100)
                        {
                                DelayKeyBLongPress = 0;
                                DelayKeyBShortPress = 0;
                                KeyState = KEYB_LONGPRESS;
                                KeyAction=4;                        //KeyB long press
                                break;
                        }

                }
                else if(KeyBPress && KeyAPress)
                {
                        DelayKeyBLongPress++;
                        DelayKeyAShortPress++;
                        if(DelayKeyBLongPress > 1 && DelayKeyAShortPress > 1)
                        {
                                DelayKeyBLongPress= 0;
                                DelayKeyAShortPress = 0;
                                DelayKeyBShortPress = 0;
                                KeyState = KEYAB_PRESS;
                                KeyAction=5;                        //KeyA and KeyB are all press in the same time

                        }
                }
                break;
        case KEYA_SHORTPRESS:
                if(KeyARelease)
                {
                        DelayKeyAShortPress++;
                        if(DelayKeyAShortPress>1)
                        {
                                KeyState = KEY_NORMAL;
                                DelayKeyAShortPress = 0;
                                DelayKeyALongPress = 0;
                        }
                }
                break;
        case KEYA_LONGPRESS:
                if(KeyARelease)
                {
                        DelayKeyAShortPress++;
                        if(DelayKeyAShortPress>1)
                        {
                                KeyState = KEY_NORMAL;
                                DelayKeyAShortPress = 0;
                                DelayKeyALongPress = 0;
                        }
                }
                break;
        case KEYB_SHORTPRESS:
                if(KeyBRelease)
                {
                        DelayKeyBShortPress++;
                        if(DelayKeyBShortPress>1)
                        {
                                KeyState = KEY_NORMAL;
                                DelayKeyBShortPress = 0;
                                DelayKeyBLongPress = 0;
                        }
                }
                break;
        case KEYB_LONGPRESS:
                if(KeyBRelease)
                {
                        DelayKeyBShortPress++;
                        if(DelayKeyBShortPress>1)
                        {
                                KeyState = KEY_NORMAL;
                                DelayKeyBShortPress = 0;
                                DelayKeyBLongPress = 0;
                        }
                }
                break;

        case KEYAB_PRESS:
                if(KeyARelease && KeyBRelease)
                {
                        DelayKeyAShortPress++;
                        DelayKeyBShortPress++;
                        if(DelayKeyAShortPress>1 && DelayKeyBShortPress>1)
                        {
                                KeyState = KEY_NORMAL;
                                DelayKeyBShortPress = 0;
                                DelayKeyBLongPress = 0;
                        }
                }
                break;
        }
}

Jach_cc 发表于 2013-9-29 15:41:59

皮爱了西 发表于 2013-9-29 15:35 static/image/common/back.gif
好像没法处理两个按钮同时按下的情况。(例如,两个按键,需要处理,每个按键短按,长按,以及两个按键同时 ...

可以处理多个按键同时操作(同时按下),在底层都一样,直接调用就可以了。主要是在你的应用中进行判断,比如多个键同时按下时,相关按键的->times都不为零。那就知道是组合键了。

qufuta 发表于 2013-9-29 18:52:18

建议你看看这个帖子,他写的非常号,整体结构非常完整,当然你写的也不错,http://www.amobbs.com/thread-5542774-1-1.html

金牛AKI 发表于 2013-9-29 20:29:53

先顶一个哈哈

kenson 发表于 2013-9-29 20:31:35

吾好东西顶一下以后再看

justSaar 发表于 2013-9-29 20:37:06

不错顶一下

dangeranimal 发表于 2013-9-29 20:55:34

不错,学习

keinYe 发表于 2013-9-30 08:39:47

标记了,留待以后使用!

wtliu 发表于 2013-9-30 09:14:28

可以有按键连击吗?
许多情况下按键需要有按住一个键不松手,一定时间后按键会自动按一个固定时间连续输出。这个键一般用在数据的加减设置中。希望楼主能增加这个功能。

JESTER9 发表于 2013-9-30 09:15:49

MARK,谢谢分享

caoxinkafei 发表于 2013-9-30 14:43:09

好贴 ,Mark

Jach_cc 发表于 2013-9-30 16:12:05

wtliu 发表于 2013-9-30 09:14 static/image/common/back.gif
可以有按键连击吗?
许多情况下按键需要有按住一个键不松手,一定时间后按键会自动按一个固定时间连续输出 ...

谢谢你的建议。其实这个功能只要用户定时判断->state = KEY_DOWN就能实现了。当然封装到void Key_RefreshState(key_inf_type* theKey)这个函数,作为它的返回值就方便多了。

Jach_cc 发表于 2013-9-30 16:16:39

wtliu 发表于 2013-9-30 09:14 static/image/common/back.gif
可以有按键连击吗?
许多情况下按键需要有按住一个键不松手,一定时间后按键会自动按一个固定时间连续输出 ...

关于连击,我想在用户定时调用更新函数时实现,因为连击和时间相关。不想因为判断连击而占用CPU大量资源。

ba_wang_mao 发表于 2013-10-14 16:30:13

不错。谢谢楼主,功能比较强大

lcmdw 发表于 2013-10-14 16:40:49

mark{:smile:}

ZBLAMDZ 发表于 2013-10-15 20:49:09

似乎有点复杂啊

edkaifa 发表于 2014-2-19 08:54:19

需要不停地扫描按键吧...

韦斯克拉 发表于 2014-2-19 11:27:02

MARK,谢谢分享

yayagepei 发表于 2014-2-19 12:35:43

学习一下!

xujihu 发表于 2014-5-8 13:30:05

厉害 学习了

10xjzheng 发表于 2014-5-17 15:40:46

Jach_cc 发表于 2013-9-28 22:45
对了啊,这个忘了说,使用如下(avr例,矩阵键盘):

key_inf_type key_up; //定义上键


不懂avr不知道你检测状态是什么意思!!!

10xjzheng 发表于 2014-5-17 18:13:41

STM32移植成功,有工程文件

本帖最后由 10xjzheng 于 2014-5-17 18:23 编辑

移植成功,一下午的时间总算没有白费,分享一下。测试正常,你按住一个按键,随便都可以同时控制另一个按键。有空楼主留个QQ交流下你写程序的思路。而且我只是用了最简单的按键获取,没有什么复杂的长按短按的,求楼主教下。
有个问题楼主需要解决。
根据楼主介绍的使用办法,是要用到下面这行代码的,Key_AccessTimes(&key_up, KEY_ACCESS_WRITE|KEY_ACCESS_CLEAR ) ;//清除按键状态这是不对的,首先看函数uint8_t Key_AccessTimes(key_inf_type* theKey, access_type option ),后面这个参数是枚举变量,但是这个枚举变量没有KEY_ACCESS_WRITE|KEY_ACCESS_CLEAR里面没有这个吧,所以呢?我就给枚举类型access_type增加了一种写入按键消除状态的枚举变量KEY_ACCESS_WRITE_CLEAR=KEY_ACCESS_WRITE|KEY_ACCESS_CLEAR,在枚举类型定义那里。应该还可以将函数的形参option类型改成其他的形式,编译的时候就不会出错了。
我用的板子是野火的ISO,芯片STM32,原理图如下:


把上面的问题解决之后,在Key.c里面增加多下面的函数,只列举了一个按键!一个按键2个定义的函数,而且内容都差不多!!这个就爽爆了,移植很容易。首先是状态获取,如果IO口是你按键按下对应的电平,那么返回1,否则返回0,可以看到我这是不是组合按键,简单到爆有木有!!第二个函数是GPIO的初始化,STM32必须的,51的就可以省了。
/****************************************************用户自定义函数***********************************/
uint8_t Key1_State(void)
{
      if(GPIO_ReadInputDataBit(KEY1_PORT,KEY1_PIN)==KEY_ON)
      {
                        return 1;
      }
      else
      {
                        return 0;
      }
}
//按键接A0
void Key1_GPIO_Config(void)
{
      GPIO_InitTypeDef GPIO_InitStructure;
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
      GPIO_InitStructure.GPIO_Pin=KEY1_PIN;
      GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
      GPIO_Init(KEY1_PORT,&GPIO_InitStructure);
}
/***************************************用户自定义函数结束***********************************/在key.h里面主要是管脚的宏定义,方便修改和移植,不用也是可以的,后面是声明上面两个函数的部分
/*****************************************用户定义声明部分************************************************/
//用户定义变量
#define KEY_ON0
#define KEY_OFF 1
#define KEY1_PINGPIO_Pin_0
#define KEY1_PORT GPIOA
//用户定义变量结束

//用户函数声明
uint8_t Key1_State(void);
void Key1_GPIO_Config(void);
//用户函数声明结束
/*****************************************用户定义声明部分************************************************/
真的是感谢作者。工程文件:,如果是想移植到其他的STM32板子,就把端口在宏定义那里改成对应的就可以啦。
我刷新是用systick做后台的一个任务调度,在刷新那里可以看到,Task_Delay=50;这个每1ms会自动减一,50个就是50ms进入一次任务,只有=0的时候才能进入刷新任务,所以这个是20Hz的。


lookdown 发表于 2014-6-12 13:52:14

学习了,谢谢

yytyu 发表于 2014-6-17 14:13:25


mark 按键 学习了

ppdd 发表于 2014-7-20 00:16:30

10xjzheng 发表于 2014-5-17 15:40
不懂avr不知道你检测状态是什么意思!!!

同问,这个有点悬虚。 对没有用过AVR的人,真是。。。

iixin 发表于 2014-7-20 00:55:51

这个要好好学习并消化一下,正准备做按键,

ppdd 发表于 2014-7-20 01:20:50

难道矩阵只用了PortD3:5和 portB ???

uint8_t KeyUp_state(void)
{
      PORTD |= ( (1u<<PORTD4)|(1u<<PORTD5) );    // PORTD 输出1
      PORTD &= ( ~(1u<<PORTD3) );    // PORTD 输出0
      asm("nop");//等待电平稳定
      if( (PINB&0x02)!=0x02 )//检测 PortB 输入状态。 难道矩阵只用了PortD3:5和 portB
      {
                return 1;
      }else
      {
                return 0;
      }
}

10xjzheng 发表于 2014-7-20 15:52:25

ppdd 发表于 2014-7-20 01:20
难道矩阵只用了PortD3:5和 portB ???

uint8_t KeyUp_state(void)


这个其实很简单,意思是说,如果现在检测出来IO口的电平跟你按键按下时候IO口的电平是一样的,那么返回1,否则返回0.

ppdd 发表于 2014-7-20 16:22:07

这个真心,没看明白,用来做什么???
typedef enum
{
        KEY_ACCESS_READ         = 0x08,
        KEY_ACCESS_WRITE        = 0x80,
        KEY_ACCESS_CLEAR        = 0X40,
        KEY_ACCESS_DECREASE = 0X20,
        KEY_ACCESS_INCREASE = 0X10, ///??????? ÓÐûÓжººÅÄØ       
}access_type;

破烂王 发表于 2014-8-19 21:04:16

没有看到松手检测,不会连发吗?

蓝蓝的恋 发表于 2014-9-1 11:34:16

按键还是比较复杂的~

Maurice 发表于 2014-9-1 13:12:06

顶!家常便饭做得好吃并非易事!

superplaim 发表于 2014-9-1 20:26:20

本帖最后由 superplaim 于 2014-9-1 20:29 编辑

请教楼主这个uint8_t (*get_state)(void);是个什么意思 语法上表示什么
这是定义了个指针变量吗 加void是什么意思

yfgww 发表于 2014-11-10 16:44:26

{:smile:}{:smile:}{:smile:}

xurenhui 发表于 2015-5-27 09:31:34

10xjzheng 发表于 2014-5-17 18:13
移植成功,一下午的时间总算没有白费,分享一下。测试正常,你按住一个按键,随便都可以同时控制另一个按键 ...

你好,请问 一下*(theKey->get_state))()到底是什么意思啊看不懂

yangpeng012 发表于 2015-7-13 23:13:50

请问楼主,这些与编译器有关的东西能不能直接去掉啊!!!在keil下始终编译不过啊!!!

daska110a 发表于 2015-7-14 07:47:36

很详细的按键

彼岸花开@ 发表于 2015-7-14 08:07:38

互斥量。。研究一下。。以前没有这么使用过。

夜猫 发表于 2015-7-14 08:37:13

很好 学习一下

wukangkang 发表于 2015-8-21 13:00:25

非常感谢楼主资料,我使用飞思卡尔M0移植成功了。现在有个疑问想请教楼主:
我在10ms伺服周期刷新按键状态,看了下函数,总感觉无论如何都要执行
key_delay1ms(KEY_WOBBLE_TIME);          // 去抖动函数
请指教,谢谢。


                        case KEY_UP:
                        {
                                if((*(theKey->get_state))())                // »ñÈ¡µçƽ״̬
                                {
                                        #ifdefKEY_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
                                }
                        }

                        case KEY_DOWN:
                        {
                                if((*(theKey->get_state))() == 0)
                                {
                                        #ifdefKEY_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
                                }
                        }

ynleo 发表于 2015-10-9 17:04:29

好东西,统统MARK...

clwang851217 发表于 2016-3-8 16:49:41

强学习了!!!

andmain999 发表于 2016-6-19 11:39:40

试了,效果不错

我要吃大葱 发表于 2017-3-24 02:29:50

mark{:handshake:} {:handshake:} {:handshake:}

glkj 发表于 2017-3-30 23:07:22

有创意,有风格。

steve_zhang 发表于 2018-9-20 10:54:55

个人觉得应该从一个软件的整体上来考虑按键程序。
1 可以将按键底层驱动与应用层分开,应用层与按键底层通过一个循环队列来交互。
2 底层驱动只负责向队列里放按键消息,应用层只需要从队列里取消息即可。
3 按照这个思想,整个软件结构就非常的清晰了。

Honey_comb 发表于 2018-9-20 11:08:12

用了很好用,谢谢。

老谷 发表于 2018-9-20 13:43:03

学习学习,谢谢分享

horary 发表于 2020-5-17 10:03:06

顶一下,学习学习!
页: [1]
查看完整版本: 分享一下我的通用按键驱动代码