nicksean 发表于 2009-1-22 22:48:05

通用按键消抖函数 -- 数据与过程分离【恢复】

    项目里经常处理按键消抖, 本来这个消抖的过程是与具体按下的键无关的, 可以前的代码总是在消抖的同时处理具体的按键值, 再加上长按 短按 组合键混在一起, 成一锅粥. 最近在一个项目中痛下决心, 想弄个通用版本的, 这样下个项目只要将文件包含一下, 处理具体按键值就可以了, 不必再关心消抖部分的代码了. 另外还发现, 这样做可以同时做出几套不同的按键处理方式.

思路是: 按照面向过程的编程方式, 将数据与过程分离. 把和按键状态相关的东西统统塞到结构里, 把消抖的代码放在一个函数中.



//key.h 头文件-------------------------------------------------------------

#ifndef _KEY_H

#define _KEY_H

#define _KEY_NONE       0



#define _HAS_NO_KEY                     0

#define _HAS_KEY_DOWN                   1

#define _HAS_KEY_SURE                   2

#define _HAS_KEY_WAITUP                 3



#define _REENTER                        1

#define _NO_REENTER                     2



typedef struct  

{

    WORD PreDownKey;                                //上次检测到的键

    BYTE KeyState;                                //状态

    WORD SameKeyCntr;                                //同一键检测到按下的次数

    WORD CurKey;                                //当前检测到的键, 用于处理长按的情况

    BYTE (*KeyDownCallBack)(WORD, WORD);                   //键确认按下的回调函数指针

    void (*KeyUpCallBack)(WORD);                //键抬起的回调函数指针

} struct_KeyInfo;



void DitherlessKey(struct_KeyInfo* pInfo);        //消抖的处理函数





#endif//_KEY_H





//消抖动的代码--------------------------------------------------------------

#include "Key.h"



//定时消抖的按键处理函数, 通常在定时中断中调用, 

void DitherlessKey(struct_KeyInfo* pInfo)

{

    switch(pInfo->KeyState)

    {

    case _HAS_NO_KEY:

        pInfo->SameKeyCntr = 0;

        if(pInfo->CurKey != _KEY_NONE)

        {

            pInfo->KeyState = _HAS_KEY_DOWN;                           //进入有键按下状态

        }

        break;

        

    case _HAS_KEY_DOWN:

        if(pInfo->PreDownKey == pInfo->CurKey)

        {

            pInfo->KeyState = _HAS_KEY_SURE;                           //确认键已按下状态

        }

        else

        {

            pInfo->KeyState = _HAS_NO_KEY;                             //回到无键状态

        }        

        break;

        

    case _HAS_KEY_SURE:

        if(pInfo->CurKey == pInfo->PreDownKey)

        {

            pInfo->KeyState = _HAS_KEY_WAITUP;

            if(pInfo->KeyDownCallBack)

            {

                //这里回调函数的返回值决定了是否允许出现长按的情况

                if(_REENTER == pInfo->KeyDownCallBack(pInfo->CurKey, pInfo->SameKeyCntr))

                {

                    pInfo->KeyState = _HAS_KEY_SURE;

                    ++pInfo->SameKeyCntr;

                }

            }

        }

        else

        {

            pInfo->KeyState = _KEY_NONE;

        }

        break;



    case _HAS_KEY_WAITUP:

        if(pInfo->CurKey != pInfo->PreDownKey)

        {

            pInfo->KeyState = _HAS_NO_KEY;



            if(pInfo->KeyUpCallBack)

            {

                pInfo->KeyUpCallBack(pInfo->PreDownKey);

            }

        }

        break;

        

    default:

        break;

    }

    

    pInfo->PreDownKey = pInfo->CurKey;                                        //保存上次按键值



    return;

}



//应用代码片段---------------------------------------------------------------------------------------

......



//声明按键回调函数

BYTE KeyDownCallBack(WORD Key, WORD Times);

BYTE KeyDownCallBack2(WORD Key, WORD Times);



//按键处理数据结构

static struct_KeyInfo g_KeyInfo1 = {0, 0, 0, 0, KeyDownCallBack};

static struct_KeyInfo g_KeyInfo2 = {0, 0, 0, 0, KeyDownCallBack2};



