按键抖动分析
常用的轻触按键内部结构为金属弹片,在手按下、松开的过程中往往会发生细微抖动。
输出的逻辑电平也会发生快速翻转,按键按下和释放的过程中,都会产生抖动,虽然时间非常短暂,但是对于单片机、FPGA这种实时性非常高的系统来说是不可接受的,为了保证系统能正确识别按键的开关,必须对按下和释放的过程进行滤波处理。
按键消抖逻辑
和单片机消抖逻辑类似,FPGA可以通过两个计数器来对持续的高低电平进行计时,当达到一定的时间时,可以认为触点稳定,可以认为是一次有效的按下或释放,从而输出对应的信号。
Verilog设计与仿真
下面是基于Verilog状态机思路,使用两个计数器分别对按下和释放进行滤波处理,理论上按下和释放不会同时出现,所以使用一个计数器也可以实现的,为了便于理解,我们使用两个计数器分别对高低电平进行计时。
按键消抖驱动模块drv_key.v
:
/*********************************************************************
* Copyright(C), 2010-2023, CSDN @ whik1194
* ModuleName : drv_key.v
* Date : 2023年2月20日
* Time : 21:26:01
* Author : https://blog.csdn.net/whik1194
* Function : drv_key
* Version : v1.0
* Version | Modify
* ----------------------------------
* v1.0 .....
*********************************************************************/
module drv_key(
//Inputs
input clk,
input rst_n,
input key,
//Outputs
output flag_press,
output flag_release
);
//parameter
parameter KEY_PRESS = 1'b0; //按键按下有效
parameter FILTER_TIME = 27_000_0; //按键消抖时间
//localparam
localparam KEY_RELEASE = !KEY_PRESS;
localparam S0_IDLE = 0;
localparam S1_KEY_PRESS = 1;
localparam S2_KEY_RELEASE = 2;
//reg
reg [31:0] cnt_press;
reg [31:0] cnt_release;
reg [4:0] key_sreg; //shift reg
reg [3:0] fsm;
reg en;
wire key_is_press = (key_sreg[4:1] == {4{KEY_PRESS}});
wire key_is_release = (key_sreg[4:1] == {4{KEY_RELEASE}});
wire key_is_shake = !(key_is_press || key_is_release);
//assign
assign flag_press = (fsm == S1_KEY_PRESS);
assign flag_release = (fsm == S2_KEY_RELEASE);
//always
//按键边沿捕获移位寄存器
always @ (posedge clk) begin
if(!rst_n) begin
key_sreg <= {5{KEY_RELEASE}};
end
else begin
key_sreg <= key_sreg << 1 | key;
end
end
always @ (posedge clk) begin
if(!rst_n) begin
fsm <= S0_IDLE;
cnt_press <= 0;
cnt_release <= 0;
en <= 0;
end
else begin
case (fsm)
S0_IDLE: begin
if(key_is_shake) begin
cnt_press <= 'd0;
cnt_release <= 'd0;
en <= 1;
end
else if(key_is_press && en) begin
if(cnt_press < FILTER_TIME)
cnt_press <= cnt_press + 1;
else begin
cnt_press <= 0; //max = FILTER_TIME
fsm <= S1_KEY_PRESS;
end
end
else if(key_is_release && en) begin
if(cnt_release < FILTER_TIME)
cnt_release <= cnt_release + 1;
else begin
cnt_release <= 0; //max = FILTER_TIME
fsm <= S2_KEY_RELEASE;
end
end
else begin
fsm <= fsm;
cnt_press <= 0;
cnt_release <= 0;
en <= 0;
end
end
S1_KEY_PRESS: begin
fsm <= S0_IDLE;
en <= 0;
end
S2_KEY_RELEASE: begin
fsm <= S0_IDLE;
en <= 0;
end
default: begin
fsm <= fsm;
end
endcase
end
end
endmodule //get_key end
Modelsim仿真激励模块drv_key_tb.v
/*********************************************************************
* Copyright(C), 2010-2023, CSDN @ whik1194
* ModuleName : drv_key.v
* Date : 2023年2月20日
* Time : 21:26:01
* Author : https://blog.csdn.net/whik1194
* Function : drv_key
* Version : v1.0
* Version | Modify
* ----------------------------------
* v1.0 .....
*********************************************************************/
`timescale 1ns/1ps
module drv_key_tb;
localparam PERIOD = 10; //ns
localparam KEY_PRESS = 1'b0;
localparam KEY_RELEASE = !KEY_PRESS;
localparam FILTER_TIME = 500;
reg clk;
reg rst_n;
reg key;
always #(PERIOD/2) clk <= !clk;
initial begin
$display("testbench: %s", drv_key_tb);
rst_n = 0;
clk = 0;
key = KEY_RELEASE;
#(PERIOD*100)
rst_n = 1;
#(PERIOD*1000)
task_press;
task_release;
task_press;
task_release;
end
drv_key #(
.FILTER_TIME(FILTER_TIME)
)drv_key(
//Inputs
.clk(clk),
.rst_n(rst_n),
.key(key),
//Outputs
.flag_press(),
.flag_release()
);
integer i;
task task_press;
begin
@(posedge clk);
for(i = 0; i <= 10; i = i + 1) begin
key = KEY_PRESS;
#(100*PERIOD);
@(posedge clk);
key = !key;
#(100*PERIOD);
@(posedge clk);
end
key = KEY_PRESS;
#(FILTER_TIME*PERIOD)
#(FILTER_TIME*PERIOD)
@(posedge clk);
end
endtask
task task_release;
begin
@(posedge clk);
for(i = 0; i <= 10; i = i + 1) begin
key = KEY_RELEASE;
#(100*PERIOD);
@(posedge clk);
key = !key;
#(100*PERIOD);
@(posedge clk);
end
key = KEY_RELEASE;
#(FILTER_TIME*PERIOD)
#(FILTER_TIME*PERIOD)
@(posedge clk);
end
endtask
endmodule //drv_key_tb end
Modelsim仿真波形:
实际调用也非常方便,只需要指定按键按下的电平状态和滤波的周期即可:
drv_key #(
.KEY_PRESS(1'b0),
.FILTER_TIME(100_000_000/20) //100ms
)drv_key_ut0(
//Inputs
.clk(gclk), //50MHz=20ns
.rst_n(gresetn),
.key(key1),
//Outputs
.flag_press(flag_press),
.flag_release(flag_release)
);