打印
[51单片机]

大神级——按键处理程序

[复制链接]
5220|29
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
shi910229|  楼主 | 2014-7-17 22:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
新型的按键扫描程序  来自网络共享

unsigned char Trg;
unsigned char Cont;
Trg(triger) 代表的是触发,Cont(continue)代表的是连续按下。
void KeyRead( void )
{
    unsigned char ReadData = P1^0xff;           // 1
    Trg = ReadData & (ReadData ^ Cont);         // 2
    Cont = ReadData;                                             // 3
}

1:读P1的端口数据,取反,然后送到ReadData 临时变量里面保存起来。
2:算法1,用来计算触发变量的。一个位与操作,一个异或操作,我想学过C语言都应该懂吧?Trg为全局变量,其它程序可以直接引用。
3:算法2,用来计算连续变量。

(1) 没有按键的时候
端口为0xff,ReadData读端口并且取反,很显然,就是 0x00 了。
Trg = ReadData & (ReadData ^ Cont); (初始状态下,Cont也是为0的)很简单的数学计算,因为ReadData为0,则它和任何数“相与”,结果也是为0的。
Cont = ReadData; 保存Cont 其实就是等于ReadData,为0;
结果就是:
ReadData = 0;
Trg = 0;
Cont = 0;
(2) 第一次P1.0按下的情况
端口数据为0xfe,ReadData读端口并且取反,很显然,就是 0x01 了。
Trg = ReadData & (ReadData ^ Cont); 因为这是第一次按下,所以Cont是上次的值,应为为0。那么这个式子的值也不难算,也就是 Trg = 0x01 & (0x01^0x00) = 0x01
Cont = ReadData = 0x01;
结果就是:
ReadData = 0x01;
Trg = 0x01;Trg只会在这个时候对应位的值为1,其它时候都为0
Cont = 0x01;
(3) P1.0按着不松(长按键)的情况
端口数据为0xfe,ReadData读端口并且取反是 0x01 了。
Trg = ReadData & (ReadData ^ Cont); 因为这是连续按下,所以Cont是上次的值,应为为0x01。那么这个式子就变成了 Trg = 0x01 & (0x01^0x01) = 0x00
Cont = ReadData = 0x01;
结果就是:
ReadData = 0x01;
Trg = 0x00;
Cont = 0x01;
因为现在按键是长按着,所以MCU会每个一定时间(20ms左右)不断的执行这个函数,那么下次执行的时候情况会是怎么样的呢?
ReadData = 0x01;这个不会变,因为按键没有松开
Trg = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01) = 0 ,只要按键没有松开,这个Trg值永远为 0 !!!
Cont = 0x01;只要按键没有松开,这个值永远是0x01!!
(4) 按键松开的情况
端口数据为0xff,ReadData读端口并且取反是 0x00 了。
Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00
Cont = ReadData = 0x00;
结果就是:
ReadData = 0x00;
Trg = 0x00;
Cont = 0x00;
很显然,这个回到了初始状态,也就是没有按键按下的状态。

Trg 表示的就是触发的意思,也就是跳变,只要有按键按下(电平从1到0的跳变),那么Trg在对应按键的位上面会置一,我们用了PB0则Trg的值为0x01,类似,如果我们PB7按下的话,Trg 的值就应该为 0x80 ,这个很好理解,还有,最关键的地方,Trg 的值每次按下只会出现一次,然后立刻被清除,完全不需要人工去干预。所以按键功能处理程序不会重复执行,省下了一大堆的条件判断,这个可是精粹哦!!Cont代表的是长按键,如果PB0按着不放,那么Cont的值就为 0x01,相对应,PB7按着不放,那么Cont的值应该为0x80,同样很好理解。

应用一:一次触发的按键处理
假设P1.0为蜂鸣器按键,按一下,蜂鸣器beep的响一声。这个很简单,但是大家以前是怎么做的呢?对比一下看谁的方便?
#define KEY_BEEP 0x01
void KeyProc(void)
{
    if (Trg & KEY_BEEP) // 如果按下的是KEY_BEEP
    {
         Beep();        // 执行蜂鸣器处理函数
    }
}

记得前面解释说Trg的精粹是什么?精粹就是只会出现一次。所以你按下按键的话,Trg & KEY_BEEP 为“真”的情况只会出现一次,所以处理起来非常的方便,蜂鸣器也不会没事乱叫


应用2:长按键的处理
项目中经常会遇到一些要求,例如:一个按键如果短按一下执行功能A,如果长按2秒不放的话会执行功能B,又或者是要求3秒按着不放,计数连加什么什么的功能,很实际。不知道大家以前是怎么做的呢?我承认以前做的很郁闷。
但是看我们这里怎么处理吧,或许你会大吃一惊,原来程序可以这么简单
这里具个简单例子,为了只是说明原理,PB0是模式按键,短按则切换模式,PB1就是加,如果长按的话则连加(玩过电子表吧?没错,就是那个!)

