在平常的按键应用中,会涉及到对按键状态进行消抖的设计。按键的抖动源于按键的硬件结构,在按键的组成中,有一个反作用弹簧,因为有这个弹簧,才能使按键在手松开后自动回弹。但是也因为这个,造成了在按下和弹起时有多余的电平抖动,按键的按下的实际情况如下:
如上图所示,在整个按键过程中,按键从稳定到按下之间,因为反作用弹簧,会有一段时间的抖动现象,但这种抖动现象一般都在1ms以内,在释放过程中的抖动现象也基本能够在1ms内趋于稳定。在这种情况下,则可以将按键的按下过程分为几个状态,通过状态的切换来判断当前按键是否成功按下。
状态切换
如上图的按键按下的实际波形,可将按键分为4个状态,分别为空闲状态(IDLE),抖动滤波状态(FILTER),按下状态(DOWN)和释放状态(FREE),按键的按下依次在这四种状态中切换,状态切换如图所示:
如图所示,在无按下状态时,按键一直为空闲状态,等待按键按下;按键按下之后,状态切换为抖动滤波状态,进行状态滤波,直到时间计数值为1ms之后,按键依旧为低,此时切换为按键按下状态;在按键按下后,采集按键的上升沿,采集到上升沿后进行按键释放滤波,在滤波计数结束后,按键仍旧为高,此时按键释放完毕,状态切换为空闲状态。按键状态再这四个状态中切换,完成按键按下的采样判断。
状态实现
按键边沿获取
在空闲状态下,需要获取按键输入得到异步信号,进行采样后,获取按键的边沿信号进行状态切换的判定,按键的边沿信号获取如下:
reg s0_key_in,s1_key_in;
reg tmp0_key_in,tmp1_key_in;
wire key_rsing,key_falling;
always@(posedge clk50m or negedge rst_n)begin
if(!rst_n)begin
s0_key_in <= 1'b0;
s1_key_in <= 1'b0;
end
else begin
s0_key_in <= key_in;
s1_key_in <= s0_key_in;
end
end
always@(posedge clk50m or negedge rst_n)begin
if(!rst_n)begin
tmp0_key_in <= 1'b0;
tmp1_key_in <= 1'b0;
end
else begin
tmp0_key_in <= s1_key_in;
tmp1_key_in <= tmp0_key_in;
end
end
assign key_rsing = tmp0_key_in & (!tmp1_key_in);
assign key_falling = !tmp0_key_in & tmp1_key_in;
如上所示,首先进行异步信号key_in的时钟域同步,s1_key_in即为异步信号输入后的同步信号,再通过两个寄存器搭建边沿获取电路获取按键边缘。
去抖计数
在按键按下和释放时,需要进行去抖的时间计数,以此来进行按键是否正确按下的判断。使用的输入时钟为50mhz,每一个时钟周期为20ns,因此,1ms的时间需要计数50次,计数代码如下:
reg [5:0]cnt;
reg cnt_en;
reg cnt_full;
always@(posedge clk50m or negedge rst_n)begin
if(!rst_n)
cnt <= 6'd0;
else if(cnt_en)
cnt <= cnt + 1'b1;
else
cnt <= 6'd0;
end
always@(posedge clk50m or negedge rst_n)begin
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt == 6'd50)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
状态切换
根据图2 所示,按键控制的4个状态,当前设计为按键短按,没有按键的长按判断,因此,在按下后不需要进行按下的时间判定,只需在按下又释放之后,输出一个时钟的高电平进行按键按下状态的输出,状态设计如下:
reg [3:0]key_status;
localparam
idle = 4'b0001, //空闲
filter = 4'b0010, //按下消抖
down = 4'b0100, //按键按下
free = 4'b1000; //释放消抖
always@(posedge clk50m or negedge rst_n)begin
if(!rst_n) begin
key_state = 1'b0;
key_status = idle;
cnt_en <= 1'b0;
end
else begin
case(key_status)
idle: begin
key_state <= 1'b0;
if(key_falling)begin
key_status <= filter;
cnt_en <= 1'b1;
end
end
filter: begin
if(cnt_full)begin
cnt_en <= 1'b0;
key_status <= down;
end
else if(key_rsing)begin
cnt_en <= 1'b0;
key_status <= idle;
end
else
key_status <= filter;
end
down: begin
if(key_rsing)begin
cnt_en <= 1'b1;
key_status <= free;
end
else
key_status <= down;
end
free:begin
if(cnt_full)begin
cnt_en <= 0;
key_state <= 1'b1;
key_status <= idle;
end
else if(key_falling)begin
key_status <= down;
end
else begin
key_status <= free;
cnt_en <= 1'b1;
end
end
default:begin
cnt_en <= 1'b0;
key_state <= 1'b0;
key_status <= idle;
end
endcase
end
end
如上的设计中,key_state作为按键状态的输出,在完成一次按键操作后,输出一个时钟的高电平。
仿真测试
在完成应用设计后,创建激励文件进行仿真测试。仿真测试中,采用task语句来模拟按键按下的过程,task语句实现如下:
task press_key1;
begin
repeat(20)begin
my_rand = {$random}%50;
#my_rand key_in1 = ~key_in1;
end
key_in1 = 0;
#1100;
repeat(20)begin
my_rand = {$random}%50;
#my_rand key_in1 = ~key_in1;
end
key_in1 = 1;
#1100;
end
endtask
在设计中,key_in作为按键输入,在空闲状态保持低电平,之后重复20次的随机抖动,抖动完成后保持一段时间的低电平,释放时又重复20次的随机抖动,最后完成释放。激励仿真测试的结果如下:
如图所示,key_in作为按键输入,在完成一次按下操作后,key_state输出一个时钟的高电平用于按键判断。