引出抖动问题
下图为按钮的抽象物理模型
如图所示,按钮的一端与FPGA芯片的管脚相连,而另一端接地。显然
- 当按钮没有被按下时,FPGA的管脚为高电平
- 当按钮被按下时,FPGA的管脚直接与地相连,为低电平
此时,理想中按下按键的波形如下图
但是,理想很美好现实很骨感,通过示波器实际看到的波形可能是这样的
从按键按下到低电平稳定,或者从按键释放到高电平稳定中间产生的电平上下浮动,称为抖动。
通过测量,抖动的时间不会超过20ms,但是FPGA的系统时钟通常为50MHz,也就是20ns,以20ns的周期来采样管脚上的电平状态。
20ns远小于20ms,由于抖动的存在,FPGA会认为按钮多次按下,而且按下的次数是不可预期的,所以对按键的消抖是很有必要的。
使用状态机解决问题
在解决问题之前,我们先来考虑该模块的输入与输出
- 作为时序逻辑的设计模块,时钟与复位的输入必不可少
- 该模块对按键进行消抖,所以按键的输入必不可收
输出的话,考虑消抖之后的电平,以及按键使能,如下图
模块抽象图:
module key_filter(clk,rst_n,key_in,key_flag,key_state);
input clk;
input rst_n;
input key_in;
output reg key_flag;
output reg ket_state;
endmodule
我们可以将整个波形分为以下四种状态
-
空闲状态(IDEL):表示按键未被按下或释放按键后稳定的状态
-
按下抖动状态(FILTER1):表示按键被按下后到低电平稳定之间的状态
-
稳定状态(DOWN):表示按键被按下抖动之后低电平稳定的状态
-
按下抖动状态(FILTER2):表示按键被释放后到高电平稳定之间的状态
localparam
IDEL = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
reg [3:0] state;
在抖动状态下,我们需要检测上升沿或下降沿来使状态发生跳转,此时我们可以使用边缘检测电路。
边缘检测的原理是在每一个时钟上升沿到来时,检测一次管脚的电平,并且使用两个寄存器寄存此时的电平状态,先使用一个寄存器reg1寄存此时的电平状态,再使用一个寄存器reg2来寄存reg1的状态。
我们可以通过前后两个时钟上升沿到来时,电平的状态来判断此时是上升沿或是下降沿,电路图如下
reg1=0 reg2=1
时,可以判断为上升沿,所以reg1
取反和reg2
相与得1
reg1=1 reg2=0
时,可以判断为下降沿,所以reg1
和reg2
取反相与得1
reg key_tmp1, key_tmp2;
wire pedge, nedge;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
key_tmp1 <= 1'b0;
key_tmp2 <= 1'b0;
end
else begin
key_tmp1 <= key_in;
key_tmp2 <= key_tmp1;
end
end
assign pedge = (~key_tmp0) & key_tmp1;
assign nedge = key_tmp0 & (~key_tmp1);
前面说到,根据示波器实际测量抖动状态一般不会超过20ms。
当状态处于抖动时,为了稳妥起见,20ms之后检测到下降沿才能将状态转换为稳定状态。
因此,我们需要一个计数电路,计数次数为20ms / 20ns = 1_000_000
次
reg [19:0] cnt;
reg cnt_en;
always @(posedge clk or negedge rst_n) begin
if(~rst) begin
cnt <= 20'd0;
end
else if(en_cnt) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 20'd0;
end
end
reg cnt_full;
always @(posedge clk or negedge rst_n) begin
if(~rst) begin
cnt_full <= 1'b0;
end
else if(cnt == 20'd999_999) begin
cnt_full <= 1'b1;
end
else begin
cnt_full <= 1'b0;
end
end
下面分析一下各状态之间的跳转条件
-
IDEL
该状态本身就处于高电平,所以不会有遇到上升沿的情况,只要不遇到下降沿状态就一直保持IDEL
当遇到下降沿时,状态跳转到FILTER1,同时计数使能,处于按下抖动状态。
-
FILTER1
该状态下需要根据是否计满数和是否有上升沿来做出不同的反应
当计满数时,表示抖动状态已过,状态跳转至DOWN,并且计数不使能,key_flag为高电平,key_state为低电平
当上升沿到来时,表示依旧有抖动存在,状态跳转会IDEL,并且计数不使能
而两者情况都不存在时,状态保持在FILTER1
-
DOWN
该状态与IDEL类似,但是由于是从FILTER1跳转而来,所以需要将key_flag置为低电平
该状态本身就处于低电平,所以不会有遇到下降沿的情况,只要不遇到上升沿状态就一直保持DOWN
当遇到上升沿时,状态跳转到FILTER2,同时计数使能,处于释放抖动状态。
-
FILTER2
该状态与FILTER1类似
当计满数时,表示抖动状态已过,状态跳转至IDEL,并且计数不使能,key_state为高电平
当下降沿到来时,表示依旧有抖动存在,状态跳转会DOWN,并且计数不使能
而两者情况都不存在时,状态保持在FILTER2
当然,这里的状态只有四种,而使用四位的格雷码还有另外12种状态,所以对于处于其他状态的情况下,将信号重置,并且状态跳转为IDEL
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state <= IDEL;
cnt_en <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDEL: begin
if(nedge) begin
cnt_en <= 1'b1;
state <= FILTER1;
end
else begin
state <= IDEL;
end
end
FILTER1: begin
if(cnt_full) begin
cnt_en <= 1'b0;
state <= DOWN;
key_flag <= 1'b1;
key_state <= 1'b0;
end
else if(pedge) begin
cnt_en <= 1'b0;
state <= IDEL;
end
else begin
state <= FILTER1;
end
end
DOWN: begin
key_flag <= 1'b0;
if(pedge) begin
cnt_en <= 1'b1;
state <= FILTER2;
end
else begin
state <= DOWN;
end
end
FILTER2: begin
if(cnt_full) begin
cnt_en <= 1'b0;
state <= IDEL;
key_state <= 1'b1;
end
else if(nedge) begin
cnt_en <= 1'b0;
state <= DOWN;
end
else begin
state <= FILTER2;
end
end
default: begin
cnt_en <= 1'b0;
state <= IDEL;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end