#define KEY_MODE 0x01    // 模式按键
#define KEY_PLUS 0x02     // 加
void KeyProc(void)
{
if (Trg & KEY_MODE) // 如果按下的是KEY_MODE,而且你常按这按键也没有用,
    {                    //它是不会执行第二次的哦 , 必须先松开再按下
         Mode++;         // 模式寄存器加1,当然,这里只是演示,你可以执行你想
                                 // 执行的任何代码
    }
    if (Cont & KEY_PLUS)         // 如果“加”按键被按着不放
    {
         cnt_plus++;               // 计时
         if (cnt_plus > 100)// 20ms*100 = 2S 如果时间到
         {
              Func();              // 你需要的执行的程序
         }           
    }
}



相关帖子

沙发
原野之狼| | 2014-7-17 22:30 | 只看该作者
挺不错的算法
就是不知道如何去抖

使用特权

评论回复
板凳
kamen588| | 2014-7-17 23:22 | 只看该作者
挺不错  

使用特权

评论回复
地板
花戒| | 2014-7-17 23:40 | 只看该作者
原野之狼 发表于 2014-7-17 22:30
挺不错的算法
就是不知道如何去抖

按键去抖应该是在这里,改变计时就好了:
   
if (Cont & KEY_PLUS)         // 如果“加”按键被按着不放
    {
         cnt_plus++;               // 计时
         if (cnt_plus > 100)// 20ms*100 = 2S 如果时间到
         {
              Func();              // 你需要的执行的程序
         }           
    }

之前不懂在哪里看过这个算法,很不错的思路。

使用特权

评论回复
5
ayb_ice| | 2014-7-18 06:35 | 只看该作者
算法就是变化没有,如果有变化,是按下,还是弹起

使用特权

评论回复
6
shi910229|  楼主 | 2014-7-18 09:21 | 只看该作者
花戒 发表于 2014-7-17 23:40
按键去抖应该是在这里,改变计时就好了:
   
if (Cont & KEY_PLUS)         // 如果“加”按键被按着不 ...

我发不全,是有消抖的算法的。

应用3:点触型按键和开关型按键的混合使用 
点触形按键估计用的最多,特别是单片机。开关型其实也很常见,例如家里的电灯,那些按下就不松开,除非关。这是两种按键形式的处理原理也没啥特别,但是你有没有想过,如果一个系统里面这两种按键是怎么处理的?我想起了我以前的处理,分开两个非常类似的处理程序,现在看起来真的是笨的不行了,但是也没有办法啊,结构决定了程序。不过现在好了,用上面介绍的办法,很轻松就可以搞定。 
原理么?可能你也会想到,对于点触开关,按照上面的办法处理一次按下和长按,对于开关型,我们只需要处理Cont就OK了,为什么?很简单嘛,把它当成是一个长按键,这样就找到了共同点,屏蔽了所有的细节。程序就不给了,完全就是应用2的内容,在这里提为了就是说明原理~~ 
好了,这个好用的按键处理算是说完了。可能会有朋友会问,为什么不说延时消抖问题?哈哈,被看穿了。果然不能偷懒。下面谈谈这个问题,顺便也就非常简单的谈谈我自己用时间片轮办法,以及是如何消抖的。 
延时消抖的办法是非常传统,也就是 第一次判断有按键,延时一定的时间(一般习惯是20ms)再读端口,如果两次读到的数据一样,说明了是真正的按键,而不是抖动,则进入按键处理程序。 
当然,不要跟我说你delay(20)那样去死循环去,真是那样的话,我衷心的建议你先放下手上所有的东西,好好的去了解一下操作系统的分时工作原理,大概知道思想就可以,不需要详细看原理,否则你永远逃不出“菜鸟”这个圈子。当然我也是菜鸟。我的意思是,真正的单片机入门,是从学会处理多任务开始的,这个也是学校程序跟公司程序的最大差别。
我的主程序架构是这样的: 
volatile unsigned char Intrcnt; 
void InterruptHandle()          // 中断服务程序 

       Intrcnt++;            // 1ms 中断1次,可变 

void main(void) 

    SysInit(); 
    while(1)                   // 每20ms 执行一次大循环 
    { 
        KeyRead();             // 将每个子程序都扫描一遍 
        KeyProc(); 
        Func1(); 
        Funt2(); 
        … 
        … 
        while(1) 
        { 
              if (Intrcnt>20)          // 一直在等,直到20ms时间到 
              { 
                   Intrcnt="0"; 
                   break;       // 返回主循环 
              } 
        } 
       } 

