设计规划
开发板上使用的机械按键在闭合及断开的瞬间均伴随有一连串的抖动,按键抖动会引起一次按键被误读多次,需要进行消抖处理:在按键闭合稳定时读取按键的状态,并且必须判别到按键释放稳定后再作处理 。
如果按键个数少,可以用硬件消抖,按键多时需要用软件消抖:检测出按键闭合后执行一个 20ms的延时程序 (抖动的时间为5ms~10ms)再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。硬件消抖需要有额外的电路,软件消抖没有这种顾虑。
先用波形图模拟出按键被按下和释放时抖动的毛刺状态:
计数器一节中已经介绍过计数的实现方法,这里20ms的延迟需要一个20ms的计数器cnt_20ms。系统检测到按键输入为低电平就开始计数,如果20ms(50MHz的晶振需要计数个数为999_999)内检测出高电平说明是一个抖动,计数器清零。计数器计满999_999之后,key_flag拉高,一个时钟周期后变低,因此key_flag是脉冲信号。计数器计满后的状态至关重要,如果清零,当输入是低电平的时间过长就会造成一次按键却有多个key_flag脉冲的情况。
如果计满后不清零,到kin_in为高电平时再清零也会有新的问题,就是key_flag维持高电平时间太长,不再是一个脉冲信号。
此时如果令计数器记到999_998,就可以达到预期的效果。
编写代码
module key_filter
#(
parameter CNT_MAX = 20'd999_999
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output reg key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//reg define
reg [19:0] cnt_20ms ; //计数器
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
首先定义了参数CNT_MAX是计数器的最大值,然后定义了输入输出和计数器。2^19<999999<2^20,因此需要20位的计数器。然后,还是要分析两个信号的变化,一个是计数器的状态,一个是标志位的状态。他们变化的条件都是时钟上升沿和复位有效(下降沿)。
cnt_20ms的变化是,如果复位有效就清零;如果检测到输入为高电平就清零;如果计满且输入为低电平就保持;如果没计满就+1。
key_flag的变化是,复位有效就清零,计数到999999-1就拉高,其他时候都为0。
编写testbench
`timescale 1ns/1ns
module tb_key_filter();
parameter CNT_1MS = 20'd19 ,
CNT_11MS = 21'd69 ,
CNT_41MS = 22'd149 ,
CNT_51MS = 22'd199 ,
CNT_60MS = 22'd249 ;
wire key_flag ;
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;
reg [21:0] tb_cnt ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key_in <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
//tb_cnt:按键过程计数器,通过该计数器的计数时间来模拟按键的抖动过程
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tb_cnt <= 22'b0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 22'b0;
else
tb_cnt <= tb_cnt + 1'b1;
//key_in:产生输入随机数,模拟按键的输入情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_in <= 1'b1; //按键未按下时的状态为为高电平
else if((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)
||(tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS))
key_in <= {$random} % 2; //随机数模拟抖动
else if(tb_cnt >= CNT_11MS && tb_cnt <= CNT_41MS)
key_in <= 1'b0;
else
key_in <= 1'b1;
//------------------------key_filter_inst------------------------
key_filter
#(
.CNT_MAX (20'd24 )
)
key_filter_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.key_in (key_in ), //input key_in
.key_flag (key_flag ) //output key_flag
);
endmodule