按键是FPGA开发板上的重要交互元件,因为按键的内部的结构设计,在按下和松开按键时,按键会无法避免地产生机械抖动,因此要对按键输入进行特殊处理,否则可能会因为机械抖动产生意外的重复触发。
按键消抖有很多方法,可以在按键后使用低通滤波器和缓冲器,也可以使用锁存器来保存按键的稳定值(如下图所示)。
本文将使用硬件编程的方式来完成消抖,使用一个计时器来保存按键稳定的时间,当发现按键不稳定时,则清空计时器重新开始,这样一来当按键稳定时间达到设定值(例如10ms)后,就会输出无抖动的按键值。
另外需要注意的是,由于按键信号属于单比特异步信号,在进入时钟域前需要使用两级同步器来避免亚稳态,关于信号同步,可以阅读这篇文章数字IC前端学习笔记:跨时钟域信号同步。
module Debounce
(
input clk,
input rst,
input button_in,
output reg button_out
);
parameter N = 32 ; // 计数器位宽
parameter FREQ = 50; // 系统时钟频率 单位MHz
parameter MAX_TIME = 20; // 设定时长,单位毫秒
localparam TIMER_MAX_VAL = MAX_TIME * 1000 * FREQ; //计数值
reg [N-1 : 0] q_reg; // 计时寄存器
reg [N-1 : 0] q_next; // 下一状态计时组合逻辑
reg DFF1, DFF2; // 两级缓冲器
wire q_add; // 计数使能信号
wire q_reset; // 计数复位信号
assign q_reset = (DFF1 ^ DFF2); // 复位信号产生逻辑
assign q_add = ~(q_reg == TIMER_MAX_VAL); // 使能信号产生逻辑
//计数器组合逻辑
always @ (q_reset, q_add, q_reg)
begin
case({q_reset , q_add})
2'b00 :
q_next = q_reg;
2'b01 :
q_next = q_reg + 1;
default :
q_next = {N{1'b0}};
endcase
end
//同步器逻辑
always @ (posedge clk or negedge rst)
begin
if(!rst)
begin
DFF1 <= 1'b0;
DFF2 <= 1'b0;
end
else
begin
DFF1 <= button_in;
DFF2 <= DFF1;
end
end
//计数器时序逻辑
always @ (posedge clk or negedge rst)
begin
if(!rst)
q_reg <= {N{1'b0}};
else
q_reg <= q_next;
end
//输出逻辑
always @ (posedge clk or negedge rst)
begin
if(!rst)
button_out <= 1'b1;
else if(q_reg == TIMER_MAX_VAL)
button_out <= DFF2;
else
button_out <= button_out;
end
当两次采集到的按键值不同时,q_reset为1,在下一个时钟沿时,计数器会被清零,TIMER_MAX_VAL中保存了特定计数时长所需的计数值。当q_reset为0,且计数值未到设定值时,q_add为1,在下一个时钟沿时,计数器加一。
图源《Verilog HDL高级数字设计》