为什么要学习按键消抖
我们经常在生活中接触到机械按键,而当我们按下和松开的时候,机械触点会在闭合和断开会产生一连串的抖动,即可能只按了一下而实际上被检测为按下多次,为了防止产生这种现象我们就需要使用到按键消抖。按键消抖多用于记录按键按下的次数这种情况。
硬件消抖
即通过RS触发器对按键进行消抖,成本较大,所以不常使用
软件消抖
第一种方法
- 第一次按键为低电平了就开始计数,然后延时一段大于30ms 的时间后再检测得到的按键电平就是稳定的按键信号,代码和仿真如下
//rtl code
module key_filter_2(
input sys_clk ,
input sys_rst_n ,
input key_in ,
output reg key_flag
);
//parameter define
parameter CNT_30MS_MAX = 24'd1_500_000 - 1'd1;
//reg define
reg [23:0] cnt_30ms ;
reg [5:0 ] cnt_jitter ;
reg key_in_reg ;
reg cnt_flag ;
//key_in_reg:对key_in延迟一个时钟周期
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_in_reg <= 1'b1;
else
key_in_reg <= key_in;
end
//cnt_jitter:记录抖动的次数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_jitter <= 6'd0;
else if(key_in_reg != key_in )
cnt_jitter <= cnt_jitter + 1'd1;
else if(cnt_30ms == CNT_30MS_MAX)
cnt_jitter <= 6'd0;
else
cnt_jitter <= cnt_jitter;
end
//cnt_flag:当第一次抖动时拉高
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_flag <= 1'b0;
else if(cnt_jitter == 1'd1)
cnt_flag <= 1'b1;
else if(cnt_jitter == 1'd0)
cnt_flag <= 1'b0;
else
cnt_flag <= cnt_flag ;
end
//cnt_30ms:从0计数到30ms的计数器
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_30ms <= 24'd0;
else if(cnt_flag) begin
if(cnt_30ms == CNT_30MS_MAX)
cnt_30ms <= cnt_30ms;
else
cnt_30ms <= cnt_30ms + 1'd1;
end
else
cnt_30ms <= 24'd0;
end
//key_flag:从第一次抖动后30ms输出按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_flag <= 1'b1;
else if((cnt_30ms == CNT_30MS_MAX - 1'd1) && (key_in == 1'b0))
key_flag <= 1'b0;
else
key_flag <= 1'b1;
end
endmodule
//仿真代码
`timescale 1ns/1ns
module tb_key_filter_2();
//parameter define
parameter CNT_30MS_MAX = 24'd70- 1'd1;
parameter CNT_1MS = 20'd19 ,//前抖动
CNT_11MS = 20'd69 ,//前抖动
CNT_41MS = 20'd149 ,//后抖动
CNT_51MS = 20'd199 ,//后抖动
CNT_60MS = 20'd249 ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;
reg [19:0] tb_cnt ; //模拟计数器
//wire define
wire key_flag ;
initial begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk ;
//tb_cnt:通过计数器来确定按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tb_cnt <= 20'd0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 20'd0;
else
tb_cnt <= tb_cnt + 1'd1;
end
//key_in:模拟输入的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
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;
end
key_filter_2 #(
.CNT_30MS_MAX(CNT_30MS_MAX)
)
u_key_filter_2(
.sys_clk (sys_clk) ,
.sys_rst_n (sys_rst_n) ,
.key_in (key_in) ,
.key_flag (key_flag)
);
endmodule
- 仿真波形如下图 (可放大查看)
- 第一种方法思路非常简单,就是考虑最差情况,按照最大抖动时间10ms再加上按键稳定时间20ms,所以并不是最优的解决方案。
第二种方法
如图所示,第二种方法是对输入按键进行检测,当按键稳定达到20ms时且按键为按下时输出一个高电平,这样和方法一比起来效率要高。代码和仿真如下
//rtl code
module key_filter(
input sys_clk ,
input sys_rst_n ,
input key_in ,
output reg key_flag
);
parameter CNT_MAX = 20'd999_999;
//reg define
reg [19:0] cnt_20ms;
//cnt_20ms:当按键稳定开始计数999_999次
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_20ms <= 20'd0;
else if(key_in == 1'b0) begin //判断按键是否按下,若是抖动状态就会进入下一个else对计数器进行清零
if(cnt_20ms == CNT_MAX)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'd1;
end
else
cnt_20ms <= 20'd0;
end
//key_flag:输出过滤后的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'd1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
end
endmodule
//仿真代码
`timescale 1ns/1ns
module key_filter_tb();
//parameter define
parameter CNT_1MS = 20'd19 ,//前抖动
CNT_11MS = 20'd69 ,//前抖动
CNT_41MS = 20'd149 ,//后抖动
CNT_51MS = 20'd199 ,//后抖动
CNT_60MS = 20'd249 ;
//wire define
wire key_flag ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;
reg [19:0] tb_cnt ; //模拟计数器
// main code
//初始化输入时钟和复位信号
initial begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:50mhz的时钟
always #10 sys_clk = ~sys_clk;
//tb_cnt:通过计数器来确定按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
tb_cnt <= 20'd0;
else if(tb_cnt == CNT_60MS)
tb_cnt <= 20'd0;
else
tb_cnt <= tb_cnt + 1'd1;
end
//key_in:模拟输入的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
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;
end
//instantiation
key_filter
#(.CNT_MAX(20'd24) //修改的值一定要小于(CNT_41MS - CNT_11MS)
)
u_key_filter(
. 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
仿真波形如下
补充
这个代码是基于正点原子的开拓者开发板编写的,按键未按下时为高电平,按下为低电平。其他开发板上的按键情况可能与之相反,只需修改一小部分的代码就可以使用。