搜索
bottom↓
回复: 54

三行按键扫描简单消抖程序

  [复制链接]

出0入0汤圆

发表于 2015-10-25 02:32:43 | 显示全部楼层 |阅读模式
本帖最后由 rain73 于 2015-10-26 01:13 编辑

看了“新型的按键扫描程序”:
http://www.amobbs.com/forum.php?mod=viewthread&tid=4308630

用异或判断跳变的方法很好,可惜帖子中按键消抖的方法是个摆设,没有意义。自己改进了一下,发出来给大家讨论下看正确与否。

void KeyRead() {
    u8 Curr;
    static u8 tmp, ii=0;
   
    if (ii==0) {
        ii = 1;
        tmp = P1 ^ 0xFF;
        return;
    } else {
        ii = 0;
        Curr = P1 ^ 0xFF;
        if (Curr != tmp)  return;
    }
   
    TrgDown = Curr & (Curr ^ Prev);      //Down跳变
    TrgUp   = Prev & (Curr ^ Prev);      //Up跳变
    Prev = Curr;                         //前次值
}

只需改进一下KeyRead()函数即可,其它不变。消抖的原理是把原来
KeyRead()执行一次就输出结果,变为执行两次才输出结果。
第一次执行把键盘口扫描值放tmp暂存,第二次判断是否与第一次的值相等,相等则输出结果,不等则退出重复上述过程。
虽然仅是二次扫描,但总算不是原贴中的假把式。类似的,可以如法炮制多次扫描,但会变得复杂失去本程序简洁的意义,不
如直接上状态机键盘算法。
上述是基于51的程序,把原来的变量命名改了一下,使之更容易理解。
Curr     键口当前值
Prev     键口上次值
TrgDown  压键跳变
TrgUp    抬键跳变

顺便把我的实验执行函数也贴出来吧:
void KeyProc() {
    //短按单次执行
    if (KEY1 & TrgDown) {
        LED1 = 0;
    } else {
        LED1 = 1;
    }
   
    //长按多次执行
    if (KEY1 & Prev) {        //KEY1键按下
        if (i++ >= 100) {
            i = 90;           //重复执行周期(100-90)*T (T为键盘扫描周期,例如10ms)
            LED2 = 0;
        } else {
            LED2 = 1;
        }
    } else {                //KEY1键松开(重新初始化参数)
        i = 0;              //长按时间(100-0)*T
        LED2 = 1;
    }
}

鉴于有人看不懂,把主程序也放上来吧:

#define KEY1 0x40

sbit LED1 = P3^2;
sbit LED2 = P3^3;

u8 TrgDown, TrgUp;      //跳变值
u8 Prev;                //前次值
u8 i;

/* 主函数 */
void main() {
    while (1) {
        KeyRead();
        KeyProc();
        
        Delay_10ms;     
//模拟中断扫描,10ms扫描一次,实际使用把KeyRead()放入中断处理中。
    }
}

为了测试消抖的效果,用串口观察函数的执行,在KeyRead()中:
if (Curr != tmp)  return;
改为:
if (Curr != tmp) {
    printf("抖动\n");
    return;
}

终端截图:


可以发现,几乎每次的压键和抬键的过程中,都可以捕捉到“抖动”,这里的“抖动”,并非完全是抖动,
如果第一次采样时刚好是按下前,第二次采样是按下后,二次的值不一样,也是当消抖处理,下二次采样
才有效,这样处理按键更加牢靠。同理,抬键时也是同样的过程。除了这两种情况,其它就是真实的按键
抖动被捕捉到了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

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

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2015-10-25 02:57:48 | 显示全部楼层
收藏,试验下

出0入0汤圆

发表于 2015-10-25 09:56:24 | 显示全部楼层
我完全看不出来KeyRead()是如何执行两次才输出结果???

出0入0汤圆

发表于 2015-10-25 10:41:30 来自手机 | 显示全部楼层
原本高大上的东西被你搞得如此不堪。

出0入0汤圆