//////////////////////////////////////////////////////////////////////////

//TIMER2 initialize - prescale:1024

// WGM: Normal

// desired value: 100Hz

// actual value: 101.024Hz (1.0%)

#pragma interrupt_handler timer2_ovf_isr:iv_TIM2_OVF

void timer2_ovf_isr(void)

{

    WORD temp;



    _TIMER2_LOAD; //reload counter value

    

    temp = Read165() ^ _KEY_MASK;                    //输入信息



    g_KeyInfo1.CurKey = temp & 0x00FF;                

    DitherlessKey(&g_KeyInfo1);



    g_KeyInfo2.CurKey = temp & 0xFF00;                //同一个消抖函数处理不同的按键

    DitherlessKey(&g_KeyInfo2);

}



//在回调函数中处理具体的键值

BYTE KeyDownCallBack(WORD Key, WORD Times)

{

    switch(Key)

    {

    case _KEY_F2:

        if(Times < 200)             //长按 2s

        {

            return _REENTER;        //2s内允许长按

        }

        break;



    case _KEY_CLR_CNTR:

        if(Times < 1000)            //四个键长按10s

        {

            return _REENTER;        //允许长按

        }



    default:

        break;

    }



    g_DownKey = Key;                //输出按键信息, 给主循环处理. 这个回调函数是由定时中断中的代码调用的.

    return _NO_REENTER;             //其余键, 不允许长按

}





BYTE KeyDownCallBack2(WORD Key, WORD Times)

{

    switch(Key)

    {

    case _KEY_I:

        if(Times == 20)              //数值 x 10 ms

        {

            g_DownKey |= _KEY_I;

        }

        else if(Times == 300)        //长按3s时执行一个动作, 只会执行一次

        {

            g_I++;

        }

        break;



    default:

        break;

    }



    return _REENTER;                 //始终允许长按, 直到键抬起

}

lrzxc 发表于 2009-1-22 22:57:24

mark,up

nhlijiaming 发表于 2009-1-22 23:00:50

好像还真复杂..  

zcllom 发表于 2009-1-22 23:25:08

动机、思路、方法、结局都不错

ndust 发表于 2009-1-22 23:49:51

jh

zlei 发表于 2009-1-23 10:41:54

mark

spy2008 发表于 2009-1-23 10:51:04

mark, 学习

y2kloach 发表于 2009-1-23 13:00:31

如果能够消掉回调函数和那个大的结构体就更好~~~~~~~~

起始要做到楼主说的那样, 只要规定好按键输出的接口, 也就是按键键值就可以实现了~~~~~~

root_007 发表于 2009-1-23 13:26:12

楼主人品不错

usbfish 发表于 2009-1-23 15:14:20

好东西

taishandadi 发表于 2009-1-23 15:36:34

不错,看看准备用了 

Bird 发表于 2009-9-12 15:44:35

学习ing!!!

w48720770 发表于 2009-10-7 15:46:43

强烈建议楼主 把程序流程再具体点

不然 众弟兄ctrl+ c   ctrl+v 就跑人了
然后资料就放盘里

就是里面金光闪闪的也没有人细研究了

Bird 发表于 2009-10-7 20:47:46

一个字,强~~~~~~~

nicksean 发表于 2009-10-7 22:14:52

这个居然又被顶起了? 呵呵, 本以为石沉大海了呢.

说说思路, 本质就是个状态机. 把键分为四个状态:
_HAS_NO_KEY:未按下,
_HAS_KEY_DOWN:检测到一次按下,
_HAS_KEY_SURE:又检测到一次按下, 两次都检测到按下, 就认为确实按下了, 达到消抖的目的, 如果想再增加可靠性, 可以增加状态或者给每个按键设置个计数器.
_HAS_KEY_WAITUP:等待键抬起.
状态转换图如下:

            /-----检测到键----->\            /--第二次检测到键-->\               /--该键仍被检测到-->\
         /                     \            /                     \             /                     \
