短按,长按,按键释放,三种模式的按键扫描程序(软件消抖动)

先来说一下这三种模式的意思:

1. 短按模式:单击按键时,返回一次有效按键值;长按时也只返回一次有效按键值。这样可以有效地排除因不小心长按带来的返回多次有效按键,进而执行多次按键处理程序。

2. 长按模式: 单击按键时,返回一次有效按键;长按时,返回多次有效按键值。这样可以很快的调节某个较大的参数,比如时间的时分秒参数。

3. 按键释放模式:这个模式与短按模式是相对的。短按模式只要按键按下去,立即返回有效键值,进而试行按键处理程序;二按键释放模式,却是要等到按下键,释放之后,才会返回有效键值,进而执行按键处理程序。


接下来说一下扫描程序:

1)采用的是轮询的方式(非中断)

2)消抖动的方式:多次扫描,来确定按键值。下面的程序的是设定了5次。主要是根据扫描周期来确定,次数的多少。

注:

扫描周期:从进入按键扫描程序开始,直到到下一次进入按键扫描程序时 结束,之间所用的时间。

下面是整个按键扫描程序的源码,可以读一读,语句都很简单,而且每一句都有注释,一步一步看下去,应该能明白。

如果不明白,可以留言谈论。

以下是 KeyScan.c 文件的内容,

//======================================================

//KeyScan.c

//======================================================

//注意:该宏定义,定义在keyscan.h文件中

//#define KEYDEBOUNCE 0x05 //消抖动,按键扫描次数。如果连续5次都是扫描的都是相同键值,则认为是有效键值,否则是误触发

unsigned int g_uiCurrKey; //当前按键值

unsigned int g_uiLastKey; //上次按键值

unsigned int g_uiKeyScanCount; //按键扫描计数,作用:消抖动

unsigned int g_uiPreKeyValue; // 上一次的有效按键值

unsigned int g_uiKeyDown; //键被按下,返回的键值。 作用:单次按键,单次返回有效键值;按住不放,也只返回被按下的一个键值

unsigned int g_uiKeyRelease; //键被释放后,返回的键值。 作用:只有按下的按键被释放后,才返回按下的键值

unsigned int g_uiKeyContinue; //键连续按键,重复返回的键值。 作用:只要按住不放,就会重复地返回相同键值

//P0口的低八位作为按键

//没有按键时,返回的是0xff,

void Int_Key_Scan(void)

