• 按键消抖——task任务和仿真平台搭建


    一、按键抖动原理

      按键抖动原理:按键存在一个反作用弹簧,因此当按下或者松开时均会产生额外的物理抖动,物理抖动会产生电平的抖动。

       消抖方法:一般情况下,抖动的总时间会持续20ms以内,按下按键后,等20ms过去了再取键值就行了。

     

      市面上有多种按键消抖的方法,我对比了各家的代码,发现有两种方法非常好用,其原理略微的不同。同时将小梅哥FPGA中的task任务和仿真模型的概念一并记录下来。

    二、第1种按键消抖

      只对按下侧的抖动进行消除,弹起的就不管了,因为我们使用按键时要的也是按下后的键值。输出为1clk的按键值。

     1 //======================================================================
     2 // --- 名称 : key_filter
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-11-02
     5 // --- 描述 : 按键消抖,输出为1个clk的输入,只关注按下侧的消抖
     6 //======================================================================
     7 
     8 module key_filter
     9 //---------------------<参数定义>---------------------------------------
    10 #(
    11 parameter   TIME_20MS   = 1000000           , //20ms时间
    12 parameter   TIME_W      = 20                , //20ms时间位宽
    13 parameter   KEY_W       = 4                   //按键个数
    14 )
    15 //---------------------<端口声明>---------------------------------------
    16 (
    17 input                   clk                 , //时钟,50Mhz
    18 input                   rst_n               , //复位,低电平有效
    19 input      [KEY_W-1:0]  key                 , //按键输入
    20 output reg [KEY_W-1:0]  key_vld               //按键消抖后的输出
    21 );
    22 //---------------------<信号定义>---------------------------------------
    23 reg  [TIME_W-1:0]       cnt                 ;
    24 wire                    add_cnt             ;
    25 wire                    end_cnt             ;
    26 reg  [KEY_W -1:0]       key_r0              ;
    27 reg  [KEY_W -1:0]       key_r1              ;
    28 reg                     flag                ;
    29 
    30 //----------------------------------------------------------------------
    31 //--   信号同步 + 消除亚稳态
    32 //----------------------------------------------------------------------
    33 always @(posedge clk or negedge rst_n)begin
    34     if(!rst_n)begin
    35         key_r0 <= 0;
    36         key_r1 <= 0;
    37     end
    38     else begin
    39         key_r0 <= key;      //信号同步
    40         key_r1 <= key_r0;   //打拍,防亚稳态
    41     end
    42 end
    43 
    44 //----------------------------------------------------------------------
    45 //--   20ms计时
    46 //----------------------------------------------------------------------
    47 always @(posedge clk or negedge rst_n)begin
    48     if(!rst_n)
    49         cnt <= 0;
    50     else if(add_cnt)begin
    51         if(end_cnt)
    52             cnt <= 0;
    53         else
    54             cnt <= cnt + 1;
    55     end
    56     else
    57         cnt <= cnt;
    58 end
    59 
    60 assign add_cnt = flag==0 && key_r1!=0 ;        //允许计数 且 按键按下
    61 assign end_cnt = add_cnt && cnt==TIME_20MS-1;  //计到20ms
    62 
    63 //计满指示
    64 always @(posedge clk or negedge rst_n)begin
    65     if(!rst_n)                  //复位
    66         flag <= 0;              //flag=0允许计数
    67     else if(end_cnt)            //20ms到
    68         flag <= 1;              //flag=1不再计数
    69     else if(key_r1==0)          //按键松开
    70         flag <= 0;              //flag=0,为下次计数做准备
    71     else                        //否则
    72         flag <= flag;           //维持自身
    73 end
    74 
    75 //----------------------------------------------------------------------
    76 //--   按键消抖完成,输出按键有效信号
    77 //----------------------------------------------------------------------
    78 always @(posedge clk or negedge rst_n)begin
    79     if(!rst_n)
    80         key_vld <= 0;
    81     else if(end_cnt)        //20ms到
    82         key_vld <= key_r1;  //按键已消抖,可以使用
    83     else
    84         key_vld <= 0;
    85 end
    86 
    87 
    88 endmodule

      现在编写仿真代码。由代码可以看到这里使用了task任务,用其定义一个完整的按下弹起的按键过程。

      task的语法如下:

    task <任务名>;<端口及数据类型声明语句>
         <语句1>
         <语句2>
            .
            .
            .
         <语句n>
    endtask

      task调用的语法如下:

    <任务名> (端口1, 端口2 ... 端口n);

      在task任务中,模拟抖动时采用了随机数发生函数来产生抖动。$random这一系统函数可以产生一个有符号的32位随机整数。一般的用法是“$random%b”,其中b > 0。这样就会生成一个范围在 [-(b-1),b-1] 中的随机数。如果只得到正数的随机数,这可采用“{$random}%b”来产生,这样就会生成一个方位在 [0,b-1] 中的随机数。

     1 `timescale 1ns/1ps  //时间精度
     2 `define    Clock 20 //时钟周期
     3 
     4 module key_filter_tb;
     5 //---------------------<端口声明>---------------------------------------
     6 reg                     clk                 ;
     7 reg                     rst_n               ;
     8 reg  [3:0]              key                 ;
     9 wire [3:0]              key_vld             ;
    10 
    11 //----------------------------------------------------------------------
    12 //--   模块例化
    13 //----------------------------------------------------------------------
    14 key_filter
    15 #(                                              //参数传递
    16     .TIME_20MS          (100                )
    17 )
    18 u_key_filter                                    //模块例化
    19 (
    20     .clk                (clk                ),
    21     .rst_n              (rst_n              ),
    22     .key                (key                ),
    23     .key_vld            (key_vld            )
    24 );
    25 
    26 //----------------------------------------------------------------------
    27 //--   时钟信号和复位信号
    28 //----------------------------------------------------------------------
    29 initial begin
    30     clk = 1;
    31     forever
    32     #(`Clock/2) clk = ~clk;
    33 end
    34 
    35 initial begin
    36     rst_n = 0; #(`Clock*20+1);
    37     rst_n = 1;
    38 end
    39 
    40 //----------------------------------------------------------------------
    41 //--   task函数编写,模拟按键抖动
    42 //----------------------------------------------------------------------
    43 reg  [15:0]             rand                ;
    44 
    45 task press_key;
    46     begin
    47         repeat(50) begin            //50次按下随机时间抖动
    48             rand = {$random}%70;
    49             #rand;
    50             key = ~key;
    51         end
    52         key = 4'b1001;
    53         #10000;
    54 
    55         repeat(50) begin            //50次释放随机时间抖动
    56             rand = {$random}%70;
    57             #rand;
    58             key = ~key;
    59         end
    60         key = 0;
    61         #10000;
    62     end
    63 endtask
    64 
    65 //----------------------------------------------------------------------
    66 //--   设计输入信号
    67 //----------------------------------------------------------------------
    68 initial begin
    69     #1;
    70     key = 0  ; #(`Clock*20+1);  //初始化完成
    71     press_key; #10000;
    72     press_key; #10000;
    73     press_key; #10000;
    74     $stop;
    75 end
    76 
    77 
    78 
    79 endmodule

       Modelsim仿真波形如下所示:

      可以看到按键抖动被非常完美的仿真出来了。但是消抖后的按键值好像有点问题,没有变化一直为0?并非如此!是因为此设计的key_vld只维持1个clk的按键值,我们放大看看,喏,出现了!

    三、第2种按键消抖

      按下和弹起的抖动都消除掉

     1 //======================================================================
     2 // --- 名称 : key_filter
     3 // --- 作者 : xianyu_FPGA
     4 // --- 日期 : 2018-11-02
     5 // --- 描述 : 按键消抖,输出为消抖后的输入,计数器一直在工作
     6 //======================================================================
     7 
     8 module key_filter
     9 //---------------------<参数定义>---------------------------------------
    10 #(
    11 parameter   TIME_20MS   = 1000000           , //20ms时间
    12 parameter   TIME_W      = 20                , //20ms时间位宽
    13 parameter   KEY_W       = 4                   //按键个数
    14 )
    15 //---------------------<端口声明>---------------------------------------
    16 (
    17 input                   clk                 , //时钟,50Mhz
    18 input                   rst_n               , //复位,低电平有效
    19 input      [KEY_W-1:0]  key                 , //按键输入
    20 output reg [KEY_W-1:0]  key_vld               //消抖后的按键输出
    21 );
    22 //---------------------<信号定义>---------------------------------------
    23 reg  [TIME_W-1:0]       cnt                 ;
    24 wire                    add_cnt             ;
    25 wire                    end_cnt             ;
    26 reg  [KEY_W -1:0]       key_r0              ;
    27 reg  [KEY_W -1:0]       key_r1              ;
    28 reg  [KEY_W -1:0]       key_r2              ;
    29 wire                    key_press           ;
    30 
    31 //----------------------------------------------------------------------
    32 //--   边沿检测
    33 //----------------------------------------------------------------------
    34 always @(posedge clk or negedge rst_n)begin
    35     if(!rst_n)begin
    36         key_r0 <= 0;
    37         key_r1 <= 0;
    38         key_r2 <= 0;
    39     end
    40     else begin
    41         key_r0 <= key;      //信号同步
    42         key_r1 <= key_r0;   //打拍,防亚稳态
    43         key_r2 <= key_r1;
    44     end
    45 end
    46 
    47 assign key_press = key_r1 ^ key_r2; //按键状态变化检测
    48 
    49 //----------------------------------------------------------------------
    50 //--   20ms计时
    51 //----------------------------------------------------------------------
    52 always @(posedge clk or negedge rst_n)begin
    53     if(!rst_n)
    54         cnt <= 0;
    55     else if(add_cnt)begin
    56         if(end_cnt)
    57             cnt <= 0;
    58         else
    59             cnt <= cnt + 1;
    60     end
    61     else
    62         cnt <= cnt;
    63 end
    64 
    65 assign add_cnt = 1 ;                                //一直处于计数状态
    66 assign end_cnt = key_press || (cnt== TIME_20MS-1);  //按键仍在抖动或计到了20ms,则清0
    67 
    68 //----------------------------------------------------------------------
    69 //--   按键消抖完成,输出按键有效信号
    70 //----------------------------------------------------------------------
    71 always @(posedge clk or negedge rst_n)begin
    72     if(!rst_n)
    73         key_vld <= 0;
    74     else if(cnt==TIME_20MS-1)   //cnt计到20ms
    75         key_vld <= key_r2;      //按键已消抖,可以使用
    76     else
    77         key_vld <= key_vld;
    78 end
    79 
    80 
    81 endmodule

       现在编写仿真代码,和上面略微不同,我们使用一个新玩意:仿真模型。示意图如下,key_filter_tb是仿真文件,key_module模块是key_filter模块的仿真模型。

      实际代码如下:

     1 `timescale 1ns/1ps  //时间精度
     2 `define    Clock 20 //时钟周期
     3 
     4 module key_filter_tb;
     5 //---------------------<端口声明>---------------------------------------
     6 reg                     clk                 ;
     7 reg                     rst_n               ;
     8 wire [3:0]              key                 ; //本是输入现在变成了内部信号,故改成wire型
     9 wire [3:0]              key_vld             ;
    10 
    11 //----------------------------------------------------------------------
    12 //--   模块例化
    13 //----------------------------------------------------------------------
    14 //按键消抖仿真模型
    15 key_module u_key_module
    16 (
    17     .key                (key                )
    18 );
    19 
    20 //按键消抖设计文件
    21 key_filter
    22 #(                                              //参数传递
    23     .TIME_20MS          (100                )
    24 )
    25 u_key_filter                                    //模块例化
    26 (
    27     .clk                (clk                ),
    28     .rst_n              (rst_n              ),
    29     .key                (key                ),
    30     .key_vld            (key_vld            )
    31 );
    32 
    33 //----------------------------------------------------------------------
    34 //--   时钟信号和复位信号
    35 //----------------------------------------------------------------------
    36 initial begin
    37     clk = 1;
    38     forever
    39     #(`Clock/2) clk = ~clk;
    40 end
    41 
    42 initial begin
    43     rst_n = 0; #(`Clock*20+1);
    44     rst_n = 1;
    45 end
    46 
    47 
    48 endmodule
    key_filter_tb
     1 //======================================================================
     2 //--名称 : key_module
     3 //--作者 : xianyu_FPGA
     4 //--日期 : 2018-11-02
     5 //--描述 : key按键消抖模块的仿真模型
     6 //======================================================================
     7 `timescale 1ns/1ps
     8 
     9 module key_module
    10 //---------------------<端口声明>---------------------------------------
    11 (
    12 output reg [15:0]       key
    13 );
    14 
    15 //----------------------------------------------------------------------
    16 //--   task函数编写,模拟按键抖动
    17 //----------------------------------------------------------------------
    18 reg  [15:0]             rand                ;
    19 
    20 task press_key;
    21     begin
    22         repeat(50) begin            //50次按下随机时间抖动
    23             rand = {$random}%70;
    24             #rand;
    25             key = ~key;
    26         end
    27         key = 4'b1001;
    28         #10000;
    29 
    30         repeat(50) begin            //50次释放随机时间抖动
    31             rand = {$random}%70;
    32             #rand;
    33             key = ~key;
    34         end
    35         key = 0;
    36         #10000;
    37     end
    38 endtask
    39 
    40 //----------------------------------------------------------------------
    41 //--   设计输入信号
    42 //----------------------------------------------------------------------
    43 initial begin
    44     #1;
    45     key = 0  ; #401;  //初始化完成
    46     press_key; #10000;
    47     press_key; #10000;
    48     press_key; #10000;
    49     $stop;
    50 end
    51 
    52 
    53 endmodule
    key_module

      Modelsim仿真波形如下,输出波形刚好是消抖后的按键值,完美!

      这两种按键消抖的本质都是一样的。第一种适用于只采1clk按键值的场景,例如按一下按键,计数器加1一次。第二种按键适用于各种场合,如果也只需要1clk的按键值,则在消抖后、使用前再用一次下降沿检测即可实现和第一种按键消抖一样的效果。

    参考资料:

    [1]小梅哥FPGA教程

    [2]锆石科技FPGA教程

  • 相关阅读:
    网易2019实习生招聘编程第3题——牛牛找工作
    Linux find、locate、whereis、which命令
    Linux 常用命令
    Java线程池
    java连接池的maxIdle该如何配置
    Idea和redis的坑
    微服务架构下分布式事务解决方案——阿里GTS
    spring管理bean的三种创建方式
    jvm内存过高及那些对象导致内存过高,那些对象不会被gc回收
    Java虚拟机 运行时数据区
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/11006276.html
Copyright © 2020-2023  润新知