关于独立按键扫描程序的思考(整合两种算法)

      最近刚开始学51单片机编程。学到按键扫描,在网上看到Etual 2008年总结的《新型按键扫描》的博文,很有感触。关于按键扫描的介绍和应用可以参照原文,本文只是我对程序的一些心得和体会。原文的链接找不到了,给个转载的:

    http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4308630&bbs_page_no=1&search_mode=4&search_text=atommann&bbs_id=9999

    源程序主要部分如下:


     volatile  unsigned char  Trg;
    volatile  unsigned char  Cont;
    volatile  unsigned char  Release;

    void  KeyRead( void  )
    {
      unsigned char  ReadData = PINB^0xff;      // 1  读键值 
      Trg = ReadData & (ReadData ^ Cont);      // 2  得到按下触发值 
      Release=  (ReadData ^ Trg ^ Cont);       // 3  得到释放触发值 
      Cont = ReadData;                         // 4  得到所有未释放的键值 
    }

    首先感觉程序的逻辑可以优化一下,因此就自作主张用了数字电路上逻辑优化方法


     void  KeyRead( void  )
    {
      unsigned char  ReadData = (~PINB)&0xff;      // 1  读键值 
      Trg = (~Cont) & ReadData;      // 2  得到按下触发值 
      Release= Cont &(~ReadData);       // 3  得到释放触发值 
      Cont = ReadData;                         // 4  得到所有未释放的键值 
    }

    这样就好更理解一些Trg、Release、cont的含义。以Trg为例,只有在Cont=0且ReadData=1的情况下,才为1,这正好对应的是ReadData波形的上升沿(由于ReadData做了取反操作,该上升沿对应按键按下瞬间IO口波形的下降沿)。

    由于是要用到51板子上,所以在程序上也做了相应的修改。这里用了4个独立按键,分别对应P1口的P1.0-P1.3。同时为了方便理解,改动了变量的名字。

    Trig---KeyPressDown  代表按键按下对应的边沿,

    ReadData---CurrKey  当前读取的键值,进行了取反操作,这样当按键按下时,相应的位为1。

    Cont -----LastKey     上次的按键值

    Release-----KeyRelease    按键释放时对应的边沿


    #define KEYMASK  0x0f
    //按键变量
    unsigned char    KeyPressDown=0x00;
    unsigned char    KeyRelease=0x00;
    unsigned char    LastKey=0x00;

    //按键扫描,定时10ms执行一次
    void  KeyScan(void )
    {
        //当前读取的键值
        unsigned char  CurrKey;
        P1|=KEYMASK;  //将按键对应的IO设置为输入状态
        CurrKey=(~P1)&KEYMASK;

        KeyPressDown=(~LastKey)&CurrKey;
        KeyRelease=LastKey&(~CurrKey);

        LastKey=CurrKey;
    }

    为了测试该程序,没想到好的办法,闭着眼数数按键,对KeyPressDown进行计数,然后用数码管显示该计数值。如果我按100下,那么数码管应该也显示的是100。但是四个按键都试了下,都比100多了好几个,用示波器看了下IO口的波形,波形大部分情况下是个矩形波,只是在个别情况下,会在按下或释放瞬间出来个干扰脉冲(也就是抖动),而程序采用10ms定时采样,由于两者间没有相关性,如果在刚好在干扰脉冲瞬间采样的话,这样有可能导致按键在一次按键中,多次触发事件。

    关于独立按键扫描程序的思考(整合两种算法)

    如果按键IO口受干扰比较严重或按键接触不良的情况下,可能在按键没有按下时也会产生一个负脉冲,或在按键按下稳定的过程中出现一个正脉冲。这样的脉冲可能不是很宽,但是一旦被检测被程序的10ms定时采样采到,就会引起一个误触发操作,增大采样时间可以减小几率,但不能避免。

    为了完善该算法,解决干扰脉冲可能带来的误触发的问题。在网上找了好久,终于找到一篇博文《一种软件去除键抖动的方法》 ,就借鉴了过来

    http://blog.csdn.net/meiyuli/article/details/5133708

    http://z86k.blog.163.com/blog/static/90796156200962012657976/

    核心算法是

    Keradyn=Ktemp Kinput+Kreadyn-1 (Ktemp ⊙Kinput)    (1)

    Ktemp=Kinput    (2)

    为了方便理解,再次对变量名作了修改,并且增加了变量,

    LastReadKey代表上次KeyScan()采样读取的键值

    CurrReadKey; //记录本次KeyScan()采样读取的键值

    LastKey 代表上次的有效判定的键值(经过逻辑判断,或者经过校正的实际键值)

    CurrKey代表本次的有效判定键值。

    按照逻辑式

    CurrKey=(CurrReadKey&LastReadKey)|LastKey&(CurrReadKey^LastReadKey)  (1)

    对CurrKey进行判定:如果上次LastReadKey和当前CurReadKey读取的键值都为1,那么当前有效键值CurrKey一定为1;如果上次和当前读取的键值不一样的话,则与上次有效键值LastKey保持一致。就是利用这种方式,来消除干扰脉冲。

    该算法要求干扰脉冲的宽度必须要小于程序定时采样周期,如果程序定时20ms采样的话,那么干扰脉冲宽度必须小于20ms。实际上,如果IO键干扰脉冲大于20ms,就该考虑是否硬件设计存在问题,或者应该在按键IO口并上滤波电容。

    最后给出两种算法整合后的程序


    #define KEYMASK 0x0f
    //按键变量
    unsigned char    KeyPressDown=0x00;
    unsigned char    KeyRelease=0x00;
    unsigned char    LastKey=0x00;

    //按键扫描,定时10ms执行一次
    void  KeyScan(void )
    {
        static  unsigned char  LastReadKey=0x00;  //记录上次KeyScan()读取的IO口键值
        unsigned char  CurrReadKey;  //记录本次KeyScan()读取的IO口键值
        unsigned char  CurrKey;      //记录本次经过消抖处理后的有效按键值 

        P1|=KEYMASK;  //将按键对应的IO设置为输入状态
        CurrReadKey=(~P1)&KEYMASK; //取反

        //消抖原理很简单:
        //如果上次LastReadKey和当前CurReadKey读取的键值都为1,那么当前有效键值CurrKey一定为1
        //如果上次和当前读取的键值不一样的话,则与上次有效键值LastKey保持一致。
        //通过这种方式来进行消抖,抖动时间必须小于keyscan()定时扫描周期,否则会出现错误。
        CurrKey=(CurrReadKey&LastReadKey)|LastKey&(CurrReadKey^LastReadKey);
        //记录按键按下及释放
        KeyPressDown=(~LastKey)&CurrKey;
        KeyRelease=LastKey&(~CurrKey);

        LastReadKey=CurrReadKey;
        LastKey=CurrKey;

    }

    下载到单片机后,还是老办法测试,这次按100下,数码管就显示100。相对于以前是稳定多了。至于该按键扫描程序的性能,还要在将来的应用中得到证明。

    单片机C语言编程规范 嵌入式开发

    单片机C语言编程规范

    1.基本规则 格式清晰、注释简明扼要、命名规范易懂、函数模块化、程序易读易维护、功能准确实现、代码空间效率和时间效率高、适度的可扩展性、单片机编程规范-标识符命名。 2.标识符命名 2.1命名基本原则...
    typedef和define的详细区别 嵌入式开发

    typedef和define的详细区别

    typedef是一种在计算机编程语言中用来声明自定义数据类型,配合各种原有数据类型来达到简化编程的目的的类型定义关键字。 #define是预处理指令。下面让我们一起来看。 typedef是C语言语句,...
    C语言运算符优先级 详细列表 WEB前后台

    C语言运算符优先级 详细列表

      优先级 运算符 名称或含义 使用形式 结合方向 说明 1 数组下标 数组名 左到右 () 圆括号 (表达式)/函数名(形参表) . 成员选择(对象) 对象.成员名 -> 成员选择(...