我们在互联网上经常会看到这种按键防抖的Verilog设计,那就是大概每20ms读取一次开关,所谓的公认按键时间小于20ms[2]。
事实上,不同的按键,其抖动时间是不一样的,参考资料:debouncing[3]中,作者测试了不同的按键的抖动时间:
不同的按键
抖动时间
用于打开和关闭每个开关(数字A至R)的抖动时间(以微秒为单位)。开关E被排除在外,因为其157毫秒的跳动会严重扭曲图形。
有兴趣的可以看下!
一种常见的按键的电路如下:
一种按键的设计电路
按键未按下键值信号为高,按下为低,物理按键都存在着时间或短或长的按键抖动!如下图所示:
按键按下以及松开波形图
如上面所说,按键抖动一般公认为20ms,如果从软件或者说逻辑设计的方式去消除抖动,就是先检测到按键信号的边沿,之后每计数20ms采样一次键值!这样就实现了按键消抖的目的!
一个按键的消抖设计
先从一个按键为例:
如下图为设计框图:
一个按键
设计文件:
`timescale 1ns / 1ps
/////////////////////////////////////////////
// Engineer: Reborn Lee
// Module Name: debounce_1b
////////////////////////////////////////////
module debounce_1b(
input clk, //50MHz
input rst,
input sw_in_n,
output reg sw_out_n
);
reg sw_mid_r1, sw_mid_r2, sw_valid;
always@(posedge clk or posedge rst) begin
if(rst) begin
sw_mid_r1 <= 1; // synchronize 1 clock
sw_mid_r2 <= 1; // delay 1 clock
sw_valid <= 0; // gen negedge
end
else begin
sw_mid_r1 <= sw_in_n;
sw_mid_r2 <= sw_mid_r1;
sw_valid <= sw_mid_r2 & (~sw_mid_r1);
end
end
reg [19:0] key_cnt;
always@(posedge clk or posedge rst) begin
if(rst) begin
key_cnt <= 0;
end
else if(sw_valid) begin
key_cnt <= 0;
end
else begin
key_cnt <= key_cnt + 1; //20ms
end
end
always@(posedge clk or posedge rst) begin
if(rst) begin
sw_out_n <= 1;
end
else if(key_cnt == 20'hfffff) begin
sw_out_n <= sw_in_n;
end
end
endmodule
很容易理解,就是先检测到键值有没有变化,检测键值的下降沿,如果检测到了下降沿则对计数器清零,否则计数,计数到20ms(20'dfffff),采样一次键值作为输出。
下面自己设计抖动来测试下这个设计。
测试文件
`timescale 1ns / 1ps
module debounce1_tb(
);
reg clk;
reg rst;
reg sw_in_n;
wire sw_out_n;
initial begin
clk = 0;
forever begin
#5 clk = ~clk;
end
end
initial begin
rst = 1;
sw_in_n = 1;
#50
rst = 0;
@(negedge clk)
sw_in_n = 0;
repeat(3) @(negedge clk);
sw_in_n = 1;
repeat(20000) begin
repeat(5) @(negedge clk);
sw_in_n = 0;
repeat(2) @(negedge clk);
sw_in_n = 1;
repeat(3) @(negedge clk);
sw_in_n = 0;
end
repeat(3000000) begin
@(negedge clk);
end
sw_in_n = 1;
end
debounce_1b inst0(
.clk(clk),
.rst(rst),
.sw_in_n(sw_in_n),
.sw_out_n(sw_out_n)
);
endmodule
仿真波形为:
模拟抖动
采样键值
设计的RTL原理图:
设计RTL原理图
不得不说明的是,资料[4]给出的除抖动原理图:
去抖动原理图
其实就是简化了上述设计的RTL图,我觉得比第一个设计要精妙一些。
这个版本设计没有检测按键的下降沿,而是直接对键值同步两拍,检测这两拍数据的异或,如果异或值为1,则代表键值变化了,这时计数值清零;否则计数,直到计数到规定值时采样键值数据。
下面给出VHDL设计,很简单,这里没必要转换成verilog了(如果实在需要,关注我微信公众号 FPGA LAB,后台给我消息,我看人多了之后抽空转下):
----------------------------------------------------------------
-- FileName: debounce.vhd
-- Version History
-- Version 1.0 3/26/2012 Scott Larson
----------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
ENTITY debounce IS
GENERIC(
counter_size : INTEGER := 19); --counter size (19 bits gives 10.5ms with 50MHz clock)
PORT(
clk : IN STD_LOGIC; --input clock
button : IN STD_LOGIC; --input signal to be debounced
result : OUT STD_LOGIC); --debounced signal
END debounce;
ARCHITECTURE logic OF debounce IS
SIGNAL flipflops : STD_LOGIC_VECTOR(1 DOWNTO 0); --input flip flops
SIGNAL counter_set : STD_LOGIC; --sync reset to zero
SIGNAL counter_out : STD_LOGIC_VECTOR(counter_size DOWNTO 0) := (OTHERS => '0'); --counter output
BEGIN
counter_set <= flipflops(0) xor flipflops(1); --determine when to start/reset counter
PROCESS(clk)
BEGIN
IF(clk'EVENT and clk = '1') THEN
flipflops(0) <= button;
flipflops(1) <= flipflops(0);
If(counter_set = '1') THEN --reset counter because input is changing
counter_out <= (OTHERS => '0');
ELSIF(counter_out(counter_size) = '0') THEN --stable input time is not yet met
counter_out <= counter_out + 1;
ELSE --stable input time is met
result <= flipflops(1);
END IF;
END IF;
END PROCESS;
END logic;
还有上一个版本的改进版:
去抖动原理图版本2.0
LIBRARY ieee;
USE ieee.std_logic_1164.all;
ENTITY debounce IS
GENERIC(
clk_freq : INTEGER := 50_000_000; --system clock frequency in Hz
stable_time : INTEGER := 10); --time button must remain stable in ms
PORT(
clk : IN STD_LOGIC; --input clock
reset_n : IN STD_LOGIC; --asynchronous active low reset
button : IN STD_LOGIC; --input signal to be debounced
result : OUT STD_LOGIC); --debounced signal
END debounce;
ARCHITECTURE logic OF debounce IS
SIGNAL flipflops : STD_LOGIC_VECTOR(1 DOWNTO 0); --input flip flops
SIGNAL counter_set : STD_LOGIC; --sync reset to zero
BEGIN
counter_set <= flipflops(0) xor flipflops(1); --determine when to start/reset counter
PROCESS(clk, reset_n)
VARIABLE count : INTEGER RANGE 0 TO clk_freq*stable_time/1000; --counter for timing
BEGIN
IF(reset_n = '0') THEN --reset
flipflops(1 DOWNTO 0) <= "00"; --clear input flipflops
result <= '0'; --clear result register
ELSIF(clk'EVENT and clk = '1') THEN --rising clock edge
flipflops(0) <= button; --store button value in 1st flipflop
flipflops(1) <= flipflops(0); --store 1st flipflop value in 2nd flipflop
If(counter_set = '1') THEN --reset counter because input is changing
count := 0; --clear the counter
ELSIF(count < clk_freq*stable_time/1000) THEN --stable input time is not yet met
count := count + 1; --increment counter
ELSE --stable input time is met
result <= flipflops(1); --output the stable value
END IF;
END IF;
END PROCESS;
END logic;
有兴趣可以转到链接:去抖动逻辑电路(以VHDL为例)[5]查看(打不开的除外)!
好吧,单个按键就到这里了,下面给出多个按键的设计。
多个按键进行消抖的设计如下:
使用一种相当简单的方法来查找开关的n个连续稳定读数,其中n是一个从1(完全没有反跳)到看似无穷大的数字。通常,代码会先检测到跳变,然后开始递增或递减计数器,每次重新读取输入时,直到n达到一些安全的,无抖动的计数。如果状态不稳定,则计数器会重置为其初始值。
`timescale 1ns / 1ps
//说明:当三个独立按键的某一个被按下后,相应的LED被点亮;
// 再次按下后,LED熄灭,按键控制LED亮灭
module sw_debounce(
clk,rst_n,
sw1_n,sw2_n,sw3_n,
led_d1,led_d2,led_d3
);
input clk; //主时钟信号,50MHz
input rst_n; //复位信号,低有效
input sw1_n,sw2_n,sw3_n; //三个独立按键,低表示按下
output led_d1,led_d2,led_d3; //发光二极管,分别由按键控制
//---------------------------------------------------------------------------
reg key_rst;
always @(posedge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
else key_rst <= sw3_n&sw2_n&sw1_n;
reg key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
else key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an = key_rst_r & (~key_rst);
/*
key_rst 1 1 1 0 0 1
~key_rst 0 0 0 1 1 0
key_rst_r 1 1 1 0 0 1
key_an 0 0 1 0 0
*/
//---------------------------------------------------------------------------
reg[19:0] cnt; //计数寄存器
always @ (posedge clk or negedge rst_n)
if (!rst_n) cnt <= 20'd0; //异步复位
else if(key_an) cnt <=20'd0;
else cnt <= cnt + 1'b1;
reg[2:0] low_sw;
always @(posedge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff
low_sw <= {sw3_n,sw2_n,sw1_n};
//---------------------------------------------------------------------------
reg [2:0] low_sw_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) low_sw_r <= 3'b111;
else low_sw_r <= low_sw;
/*
low_sw 111 111 111 110 110 110
~low_sw 000 000 000 001 001 001
low_sw_r 111 111 111 110 110 110
led_ctrl 000 000 000 001 000 000
*/
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);
reg d1;
reg d2;
reg d3;
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
d1 <= 1'b0;
d2 <= 1'b0;
d3 <= 1'b0;
end
else begin //某个按键值变化时,LED将做亮灭翻转
if ( led_ctrl[0] ) d1 <= ~d1;
if ( led_ctrl[1] ) d2 <= ~d2;
if ( led_ctrl[2] ) d3 <= ~d3;
end
assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
endmodule
按键消抖的部分,从开头到这里:
always @(posedge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff
low_sw <= {sw3_n,sw2_n,sw1_n};
已经结束 ,剩下的都是控制LED的部分!
我们来简单看下这个代码:
always @(posedge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
else key_rst <= sw3_n&sw2_n&sw1_n;
只要检测到键值有变化(哪怕是抖动),在这里是变低,就会令key_rst拉低!
reg key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( posedge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
else key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an = key_rst_r & (~key_rst);
对key_rst延迟一拍之后,在做下降沿检测,如果检测到下降沿key_an有效!也就是说,如果键值变化,key_an就能有效!
reg[19:0] cnt; //计数寄存器
always @ (posedge clk or negedge rst_n)
if (!rst_n) cnt <= 20'd0; //异步复位
else if(key_an) cnt <=20'd0;
else cnt <= cnt + 1'b1;
在key_an有效后,我们就将计数值清零,之后计数(key_an无效),如果在计数过程中,键值处于抖动阶段,则key_an会再次有效,计数值再次清零。直到抖动结束后,一直计数,计数到20ms,取键值信号!
always @(posedge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff
low_sw <= {sw3_n,sw2_n,sw1_n};
这段程序的意思就是计数满,如果系统时钟频率为50MHz,则意味着计数20ms左右,将采样键值,这里以三个按键为例。到这里为止,也就结束了按键消抖的部分!
其实对于按键抖动消除问题,还可以通过硬件的方式去抖动,但不在本文的讨论范围之内,可以参考资料5!参考资料5[6]