发表于 2015-10-25 11:06:51 | 显示全部楼层
  1. if(READ_KEY)        key_tmp = 1;
  2. if(key_tmp & (key_tmp ^ key_cnt)
  3.         key_time = 0;
  4. key_cnt = key_tmp;
  5. if(key_time < 5)        key_time ++;
  6. if((key_time == 2)&&(key_cnt))
  7.         return 1;
  8. else return 0;
复制代码

出0入0汤圆

发表于 2015-10-25 18:24:03 来自手机 | 显示全部楼层
mark,期待强大

出0入0汤圆

 楼主| 发表于 2015-10-25 18:33:27 | 显示全部楼层
mtlsh 发表于 2015-10-25 09:56
我完全看不出来KeyRead()是如何执行两次才输出结果???

分时概念,仔细想想就知道。

出0入0汤圆

 楼主| 发表于 2015-10-25 18:35:47 | 显示全部楼层
zhaotyue 发表于 2015-10-25 10:41
原本高大上的东西被你搞得如此不堪。

算不上高大尚,原程序把该处理的事情给省了,所以看起来简单,不消抖是其内伤。

出0入0汤圆

发表于 2015-10-25 18:47:05 | 显示全部楼层
你这叫哪门子消抖!打回重做!

出0入0汤圆

 楼主| 发表于 2015-10-25 18:57:16 | 显示全部楼层

这个仅是程序片断,没能看懂,能解释一下吗?

出0入0汤圆

 楼主| 发表于 2015-10-25 18:59:24 | 显示全部楼层
embeddev_1 发表于 2015-10-25 18:47
你这叫哪门子消抖!打回重做!

不懂就别乱说,上面已经解释得很清楚,二次扫描值一致才确认按键。

出0入0汤圆

发表于 2015-10-25 19:15:58 | 显示全部楼层
rain73 发表于 2015-10-25 18:33
分时概念,仔细想想就知道。

    static u8 tmp, i=0;都是局部变量, 如何分时????

出0入0汤圆

发表于 2015-10-25 19:23:07 | 显示全部楼层
rain73 发表于 2015-10-25 18:57
这个仅是程序片断,没能看懂,能解释一下吗?

利用延时的概念,上升沿复位计时,计时一定时间后判断按键,如果还在按下状态则返回按键值,不在状态不返回.

出0入0汤圆

 楼主| 发表于 2015-10-25 20:23:32 | 显示全部楼层
mtlsh 发表于 2015-10-25 19:15
static u8 tmp, i=0;都是局部变量, 如何分时????

哦这个,其实那个i是全局变量来的,和KeyRead()内部的i名称重复了,为避免误解,KeyRead()内部的改名为ii。
看main()上面的变量定义就明白了。

出0入76汤圆

发表于 2015-10-25 20:29:39 | 显示全部楼层
之前有个坛友已经分享了一个在原基础上增加消抖功能例程, 看过 还算比较不错的,  你可以参考一下(自己搜一下)

出0入0汤圆

 楼主| 发表于 2015-10-25 20:41:10 | 显示全部楼层
i7gly 发表于 2015-10-25 19:23
利用延时的概念,上升沿复位计时,计时一定时间后判断按键,如果还在按下状态则返回按键值,不在状态不返回. ...

明白了,原来是单键的程序。
上面的是多键的,一个口8位都可以处理。

出0入0汤圆

发表于 2015-10-25 22:17:02 | 显示全部楼层
rain73 发表于 2015-10-25 20:41
明白了,原来是单键的程序。
上面的是多键的,一个口8位都可以处理。

我比较喜欢这样用
连续两次为1则为1,连续两次为0则为0,其他保持不变。
  1.         static uint32_t temp1 = 0;
  2.         static uint32_t temp2 = 0;
  3.         static uint32_t key_in = 0;

  4.         temp2 = temp1;
  5.         temp1 = READ_KEY();
  6.         key_in = (temp1&temp2) | (key_in&(temp1|temp2));
复制代码

出0入0汤圆

 楼主| 发表于 2015-10-26 09:51:40 | 显示全部楼层
blxy 发表于 2015-10-25 22:17
我比较喜欢这样用
连续两次为1则为1,连续两次为0则为0,其他保持不变。
...

temp1和temp2没有时间间隔?

出0入0汤圆

发表于 2015-10-26 12:12:23 | 显示全部楼层
越基本的东西 越考功力,值得研究

出0入0汤圆

发表于 2015-10-26 12:29:00 | 显示全部楼层
我发现你没有返回键值呀,你用全局变量 这个函数移值起来就不方便了。反复测试就知道了。

出0入0汤圆

 楼主| 发表于 2015-10-26 15:39:21 | 显示全部楼层
wind2100 发表于 2015-10-26 12:29
我发现你没有返回键值呀,你用全局变量 这个函数移值起来就不方便了。反复测试就知道了。 ...

原贴的例子就是这样的。这里用到了分时结构,分时结构中全局变量是免不了的,而且也是多变量输出。
有兴趣可以自己研究下如何封装,不过我觉得没有多大必要。

出0入0汤圆

发表于 2015-10-26 20:33:50 | 显示全部楼层
rain73 发表于 2015-10-26 09:51
temp1和temp2没有时间间隔?

temp1先赋值给temp2,然后再更新内容的,temp2是temp1上一次扫描的内容

出0入0汤圆

 楼主| 发表于 2015-10-27 02:16:14 | 显示全部楼层
blxy 发表于 2015-10-26 20:33
temp1先赋值给temp2,然后再更新内容的,temp2是temp1上一次扫描的内容

嗯,这样不错,这倒启发了我,“异或”用来做跳变检测,“非异或”可以用来防抖。
那可以改成这样:
tmp1 = tmp2;
tmp2 = P1 ^ 0xFF;
if (tmp1 ^ tmp2)  return;
...
跳变检测

也还可以再加一层防抖:
tmp3 = tmp1;
tmp2 = P1 ^ 0xFF;
if (tmp3 ^ tmp2)  return;

这里,tmp1为上次值,tmp3为上上次值,tmp2为当前次值。

如此,多次采样也能简单实现,明天实际测试一下,如逻辑正确这样的防抖就更完美了!

出0入0汤圆

发表于 2015-10-27 07:53:33 | 显示全部楼层
mtlsh 发表于 2015-10-25 19:15
static u8 tmp, i=0;都是局部变量, 如何分时????

static 是静态变量,,分配的内存是一值要保存的.

出0入0汤圆

发表于 2015-10-27 09:47:51 | 显示全部楼层
LZ可以看看我写的关于独立按键扫描的博文

http://blog.csdn.net/xuechaojie/article/details/6761772

出0入0汤圆

发表于 2015-10-27 10:57:59 | 显示全部楼层
我觉得 这10MS 消抖动的时间 要能调整才行,也就是说当按下的时间稳定的在50MS 才能算是短按,那么如果是10MS扫描一次得有5次才成功,会比较可靠。
或是说要能调整。

出0入0汤圆

 楼主| 发表于 2015-10-27 11:08:18 | 显示全部楼层
Eric_Xue 发表于 2015-10-27 09:47
LZ可以看看我写的关于独立按键扫描的博文

http://blog.csdn.net/xuechaojie/article/details/6761772 ...

先拜读一下,回头再写读后感。

出0入0汤圆

 楼主| 发表于 2015-10-27 11:12:47 | 显示全部楼层
wind2100 发表于 2015-10-27 10:57
我觉得 这10MS 消抖动的时间 要能调整才行,也就是说当按下的时间稳定的在50MS 才能算是短按,那么如果是10 ...

你看一下23楼,应该找到了简单多次采样的方法,要多少次采样都行,回头我测试正确再发布出来。

出0入0汤圆

发表于 2015-10-27 11:32:25 | 显示全部楼层
wind2100 发表于 2015-10-27 10:57
我觉得 这10MS 消抖动的时间 要能调整才行,也就是说当按下的时间稳定的在50MS 才能算是短按,那么如果是10 ...

这是按键扫描的底层程序,每隔10ms采样一次,只消抖。
之所以以10ms为例,是因为10ms经常为RTOS的系统时钟中断间隔。只要系统每10ms调用keyscan()就可以了,用不用在按键扫描程序里面等待延迟。
不能把底层写的很复杂,比如实现长按、短按等功能,把这些留给上层程序去做。

出0入0汤圆

发表于 2015-10-27 13:17:19 | 显示全部楼层
Eric_Xue 发表于 2015-10-27 11:32
这是按键扫描的底层程序,每隔10ms采样一次,只消抖。
之所以以10ms为例,是因为10ms经常为RTOS的系统时 ...

那底层 也应该有 按下(下降沿),弹起(上升沿) 吧
分层对软件来讲是一个有意思的东西,好的代码,一定分得很清晰。
那么 长按和多击 应该是在上一层了。

出0入0汤圆

发表于 2015-10-27 13:32:57 | 显示全部楼层
超过三行了。

出0入0汤圆

发表于 2015-10-27 14:31:21 | 显示全部楼层
wind2100 发表于 2015-10-27 13:17
那底层 也应该有 按下(下降沿),弹起(上升沿) 吧
分层对软件来讲是一个有意思的东西,好的代码,一 ...
  1. //按键变量  
  2. unsigned char   KeyPressDown=0x00;  
  3. unsigned char   KeyRelease=0x00;  
  4. unsigned char   LastKey=0x00;  
复制代码


KeyPressDown用来记录按下
KeyRelease用来记录弹起。
lastkey用来记录按键(经过滤波后)的实际状态(低或高)。

出0入0汤圆

 楼主| 发表于 2015-10-27 15:57:24 | 显示全部楼层
Eric_Xue 发表于 2015-10-27 09:47
LZ可以看看我写的关于独立按键扫描的博文

http://blog.csdn.net/xuechaojie/article/details/6761772 ...

读了你的博文,解释得很详细。核心算法是:
CurrKey=(CurrReadKey&LastReadKey)|LastKey&(CurrReadKey^LastReadKey)

结合以下真值表:
时 刻         CurrKey         LastReadKey         CurrReadKey
1                 0                 0                         0
2                 0                 0                         1
3                 0                 1                         0
4                 0                 0                         1
5                 1                 1                         1
6                 1                 1                         1
7                 1                 1                         0
8                 1                 0                         1
9                 1                 1                         0
10                 0                 0                         0
11                 0                 0                         0

总结起来就是:前后两次采样值相同则保存新值,不同则维持上次值(原值)。
这跟我23楼说的:“异或”用来做跳变检测,“非异或”可以用来防抖的思路是一样的。
这个算法是二次采样,我上面应该找到了突破二次采样变为多次采样的简单方法了。

出0入0汤圆

 楼主| 发表于 2015-10-27 15:59:39 | 显示全部楼层

你这是要计较万用表不万用。
人家讲的是核心算法三行而已。

出0入0汤圆

发表于 2015-10-27 19:24:53 | 显示全部楼层
一个小按键,搞的这么复杂

出0入0汤圆

 楼主| 发表于 2015-10-27 22:11:58 | 显示全部楼层
在大家的指导下,参考了多种消抖算法,终于总结出多次采样的程序,经测试成功。
这样,以三行核心算法为主导,可以识别压下(下降沿)、弹起(上升沿)、短按、长按、连发、多次采样消抖的程序基本完善,可以和状态机键盘媲美了。

#define TIMES                        3                        //采样次数设定
#define READ_KEYPORT        P1^0xFF                //读键盘口

/* 2次采样 */
void KeyRead() {
        static u8 Curr = 0;
        static u8 Last;
       
        Last = Curr;
        Curr = READ_KEYPORT;
        if (Curr^Last)  return;
       
        TrgDown = Curr & (Curr ^ Prev);                //Down跳变
        TrgUp   = Prev & (Curr ^ Prev);                //Up跳变
        Prev = Curr;                                        //前次值
}

/* TIMES+1次采样 */
void KeyRead() {
        static u8 Curr = 0;
        static u8 Last;
        static u8 ii = 0;
       
        if(ii==0)  Last = Curr;
       
        while (ii < TIMES) {
                Curr = READ_KEYPORT;
                if (Curr^Last)        { ii = 0; return; }
                else                        { ii++;   return; }
        }
        ii = 0;
       
        TrgDown = Curr & (Curr ^ Prev);                //Down跳变
        TrgUp   = Prev & (Curr ^ Prev);                //Up跳变
        Prev = Curr;                                        //前次值
}

程序分二种,如果只需要2次采样,则用第一种方式,简单明了。
如果需要多次采样,则用第二种方式,适用于任何干扰的场合,采样次数TIMES最大值255,也就是255*10ms=2.55s,一般4-5次采样足够了。

出0入0汤圆

发表于 2016-3-19 21:44:15 | 显示全部楼层
写的不错!赞一个

出0入0汤圆

发表于 2017-10-16 10:29:11 | 显示全部楼层
精巧三行按键扫描消抖程序,标记下

出0入0汤圆

发表于 2017-10-16 11:57:47 | 显示全部楼层
return 用的比较好

出0入0汤圆

发表于 2018-3-13 08:58:22 | 显示全部楼层
写的不错.标记下。

出0入0汤圆

发表于 2018-3-13 12:27:38 | 显示全部楼层
按键处理,学习一下,谢谢

出0入4汤圆

发表于 2018-3-23 22:27:40 | 显示全部楼层
学习了 谢谢

出0入0汤圆

发表于 2018-4-20 17:28:56 | 显示全部楼层
mtlsh 发表于 2015-10-25 19:15
static u8 tmp, i=0;都是局部变量, 如何分时????

static 以后则个ii就不会每次都赋值0了,它只有第一次会=0,以后就是受程序控制了。

出0入0汤圆

发表于 2018-4-20 20:21:54 | 显示全部楼层
学习了,谢谢!

出0入0汤圆

发表于 2018-4-20 20:41:57 | 显示全部楼层
谢谢 学习了,谢谢!

出0入0汤圆

发表于 2018-5-23 17:48:05 | 显示全部楼层
请教楼主,如果K1按键有长按和短按功能,如果在长按的时候怎么写程序让它不响应短按呢?

出0入0汤圆

发表于 2018-9-25 16:30:21 | 显示全部楼层
很好的帖子 这两天弄TM1628的按键处理程序   找个参考  翻到这里  非常详细   复制了代码去了  改了一下 可以用  前来拜谢

出0入0汤圆

发表于 2018-9-26 16:45:19 | 显示全部楼层
perfect, mark

出10入12汤圆

发表于 2018-11-13 21:19:52 | 显示全部楼层
相当nice!!!!!!

出0入0汤圆

发表于 2018-11-14 12:20:44 | 显示全部楼层
学习了,谢谢!

出0入0汤圆

发表于 2018-11-14 18:44:46 | 显示全部楼层
收下了,后面用的着,慢慢看。

出0入4汤圆

发表于 2019-9-15 20:15:28 | 显示全部楼层
正在研究这个,多谢分享! 这两天实验一下楼主的方法.

出0入4汤圆

发表于 2019-9-17 09:44:34 | 显示全部楼层
经过实验,我觉得多次去抖程序改为以下内容比较合理,请各位指教.
void KeyScan(void)
{
        static uchar ii;//去抖次数计数
        static uint Curr = 0, Last;//当前及上次键值暂存
        if (ii == 0) Last = Curr;//存上次键值
        Curr = ReadKey();//取当前键值
        if (Curr^Last) { ii = 0; return; }//前后两次键值不一致,返回重新去抖判断
        else //前后两次键值相同
        {
                if ((++ii) < TIMES) return;//去抖次数累加未到设定值,返回再次去抖判断
        }
        ii = 0;//设定的去抖次数内,键值稳定,则累加清零,并更新状态参数
        TrgDown = Curr & (Curr^Prev);
        TrgUp = Prev & (Curr^Prev);
        Prev = Curr;
}

出0入4汤圆

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

本版积分规则

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

GMT+8, 2024-4-25 09:43

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

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