找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 8311|回复: 24
打印 上一主题 下一主题
收起左侧

一个单片机按键消抖程序的分享

  [复制链接]
跳转到指定楼层
楼主
ID:825513 发表于 2021-5-23 19:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一个之前自己写的按键消抖的程序,相信有和我一样的新手也有遇到过因为按键按下时抖动而导致失灵的情况,后来自己在学校里的一位师兄的启发下弄懂了如何通过软件方式进行按键消抖,现在分享一下源程序。

开发板上的按键由于机械触点的弹性作用,当按下按键时机械触点并不会立刻稳定闭合,松开按键时机械触点也不会立刻稳定断开,因此在按下或松开按键的瞬间(在大约10ms内)电位会有一定的波动(或者叫做抖动);软件消抖就是当检测到按键状态发生变化后,先延时大约10ms待电位稳定后,再进行一次检测,如果仍然保持闭合状态的电位,则确认为按键真正被按下。

按键消抖程序如下:
# K5表示按键,D8表示LED灯,按下独立按键K5,LEDD8亮;再次按下K5,D8灭。

单片机源程序如下:
#include<reg51.h>

sbit K5=P3^5;
sbit D8=P2^7;

void Delay10ms()                //延时10ms
{
        unsigned char i,j;
        i=20;
        j=113;
        do
        {
                while(--j);
        }
        while(--i);
}

void main()
{
        int Key=0;                //定义一个标志位,变量Key的值置0,表示按键处于松开状态
        while(1)
        {
                if(K5==0&&Key==0)                //判断按键是否被按下
                {
                        Delay10ms();                //延时消抖
                        Key=1;                //把标志位置1,防止程序循环执行
                        if(K5==0)                //再次判断按键状态,排除干扰
                        {
                                D8=~D8;
                        }
                }
                else if(K5==1)
                {
                        Key=0;                //当按键没有被按下时,重置标志位
                }
        }
}

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏10 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:47286 发表于 2021-5-24 01:20 来自手机 | 只看该作者
我个人认为一切需要for或 whell这种死延时的防抖都不好 20mS的时间会影响很多事 至少要定时器防抖

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

板凳
ID:554500 发表于 2021-5-24 10:20 | 只看该作者
这个代码还可以,但是感觉效率不高。
一定要想办法去掉delay函数。小工程代码看不出效果,工程代码大了就体现出来了。
#include "reg52.h"

sbit key=P1^2;
//按键扫描
void Key_Scan()
{
        static u8 j=0,k=0;
       
        if(key==0)
        {
                if(j==0)
                {
                        if(key==0&&k++>=20)
                        {
                                j=1;
                                //任务代码       
                        }                       
                }
        }
        else  //按键释放
        {
                j=0;
                k=0;
        }
}

//k的取值和函数调度周期有关系,适当调节,稳定性还是不错的。
//k取值越大,灵敏度越低,反之越高
回复

使用道具 举报

地板
ID:301715 发表于 2021-5-24 16:55 | 只看该作者
用定时器计次数啊,Delay10ms();这些如果在程序大点的会耗费很多资源。
回复

使用道具 举报

5#
ID:475858 发表于 2021-5-24 17:41 | 只看该作者
用定时器计数可以
回复

使用道具 举报

6#
ID:825513 发表于 2021-5-25 16:33 | 只看该作者
18701931930 发表于 2021-5-24 10:20
这个代码还可以,但是感觉效率不高。
一定要想办法去掉delay函数。小工程代码看不出效果,工程代码大了就 ...

感谢老哥,已受教
回复

使用道具 举报

7#
ID:825513 发表于 2021-5-25 16:35 | 只看该作者
lihui2558 发表于 2021-5-24 17:41
用定时器计数可以

我觉得没必要给按键用定时器,在一些稍微大一点的项目中,那样就太浪费定时器资源了
回复

使用道具 举报

8#
ID:47286 发表于 2021-5-25 21:33 来自手机 | 只看该作者
NIMITIZ 发表于 2021-5-25 16:35
我觉得没必要给按键用定时器,在一些稍微大一点的项目中,那样就太浪费定时器资源了

定时器可以用多变量计数 能耽误什么
回复

使用道具 举报

9#
ID:384820 发表于 2021-5-25 21:53 | 只看该作者
发一个在用的按键消抖程序,供参考
定时器设置每隔20ms扫描一次按键

