搜索
bottom↓
回复: 191

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

[复制链接]

出0入0汤圆

发表于 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;                 //始终允许长按, 直到键抬起

}

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

发表于 2009-1-22 22:57:24 | 显示全部楼层
mark,up

出0入0汤圆

发表于 2009-1-22 23:00:50 | 显示全部楼层
好像还真复杂..  

出330入0汤圆

发表于 2009-1-22 23:25:08 | 显示全部楼层
动机、思路、方法、结局都不错

出0入0汤圆

发表于 2009-1-22 23:49:51 | 显示全部楼层
jh

出0入0汤圆

发表于 2009-1-23 10:41:54 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-1-23 10:51:04 | 显示全部楼层
mark, 学习

出0入0汤圆

发表于 2009-1-23 13:00:31 | 显示全部楼层
如果能够消掉回调函数和那个大的结构体就更好~~~~~~~~

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

出0入0汤圆

发表于 2009-1-23 13:26:12 | 显示全部楼层
楼主人品不错

出0入0汤圆

发表于 2009-1-23 15:14:20 | 显示全部楼层
好东西

出75入4汤圆

发表于 2009-1-23 15:36:34 | 显示全部楼层
不错,看看准备用了 

出0入0汤圆

发表于 2009-9-12 15:44:35 | 显示全部楼层
学习ing!!!

出0入0汤圆

发表于 2009-10-7 15:46:43 | 显示全部楼层
强烈建议楼主 把程序流程再具体点

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

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

出0入0汤圆

发表于 2009-10-7 20:47:46 | 显示全部楼层
一个字,强~~~~~~~

出0入0汤圆

 楼主| 发表于 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)

出0入0汤圆

发表于 2009-10-7 22:31:07 | 显示全部楼层
好帖

出0入0汤圆

发表于 2009-10-8 00:19:19 | 显示全部楼层
谢谢LZ分享经验

出0入0汤圆

发表于 2009-10-8 10:14:08 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-10-8 11:01:09 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-10-8 12:17:17 | 显示全部楼层
好思路

出0入0汤圆

发表于 2009-10-8 12:33:37 | 显示全部楼层
这是模块化设计的必要方法,本来按键就与处理无关。
键扫的任务就是扫键,别的程序做什么处理,与键扫无关。

出0入0汤圆

发表于 2009-10-9 09:31:43 | 显示全部楼层
不错,学习了!

出0入0汤圆

发表于 2009-10-9 09:40:37 | 显示全部楼层
要好好学习一下。

出0入0汤圆

发表于 2009-10-9 09:44:40 | 显示全部楼层
好思路

出0入0汤圆

发表于 2009-10-9 09:51:45 | 显示全部楼层
不错

出0入10汤圆

发表于 2009-10-9 16:04:33 | 显示全部楼层
MARK

出0入0汤圆

发表于 2009-10-9 17:30:37 | 显示全部楼层
Mark

出0入0汤圆

发表于 2009-11-14 16:06:46 | 显示全部楼层
记号!

出0入0汤圆

发表于 2009-11-14 18:32:24 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-11-14 19:14:31 | 显示全部楼层

出0入0汤圆

发表于 2009-11-14 22:52:13 | 显示全部楼层
强烈mark

出0入0汤圆

发表于 2009-11-14 22:55:33 | 显示全部楼层
。。先标记一下··嗯··看起来不错啊

出0入0汤圆

发表于 2009-11-14 23:33:35 | 显示全部楼层
学习了 lz好共享

出0入0汤圆

发表于 2009-11-14 23:37:39 | 显示全部楼层
呃……这么久了居然没看到这贴!

出0入0汤圆

 楼主| 发表于 2009-11-16 21:30:01 | 显示全部楼层
居然还没沉下去, 呵呵

出0入0汤圆

发表于 2009-11-17 16:30:55 | 显示全部楼层
需要马克

出0入0汤圆

发表于 2009-11-17 16:39:40 | 显示全部楼层
mark

出0入0汤圆

发表于 2009-11-22 20:29:18 | 显示全部楼层
我也顶一下
真是好思路啊

出0入0汤圆

发表于 2010-2-6 13:23:44 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-1 20:11:18 | 显示全部楼层
太好了,不错

出0入0汤圆

发表于 2010-3-1 20:39:12 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-1 20:43:55 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-3-1 21:06:03 | 显示全部楼层
好文,收藏了

出10入10汤圆

发表于 2010-3-1 21:45:36 | 显示全部楼层
谢谢楼主分享经验!

出0入0汤圆

发表于 2010-3-1 23:42:59 | 显示全部楼层
好帖收藏

出0入0汤圆

发表于 2010-3-1 23:49:36 | 显示全部楼层
挺复杂 看了有收获 谢谢楼主

