上面的时序图 大致描述了按键消抖的主要思路:由key_r1和key_r2形成两个边沿触发信号上升沿(cnt_pos)和 下降沿信号(cnt_neg),计数器cnt根据cnt_pos和cnt_neg来判断开始计数和终止计数。当计数器记满20ms后产生key_flag信号,并且key_state跳转低电平,整个前抖动滤除动作完成,后抖动同样道理。
边沿检测代码
以下降沿为例:assign key_neg = ~r1_key & r2_key;就是将r1_key取反这样就会和r2_key‘与’出key_neg ,即检测到下降沿。上升沿同理。
代码
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
r1_key <= 1'b0;
r2_key <= 1'b0;
end
else begin
r1_key<= key_in;
r2_key <= r1_key;
end
assign nedge = !r1_key && r2_key ;
assign pedge = r1_key && (!r2_key );
消除亚稳态
由于按键信号与系统时钟为异步信号 故在使用时需要将其同步。即利用D触发器的延迟性进行延迟两拍以下是相应代码,与边沿检测代码大致相似。
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
key_in_s0 <= 1'b0;
key_in_s1 <= 1'b0;
end
else begin
key_in_s0 <= key_in;
key_in_s1 <= key_in_s0;
end
即key_in_s1就是消除亚稳态后的输入信号。
状态机
代码
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
state <= idle;
cnt_en <= 1'b0;
key_state <= 1'b1;
key_flag <= 1'b0;//稳定标志 通知外部读取key_state
end
else begin
case (state)
idle:
begin
key_flag <= 1'b0;
if(nedge)begin
state <= filter0;
cnt_en <= 1'b1;
end
else
state <= idle;
end
filter0:
if(cnt_full)begin
key_flag <= 1'b1;
state <= down;
cnt_en <= 1'b0;
key_state <= 1'b0;
end
else if (pedge)begin
state <= idle;
cnt_en <= 1'b0;
end
else
state <= filter0;
down:
begin
key_flag <= 1'b0;
if(pedge)begin
state <= filter1;
cnt_en <= 1'b1;
end
else
state <= down;
end
filter1:
if(cnt_full)begin
state <= idle;
key_state <= 1'b1;
key_flag <= 1'b1;
end
else if (nedge)begin
state <= down;//idle
cnt_en <= 1'b0;
end
else
state <= filter1;
default:
begin
state <=idle;
key_flag <= 1'b0;
key_state <= 1'b1;
cnt_en <= 1'b0;
end
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt <= 20'd0;
else if(cnt_en == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
testbench
`timescale 1ns/1ns
module key_filter_tb();
reg clk;
reg rst_n;
reg key_in;
wire key_flag;
wire key_state;
//wire key_state;
parameter clock = 20;
key_filter u_key_filter(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
key_model key_model(.key(key_in));
initial clk = 0;
always #(clock/2) clk = ~clk;
rst_n =1'b0;
key_in = 1'b1;
#(3*clock + 5)
rst_n = 1'b1;
press_key;
$stop();
end
integer i;
task press_key;
begin
repeat(15)begin
i = {$random}%10_0000;
#(i*clock )
key_in = ~key_in;
end
key_in = 1'b0;
#(110_0000*clock)
repeat(26)begin
i = {$random} % 10_0000;
#(i*clock)
key_in = ~key_in;
end
key_in = 1'b1;
#(110_0000*clock);
end
endtask
endmodule
效果