/***** 按键扫描 *****/
void keyscan()
{
  if(l_debnce ==0)               //是否需要消抖?
  {              
    P1 |=0x0F;                   //先拉高端口
                if((P1 &0x0F)!=0x0F)         //不需消抖,则判断有键按下否
                {
                        //--- 扫描key1
                        if(key1 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key1键按下,则按下标记置1
                                        l_debnce =1;           //消抖标记置1
                                        key_proc(1);           //判为key1短按
                                }
                        }                        
                        //--- 扫描key2
                        if(key2 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key2键按下,则按下标记置1
                                        l_debnce =1;           //消抖标记置1
                                        key_proc(2);           //判为key2短按
                                }
                        }
                        //--- 扫描key3
                        if(key3 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key3键按下,则按下标记置1
                                        l_debnce =1;           //消抖标记置1
                                        key_proc(3);           //判为key3短按
                                }
                        }
              }
    }
    //--- 无键按下,则清除标记
    else  
                        l_keypress =0;
  }
  //--- 需要20ms的消抖
  else  
    l_debnce =0;
}

若加入 n_keypress 计数器,还可以修改为判断长按短按键的情况
有兴趣可以私信留言交流
回复

使用道具 举报

10#
ID:702127 发表于 2021-5-26 14:42 | 只看该作者
直接状态机吧,定时器消抖,效率高还好用。
回复

使用道具 举报

11#
ID:548551 发表于 2021-5-26 18:09 | 只看该作者
NIMITIZ 发表于 2021-5-25 16:35
我觉得没必要给按键用定时器,在一些稍微大一点的项目中,那样就太浪费定时器资源了

什么是浪费资源? 没有懂。你这个delay 就是在浪费资源。定时器有毒是不是,为什么不用定时器来定时呢。你如果只是亮灯,只是检测按键,适当的延时没有太大的问题。 但是你是检测按键,又是LED灯,双是输出PWM,叒是检测电池电压,即ADC检测,叕是定时自动关机。 你说怎么弄? 用定时器设置一个基准定时,比如5MS,所有的操作都在这5MS的基准之上,比如按键检测,消抖需要40MS,给个变量加8次,一样的到了40MS, LED灯一样的效果,实现1S的闪烁,变量到达100的时候打开达到200的时候关闭。一样的效果,同样定时自动关机,如果自动定时一分钟,一个unsigned int 变量 到达12000的时候就表示时间到了需要关机。 这些都是可以同时同步执行的,如果delay你想想,一次delay假如说延时40ms 那到一分钟的时候不知道延时到几分钟去了。所以定时器,不要浪费,它是你提高效率的重要组成部分。
回复

使用道具 举报

12#
ID:825513 发表于 2021-5-28 14:38 | 只看该作者
顺德动力 发表于 2021-5-25 21:53
发一个在用的按键消抖程序,供参考
定时器设置每隔20ms扫描一次按键

你好,我还不太懂 变量l_debnce 和函数 key_proc() ,可以讲解一下吗,谢谢
回复

使用道具 举报

13#
ID:47286 发表于 2021-5-28 21:14 | 只看该作者
NIMITIZ 发表于 2021-5-28 14:38
你好,我还不太懂 变量l_debnce 和函数 key_proc() ,可以讲解一下吗,谢谢

他程序给的不太全 我也看不懂 而且 感觉有点麻烦

大概是它吧按键接在P1的0123口上 检测前先拉高端口 如果按下 那么端口拉不高 然后 应该定义了sbit key1=P1^0;之类的

I_keypress和i_debnce应该是bit 不是char 标记 不是变量 做两个标记 靠标记变化来判断和处理 不过没看见变量累加延时或者计数器累加延时 不知道怎么实现20ms防抖的 也没有判断长短按的变量

其实不用先拉高端口吧 每个端口接个10k的弱上拉或者开单片机内部上拉电阻 只要没按下就一定是高 直接判断就行了 这样少一层if嵌套 另外 如果KEY1和KEY2同时按下 怎么防抖 虽然理论上20ms的防抖很短 不会冲突 不过感觉用起来还是不太保险吧 多个单独按键检测 我是每个按键一套标记 独立的 防止干扰
回复

使用道具 举报

14#
ID:141497 发表于 2021-5-28 22:16 | 只看该作者
楼上正解,用定时器,效率比较高。
回复