{

static unsigned short LastReadKey; //上次从IO口读取的键值 ,注意是静态变量

unsigned short CurrReadKey; //当前从IO口读取的键值

CurrReadKey = P0 & 0x00ff; //获取当前的键值

if(CurrReadKey == LastReadKey) //如果当前读取的键值与上次从IO口读取的键值相同

{

if(g_uiKeyScanCount >= KEYDEBOUNCE) //首先判断是否大于等于debounce的设定值(即是,是否大于等于设定的采样次数)

{

//按住不放,多次响应

g_uiCurrKey = CurrReadKey; //如果是,将当前的读取值判定为有效按键值(如果是,在采样周期中,都是这个值,则判定为有效按键值)

g_uiKeyContinue = g_uiCurrKey ; //长按,多次响应 按键值

//按住不放只响应一次

if(g_uiPreKeyValue == g_uiCurrKey)

{

g_uiKeyDown = 0xff; //没有键值

}

else

{

g_uiKeyDown = g_uiCurrKey; //如果不同,按键有效,(就是第一次有效值时)

}

//按键释放时,按键值才有效

if(g_uiCurrKey == 0xff) //当有效按键值从非0到0的状态时(即是,从有按键到无按键,表示已经释放了),表示之前按键已经释放了

{

g_uiKeyRelease = g_uiPreKeyValue;

}

g_uiLastKey = g_uiCurrKey; //记录上次有效按键值

}

else //如果否,则debounce加一(如果否,则继续采样键值)

{

g_uiKeyScanCount++;

}

}

else //如果当前读取的键值与上次从IO口读取的键值不同,说明按键已经变化

{

g_uiKeyDown = 0xff; //放开按键后第一次进入扫描程序,清零g_uiKeyDown.作用:消除一个BUG(你猜BUG是什么?)

g_uiKeyScanCount = 0; //清零之前的按键的debounce计数

LastReadKey = CurrReadKey; //将当前读取的键值记录为上次读取的按键值

}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 以下是KeyScan.h文件内容

    //======================================================

    //KeyScan.h

    //======================================================

    //宏定义

    #define KEYDEBOUNCE 0x05 //消抖动,按键扫描次数。如果连续5次都是扫描的都是相同键值,则认为是有效键值,否则是误触发

    //声明变量

    extern unsigned int g_uiCurrKey; //当前按键值

    extern unsigned int g_uiLastKey; //上次按键值

    extern unsigned int g_uiKeyScanCount; //按键扫描计数,作用:消抖动

    extern unsigned int g_uiPreKeyValue; //上一次的有效按键值

    extern unsigned int g_uiKeyDown; //键被按下,返回的键值。 作用:单次按键,单次返回有效键值;按住不放,也只返回被按下的一个键值

    extern unsigned int g_uiKeyRelease; //键被释放后,返回的键值。 作用:只有按下的按键被释放后,才返回按下的键值

    extern unsigned int g_uiKeyContinue; //键连续按键,重复返回的键值。 作用:只要按住不放,就会重复地返回相同键值

    //函数声明

    void Int_Key_Scan(void);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  • 使用注意:

    1.作为按键使用的相应IO口,必须设置为输入模式(如果是51单片机的话,无需关心)

    2.按键的硬件连接必须是一端接GND,一端接IO口。

    下面介绍一下程序的使用方法:

    这里以51单片机的 按键点亮和熄灭LED灯作为例子。

    硬件:

    1)按键使用单片机的P0端口

    2)LED灯使用P1.0的IO口,低电平点亮

    返回的按键值:

    没有键按下, 返回键值是0xFF

    如果P0.0按下,返回键值是0xFE

    如果P0.1按下,返回键值是0xFD

    如果P0.2按下,返回键值是0xFB

    如果P0.3按下,返回键值是0xF7

    如果P0.7按下,返回键值是0x7F

    下面是例子的参考源码:

    //======================================================

    //main.c

    //======================================================

    #include “reg51.h”

    #include “KeyScan.h”

    sbit LED = P1.0; //定义LEDIO口

    char time_10ms_ok; // 10ms 定时标志

    void init_timer0(void)

    {

    //定时器的初始化

    TMOD = 0x01; //选择定时器的工作模式:定时器0,方式1

    TH0 = (65535 – 10000)/256; //定时器的初值

    TL0 = (65535 – 10000)%256;

    EA = 1; //开打总中断使能

    ET0 = 1; //打开定时器0 的使能

    TR0 = 1; //打开定时器0 ,开始工作

    }

    void main(void)

    {

    P0 = 0xff;

    LED = 0; //点亮LED

    init_timer0(); //初始化定时器 定时10ms

    while(1)

    {

    if(time_10ms_ok) //这里表示10ms扫描一次

    {

    time_10ms_ok = 0; //清除10ms定时标志

    Int_Key_Scan(); //按键扫描程序

    }

    //第一种:KeyDown的使用

    //单按时和长按时,都只返回一次有效键值(无需等到按键释放,就可以返回有效键值)

    switch(g_uiKeyDown)

    {

    case 0xFE:

    //P0.0按键程序

    LED = !LED;

    break;

    case 0xFD:

    //P0.1按键程序

    //…

    break;

    case 0xFB:

    //P0.2按键程序

    //…

    break;

    case 0xF7:

    break;

    case 0xEF:

    break;

    case 0xDF:

    break;

    case 0xBF:

    break;

    case 0x7F:

    break;

    case 0xFF:

    //没有按键程序

    //…

    break;

    }

    //第二种:KeyRelease的使用

    //只有当按键释放之后,才返回一次有效键值,即是按键释放后,才执行相应的函数

    switch(g_uiKeyRelease)

    {

    case 0xFE:

    //P0.0按键程序

    LED = !LED;

    break;

    case 0xFD:

    //P0.1按键程序

    //…

    break;

    case 0xFB:

    //P0.2按键程序

    //…

    break;

    case 0xF7:

    break;

    case 0xEF:

    break;

    case 0xDF:

    break;

    case 0xBF:

    break;

    case 0xEF:

    break;

    case 0xFF:

    //没有按键程序

    //…

    break;

    }

    //第三种:KeyContinue的使用

    //1)单次按键(非长按),返回一次有效值。

    //2)长按,返回多次相同有效值

    switch(g_uiKeyContinue)

    {

    case 0xFE:

    //P0.0按键程序

    LED = !LED;

    break;

    case 0xFD:

    //P0.1按键程序

    //…

    break;

    case 0xFB:

    //P0.2按键程序

    //…

    break;

    case 0xF7:

    break;

    case 0xEF:

    break;

    case 0xDF:

    break;

    case 0xBF:

    break;

    case 0xEF:

    break;

    case 0xFF:

    //没有按键程序

    //…

    break;

    }

    }

    }

    void timer0(void) interrupt 1 //用的是定时器0, 这个“interrupt 1”中的“1”代表1 中断即是定时器0中断。如果是“0”就是外部中断0;“2“=外部中断1;”3“定时器1中断;”4“=串行口中断

    {

    TH0 = (65535 – 10000)/256;

    TL0 = (65535 – 10000)%256; //定时器0的方式1,得在中断程序中重复初值。

    time_10ms_ok = 1; //定时10MS 的标志

    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • Pillar Peng

    2015.5.25 – 18:23

    log:

    感谢 友“Shiow1984”的提醒,有漏掉和不足的地方,我已经修改。

    之前写的文章意在按键程序的思路,就没有将定时扫描程序添加进去,怕影响按键程序的理解,若要稳定地运用于程序中,就要使用定时扫描了,这样按键扫描的时间就可以确定,调节好扫描次数后,几乎就没有什么误触了。

    我也在上面的程序中添加了定时扫面程序。

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2018年6月15日
    下一篇 2018年6月15日

    相关推荐