貌似扯远了,回到我们刚才的问题,也就是怎么做按键消抖处理。我们将读按键的程序放在了主循环,也就是说,每20ms我们会执行一次KeyRead()函数来得到新的Trg 和 Cont 值。好了,下面是我的消抖部分:很简单 
基本架构如上,我自己比较喜欢的,一直在用。当然,和这个配合,每个子程序必须执行时间不长,更加不能死循环,一般采用有限状态机的办法来实现,具体参考其它资料咯。 
懂得基本原理之后,至于怎么用就大家慢慢思考了,我想也难不到聪明的工程师们。例如还有一些处理,怎么判断按键释放?很简单,Trg 和Cont都为0 则肯定已经释放了。

使用特权

评论回复
7
xyz769| | 2014-7-18 09:43 | 只看该作者
算法不错,细节还有待完善。。比如解决连按问题,降低代码间的耦合度,方便移植等。。加油!!

使用特权

评论回复
8
gyh974| | 2014-7-18 13:18 | 只看该作者

使用特权

评论回复
9
sdlfkajsdf| | 2014-7-18 13:35 | 只看该作者
这是我按照这个算法写的一个pic程序,用protues仿真好像没什么问题,按的次数多了会出现错误的键值。

使用特权

评论回复
10
sdlfkajsdf| | 2014-7-18 13:36 | 只看该作者
#include p16f877a.inc
__config        h'3f39'
        cblock        0x20
        keycode
        keycont
        keyrelease
        keytrag
        keyflag
        readdata
        intnum
        keyfifo:8
        putptr
        getptr
        key
        endc
        cblock        0x70
        status_tmp
        w_tmp
        endc
#DEFINE        KEY1 0X01
#DEFINE        KEY2 0X02
#DEFINE        KEY3 0X04
#DEFINE        KEY4 0X08
#define        led        PORTD       
        org        0
        goto        init
        org 4
        goto        int_sv
init
        BANKSEL        PORTD
        clrf        portd
        clrf        portb
        BANKSEL        TRISD
        clrf        trisd
        movlw        0x0f
        movwf        trisb
        bcf                option_reg,not_rbpu
        BANKSEL        TMR1H
        MOVLW        0xd8
        MOVWF        TMR1H
        MOVLW        0xf0
        MOVWF        TMR1L       
        BANKSEL        INTCON
        MOVLW        0xC0
        MOVWF        INTCON
        BANKSEL        PIE1
        MOVLW        0x01
        MOVWF        PIE1
        BANKSEL PIR1
        CLRF        PIR1
        BANKSEL        T1CON
        MOVLW        0x01
        MOVWF        T1CON
main
        call        _keyscan
        call        _keypro
        call        _keyget
        movf        key,0
        movwf        led
loop
        movf        intnum,0
        xorlw        0x01
        bnz                loop
        clrf        intnum
        goto        main               
_keyscan
        MOVF        PORTB,0
        XORLW        0XFF
        andlw        0x0F
        MOVWF        ReadData
        XORWF        KEYCont,0
        ANDWF        ReadData,0
        MOVWF        KEYTRAG
        MOVF        ReadData,0
        XORWF        KEYCont,0
        ANDWF        KEYCONT,0
        MOVWF        KEYRELEASE       
        MOVF        ReadData,0
        MOVWF        KEYCont
        RETURN
_keypro
_key1_01
        movf        keytrag,0
        xorlw        key1
        bnz                _key2_01
        movlw        key1
        movwf        keycode
        goto        _keyput
_key2_01                       
        movf        keytrag,0
        xorlw        key2
        bnz                _key3_01
        movlw        key2
        movwf        keycode
        goto        _keyput
_key3_01                       
        movf        keytrag,0
        xorlw        key3
        bnz                _key4_01
        movlw        key3
        movwf        keycode
        goto        _keyput
_key4_01
        movf        keytrag,0
        xorlw        key4
        bnz                _key1_10
        movlw        key4
        movwf        keycode
        goto        _keyput
_key1_10                                               
        movf        keyrelease,0
        xorlw        key1
        bnz                _key2_10
        movlw        key1+0x10
        movwf        keycode
        goto        _keyput
_key2_10                                               
        movf        keyrelease,0
        xorlw        key2
        bnz                _key3_10
        movlw        key2+0x10
        movwf        keycode
        goto        _keyput
_key3_10                                               
        movf        keyrelease,0
        xorlw        key3
        bnz                _key4_10
        movlw        key3+0x10
        movwf        keycode
        goto        _keyput
_key4_10                                               
        movf        keyrelease,0
        xorlw        key4
        bnz                _keypro_90
        movlw        key4+0x10
        movwf        keycode
        goto        _keyput
