FPGA培训专家 V3学院
一般情况下,我们从按下按键到松开基本需要大于几十毫秒的时间,系统时钟的周期处于纳秒级,因此我们按下一次按键会被大于十万个时钟的上升沿采集到,然而我们希望的是按下一次按键只被一次上升沿采集到,不然会被认为按了多次按键,所以我们需要对我们的按键进行处理。假设按键在没被按下时为高电平,被按下时处于低电平,如图1所示的波形图。
图1 按键波形图
由图1 分析可知在key被按下时有且仅有一个key的上升沿和一个key的下降沿,我们可以通过检测key的上升沿或者下降沿来确定按键被按下一次,这就涉及到边缘检测,具体方法如图2所示,可以用clk的上升沿将key延时一个周期产生key_reg,通过key和key_reg的值同时判断key的上升沿或下降沿。由图2可知,当用clk的上升沿检测到key等于1的同时key_reg等于0,此时则为key的上升沿,若key等于0的同时key_reg等于1则为key的下降沿。
图2 边缘检测
由图2可知根据key的上升沿或下降沿可确定按了一次键,然而事实却不允许我们这么做。图3给出了一个按键的模型,当不按下按键s时,a、b两点是断开的,按下按键s时线路才接通,然而当按下按键时,会存在物理上的抖动现象,此时a、b两点会在断开和接通之间反复的一段时间,就会出现图4所示的抖动、稳定的波形。
图3 按键模型
图4 按键抖动
图4所示前抖动为按下时产生的抖动,后抖动为按键松开时造成的抖动,前、后抖动持续时间一般均为5~10ms,在有抖动的情况下,key会被clk上升沿采集到很多的上升沿和下降沿,因此用key的上升沿和下降沿判断按键一次就不成立了,我们需要寻找新的方法。
我们知道按键被按下时key值为低电平(0),在抖动期间key既有高电平也有低电平,我们可以使用clk的上升沿计算key连续为低电平的时间,期间当检测到key为高电平时,则从头开始计数,当计数超过5~10ms时,我们可以认定按键有被按下的时候,此时我们可以产生一个clk周期为高电平的标志,当该标志位高电平认为有一次按键即可,具体波形如图5所示。
图5 按键消抖波形图
此处我们认为key值有连续性的5ms时为按键被按下,时钟周期为50MHz,即20ns,可以算出5ms占250000个clk周期,也就是说cnt从0计数到249999为5ms,当cnt等于249999时可以将po_key_flag拉高一个周期,由于按键时间长短无法确定,因此cnt有可能多次达到249999这个值,会造成po_key_flag多次被拉高,这样有会出现按下一次键被当成按下多次,所以在图5中出现了cnt_flag变量,在遇到cnt等于249999时,cnt_flag就会被置高,直到遇到key等于高电平时才会被拉低,这样我们就可以将第一个cnt等于249999和后面的区分开,那么我们也可以用cnt和cnt_flag共同控制在一次按键中,保证po_key_flag有且仅有一个时钟周期的高电平。根据波形图可得到如下所示的代码。
代码示例:
1 //按键消抖模块
2 module key_debounce(
3 input wire sclk ,//系统时钟
4 input wire rst_n ,
5 input wire key ,//按键输入
6
7 output reg po_key_flag //消抖后产生的标志
8 );
9
10 reg[17:0] cnt ;//消抖计数器
11
12 reg cnt_flag ;//计数器标志
13
14 //计数到5ms的最终值
15 parameter CNT_END = 500000-1;
16
17 //产生消抖计数器
18 always@(posedge sclk or negedge rst_n)
19 if(rst_n==1‘b0)
20 cnt <= 18‘b0;
21 else if(key==1‘b0)
22 cnt <= cnt+1‘b1;
23 else
24 cnt <= 18‘b0;
25
26 //产生消抖计数器的标志信号
27 always@(posedge sclk or negedge rst_n)
28 if(rst_n==1‘b0)
29 cnt_flag <= 1‘b0;
30 else if(cnt==CNT_END)
31 cnt_flag <= 1‘b1;
32 else if(key==1‘b1)
33 cnt_flag <= 1‘b0;
34
35 //产生消抖后的po_key_flag
36 always@(posedge sclk or negedge rst_n)
37 if(rst_n==1‘b0)
38 po_key_flag <= 1‘b0;
39 else if(cnt==CNT_END&&cnt_flag==1‘b0)
40 po_key_flag <= 1‘b1;
41 else
42 po_key_flag <= 1‘b0;
43
44 endmodule
代码解析:
① 第18行的always块实现了一个计数器,当key键按下(值为0)的时候开始计数,当检测到按键松开(值为1)的时候计数器归零,这样在抖动时候,该计数器会出现计数、归零循环的一个过程,直到key值一直为0,即按键持续被按下并且抖动过程结束,计数器才会持续计数到5ms;
② 第27行的always块产生一个标志,当检测到cnt第一次等于5ms的时候,该标志则被置为高电平,这样就可以将cnt第一次等于5ms和之后的cnt等于5ms区分开,直到key松开(值为1)时,该标志才被置为低电平;
③ 第30行的always块实现产生整个按键过程中的一个时钟周期高电平的标志,当cnt计数到5ms时,为了确保只有一次的po_key_flag有效,因此此处条件中不仅需要cnt==5ms,而且需要cnt_flag==0。
来自于v3学院 www.v3edu.org