单片机 可靠 按键_单片机按键模拟手机按键 - CSDN
  • 1.按键分类与输入原理 ...在单片机应用系统中,除了复位按键有专门的复位电路及专一的复位功能外,其他按键都是以开关状态来设置控制功能或输入数据的。当所设置的功能键或数字键按下时,计算机应用系统应完成该...

    1.按键分类与输入原理

    按键按照结构原理科分为两类,一类是触点式开关按键,如机械式开关、导电橡胶式开关灯;另一类是无触点式开关按键,如电气式按键,磁感应按键等。前者造价低,后者寿命长。目前,微机系统中最常见的是触点式开关按键。

    单片机应用系统中,除了复位按键有专门的复位电路及专一的复位功能外,其他按键都是以开关状态来设置控制功能或输入数据的。当所设置的功能键或数字键按下时,计算机应用系统应完成该按键所设定的功能,键信息输入时与软件结构密切相关的过程。

    对于一组键或一个键盘,总有一个接口电路与CPU相连。CPU可以采用查询或中断方式了解有无将按键输入,并检查是哪一个按键按下,将该键号送人累加器,然后通过跳转指令转入执行该键的功能程序,执行完成后再返回主程序。

    2.按键结构与特点

    微机键盘通常使用机械触点式按键开关,其主要功能式把机械上的通断转换为电气上的逻辑关系。也就是说,它能提供标准的TTL逻辑电平,以便于通用数字系统的逻辑电平相容。机械式按键再按下或释放时,由于机械弹性作用的影响,通常伴随有一定的时间触点机械抖动,然后其触点才稳定下来。其抖动过程如下图1所示,抖动时间的长短与开关的机械特性有关,一般为5-10ms。在触点抖动期间检测按键的通与断,可能导致判断出错,即按键一次按下或释放错误的被认为是多次操作,这种情况是不允许出现的。为了克服你、按键触点机械抖动所致的检测误判,必须采取消抖措施。按键较少时,可采用硬件消抖;按键较多式,采用软件消抖。

    单片机学习:单片机独立按键和矩阵键盘概念及原理
    图1 按键触点机械抖动

    (1)按键编码

    一组按键或键盘都要通过I/O口线查询按键的开关状态。根据键盘结构的不同,采用不同的编码。无论有无编码,以及采用什么编码,最后都要转换成为与累加器中数值相对应的键值,以实现按键功能程序的跳转。

    (2)键盘程序

    一个完整的键盘控制程序应具备以下功能:

    a.检测有无按键按下,并采取硬件或软件措施消抖。

    b.有可靠的逻辑处理办法。每次只处理一个按键,期间对任何按键的操作对系统不产生影响,且无论一次按键时间有多长,系统仅执行一次按键功能程序。

    c.准确输出按键值(或键号),以满足跳转指令要求。

    3.独立按键与矩阵键盘

    (1)独立按键

    单片机控制系统中,如果只需要几个功能键,此时,可采用独立式按键结构。

    独立按键式直接用I/O口线构成的单个按键电路,其特点式每个按键单独占用一根I/O口线,每个按键的工作不会影响其他I/O口线的状态。独立按键的典型应用如图所示。独立式按键电路配置灵活,软件结构简单,但每个按键必须占用一个I/O口线,因此,在按键较多时,I/O口线浪费较大,不宜采用。独立按键如图2所示。

    单片机学习:单片机独立按键和矩阵键盘概念及原理
    图2 独立键盘

    独立按键的软件常采用查询式结构。先逐位查询没跟I/O口线的输入状态,如某一根I/O口线输入为低电平,则可确认该I/O口线所对应的按键已按下,然后,再转向该键的功能处理程序。

    (2)矩阵键盘

    单片机系统中,若使用按键较多时如电子密码锁、电话机键盘等一般都至少有12到16个按键,通常采用矩阵键盘。

    矩阵键盘又称行列键盘,它是用四条I/O线作为行线,四条I/O线作为列线组成的键盘。在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为4*4个。这种行列式键盘结构能有效地提高单片机系统中I/O口的利用率。

    矩阵键盘的工作原理

    最常见的键盘布局如图3所示。一般由16个按键组成,在单片机中正好可以用一个P口实现16个按键功能,这也是在单片机系统中最常用的形式,4*4矩阵键盘的内部电路如图4所示。

    单片机学习:单片机独立按键和矩阵键盘概念及原理
    图3 矩阵键盘布局图

    单片机学习:单片机独立按键和矩阵键盘概念及原理
    图4 矩阵键盘内部电路图

    当无按键闭合时,P3.0P3.3与P3.4P3.7之间开路。当有键闭合时,与闭合键相连的两条I/O口线之间短路。判断有无按键按下的方法是:第一步,置列线P3.4P3.7为输入状态,从行线P3.0P3.3输出低电平,读入列线数据,若某一列线为低电平,则该列线上有键闭合。第二步,行线轮流输出低电平,从列线P3.4~P3.7读入数据,若有某一列为低电平,则对应行线上有键按下。综合一二两步的结果,可确定按键编号。但是键闭合一次只能进行一次键功能操作,因此须等到按键释放后,再进行键功能操作,否则按一次键,有可能会连续多次进行同样的键操作。

    识别按键的方法很多其中,最常见的方法是扫描法

    按键按下时,与此键相连的行线与列线导通,行线在无按键按下时处在高电平。如果所有的列线都处在高电平,则按键按下与否不会引起行线电平的变化,因此必须使所有列线处在电平。这样,当有按键按下时,改键所在的行电平才回由高变低。才能判断相应的行有键按下。

    独立按键数量少,可根据实际需要灵活编码。矩阵键盘,按键的位置由行号和列号唯一确定,因此可以分别对行号和列号进行二进制编码,然后两值合成一个字节,高4位是行号,低4位是列号。

    4.键盘的工作方式

    对键盘的响应取决于键盘的工作方式,键盘的工作方式应根据实际应用系统中的CPU的工作状况而定,其选取的原则是既要保证CPU能及时响应按键操作,又不要过多占用CPU的工作时间。通常键盘的工作方式有三种,编程扫描、定时扫描和中断扫描。

    (1)编程扫描方式

    编程扫描方式是利用CPU完成其它工作的空余时间,调用键盘扫描子程序来响应键盘输入的要求。在执行键功能程序时,CPU不再响应键输入要求,直到CPU重新扫描键盘为止。

    (2)定时扫描方式

    定时扫描方式就是每隔一段时间对键盘扫描一次,它利用单片机内部的定时器产生一定时间(例如10ms)的定时,当定时时间到就产生定时器溢出中断。CPU响应中断后对键盘进行扫描,并在有按键按下时识别出该键,再执行该键的功能程序。

    (3)中断扫描方式

    上述两种键盘扫描方式,无论是否按键,CPU都要定时扫描键盘,而单片机应用系统工作时,并非经常需要键盘输入,因此,CPU经常处于空扫描状态。

    为提高CPU工作效率,可采用中断扫描工作方式。其工作过程如下:当无按键按下时,CPU处理自己的工作,当有按键按下时,产生中断请求,CPU转去执行键盘扫描子程序,并识别键号。

    展开全文
  • 一、基本知识  1.按键分类与输入原理 ...在单片机应用系统中,除了复位按键有专门的复位电路及专一的复位功能外,其他按键都是以开关状态来设置控制功能或输入数据的。当所设置的功能键或数字键按下时

    一、基本知识

            1.按键分类与输入原理

            按键按照结构原理科分为两类,一类是触点式开关按键,如机械式开关、导电橡胶式开关灯;另一类是无触点式开关按键,如电气式按键,磁感应按键等。前者造价低,后者寿命长。目前,微机系统中最常见的是触点式开关按键。

            在单片机应用系统中,除了复位按键有专门的复位电路及专一的复位功能外,其他按键都是以开关状态来设置控制功能或输入数据的。当所设置的功能键或数字键按下时,计算机应用系统应完成该按键所设定的功能,键信息输入时与软件结构密切相关的过程。

             对于一组键或一个键盘,总有一个接口电路与CPU相连。CPU可以采用查询或中断方式了解有无将按键输入,并检查是哪一个按键按下,将该键号送人累加器,然后通过跳转指令转入执行该键的功能程序,执行完成后再返回主程序。

            2.按键结构与特点

            微机键盘通常使用机械触点式按键开关,其主要功能式把机械上的通断转换为电气上的逻辑关系。也就是说,它能提供标准的TTL逻辑电平,以便于通用数字系统的逻辑电平相容。机械式按键再按下或释放时,由于机械弹性作用的影响,通常伴随有一定的时间触点机械抖动,然后其触点才稳定下来。其抖动过程如下图1所示,抖动时间的长短与开关的机械特性有关,一般为5-10ms。在触点抖动期间检测按键的通与断,可能导致判断出错,即按键一次按下或释放错误的被认为是多次操作,这种情况是不允许出现的。为了克服你、按键触点机械抖动所致的检测误判,必须采取消抖措施。按键较少时,可采用硬件消抖;按键较多式,采用软件消抖。


    图1  按键触点机械抖动

            (1)按键编码

             一组按键或键盘都要通过I/O口线查询按键的开关状态。根据键盘结构的不同,采用不同的编码。无论有无编码,以及采用什么编码,最后都要转换成为与累加器中数值相对应的键值,以实现按键功能程序的跳转。

           (2)键盘程序

             一个完整的键盘控制程序应具备以下功能:

              a.检测有无按键按下,并采取硬件或软件措施消抖。

              b.有可靠的逻辑处理办法。每次只处理一个按键,期间对任何按键的操作对系统不产生影响,且无论一次按键时间有多长,系统仅执行一次按键功能程序。

              c.准确输出按键值(或键号),以满足跳转指令要求。

            3.独立按键与矩阵键盘

           (1)独立按键

            单片机控制系统中,如果只需要几个功能键,此时,可采用独立式按键结构。

            独立按键式直接用I/O口线构成的单个按键电路,其特点式每个按键单独占用一根I/O口线,每个按键的工作不会影响其他I/O口线的状态。独立按键的典型应用如图所示。独立式按键电路配置灵活,软件结构简单,但每个按键必须占用一个I/O口线,因此,在按键较多时,I/O口线浪费较大,不宜采用。独立按键如图2所示。


    图2  独立键盘

            独立按键的软件常采用查询式结构。先逐位查询没跟I/O口线的输入状态,如某一根I/O口线输入为低电平,则可确认该I/O口线所对应的按键已按下,然后,再转向该键的功能处理程序。

           (2)矩阵键盘

            单片机系统中,若使用按键较多时如电子密码锁、电话机键盘等一般都至少有12到16个按键,通常采用矩阵键盘。

    矩阵键盘又称行列键盘,它是用四条I/O线作为行线,四条I/O线作为列线组成的键盘。在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为4*4个。这种行列式键盘结构能有效地提高单片机系统中I/O口的利用率。

        矩阵键盘的工作原理 

        最常见的键盘布局如图3所示。一般由16个按键组成,在单片机中正好可以用一个P口实现16个按键功能,这也是在单片机系统中最常用的形式,4*4矩阵键盘的内部电路如图4所示。

     

    图3 矩阵键盘布局图 

    图4 矩阵键盘内部电路图         

       

        当无按键闭合时,P3.0~P3.3与P3.4~P3.7之间开路。当有键闭合时,与闭合键相连的两条I/O口线之间短路。判断有无按键按下的方法是:第一步,置列线P3.4~P3.7为输入状态,从行线P3.0~P3.3输出低电平,读入列线数据,若某一列线为低电平,则该列线上有键闭合。第二步,行线轮流输出低电平,从列线P3.4~P3.7读入数据,若有某一列为低电平,则对应行线上有键按下。综合一二两步的结果,可确定按键编号。但是键闭合一次只能进行一次键功能操作,因此须等到按键释放后,再进行键功能操作,否则按一次键,有可能会连续多次进行同样的键操作。  

       

        识别按键的方法很多其中,最常见的方法是扫描法

        按键按下时,与此键相连的行线与列线导通,行线在无按键按下时处在高电平。如果所有的列线都处在高电平,则按键按下与否不会引起行线电平的变化,因此必须使所有列线处在电平。这样,当有按键按下时,改键所在的行电平才回由高变低。才能判断相应的行有键按下。

        独立按键数量少,可根据实际需要灵活编码。矩阵键盘,按键的位置由行号和列号唯一确定,因此可以分别对行号和列号进行二进制编码,然后两值合成一个字节,高4位是行号,低4位是列号。

        4.键盘的工作方式

        对键盘的响应取决于键盘的工作方式,键盘的工作方式应根据实际应用系统中的CPU的工作状况而定,其选取的原则是既要保证CPU能及时响应按键操作,又不要过多占用CPU的工作时间。通常键盘的工作方式有三种,编程扫描、定时扫描和中断扫描。

        (1)编程扫描方式

        编程扫描方式是利用CPU完成其它工作的空余时间,调用键盘扫描子程序来响应键盘输入的要求。在执行键功能程序时,CPU不再响应键输入要求,直到CPU重新扫描键盘为止。

        (2)定时扫描方式

        定时扫描方式就是每隔一段时间对键盘扫描一次,它利用单片机内部的定时器产生一定时间(例如10ms)的定时,当定时时间到就产生定时器溢出中断。CPU响应中断后对键盘进行扫描,并在有按键按下时识别出该键,再执行该键的功能程序。

        (3)中断扫描方式

        上述两种键盘扫描方式,无论是否按键,CPU都要定时扫描键盘,而单片机应用系统工作时,并非经常需要键盘输入,因此,CPU经常处于空扫描状态。

        为提高CPU工作效率,可采用中断扫描工作方式。其工作过程如下:当无按键按下时,CPU处理自己的工作,当有按键按下时,产生中断请求,CPU转去执行键盘扫描子程序,并识别键号。



     
    展开全文
  • 单片机在各种领域运用相当广泛,而作为人机交流的按键设计也有很多种。不同的设计方法,有着不同的优缺点。而又由于单片机I/O资源有限,如何用最少的I/O口扩展更多的按键是我所研究的问题。接下来我给大家展示几种...

    http://blog.sina.com.cn/s/blog_634771fd0100hd1h.html

    单片机在各种领域运用相当广泛,而作为人机交流的按键设计也有很多种。不同的设计方法,有着不同的优缺点。而又由于单片机I/O资源有限,如何用最少的I/O口扩展更多的按键是我所研究的问题。接下来我给大家展示几种自己觉得比较好的按键扩展方案,大家可以在以后的单片机电路设计中灵活运用。

    1)、第一种是最为常见的,也就是一个I/O口对应一个按钮开关。


     

    这种方案是一对一的,一个I/O口对应一个按键。这里P00到P04,都外接了一个上拉电阻,在没有开关按下的时候,是高电平,一旦有按键按下,就被拉成低电平。这种方案优点是电路简单可靠,程序设计也很简单。缺点是占用I/O资源多。如果单片机资源够多,不紧缺,推荐使用这种方案。

     

    2)、第二种方案也比较常见,但是比第一种的资源利用率要高,硬件电路也不复杂。

     


    这是一种矩阵式键盘,用8个I/O控制了16个按钮开关,优点显而易见。当然这种电路的程序设计相对也还是很简单的。由P00到P03循环输出低电平,然后检测P04到P07的状态。比方说这里P00到P03口输出1000,然后检测P04到P07,如果P04为1则说明按下的键为s1,如果P05为1则说明按下的是s2等等。为了电路的可靠,也可以和第一种方案一样加上上拉电阻。

    3)、第三种是我自己搞的一种方案,可以使用4个I/O控制8个按键,电路多了一些二极管,稍微复杂了一点。

     



    这个电路的原理很简单,就是利用二极管的单向导电性。也是和上面的方案一样,程序需要采用轮训的方法。比方说,先置P00到P03都为低电平,然后把P00置为高电平,接着查询P02和P03的状态,如果P02为高则说明按下的是s5,若P03为高则说明按下的是s6,然后再让P00为低,P01为高,同样检测P02和P03的状态。接下来分别让P02和P03为高,其他为低,分别检测P00和P01的状态,然后再做判断。这种方案的程序其实也不难。

    4)这是我在一本书上看到的,感觉设计的非常巧妙,同样它也用到了二极管,不过比我的上一种方案的I/O利用率更高,他用4个I/O口控制了12个按键。我相信你了解了之后也会惊奇的。

    首先好好品味一下这个方案吧,想想怎么来识别按键呢!

    首先,我们让P00到P03全输出高电平。如果这个时候从P00到P03的任意一个端口检测到低电平,很容易知道是按下了那个键,肯定是s13到s16的其中一个。如果没有检测到信号,就进行下一次的检测,让P01到P03为高电平,P00为低电平,然后检测P01到P03的状态。如果P01为低,则按下的是s1,;P02为低,则按下的是s2;P03为低,则按下的是s3。

    然后再让P00,P02,P03为高电平,P01为低电平。同理用上面的方法可以检测出按下的那个按键。(部分程序源代码会在后面贴出来,阅读代码可以更好理解电路)

    5)、接下来这种方案则更为强大。不过需要用到一个A/D转换器(有的单片机集成有A/D转换器,则更为方便)。如果A/D转化器的分辨率为n位,理论上是可以扩展2^n(2的n次方)个按键。


        这是一种接AD转化器的方案,有两种:第一种是并联式;第二种是串联式。在功能上也有些不同。第一种的话各个电阻值各不相同,当按下不同按键时,进入AD的模拟量是不一样的,通过AD转换,就可以得到按下的是哪个按键。方式一还可以同时识别多个按键,即可以设置组合键,只要电阻取得合适。
    方式二各个电阻可以取一样的,方便计算,但是不能有组合按键。因为当按下上面的按键后,下面所有按键都会被短路。(在实际运用中,还需要接地,这里没有画出) 。前面说理论上可以扩展2^n个按键,这只是理论,因为这里电阻的精度有限,所以实际是不可能的,两个模拟量之间要有足够大的差值,程序才可能准确的分辨。

     

    上面就是我介绍的五种按键扩展方案,后面几种比较另类,不过也有他们的优点。以上电路我都仿真过,可以实现。

    附方案4键盘扫描源代码:

    sbit line_1=P0.1;
    
    sbit line_2=P0.2;
    
    sbit line_3=P0.3;
    
    sbit line_4=P0.4
    
    char key=0;  
    
    void key_scan()
    {
        line_1=line_2=line_3=line_4=1;
        if(~(line_1&&line_2&&line_3&&line_4))
        {
            if(line_1==0) {key=13;return;}
            if(line_2==0) {key=14; return;}
            if(line_3==0) {key=15;return;}
            if(line_4==0) {key=16; return;}
        }
          
        line_2=line_3=line_4=1;
        line_1=0;
        if(~(line_2&&line_3&&line_4))
        { 
             delay();
             if(line_2==0) {key=1;return;}
             if(line_3==0) {key=2;return;}
             if(line_4==0) {key=3;return;}
        }
        line_1=line_3=line_4=1;
        line_2=0;
        if(~(line_1&&line_3&&line_4))
        { 
             delay();
             if(line_3==0) {key=5;return;}
             if(line_4==0) {key=6;return;}
        }
        line_1=line_2=line_4=1;
        line_3=0;
        if(~(line_2&&line_1&&line_4))
        { 
             delay();
             if(line_4==0) {key=9;return;}
        }
        line_4=0;
        line_1=line_2=line_3=1;
        if(~(line_2&&line_3&&line_1))
        { 
             delay();
             if(line_1==0) {key=4;return; }
        }
        return;
    }


             

    展开全文
  • 单片机按键FIFO

    千次阅读 2020-06-12 10:19:36
    一般的单片机系统,按键作为人机交互工具是必不可少的,但是普通的按键需要消抖处理,极大的增加了程序开销,降低系统实时性。 安富莱的FIFO按键,无需延时处理消抖,可以记录按键按下、弹起、长按、组合按,并且...

    本文代码参考安富莱按键FIFO

    FIFO数据结构如果不清楚可以参考博文简单的FIFO

    一般的单片机系统,按键作为人机交互工具是必不可少的,但是普通的按键需要消抖处理,极大的增加了程序开销,降低系统实时性。

    安富莱的FIFO按键,无需延时处理消抖,可以记录按键按下、弹起、长按、组合按,并且移植起来也十分方便。之前在做一个项目时,用到一个矩阵键盘,移植了这个按键FIFO程序,用起来效果很不错。

    主要流程就是开启一个10ms的定时器中断,在中断中扫描按键状态,并对按键状态进行分析消抖处理,如果按键动作,将按键动作压入FIFO中,在主循环中读取FIFO,获取按键状态。
    在这里插入图片描述

    使用时首先要调用初始化函数,此函数有两个子函数,分别完成变量初始化和板子硬件初始化。

    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKey
    *	功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_InitKey(void)
    {
    	bsp_InitKeyVar();		/* 初始化按键变量 */
    	bsp_InitKeyHard();		/* 初始化按键硬件 */
    }
    

    移植时需要注意对应IO硬件初始化修改成自己的,这里使用的STC8A单片机,3*4矩阵键盘,使用的IO通过宏定义封装起来。
    在这里插入图片描述

    
    /* 矩阵键盘 */
    #define C1_PIN             1
    #define C1_GPIO_PORT       2
    
    #define C2_PIN             2
    #define C2_GPIO_PORT       2
    
    #define C3_PIN             3
    #define C3_GPIO_PORT       2
    
    #define C4_PIN             4
    #define C4_GPIO_PORT       2
    
     
    // Row1, Row2, Row3, Row4
    #define R1_PIN             1
    #define R1_GPIO_PORT       4
    
    #define R2_PIN             0
    #define R2_GPIO_PORT       2
    
    #define R3_PIN             2
    #define R3_GPIO_PORT       4
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKeyHard
    *	功能说明: 配置按键对应的GPIO
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_InitKeyHard(void)
    {
    	//KEY 初始化
        PIN_InitPushPull(C1_GPIO_PORT, C1_PIN);
    	PIN_InitPushPull(C2_GPIO_PORT, C2_PIN);
    	PIN_InitPushPull(C3_GPIO_PORT, C3_PIN);
    	PIN_InitPushPull(C4_GPIO_PORT, C4_PIN);
    	PIN_InitOpenDrain(R1_GPIO_PORT, R1_PIN);
    	PIN_InitOpenDrain(R2_GPIO_PORT, R2_PIN);
    	PIN_InitOpenDrain(R3_GPIO_PORT, R3_PIN);
    	
    }
    
    

    按键参数初始化需要注意设置连发速度来确定对应按键是否支持连按。还要自行修改判断按键按下函数和按键个数

    /*
    	按键滤波时间50ms, 单位10ms。
    	只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
    	即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
    */
    #define KEY_FILTER_TIME   5
    #define KEY_LONG_TIME     100		     	/* 单位10ms, 持续1秒,认为长按事件 */
    #define KEY_COUNT    13	   					/* 按键个数, 12个独立建 + 1 个组合键 */
    static KEY_T xdata s_tBtn[KEY_COUNT];
    static KEY_FIFO_T xdata s_tKey;		/* 按键FIFO变量,结构体 */
    
    /* 检测按键按下函数 */
    static uint8_t IsKeyDown0(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown1(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; NOP(50); if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown2(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown3(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown4(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown5(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown6(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown7(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown8(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown9(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown10(void) {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; NOP(50); if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown11(void) {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    
    /* 组合按键 key1 && key2 */
    static uint8_t IsKeyDown12(void)  { return IsKeyDown1() && IsKeyDown2();}
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKeyVar
    *	功能说明: 初始化按键变量
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_InitKeyVar(void)
    {
    	uint8_t xdata i;
    
    	/* 对按键FIFO读写指针清零 */
    	fifo_init(&s_tKey);
    	
    	/* 给每个按键结构体成员变量赋一组缺省值 */
    	for (i = 0; i < KEY_COUNT; i++)
    	{
    		s_tBtn[i].LongTime = KEY_LONG_TIME;			/* 长按时间 0 表示不检测长按键事件 */
    		s_tBtn[i].Count = KEY_FILTER_TIME / 2;		/* 计数器设置为滤波时间的一半 */
    		s_tBtn[i].State = 0;							/* 按键缺省状态,0为未按下 */
    		//s_tBtn[i].KeyCodeDown = 3 * i + 1;				/* 按键按下的键值代码 */
    		//s_tBtn[i].KeyCodeUp   = 3 * i + 2;				/* 按键弹起的键值代码 */
    		//s_tBtn[i].KeyCodeLong = 3 * i + 3;				/* 按键被持续按下的键值代码 */
    		s_tBtn[i].RepeatSpeed = 0;						/* 按键连发的速度,0表示不支持连发 */
    		s_tBtn[i].RepeatCount = 0;						/* 连发计数器 */
    	}
    
    
    	/* 判断按键按下的函数 */
    	s_tBtn[0].IsKeyDownFunc = IsKeyDown0;
    	s_tBtn[1].IsKeyDownFunc = IsKeyDown1;
    	s_tBtn[2].IsKeyDownFunc = IsKeyDown2;
    	s_tBtn[3].IsKeyDownFunc = IsKeyDown3;
    	s_tBtn[4].IsKeyDownFunc = IsKeyDown4;
    	s_tBtn[5].IsKeyDownFunc = IsKeyDown5;
    	s_tBtn[6].IsKeyDownFunc = IsKeyDown6;
    	s_tBtn[7].IsKeyDownFunc = IsKeyDown7;
    	s_tBtn[8].IsKeyDownFunc = IsKeyDown8;
    	s_tBtn[9].IsKeyDownFunc = IsKeyDown9;
    	s_tBtn[10].IsKeyDownFunc = IsKeyDown10;
    	s_tBtn[11].IsKeyDownFunc = IsKeyDown11;
    	
    	/* 组合按键 */
    	s_tBtn[12].IsKeyDownFunc = IsKeyDown12;
    }
    

    按键扫描函数,需要开启一个10ms的定时器中断,在中断中对按键状态进行扫描。

    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_DetectKey
    *	功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
    *	形    参:  按键结构变量指针
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_DetectKey(uint8_t i)
    {
    	KEY_T xdata *pBtn;
    
    	/*
    		如果没有初始化按键函数,则报错
    		if (s_tBtn[i].IsKeyDownFunc == 0)
    		{
    			printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine");
    		}
    	*/
    
    	pBtn = &s_tBtn[i];
    	if (pBtn->IsKeyDownFunc())
    	{
    		if (pBtn->Count < KEY_FILTER_TIME)
    		{
    			pBtn->Count = KEY_FILTER_TIME;
    		}
    		else if(pBtn->Count < 2 * KEY_FILTER_TIME)
    		{
    			pBtn->Count++;
    		}
    		else
    		{
    			if (pBtn->State == 0)
    			{
    				pBtn->State = 1;
    
    				/* 发送按钮按下的消息 */
    				bsp_PutKey((uint8_t)(3 * i + 1));
    			}
    
    			if (pBtn->LongTime > 0)
    			{
    				if (pBtn->LongCount < pBtn->LongTime)
    				{
    					/* 发送按钮持续按下的消息 */
    					if (++pBtn->LongCount == pBtn->LongTime)
    					{
    						/* 键值放入按键FIFO */
    						bsp_PutKey((uint8_t)(3 * i + 3));
    					}
    				}
    				else
    				{
    					if (pBtn->RepeatSpeed > 0)
    					{
    						if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
    						{
    							pBtn->RepeatCount = 0;
    							/* 常按键后,每隔10ms发送1个按键 */
    							bsp_PutKey((uint8_t)(3 * i + 1));
    						}
    					}
    				}
    			}
    		}
    	}
    	else
    	{
    		if(pBtn->Count > KEY_FILTER_TIME)
    		{
    			pBtn->Count = KEY_FILTER_TIME;
    		}
    		else if(pBtn->Count != 0)
    		{
    			pBtn->Count--;
    		}
    		else
    		{
    			if (pBtn->State == 1)
    			{
    				pBtn->State = 0;
    
    				/* 发送按钮弹起的消息 */
    				bsp_PutKey((uint8_t)(3 * i + 2));
    			}
    		}
    
    		pBtn->LongCount = 0;
    		pBtn->RepeatCount = 0;
    	}
    }
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_KeyScan
    *	功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_KeyScan(void)
    {
    	uint8_t xdata i;
    
    	for (i = 0; i < KEY_COUNT; i++)
    	{
    		bsp_DetectKey(i);
    	}
    }
    

    在主函数中,非堵塞方式获取按键键值

    /*
    *********************************************************************************************************
    *	函 数 名: bsp_GetKey
    *	功能说明: 从按键FIFO缓冲区读取一个键值。
    *	形    参:  无
    *	返 回 值: 按键代码
    *********************************************************************************************************
    */
    uint8_t bsp_GetKey(void)
    {
    	uint8_t xdata ret;
    
    	if (fifo_pop(&s_tKey, &ret) == 1)
    	{
    		return KEY_NONE;
    	}
    	else
    	{
    		return ret;
    	}
    }
    
    

    完整代码如下
    bsp_key.h

    /*!
      * @file     BSP_KEY.h
      *
      * @brief    按键驱动文件
      *
      * @company  
      *
      * @author   不咸不要钱
      *
      * @note     无
      *
      * @version  
      *
      * @date     2019/10/18 星期五
      */ 
    #ifndef __LQ_KEY_H
    #define __LQ_KEY_H
    
    #include "LQ_GPIO.h"
    
    
    
    /* 矩阵键盘 */
    #define C1_PIN             1
    #define C1_GPIO_PORT       2
    
    #define C2_PIN             2
    #define C2_GPIO_PORT       2
    
    #define C3_PIN             3
    #define C3_GPIO_PORT       2
    
    #define C4_PIN             4
    #define C4_GPIO_PORT       2
    
     
    // Row1, Row2, Row3, Row4
    #define R1_PIN             1
    #define R1_GPIO_PORT       4
    
    #define R2_PIN             0
    #define R2_GPIO_PORT       2
    
    #define R3_PIN             2
    #define R3_GPIO_PORT       4
    
    
    
    
    #define KEY_COUNT    13	   					/* 按键个数, 12个独立建 + 1 个组合键 */
    
    
    
    /* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
    typedef enum
    {
    	KID_K0,
    	KID_K1, 
    	KID_K2,
    	KID_K3,
    	KID_K4,
    	KID_K5,
    	KID_K6,
    	KID_K7,
    	KID_K8,
    	KID_K9,
    	KID_K10,
    	KID_K11,
    	KID_K12,
    	
    }KEY_ID_E;
    
    /*
    	按键滤波时间50ms, 单位10ms。
    	只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
    	即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
    */
    #define KEY_FILTER_TIME   5
    #define KEY_LONG_TIME     100			/* 单位10ms, 持续1秒,认为长按事件 */
    
    /*
    	每个按键对应1个全局的结构体变量。
    */
    typedef struct
    {
    	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
    	uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */
    
    	uint8_t  Count;			/* 滤波器计数器 */
    	uint16_t LongCount;		/* 长按计数器 */
    	uint16_t LongTime;		/* 按键按下持续时间, 0表示不检测长按 */
    	uint8_t  State;			/* 按键当前状态(按下还是弹起) */
    	uint8_t  RepeatSpeed;	/* 连续按键周期 */
    	uint8_t  RepeatCount;	/* 连续按键计数器 */
    }KEY_T;
    
    /*
    	定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件
    
    	推荐使用enum, 不用#define,原因:
    	(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
    	(2) 编译器可帮我们避免键值重复。
    */
    typedef enum
    {
    	KEY_NONE = 0,			/* 0 表示按键事件 */
    
    	KEY_0_DOWN,			    /* 0键按下 */
    	KEY_0_UP,				/* 0键弹起 */
    	KEY_0_LONG,			    /* 0键长按 */
    	
    	KEY_1_DOWN,				/* 1键按下 */
    	KEY_1_UP,				/* 1键弹起 */
    	KEY_1_LONG,				/* 1键长按 */
    
    	KEY_2_DOWN,				/* 2键按下 */
    	KEY_2_UP,				/* 2键弹起 */
    	KEY_2_LONG,				/* 2键长按 */
    
    	KEY_3_DOWN,				/* 3键按下 */
    	KEY_3_UP,				/* 3键弹起 */
    	KEY_3_LONG,				/* 3键长按 */
    
    	KEY_4_DOWN,				/* 4键按下 */
    	KEY_4_UP,				/* 4键弹起 */
    	KEY_4_LONG,				/* 4键长按 */
    
    	KEY_5_DOWN,				/* 5键按下 */
    	KEY_5_UP,				/* 5键弹起 */
    	KEY_5_LONG,				/* 5键长按 */
    
    	KEY_6_DOWN,				/* 6键按下 */
    	KEY_6_UP,				/* 6键弹起 */
    	KEY_6_LONG,				/* 6键长按 */
    
    	KEY_7_DOWN,				/* 7键按下 */
    	KEY_7_UP,				/* 7键弹起 */
    	KEY_7_LONG,				/* 7键长按 */
    
    	KEY_8_DOWN,				/* 8键按下 */
    	KEY_8_UP,				/* 8键弹起 */
    	KEY_8_LONG,				/* 8键长按 */
    
    	KEY_9_DOWN,				/* 9键按下 */
    	KEY_9_UP,				/* 9键弹起 */
    	KEY_9_LONG,				/* 9键长按 */
    
    	KEY_10_DOWN,			/* 10键按下 */
    	KEY_10_UP,				/* 10键弹起 */
    	KEY_10_LONG,			/* 10键长按 */
    	
    	KEY_11_DOWN,			/* 11键按下 */
    	KEY_11_UP,				/* 11键弹起 */
    	KEY_11_LONG,			/* 11键长按 */
    	
    	KEY_12_DOWN,			/* 12键按下 */
    	KEY_12_UP,				/* 12键弹起 */
    	KEY_12_LONG,			/* 12键长按 */
    }KEY_ENUM;
    
    /* 按键FIFO用到变量 */
    #define FIFO_SIZE	15
    
    /*! fifo缓冲区类型 */
    #define FIFO_TYPE    uint8_t
    
    /*! fifo缓冲区满后 是否覆盖旧数据 0进行覆盖  1报错入栈失败*/
    #define FIFO_COVER   0
    typedef struct
    {
        FIFO_TYPE  buff[FIFO_SIZE];       /* FIFO 缓冲区 */
    
        uint32_t   fifoLen;               /* FIFO 缓冲区有效数据长度 */
    
        uint32_t   fifoWrite;             /* 缓冲区写指针 */
        uint32_t   fifoRead;              /* 缓冲区读指针 */
    
    }fifo_t;
    
    typedef fifo_t KEY_FIFO_T;
    
    /* 供外部调用的函数声明 */
    void bsp_InitKey(void);
    void bsp_KeyScan(void);
    void bsp_PutKey(uint8_t _KeyCode);
    uint8_t bsp_GetKey(void);
    uint8_t bsp_GetKey2(void);
    uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID);
    void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed);
    void bsp_ClearKey(void);
    
    
    
    
    #endif
    

    bsp_key.c

    /*!
      * @file     BSP_KEY.c
      *
      * @brief    按键驱动文件
      *
      * @company  
      *
      * @author   不咸不要钱
      *
      * @note     无
      *
      * @version  
      *
      * @date     2019/10/18 星期五
      */ 
    #include "bsp_key.h"
    #include "lq_gpio.h"
    #include "stdio.h"
    
    
    
    static KEY_T xdata s_tBtn[KEY_COUNT];
    static KEY_FIFO_T xdata s_tKey;		/* 按键FIFO变量,结构体 */
    
    static void bsp_InitKeyVar(void);
    static void bsp_InitKeyHard(void);
    static void bsp_DetectKey(uint8_t i);
    
    static uint8_t IsKeyDown0(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown1(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; NOP(50); if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown2(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown3(void)  {P(C1_GPIO_PORT, C1_PIN) = 0;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown4(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown5(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown6(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 0;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown7(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown8(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R2_GPIO_PORT, R2_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown9(void)  {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 0; P(C4_GPIO_PORT, C4_PIN) = 1; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown10(void) {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; NOP(50); if (P(R1_GPIO_PORT, R1_PIN) == 0) return 1;else return 0;}
    static uint8_t IsKeyDown11(void) {P(C1_GPIO_PORT, C1_PIN) = 1;  P(C2_GPIO_PORT, C2_PIN) = 1;  P(C3_GPIO_PORT, C3_PIN) = 1; P(C4_GPIO_PORT, C4_PIN) = 0; if (P(R3_GPIO_PORT, R3_PIN) == 0) return 1;else return 0;}
    
    /* 组合按键 key1 && key2 */
    static uint8_t IsKeyDown12(void)  { return IsKeyDown1() && IsKeyDown2();}
    
    /*!
     * @brief    fifo初始化
     *
     * @param    fifo_t    :  FIFO
     *
     * @return   无
     *
     * @note     无
     *
     * @see      fifo_t  tempFifo;
     *           fifo_init(tempFifo);  //fifo初始化
     *
     * @date     2020/5/21
     */
    void fifo_init(fifo_t *fifo)
    {
        fifo->fifoLen   = 0;
        fifo->fifoRead  = 0;
        fifo->fifoWrite = 0;
    }
    
    
    /*!
     * @brief    fifo压入数据
     *
     * @param    fifo_t    :  FIFO
     * @param    data      :  入栈数据
     *
     * @return   0 :成功   1 :失败
     *
     * @note     FIFO_COVER 宏定义进行判断缓冲区满后的操作
     *
     * @see      fifo_t  tempFifo;
     *           fifo_push(tempFifo, 120);  //fifo中压入一个数据
     *
     * @date     2020/5/21
     */
    uint8_t fifo_push(fifo_t *fifo, FIFO_TYPE dat)
    {
        fifo->fifoLen++;
    
        /* 判断缓冲区是否已满 */
        if(fifo->fifoLen > FIFO_SIZE)
        {
            fifo->fifoLen = FIFO_SIZE;
    
    #if FIFO_COVER
            return 1;
    #else
            if(++fifo->fifoRead >= FIFO_SIZE)
            {
                fifo->fifoRead = 0;
            }
    #endif
        }
    
        fifo->buff[fifo->fifoWrite] = dat;
    
        if(++fifo->fifoWrite >= FIFO_SIZE)
        {
            fifo->fifoWrite = 0;
        }
    
        return 0;
    
    }
    
    /*!
     * @brief    fifo弹出数据
     *
     * @param    fifo_t    :  FIFO
     * @param    data      :  出栈数据
     *
     * @return   0 :成功   1 :失败
     *
     * @note     无
     *
     * @see      fifo_t  tempFifo;
     *           FIFO_TYPE tempData;
     *           fifo_push(tempFifo, 120);       //fifo中压入一个数据
     *           fifo_pop(tempFifo, &tempData);  //fifo中弹出一个数据
     *
     * @date     2020/5/21
     */
    uint8_t fifo_pop(fifo_t *fifo, FIFO_TYPE *dat)
    {
        /* 缓冲区为空 */
        if(fifo->fifoLen == 0)
        {
            return 1;
        }
    
        fifo->fifoLen--;
    
        *dat = fifo->buff[fifo->fifoRead];
    
        if(++fifo->fifoRead >= FIFO_SIZE)
        {
            fifo->fifoRead = 0;
        }
    
        return 0;
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKey
    *	功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_InitKey(void)
    {
    	bsp_InitKeyVar();		/* 初始化按键变量 */
    	bsp_InitKeyHard();		/* 初始化按键硬件 */
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_PutKey
    *	功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
    *	形    参:  _KeyCode : 按键代码
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_PutKey(uint8_t _KeyCode)
    {
    	fifo_push(&s_tKey, _KeyCode);
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_GetKey
    *	功能说明: 从按键FIFO缓冲区读取一个键值。
    *	形    参:  无
    *	返 回 值: 按键代码
    *********************************************************************************************************
    */
    uint8_t bsp_GetKey(void)
    {
    	uint8_t xdata ret;
    
    	if (fifo_pop(&s_tKey, &ret) == 1)
    	{
    		return KEY_NONE;
    	}
    	else
    	{
    		return ret;
    	}
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_GetKeyState
    *	功能说明: 读取按键的状态
    *	形    参:  _ucKeyID : 按键ID,从0开始
    *	返 回 值: 1 表示按下, 0 表示未按下
    *********************************************************************************************************
    */
    uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
    {
    	return s_tBtn[_ucKeyID].State;
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_SetKeyParam
    *	功能说明: 设置按键参数
    *	形    参:_ucKeyID : 按键ID,从0开始
    *			_LongTime : 长按事件时间
    *			 _RepeatSpeed : 连发速度
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed)
    {
    	s_tBtn[_ucKeyID].LongTime = _LongTime;			/* 长按时间 0 表示不检测长按键事件 */
    	s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed;			/* 按键连发的速度,0表示不支持连发 */
    	s_tBtn[_ucKeyID].RepeatCount = 0;						/* 连发计数器 */
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_ClearKey
    *	功能说明: 清空按键FIFO缓冲区
    *	形    参:无
    *	返 回 值: 按键代码
    *********************************************************************************************************
    */
    void bsp_ClearKey(void)
    {
    	s_tKey.fifoRead = s_tKey.fifoWrite;
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKeyHard
    *	功能说明: 配置按键对应的GPIO
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_InitKeyHard(void)
    {
    	//KEY 初始化
        PIN_InitPushPull(C1_GPIO_PORT, C1_PIN);
    	PIN_InitPushPull(C2_GPIO_PORT, C2_PIN);
    	PIN_InitPushPull(C3_GPIO_PORT, C3_PIN);
    	PIN_InitPushPull(C4_GPIO_PORT, C4_PIN);
    	PIN_InitOpenDrain(R1_GPIO_PORT, R1_PIN);
    	PIN_InitOpenDrain(R2_GPIO_PORT, R2_PIN);
    	PIN_InitOpenDrain(R3_GPIO_PORT, R3_PIN);
    	
    }
    
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_InitKeyVar
    *	功能说明: 初始化按键变量
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_InitKeyVar(void)
    {
    	uint8_t xdata i;
    
    	/* 对按键FIFO读写指针清零 */
    	fifo_init(&s_tKey);
    	
    	/* 给每个按键结构体成员变量赋一组缺省值 */
    	for (i = 0; i < KEY_COUNT; i++)
    	{
    		s_tBtn[i].LongTime = KEY_LONG_TIME;			/* 长按时间 0 表示不检测长按键事件 */
    		s_tBtn[i].Count = KEY_FILTER_TIME / 2;		/* 计数器设置为滤波时间的一半 */
    		s_tBtn[i].State = 0;							/* 按键缺省状态,0为未按下 */
    		//s_tBtn[i].KeyCodeDown = 3 * i + 1;				/* 按键按下的键值代码 */
    		//s_tBtn[i].KeyCodeUp   = 3 * i + 2;				/* 按键弹起的键值代码 */
    		//s_tBtn[i].KeyCodeLong = 3 * i + 3;				/* 按键被持续按下的键值代码 */
    		s_tBtn[i].RepeatSpeed = 0;						/* 按键连发的速度,0表示不支持连发 */
    		s_tBtn[i].RepeatCount = 0;						/* 连发计数器 */
    	}
    
    
    	/* 判断按键按下的函数 */
    	s_tBtn[0].IsKeyDownFunc = IsKeyDown0;
    	s_tBtn[1].IsKeyDownFunc = IsKeyDown1;
    	s_tBtn[2].IsKeyDownFunc = IsKeyDown2;
    	s_tBtn[3].IsKeyDownFunc = IsKeyDown3;
    	s_tBtn[4].IsKeyDownFunc = IsKeyDown4;
    	s_tBtn[5].IsKeyDownFunc = IsKeyDown5;
    	s_tBtn[6].IsKeyDownFunc = IsKeyDown6;
    	s_tBtn[7].IsKeyDownFunc = IsKeyDown7;
    	s_tBtn[8].IsKeyDownFunc = IsKeyDown8;
    	s_tBtn[9].IsKeyDownFunc = IsKeyDown9;
    	s_tBtn[10].IsKeyDownFunc = IsKeyDown10;
    	s_tBtn[11].IsKeyDownFunc = IsKeyDown11;
    	
    	/* 组合按键 */
    	s_tBtn[12].IsKeyDownFunc = IsKeyDown12;
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_DetectKey
    *	功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
    *	形    参:  按键结构变量指针
    *	返 回 值: 无
    *********************************************************************************************************
    */
    static void bsp_DetectKey(uint8_t i)
    {
    	KEY_T xdata *pBtn;
    
    	/*
    		如果没有初始化按键函数,则报错
    		if (s_tBtn[i].IsKeyDownFunc == 0)
    		{
    			printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine");
    		}
    	*/
    
    	pBtn = &s_tBtn[i];
    	if (pBtn->IsKeyDownFunc())
    	{
    		if (pBtn->Count < KEY_FILTER_TIME)
    		{
    			pBtn->Count = KEY_FILTER_TIME;
    		}
    		else if(pBtn->Count < 2 * KEY_FILTER_TIME)
    		{
    			pBtn->Count++;
    		}
    		else
    		{
    			if (pBtn->State == 0)
    			{
    				pBtn->State = 1;
    
    				/* 发送按钮按下的消息 */
    				bsp_PutKey((uint8_t)(3 * i + 1));
    				P27 = 1;                   //开启蜂鸣器
    			}
    
    			if (pBtn->LongTime > 0)
    			{
    				if (pBtn->LongCount < pBtn->LongTime)
    				{
    					/* 发送按钮持续按下的消息 */
    					if (++pBtn->LongCount == pBtn->LongTime)
    					{
    						/* 键值放入按键FIFO */
    						bsp_PutKey((uint8_t)(3 * i + 3));
    					}
    				}
    				else
    				{
    					if (pBtn->RepeatSpeed > 0)
    					{
    						if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
    						{
    							pBtn->RepeatCount = 0;
    							/* 常按键后,每隔10ms发送1个按键 */
    							bsp_PutKey((uint8_t)(3 * i + 1));
    						}
    					}
    				}
    			}
    		}
    	}
    	else
    	{
    		if(pBtn->Count > KEY_FILTER_TIME)
    		{
    			pBtn->Count = KEY_FILTER_TIME;
    		}
    		else if(pBtn->Count != 0)
    		{
    			pBtn->Count--;
    		}
    		else
    		{
    			if (pBtn->State == 1)
    			{
    				pBtn->State = 0;
    
    				/* 发送按钮弹起的消息 */
    				bsp_PutKey((uint8_t)(3 * i + 2));
    				P27 = 0;                   //关闭蜂鸣器
    			}
    		}
    
    		pBtn->LongCount = 0;
    		pBtn->RepeatCount = 0;
    	}
    }
    
    /*
    *********************************************************************************************************
    *	函 数 名: bsp_KeyScan
    *	功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用
    *	形    参:  无
    *	返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_KeyScan(void)
    {
    	uint8_t xdata i;
    
    	for (i = 0; i < KEY_COUNT; i++)
    	{
    		bsp_DetectKey(i);
    	}
    }
    
    

    思考

    这个按键底层驱动可以检测到底层按下弹起,是否支持双击?
    双击实现可以基于此底层驱动,不需要修改代码,只需要在修改应用层,比如检测到某按键按下,开启一个软件定时器,在定时器定时结束前,再次检测到按下,则为双击。

    展开全文
  • 一种单片机按键长按短按识别处理方法 本文介绍了一种单片机区别处理按键长按(大于3秒)和短按(小于3秒)的方法,源码来自于他人,仅供学习参考。QQ 2531263726 单片机按键的引脚用WKUP_KD表示,需要事先进行定义;...
  • 单片机按键消抖程序

    万次阅读 2014-09-15 15:41:37
    通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如...
  • 51单片机(AT89C51/STC89C52)的按键输入实验,包含单个按键输入实验、矩阵按键输入实验代码,proteus仿真,精简代码,稳定可靠多种按键输入解决方案
  • c51独立按键

    千次阅读 多人点赞 2018-05-04 00:44:09
    由于单片机检测 IO口速度非常快,超过弹片抖动的频率,所以在检测按键状态时,要消除按键抖动的影响。通常抖动不超过10ms硬件消抖就是在按键上并联一个电容,利用电容的充放电特性来对抖动过...
  • 8.6 单片机按键消抖

    2019-02-18 17:29:06
    通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如...
  • 单片机和嵌入式设计的区别

    万次阅读 2017-01-11 18:44:18
    单片机和嵌入式,其实没有什么标准的定义来区分他们,对于进行过单片机和嵌入式开发的开发者来说,都有他们自己的定义,接下来,就谈谈本人对这两个概念的理解和感悟。 首先明确概念,什么是单片机单片机是一种...
  • 单片机有锁存为啥子还需要while循环简单的说就是:为了保证程序的可靠性,或者叫健壮性;如果我输出了零,那么就一直是零,但是为了保证程序的可靠性,或者叫健壮性,应该重复输出0,使用while循环就是为了保证...
  • /************************************************************************************** ...实现现象:下载程序后按下s4按键可以对流水灯逐个点亮,观察按钮消抖效果。 注意事项:电路板上按钮及LED灯接法区别。...
  • 一个ADC做多个按键扫描检测

    千次阅读 2019-04-09 09:31:02
    独立按键直接用MCU的I/O管脚构成的单个按键电路,其特点式每个按键单独占用一个I/O,每个按键的工作不会影响其他I/O的状态。独立按键很浪费MCU管脚,但编程比较简单。 2、矩阵按键 矩阵键盘又称行列键盘,它是用四...
  • 单片机数字电压表设计

    万次阅读 多人点赞 2018-07-11 17:22:56
    摘要:本设计中利用STC89C52单片机制作...硬件电路主要包括单片机最小系统、显示模块、按键模块、报警模块和AD转换模块、数据存储模块六个部分。软件采用了模块化的设计方法,主要分为主程序、A/D转换子程序、按键检...
  • MCU实战经验:多种的按键处理

    万次阅读 2015-08-25 00:10:08
    之前的一个项目按键比较多,面板上面有按键,遥控器,处理的稍微复杂一点,MCU使用的是STM8S005K6. 关于按键部分的处理,现在拿处理来和大家分享一下,说的不对的地方还请各位大侠请教,大家共同进步。   按键通常...
  • CUIGUI, 为单片机写的GUI 界面, 支持字体,按键,图片,滑动条. 测试环境STM32F103ZE One OS是一个专注于可靠性和简易可用的小型实时系统。它通过采取形式化方法来确保系统的可靠性 ... ......
  • 在这篇文章中,我们以按键作为输入器件对飞思卡尔XEP100单片机的GPIO的输入功能进行测试。对应的硬件电路如下图所示。 当按键未按下时,由于有上拉电阻R39~R312的作用,单片机检测到的电平为高电平;当按键按下...
  • 题目:多功能按键设计。利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按。  ============================================================================  用户基本操作定义:   1...
1 2 3 4 5 ... 20
收藏数 1,317
精华内容 526
关键字:

单片机 可靠 按键