按键消抖——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教程

posted @ 2018-12-05 22:44  咸鱼IC  阅读(2167)  评论(0编辑  收藏  举报