关于按键消抖
按键消抖的作用还有原理就不科普了,网上解释和实现方案也挺多的,但在我玩FPGA时,我会遇到三种按键的需求,以点灯为例:a)是按下灯亮,松开灯灭;b)是按下松开,灯亮,再按下松开,灯灭(借鉴电气电路中的名词,我把这种方案称为“自锁”);c)每次按键发出一个时钟周期的脉冲,比如在设置时钟时会使用这种方案。
网上给的示例不全,到处找比较麻烦。另外有些通过状态机实现的方案都是一段式状态机,代码质量不高,所以我自己把这三种方案都写了一遍。
- 以下方案均采用三段式状态机实现,初学verilog的也可以把这几段代码作为三段式状态机的一个示例;
- 以下方案均通过参数化配置设计为可根据不同时钟频率复用的形式,我是以50MHz的频率为上限设计的,如果需要用更高的频率请修改20ms计数器的位宽;
- 以下方案均已在FPGA上验证通过,在这里就不放testbench文件和仿真波形了。
2022.11更新:
这篇文章还是刚转到数字IC方向时写的,今天回过头来看,对原来三个按键消抖的代码也不是很满意,又重写了一遍。
直接从 VS code 粘贴过来,许多缩进有问题,就不改了。
不带自锁的方案
/****************************************************************************
* A basic key filter.
*
* Here the key is active-low. When key is pressed, output high.
*****************************************************************************/
module keyFilter #(
parameter SYS_CLK = 50_000_000
)(
input clk ,
input rstn,
input ikey,
output reg okey
);
// localparam CNT_MAX = SYS_CLK / 50;
/* debug */
localparam CNT_MAX = 50;
reg [25:0] cnt ;
/* edge detect */
reg key_in_r;
wire key_pos, key_neg;
always @(posedge clk or negedge rstn) begin
if (~rstn) begin
key_in_r <= 1'b1;
end else begin
key_in_r <= ikey;
end
end
assign key_pos = (ikey & ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey & key_in_r) ? 1'b1 : 1'b0;
/* FSM */
reg [3:0] nstate, cstate;
localparam IDLE = 4'b0001,
SAMPLE = 4'b0010,
PRESS = 4'b0100,
LOOSE = 4'b1000;
always @(posedge clk or negedge rstn) begin
if (~rstn)
cstate <= IDLE;
else
cstate <= nstate;
end
always @(*) begin
nstate = cstate;
case (cstate)
IDLE :
if (key_neg)
nstate = SAMPLE;
SAMPLE:
begin
if (okey == 1'b0 && key_pos) begin
nstate = IDLE;
end else if (okey == 1'b1 && key_neg) begin
nstate = PRESS;
end else if (cnt == CNT_MAX - 1 && okey == 1'b0) begin
nstate = PRESS;
end else if (cnt == CNT_MAX - 1 && okey == 1'b1)
nstate = IDLE;
end
PRESS:
if (key_pos)
nstate = SAMPLE;
default: nstate = cstate;
endcase
end
always @(posedge clk or negedge rstn) begin
if(~rstn)
okey <= 1'b0;
else begin
case (nstate)
IDLE :
okey <= 1'b0;
SAMPLE:
okey <= okey;
PRESS:
okey <= 1'b1;
default:
okey <= okey;
endcase
end
end
/* 20ms counter */
always @(posedge clk or negedge rstn) begin
if (~rstn)
cnt <= 26'd0;
else if (nstate == SAMPLE)
cnt <= cnt + 1'b1;
else
cnt <= 26'd0;
end
endmodule
带自锁的方案
实际上和不带自锁的方案只有一点点改动。
/****************************************************************************
* Change to the opposite side and hold after pressing the key,
* behavouring like a self-lock button.
*
* Here the key is active-low. When key is pressed, output high.
*****************************************************************************/
module keyHold #(
parameter SYS_CLK = 50_000_000
)(
input clk ,
input rstn,
input ikey,
output reg okey
);
localparam CNT_MAX = SYS_CLK / 50;
/* debug */
// localparam CNT_MAX = 50;
reg [25:0] cnt ;
/* edge detect */
reg key_in_r;
wire key_pos, key_neg;
always @(posedge clk or negedge rstn) begin
if (~rstn) begin
key_in_r <= 1'b1;
end else begin
key_in_r <= ikey;
end
end
assign key_pos = (ikey & ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey & key_in_r) ? 1'b1 : 1'b0;
/* FSM */
reg [3:0] nstate, cstate;
localparam IDLE = 4'b0001,
SAMPLE = 4'b0010,
PRESS = 4'b0100,
LOOSE = 4'b1000;
always @(posedge clk or negedge rstn) begin
if (~rstn)
cstate <= IDLE;
else
cstate <= nstate;
end
always @(*) begin
nstate = cstate;
case (cstate)
IDLE :
if (key_neg)
nstate = SAMPLE;
SAMPLE:
begin
if (key_pos) begin
if (okey == 1'b0)
nstate = LOOSE;
else
nstate = PRESS;
end else if (cnt == CNT_MAX - 1 && okey == 1'b0)
nstate = PRESS;
else if (cnt == CNT_MAX - 1 && okey == 1'b1)
nstate = LOOSE;
end
PRESS:
if (key_neg)
nstate = SAMPLE;
LOOSE:
if (key_neg)
nstate = SAMPLE;
default: nstate = cstate;
endcase
end
always @(posedge clk or negedge rstn) begin
if(~rstn)
okey <= 1'b0;
else begin
case (nstate)
IDLE :
okey <= 1'b0;
SAMPLE:
okey <= okey;
PRESS:
okey <= 1'b1;
LOOSE:
okey <= 1'b0;
default:
okey <= okey;
endcase
end
end
/* 20ms counter */
always @(posedge clk or negedge rstn) begin
if (~rstn)
cnt <= 26'd0;
else if (nstate == SAMPLE)
cnt <= cnt + 1'b1;
else
cnt <= 26'd0;
end
endmodule
输出一个时钟周期的方案
/******************************************************************************
* Output a pulse everytime pressing the key.
*******************************************************************************/
module keyPulse #(
parameter SYS_CLK = 50_000_000
)(
input clk ,
input rstn,
input ikey,
output reg okey
);
localparam CNT_MAX = SYS_CLK / 50;
/* debug */
// localparam CNT_MAX = 50;
reg [25:0] cnt_20ms ;
/* detect edge */
reg key_in_r;
wire key_pos, key_neg;
always @(posedge clk or negedge rstn) begin
if (~rstn) begin
key_in_r <= 1'b1;
end else begin
key_in_r <= ikey;
end
end
assign key_pos = (ikey && ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey && key_in_r) ? 1'b1 : 1'b0;
/* FSM */
reg [2:0] nstate, cstate;
localparam IDLE = 3'b001,
SAMPLE = 3'b010,
CONFIRM = 3'b100;
always @(posedge clk or negedge rstn) begin
if (~rstn)
cstate <= IDLE;
else
cstate <= nstate;
end
always @(*) begin
nstate = cstate;
case (cstate)
IDLE:
if (key_neg)
nstate = SAMPLE;
SAMPLE:
begin
if (key_pos)
nstate = IDLE;
else if (cnt_20ms == CNT_MAX - 1)
nstate = CONFIRM;
end
CONFIRM:
nstate = IDLE;
default: nstate = cstate;
endcase
end
always @(posedge clk or negedge rstn) begin
if(~rstn)
okey <= 1'b0;
else begin
case (nstate)
IDLE:
okey <= 1'b0;
SAMPLE:
okey <= 1'b0;
CONFIRM:
okey <= 1'b1;
default:
okey <= okey;
endcase
end
end
/* cnt_20ms */
always @(posedge clk or negedge rstn) begin
if (~rstn)
cnt_20ms <= 26'd0;
else if (nstate == SAMPLE)
cnt_20ms <= cnt_20ms + 1'b1;
else
cnt_20ms <= 26'd0;
end
endmodule