出0入0汤圆

发表于 2010-3-2 00:55:53 | 显示全部楼层
我实在太菜了,只会If(!s1)
{delay(10);
if(!s1)

出0入0汤圆

发表于 2010-3-2 14:58:49 | 显示全部楼层
理解了,呵呵呵

出0入0汤圆

 楼主| 发表于 2010-4-3 12:21:03 | 显示全部楼层
up一下, 呵呵

出0入0汤圆

发表于 2010-4-3 14:33:05 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-3 18:28:38 | 显示全部楼层
谢谢楼主

出0入0汤圆

发表于 2010-4-3 20:24:50 | 显示全部楼层
再顶一次

出0入42汤圆

发表于 2010-4-3 21:57:15 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-4 01:08:34 | 显示全部楼层
支持下

出0入0汤圆

发表于 2010-4-4 07:47:53 | 显示全部楼层
up

出0入0汤圆

发表于 2010-4-4 08:50:35 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-5 12:57:24 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-4-5 13:09:32 | 显示全部楼层
mark

出100入0汤圆

发表于 2010-4-5 15:01:29 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-5 15:58:50 | 显示全部楼层
mark,谢谢楼主.

出0入0汤圆

发表于 2010-4-9 08:58:22 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-9 09:07:48 | 显示全部楼层
学习

出0入0汤圆

发表于 2010-4-9 10:55:00 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-4-9 21:33:10 | 显示全部楼层
好,LZ的思路和程序都非常好,谢谢

出0入0汤圆

发表于 2010-4-9 21:47:03 | 显示全部楼层
支持

出0入0汤圆

发表于 2010-4-9 23:25:25 | 显示全部楼层
mark!

出0入0汤圆

发表于 2010-4-10 20:07:08 | 显示全部楼层
嗯 MARK!!好东西,顶LZ

出0入0汤圆

发表于 2010-4-10 20:31:55 | 显示全部楼层
MARK

出0入0汤圆

发表于 2010-4-11 13:55:56 | 显示全部楼层
mark

出0入50汤圆

发表于 2010-5-5 21:24:05 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-6-16 14:37:11 | 显示全部楼层
又遇高人,学习了

出0入0汤圆

发表于 2010-6-16 14:44:56 | 显示全部楼层
好贴啊,谢谢分享!

出0入0汤圆

发表于 2010-6-16 15:39:37 | 显示全部楼层
mark

出0入42汤圆

发表于 2010-6-16 18:09:22 | 显示全部楼层
re-mark

出0入0汤圆

发表于 2010-6-18 02:13:00 | 显示全部楼层
markmark

出0入0汤圆

发表于 2010-6-18 09:03:53 | 显示全部楼层
学习!!

出0入0汤圆

发表于 2010-6-19 20:12:28 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-6-19 22:44:36 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-6-19 22:47:29 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-6-20 15:52:22 | 显示全部楼层
一定要看

出0入0汤圆

发表于 2010-6-20 20:05:52 | 显示全部楼层
mark,学习了。。

出0入0汤圆

 楼主| 发表于 2010-7-30 17:34:20 | 显示全部楼层
这个是不是也可以"酷"一下呀?

出425入0汤圆

发表于 2010-7-30 21:27:47 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-7-30 22:52:34 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-7-30 23:26:03 | 显示全部楼层
mARK

出0入0汤圆

发表于 2010-7-30 23:44:43 | 显示全部楼层
正在折腾按键,收藏了

出0入0汤圆

发表于 2010-7-31 00:05:10 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-7-31 07:31:26 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-7-31 09:59:43 | 显示全部楼层
好好好!

出0入0汤圆

发表于 2010-7-31 21:22:04 | 显示全部楼层
收藏了

出0入0汤圆

发表于 2010-9-11 15:33:27 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-18 07:15:31 | 显示全部楼层
http://dl.dbank.com/c0hcvqocvc和 http://dl.dbank.com/c0brkn6qsx

出0入0汤圆

发表于 2010-9-18 11:23:00 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-18 18:43:20 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-18 20:59:46 | 显示全部楼层
马克

出0入0汤圆

发表于 2010-9-19 08:16:53 | 显示全部楼层
很好!

出0入0汤圆

发表于 2010-9-19 08:22:45 | 显示全部楼层
mark

出0入0汤圆

发表于 2010-9-19 08:24:14 | 显示全部楼层
记号

出0入0汤圆

发表于 2010-9-19 08:49:53 | 显示全部楼层
好东西!

出0入0汤圆

发表于 2010-9-19 09:41:08 | 显示全部楼层
高手的东西总是看这头痛,慢慢看吧
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-6-2 11:50

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表