_keyput
        bankisel        keyfifo
        movlw        keyfifo&0xff
        addwf        putptr,0
        movwf        fsr       
        movf        keycode,0
        movwf        indf
        incf        putptr,1
        movlw        0x07
        andwf        putptr,1       
_keypro_90
        return
_keyget
        banksel        putptr
        movf        putptr,0
        xorwf        getptr,0
        bz                _keyget90
        bankisel        keyfifo
        movlw        keyfifo&0xff
        addwf        getptr,0
        movwf        fsr
        movf        indf,0
        movwf        key
        incf        getptr,1
        movlw        0x07
        andwf        getptr,1
_keyget90
        return       
int_sv
        movwf        W_TMP
        MOVF        STATUS,W
        MOVWF        W_TMP
        BANKSEL        PIE1
        BTFSS        PIE1,0
        GOTO        INT_EXIT
        BANKSEL        PIR1
        BTFSS        PIR1,0
        GOTO        INT_EXIT
        BCF                PIR1,0
;        bsf                fkeyscan_en
        BANKSEL        T1CON
;        BCF                T1CON,0
        BANKSEL TMR1H
        movLW        0xb0
        addWF        TMR1L
        clrf        tmr1h
        skpnc
        incf        tmr1h
        MOVLW        0x3c
        addWF        TMR1H       
;        BSF                T1CON,0
        INCF        INTNUM,1
INT_EXIT
        MOVF        STATUS_TMP,W
        MOVWF        STATUS
        SWAPF        W_TMP,F
        SWAPF        W_TMP,W
        RETFIE
        end

使用特权

评论回复
11
NWPU_CHEN| | 2014-7-18 13:52 | 只看该作者
本帖最后由 NWPU_CHEN 于 2014-7-18 14:15 编辑

想法还可以,至于你说的延时消抖,根据实际需要处理,用DELAY当然是一种很浪费CPU的方式,延时只需要在中断中加一个标志即可,裸奔也就不要谈什么分时处理,多任务处理

使用特权

评论回复
12
Leeone| | 2014-7-18 16:17 | 只看该作者
这个之前看过,也是在定时扫描按键程序去抖的

使用特权

评论回复
13
SevenWans| | 2014-7-18 17:05 | 只看该作者
花戒 发表于 2014-7-17 23:40
按键去抖应该是在这里,改变计时就好了:
   
if (Cont & KEY_PLUS)         // 如果“加”按键被按着不 ...

这个不是去抖,这个应该是长按键吧…………

去抖应该定时按键扫描吧

使用特权

评论回复
14
二月十五| | 2014-7-18 17:40 | 只看该作者
这个之前看过,

使用特权

评论回复
15
老鱼探戈| | 2014-7-18 18:33 | 只看该作者
好算法,Trg是点睛之笔。
消抖可以用多不扫描,当然不能用delay()来实现

使用特权

评论回复
16
teddeng| | 2014-7-18 18:36 | 只看该作者
大神个毛线,根本就没去抖。这么多废话,无非就是真值表:
A      B        Y
0      0        0
0      1        1
1      0        0
1      1        0
的表达式:Y=A'&B。其中A=counter,本质是上次端口采样取反的值;B=readdata,也就是本次端口采样取反的值;Y=trigger,有键按下标志,那个故弄玄虚的表达式B&(B^A)=B&(B&A'+B'A)=B&B&A'+B&B'&A=B&A',完了。

使用特权

评论回复
17
老鱼探戈| | 2014-7-18 18:44 | 只看该作者
teddeng 发表于 2014-7-18 18:36
大神个毛线,根本就没去抖。这么多废话,无非就是真值表:
A      B        Y
0      0        0

这肯定是没看完帖子的。

使用特权

评论回复
18
teddeng| | 2014-7-18 19:19 | 只看该作者
老鱼探戈 发表于 2014-7-18 18:44
这肯定是没看完帖子的。

我要看完干嘛?他解释得清楚还是我解释得清楚?很自然的东西,并且我早不用这种,我用Y=A^B&C+A&B,不了解本质,永远也只能跟在别人屁股后面抄。

使用特权

评论回复
评论
老鱼探戈 2014-7-19 14:38 回复TA
俺说的是消抖~ o(╯□╰)o 
评分
参与人数 1威望 +6 收起 理由
dong_abc + 6
19
xinxi| | 2014-7-18 19:26 | 只看该作者
本帖最后由 xinxi 于 2014-7-18 19:28 编辑

外表很花俏,实际没什么用,年轻人喜欢

使用特权

评论回复
20
朝阳之光| | 2014-7-18 20:32 | 只看该作者
很早以前看过

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

21

主题

114

帖子

5

粉丝