前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Verilog设计实例(8)按键防抖设计之软件防抖

Verilog设计实例(8)按键防抖设计之软件防抖

作者头像
Reborn Lee
发布2020-06-28 16:46:11
1.4K0
发布2020-06-28 16:46:11
举报
  • 写在前面
  • 正文
    • 背景介绍及回顾
    • 单个按键
    • 单按键的其他设计版本
    • 多个按键
    • 写在最后
  • 参考资料
  • 交个朋友

写在前面

  • 个人微信公众号:FPGA LAB
  • 个人博客首页[1]
  • 注:学习交流使用!

正文

背景介绍及回顾

我们在互联网上经常会看到这种按键防抖的Verilog设计,那就是大概每20ms读取一次开关,所谓的公认按键时间小于20ms[2]

事实上,不同的按键,其抖动时间是不一样的,参考资料:debouncing[3]中,作者测试了不同的按键的抖动时间:

不同的按键

抖动时间

用于打开和关闭每个开关(数字A至R)的抖动时间(以微秒为单位)。开关E被排除在外,因为其157毫秒的跳动会严重扭曲图形。

有兴趣的可以看下!

一种常见的按键的电路如下:

一种按键的设计电路

按键未按下键值信号为高,按下为低,物理按键都存在着时间或短或长的按键抖动!如下图所示:

按键按下以及松开波形图

如上面所说,按键抖动一般公认为20ms,如果从软件或者说逻辑设计的方式去消除抖动,就是先检测到按键信号的边沿,之后每计数20ms采样一次键值!这样就实现了按键消抖的目的!

单个按键

一个按键的消抖设计

先从一个按键为例:

如下图为设计框图:

一个按键

设计文件:

代码语言:javascript
复制
`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),采样一次键值作为输出。

下面自己设计抖动来测试下这个设计。

测试文件

代码语言:javascript
复制
`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,后台给我消息,我看人多了之后抽空转下):

代码语言:javascript
复制
----------------------------------------------------------------
--   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

代码语言:javascript
复制
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达到一些安全的,无抖动的计数。如果状态不稳定,则计数器会重置为其初始值。

代码语言:javascript
复制
`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

按键消抖的部分,从开头到这里:

代码语言:javascript
复制
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的部分!

我们来简单看下这个代码:

代码语言:javascript
复制
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拉低!

代码语言:javascript
复制
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就能有效!

代码语言:javascript
复制
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,取键值信号!

代码语言:javascript
复制
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]

参考资料

  • 参考资料1[7]
  • 参考资料2[8]
  • 参考资料3[9]
  • 参考资料4[10]
  • 参考资料5[11]
  • 参考资料6[12]
  • 参考资料7[13]
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FPGA LAB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 正文
    • 背景介绍及回顾
      • 单个按键
        • 单按键的其他设计版本
          • 多个按键
            • 写在最后
            • 参考资料
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档