_HAS_NO_KEY                      _HAS_KEY_DOWN                     _HAS_KEY_SURE                   _HAS_KEY_WAITUP
         \                     /                                  /                                 /
            \<--本次与上次不同--/                                  /                                 /
             \                                                    /                                 /
            \<--------------------本次与上次不同---------------/                                 /
               \                                                                                    /
                \<---------------------------------本次与上次不同----------------------------------/             '

    状态是与具体的键相关的, 如果不考虑通用性的话, 可以把具体的键值写到代码里. 这里想把状态从处理过程中分离出来, 就定义了struct_KeyInfo结构用来保存键值和键的状态, 同时也把对键的处理以回调函数(函数指针)的形式放到结构里了, 由它去处理具体的按键值, 这样就把对具体键的处理与消抖分离了.
    由于使用的状态机, 消抖只关心状态改变的条件, 而不关心状态本身, 这样就可以把按键检测放到定时中断中执行了. 同样消抖过程也不关心按键值的获得过程, 扫描也好, 直读也行. 上面的例子是用并转串方式得到键值的.
    键本身是否允许长按与短按是通过回调函数的返回值控制的, 至于长按的时间长短, 是通过回调函数的Times参数给出, 由用户的键处理代码判断的. 在使用时
可以根据程序当前的状态来灵活处理. 对于组合键, 是通过键值的定义实现的, 比如:
#define _KEY_1                        0x0080
#define _KEY_2                        0x0040
#define _KEY_3                        0x0020
#define _KEY_4                        0x0010
#define _KEY_5                        0x0008
#define _KEY_6                        0x0004
#define _KEY_7                        0x0002
#define _KEY_8                        0x0001
#define _KEY_LOAD_DEFAULT               (_KEY_1 | _KEY_8 | _KEY_7 | _KEY_6 | _KEY_5 | _KEY_4)
#define _KEY_SAVE_MANUFACTURE         (_KEY_2 | _KEY_3 | _KEY_5)
#define _KEY_LOAD_MANUFACTURE         (_KEY_1 | _KEY_8 | _KEY_4 | _KEY_5)

Jmjmjm 发表于 2009-10-7 22:31:07

好帖

jchqxl 发表于 2009-10-8 00:19:19

谢谢LZ分享经验

liangbmw 发表于 2009-10-8 10:14:08

mark

kugel 发表于 2009-10-8 11:01:09

mark

w418781840 发表于 2009-10-8 12:17:17

好思路

coody 发表于 2009-10-8 12:33:37

这是模块化设计的必要方法,本来按键就与处理无关。
键扫的任务就是扫键,别的程序做什么处理,与键扫无关。

alexmayer 发表于 2009-10-9 09:31:43

不错,学习了!

mayitbey 发表于 2009-10-9 09:40:37

要好好学习一下。

farmer 发表于 2009-10-9 09:44:40

好思路

smtgg 发表于 2009-10-9 09:51:45

不错

chengtina 发表于 2009-10-9 16:04:33

MARK

Pyrrho 发表于 2009-10-9 17:30:37

Mark

lv998127 发表于 2009-11-14 16:06:46

记号!

z7926573 发表于 2009-11-14 18:32:24

mark

stefgq 发表于 2009-11-14 19:14:31

baggio18 发表于 2009-11-14 22:52:13

强烈mark

cain.lee 发表于 2009-11-14 22:55:33

。。先标记一下··嗯··看起来不错啊

kclc 发表于 2009-11-14 23:33:35

学习了 lz好共享

feiyang007 发表于 2009-11-14 23:37:39

呃……这么久了居然没看到这贴!

nicksean 发表于 2009-11-16 21:30:01

居然还没沉下去, 呵呵

hollypower 发表于 2009-11-17 16:30:55

需要马克

niceboat 发表于 2009-11-17 16:39:40

mark

kangkang 发表于 2009-11-22 20:29:18

我也顶一下
真是好思路啊

zgcumt 发表于 2010-2-6 13:23:44

mark

liveshuang 发表于 2010-3-1 20:11:18

太好了,不错

walter_wang 发表于 2010-3-1 20:39:12

mark

gz_dailin 发表于 2010-3-1 20:43:55

mark

cat_li 发表于 2010-3-1 21:06:03

好文,收藏了

smhh 发表于 2010-3-1 21:45:36

谢谢楼主分享经验!

jielove2003 发表于 2010-3-1 23:42:59

好帖收藏

dream_ss 发表于 2010-3-1 23:49:36

