一. 设计思路
按键通常采用机械弹性开关,当机械触点闭合/断开时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定的接通,在断开时也不会马上断开。而是会在闭合/断开的瞬间伴随一连串的抖动,为避免这种现象带来的问题,需要进行按键消抖。
使用状态机来描述按键按下时电路的状态。
按键未被按下时,电路处于IDLE状态;按键按下后,电路检测到按键被按下的状态,即key_in = 0,此时进入滤波状态,开始进行计数。计数过程中,如果检测到key_in = 1,则再次回到IDLE状态,直到再次检测到key_in = 0. 如果在滤波状态FILTER_DOWN中检测到key_in稳定在低电平,则持续进行计数,直到达到我们制定的时间标准,此时说明按键状态稳定,产生一个周期的高电平握手信号,状态机检测到握手信号,进入DOWN状态,输出key_out = 0. 按键松开时的状态转移也是类似的。
二. Verilog代码
本次设计使用三段式状态机,第一段使用时序电路实现状态切换,第二段使用组合电路描述状态转移,第三段使用时序电路描述电路输出,以避免常规的两段式状态机组合电路输出时可能带来的竞争与冒险(虽然这个按键消抖电路不可能产生冒险就是了)。
代码里的end_cnt_down和end_cnt_idle的值是随便设置的,为的是仿真时方便看波形。实际设计中我们应根据电路的时钟频率确定具体的计数数值。当我们的电路使用的时钟频率为50MHz时,由于通常电路产生的抖动为10ms左右,我们就把10ms作为我们计数的目标,即:
assign end_cnt_down = cnt_down == 19'd499_999
在按键消抖模块中,使用输入直接做状态转移的条件可能并非完全正确,实际可以在输入加两级寄存器来消除亚稳态的影响,然后用两级寄存器之间产生的脉冲来做状态转移的条件。但是在我们的设计中,输入信号key_in并不直接作为数据进行转移,因此我觉得直接使用这个信号也不会产生亚稳态,就没有进行修改。
下面来看具体代码。
`timescale 1ns / 1ps
module Debounce
#(
parameter IDLE = 4'b0001,
parameter FILTER_DOWN = 4'b0010,
parameter DOWN = 4'b0100,
parameter FILTER_IDLE = 4'b1000
)
(
//-------------<输入信号>-----------------------------
input clk,
input rst,
input key_in,
//-------------<输出信号>-----------------------------
output reg key_out,
//-------------<用于测试的临时输出信号>-----------------
output current_state,
output cnt_down
);
reg [4 : 0] current_state,
next_state;
reg [3 : 0] cnt_down,
cnt_idle;
wire end_cnt_down, end_cnt_idle;
always@(posedge clk)
begin
if