使用道具 举报

15#
ID:970121 发表于 2021-10-26 17:58 | 只看该作者
dzbj 发表于 2021-5-28 21:14
他程序给的不太全 我也看不懂 而且 感觉有点麻烦

大概是它吧按键接在P1的0123口上 检测前先拉高端口  ...

每个字我都能看懂,也明白意思,但逻辑关系还是搞不定,能出一个示例吗?
回复

使用道具 举报

16#
ID:384475 发表于 2021-10-26 21:13 | 只看该作者
下面这个是金沙滩宋老师的教程,很好:
那么消抖操作所需要的延时该怎么处理呢?其实除了这种简单的延时,我们还有更优异
的方法来处理按键抖动问题。举个例子:我们启用一个定时中断,每 2ms 进一次中断,扫描
一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。
8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按
键处于稳定的阶段,而非处于抖动的阶段,如图 8-12。 图 8-12 按键连续扫描判断
假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次
按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果
0 和 1 交错,就认为是抖动,不做任何判定。想一下,这样是不是比简单的延时更加可靠?
利用这种方法,就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键
状态判定而非按键过程判定,我们只对当前按键的连续 16ms 的 8 次状态进行判断,而不再
关心它在这 16ms 内都做了什么事情,那么下面就按照这种思路用程序实现出来,同样只以
K4 为例。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1; //当前按键状态
void main()
{
bit backup = 1; //按键值备份,保存前一次的扫描值
unsigned char cnt = 0; //按键计数,记录按键按下的次数

EA = 1; //使能总中断
ENLED = 0; //选择数码管 DS1 进行显示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
TMOD = 0x01; //设置 T0 为模式 1
TH0 = 0xF8; //为 T0 赋初值 0xF8CD,定时 2ms
TL0 = 0xCD;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
P2 = 0xF7; //P2.3 置 0,即 KeyOut1 输出低电平
P0 = LedChar[cnt]; //显示按键次数初值

while (1)
{
if (KeySta != backup) //当前值与前次值不相等说明此时按键有动作
{
if (backup == 0) //如果前次值为 0,则说明当前是弹起动作
{
cnt++; //按键次数+1
if (cnt >= 10)
{ //只用 1 个数码管显示,所以加到 10 就清零重新开始
cnt = 0;
}
P0 = LedChar[cnt]; //计数值显示到数码管上
}
backup = KeySta; //更新备份为当前值,以备进行下次比较
}
} }
/* T0 中断服务函数,用于按键状态的扫描并消抖 */
void InterruptTimer0() interrupt 1
{
static unsigned char keybuf = 0xFF; //扫描缓冲区,保存一段时间内的扫描值

TH0 = 0xF8; //重新加载初值
TL0 = 0xCD;
keybuf = (keybuf<<1) | KEY4; //缓冲区左移一位,并将当前扫描值移入最低位
if (keybuf == 0x00)
{ //连续 8 次扫描值都为 0,即 16ms 内都只检测到按下状态时,可认为按键已按下
KeySta = 0;
}
else if (keybuf == 0xFF)
{ //连续 8 次扫描值都为 1,即 16ms 内都只检测到弹起状态时,可认为按键已弹起
KeySta = 1;
}
else
{} //其它情况则说明按键状态尚未稳定,则不对 KeySta 变量值进行更新
}
这个算法是我们在实际工程中经常使用按键所总结的一个比较好的方法,介绍给大家
回复

使用道具 举报

17#
ID:298123 发表于 2021-10-27 10:13 | 只看该作者
jjkk11 发表于 2021-10-26 21:13
下面这个是金沙滩宋老师的教程,很好:
那么消抖操作所需要的延时该怎么处理呢?其实除了这种简单的延时,我 ...

教程只是用来学习的,没有别的方法来教你,只能用这种方式。实际项目绝对不是你认知里这样方式来实现的,等你向上跳过这个阶段,你就明白我说的意思了。千万不要用的自己的认知来误导别人。
回复

使用道具 举报