挺复杂 看了有收获 谢谢楼主

fshunj 发表于 2010-3-2 00:55:53

我实在太菜了,只会If(!s1)
{delay(10);
if(!s1)

lwb_2888 发表于 2010-3-2 14:58:49

理解了,呵呵呵

nicksean 发表于 2010-4-3 12:21:03

up一下, 呵呵

lovecsu 发表于 2010-4-3 14:33:05

mark

ammcu 发表于 2010-4-3 18:28:38

谢谢楼主

xiaolei0428 发表于 2010-4-3 20:24:50

再顶一次

wangjiati 发表于 2010-4-3 21:57:15

mark

zhg_wx 发表于 2010-4-4 01:08:34

支持下

minicatcatcn 发表于 2010-4-4 07:47:53

up

xuejianhua1986 发表于 2010-4-4 08:50:35

mark

tomason 发表于 2010-4-5 12:57:24

MARK

langley 发表于 2010-4-5 13:09:32

mark

wochai 发表于 2010-4-5 15:01:29

mark

AIHHLI 发表于 2010-4-5 15:58:50

mark,谢谢楼主.

beixue 发表于 2010-4-9 08:58:22

mark

benladn911 发表于 2010-4-9 09:07:48

学习

zhangxun0712 发表于 2010-4-9 10:55:00

mark

sodohe 发表于 2010-4-9 21:33:10

好,LZ的思路和程序都非常好,谢谢

qzf368 发表于 2010-4-9 21:47:03

支持

wwuchang 发表于 2010-4-9 23:25:25

mark!

by886 发表于 2010-4-10 20:07:08

嗯 MARK!!好东西,顶LZ

xinyou 发表于 2010-4-10 20:31:55

MARK

lvyi913 发表于 2010-4-11 13:55:56

mark

span42 发表于 2010-5-5 21:24:05

mark

wukaka 发表于 2010-6-16 14:37:11

又遇高人,学习了

mrhhj 发表于 2010-6-16 14:44:56

好贴啊,谢谢分享!

bad_fpga 发表于 2010-6-16 15:39:37

mark

ahfong2006 发表于 2010-6-16 18:09:22

re-mark

lininglive 发表于 2010-6-18 02:13:00

markmark

ITOP 发表于 2010-6-18 09:03:53

学习!!

avrgcc 发表于 2010-6-19 20:12:28

mark

xuejianhua1986 发表于 2010-6-19 22:44:36

mark

xuejianhua1986 发表于 2010-6-19 22:47:29

mark

coolwater 发表于 2010-6-20 15:52:22

一定要看

wxx116zh 发表于 2010-6-20 20:05:52

mark,学习了。。

nicksean 发表于 2010-7-30 17:34:20

这个是不是也可以"酷"一下呀?

guolun 发表于 2010-7-30 21:27:47

mark

fanwt 发表于 2010-7-30 22:52:34

mark

tangwei039 发表于 2010-7-30 23:26:03

mARK

cat_li 发表于 2010-7-30 23:44:43

正在折腾按键,收藏了

major888 发表于 2010-7-31 00:05:10

mark

382383706 发表于 2010-7-31 07:31:26

mark

lsy5110 发表于 2010-7-31 09:59:43

好好好!

jiege0119 发表于 2010-7-31 21:22:04

收藏了

wangyj173 发表于 2010-9-11 15:33:27

mark

myhonour 发表于 2010-9-18 07:15:31

http://dl.dbank.com/c0hcvqocvc和 http://dl.dbank.com/c0brkn6qsx

caixiong 发表于 2010-9-18 11:23:00

mark

qiujianben 发表于 2010-9-18 18:43:20

mark

cqwshll 发表于 2010-9-18 20:59:46

马克

xiaowei0588 发表于 2010-9-19 08:16:53

很好!

zylaputa 发表于 2010-9-19 08:22:45

mark

XQGG 发表于 2010-9-19 08:24:14

记号

hdd961140543 发表于 2010-9-19 08:49:53

好东西!

mcu2007 发表于 2010-9-19 09:41:08

高手的东西总是看这头痛,慢慢看吧
页: [1] 2
查看完整版本: 通用按键消抖函数 -- 数据与过程分离【恢复】