18#
ID:954686 发表于 2021-10-27 16:29 | 只看该作者
我常用的,可作为参考。
uchar KeyGetKeyCode(void)
{
    if(IDR_ADJ== 0)
    {
        return  KEY_ADJ;
    }
    if(IDR_SET== 0)
    {
        return  KEY_SET;
    }
    return  KEY_NONE;

void KeyScan(void)
{
    uchar CurrentkeyVal;
    static uint Cnt;
    if(PLScanStatus == NotAllowScan)return; //5ms 扫描一次,时间不到则返回
    PLScanStatus = NotAllowScan;
    switch(KeyScanState)
    {
        case KEY_STATE_Idle: //在空闲状态下
            CurrentkeyVal = KeyGetKeyCode(); //读取当前的按键值,
            if(CurrentkeyVal != KEYCODE_NOPRESS) //如果不等于KEYCODE_NOPRESS,说明此时有按键按下
            {
                KeyScanState = KEY_STATE_FirstPress; //把扫描状态置为第一次按下状态
                KeyVal = CurrentkeyVal;  //并且记录当前的按键值
            }
            else
                KeyVal = KEYCODE_NOPRESS;
            break;
        case KEY_STATE_FirstPress://第一次按下状态
            if(KeyVal == KeyGetKeyCode()) //如果KeyGetKeyCode读出的值和KeyVal一致的话(即和空闲状态下的记录的按键值一致的话)
            {
                KeyScanState = KEY_STATE_RepeatPress; //把按键状态置为 重复按下状态,
                //  dPuts("\r\n key repeat");

                Cnt = 0;
            }
            else  //不一致的话(和上次读的按键不一致),
            {
                KeyScanState = KEY_STATE_Idle; //把按键状态置为空闲状态,要重新再读
                KeyVal = KEYCODE_NOPRESS;

            }
            break;
        case KEY_STATE_RepeatPress://在重复按下状态下,
            if(KeyVal == KeyGetKeyCode())
            {
                if(++Cnt == 60)
                {
                    Cnt = 0;
                    KeyScanState = KEY_STATE_LongPress;
                    // dPuts("\r\n key long");
                }
            }
            else
            {
                KeyScanState = KEY_STATE_Idle;
                GetKey = KeyVal;                              //得到键值
                //   dPuts("\r\nget key");
                //  if(GetKey == KEY_MENU)  //
                //     GetKey = KEY_NONE;
            }
            break;
        case KEY_STATE_LongPress://长按状态
            if(KeyVal == KeyGetKeyCode())
            {
                if(++Cnt == 15)
                {
                    Cnt = 0;
                    GetKey = KeyVal + 0x10;
                    //   dPuts("\r\nget long key");
                    //  if(GetKey == KEY_SET_LONG)
                    KeyScanState = KEY_STATE_NoneStatus;
                }
            }
            else
            {
                Cnt = 0;
                KeyScanState = KEY_STATE_Idle;
                KeyVal = KEYCODE_NOPRESS;
            }
            break;

        case KEY_STATE_NoneStatus:
            if( KeyGetKeyCode() == KEY_NONE)
            {
                KeyScanState = KEY_STATE_Idle; //把按键状态置为空闲状态,要重新再读
                KeyVal = KEYCODE_NOPRESS;
            }
            break;

    }
}


回复

使用道具 举报

19#
ID:977804 发表于 2022-2-13 17:41 | 只看该作者
18701931930 发表于 2021-5-24 10:20
这个代码还可以,但是感觉效率不高。
一定要想办法去掉delay函数。小工程代码看不出效果,工程代码大了就 ...

亲测好用。
回复

使用道具 举报

20#
ID:1006076 发表于 2022-3-20 18:24 | 只看该作者
试了一下,很好用
回复

使用道具 举报

21#
ID:1006181 发表于 2022-3-22 16:45 来自手机 | 只看该作者
挺好,很多可以学习参考的
回复

使用道具 举报

22#
ID:148126 发表于 2022-3-23 16:43 | 只看该作者
我认为你这个做不到消抖的目的,要不你认真看一下程序
回复

使用道具 举报

23#
ID:1011952 发表于 2022-3-23 16:52 | 只看该作者
为什么不做硬消斗?有什么原因吗??
回复

使用道具 举报

24#
ID:415064 发表于 2022-3-23 16:56 | 只看该作者
乙猪 发表于 2022-3-23 16:52
为什么不做硬消斗?有什么原因吗??

机械轻触按键,硬消抖,钱多?
回复

使用道具 举报

25#
ID:499136 发表于 2022-3-23 17:39 | 只看该作者
任何形式的while或for循环的延时在实际项目中都是不被允许的,一个延时会错过好多事情的。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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