casio1374633

导航

 

针对按键消抖模块有多种方式.

//===========================================================================

黑金教程中的按键消抖方式.

其中核心的去抖方法是使用边沿检测,这一点和特权同学的方式是一样的.不同的是,边沿检测和100us延时是同时执行的.在100us以后直接判断当次的边沿是上升沿还是下降沿.这种方法有一定的冒险性.

Verilog代码见key0.v

//===========================================================================
// Created by    : Casio
// Filename      : key0.v
// Author        : 
// Created On    : 2013-04-25 15:01
// Last Modified : 2013-04-25 16:12
//
//
// Revision::
//
// Description:
//
//
//===========================================================================

module key0(
   //Input  Ports
   clk,
   rst_n,
   pin_in,
   //Output Ports
   pin_out
);

//===========================================================================
//Input and output declaration
//===========================================================================

input  clk;
input  rst_n;
input  pin_in;

output pin_out;
//===========================================================================
//Wire and reg declaration
//===========================================================================

wire clk;
wire rst_n;
//===========================================================================
//Wire and reg in the module
//===========================================================================

wire h2l_sig;
wire l2h_sig;
//===========================================================================
//边沿检测模块
//===========================================================================
//--------------------------------------
//100us延时
parameter T100US = 11'd1200;

reg [10:0] count1;
reg is_en;

always @ (posedge clk or negedge rst_n)
   if (!rst_n) begin
      count1 <= 11'd0;
      is_en <= 1'b0;
   end
   else if (count1 == T100US)
      is_en <= 1'b1;
   else
      count1 <= count1 + 1'b1;
//--------------------------------------
//边沿检测
reg h2l_f1;
reg h2l_f2;
reg l2h_f1;
reg l2h_f2;

always @ (posedge clk or negedge rst_n)
   if (!rst_n) begin
      h2l_f1 <= 1'b1;
      h2l_f2 <= 1'b1;
      l2h_f1 <= 1'b0;
      l2h_f2 <= 1'b0;
   end
   else begin
      h2l_f1 <= pin_in;
      h2l_f2 <= h2l_f1;
      l2h_f1 <= pin_in;
      l2h_f2 <= l2h_f1;
   end

assign h2l_sig = is_en ? (h2l_f2 & !h2l_f1) : 1'b0;
assign l2h_sig = is_en ? (l2h_f2 & !l2h_f1) : 1'b0;

//===========================================================================
//LED灯输出模块
//===========================================================================

//--------------------------------------
//10ms延时
parameter T1MS = 17'd120000;

reg [16:0] count1MS;

always @ (posedge clk or negedge rst_n)
   if (!rst_n)
      count1MS <= 17'd0;
   else if (is_count && count1MS == T1MS)
      count1MS <= 17'd0;
   else if (is_count)
      count1MS <= count1MS + 1'b1;
   else if (!is_count)
      count1MS <= 17'd0;
//--------------------------------------
//MS累加计数
reg [3:0] cntMS;

always @ (posedge clk or negedge rst_n)
   if (!rst_n)
      cntMS <= 4'd0;
   else if (is_count && count1MS == T1MS)
      cntMS <= cntMS + 1'b1;
   else if (!is_count)
      cntMS <= 4'd0;
//--------------------------------------
//led灯输出
reg is_count;
reg pin_out_r;
reg [1:0] i;

always @ (posedge clk or negedge rst_n)
   if (!rst_n) begin
      is_count <= 1'b0;
      pin_out_r <= 1'b0;
      i <= 2'd0;
   end
   else
      case (i)
         2'd0:
            if (h2l_sig)
               i <= 2'd1;
            else if (l2h_sig)
               i <= 2'd2;
         2'd1:
            if (cntMS == 4'd10) begin
               is_count <= 1'b0;
               pin_out_r <= 1'b1;
               i <= 2'd0;
            end
            else
               is_count <= 1'b1;
         2'd2:
            if (cntMS == 4'd10) begin
               is_count <= 1'b0;
               pin_out_r <= 1'b0;
               i <= 2'd0;
            end
            else
               is_count <= 1'b1;
      endcase

assign pin_out = pin_out_r;
//--------------------------------------

endmodule

RTL视图 (无)

特权同学的按键消抖代码中规中矩,使用边沿检测方式判断电平变化.

Verilog代码见key1.v

//===========================================================================
// Created by    : Casio
// Filename      : key1.v
// Author        : 
// Created On    : 2013-04-25 16:50
// Last Modified : 2013-04-25 19:44
//
//
// Revision::
//
// Description:采用特权同学的计算方式
//
//Resource    Usage
//Total logic elements    38
//-- Combinational with no register    8
//-- Register only    4
//-- Combinational with a register    26
//    
//Logic element usage by number of LUT inputs    
//-- 4 input functions    7
//-- 3 input functions    2
//-- 2 input functions    20
//-- 1 input functions    5
//-- 0 input functions    0
//    
//Logic elements by mode    
//-- normal mode    19
//-- arithmetic mode    19
//-- qfbk mode    0
//-- register cascade mode    0
//-- synchronous clear/load mode    20
//-- asynchronous clear/load mode    30
//    
//Total registers    30
//Total logic cells in carry chains    20
//I/O pins    6
//Maximum fan-out node    clk
//Maximum fan-out    30
//Total fan-out    167
//Average fan-out    3.80
//===========================================================================

module key1(
   //Input  Ports
            clk,
         rst_n,
            sw1_n,
         sw2_n,
   //Output Ports
         led_d1,
         led_d2
);

//===========================================================================
//Input and output declaration
//===========================================================================

input   clk;    //主时钟信号,50MHz
input   rst_n;    //复位信号,低有效
input   sw1_n;    //三个独立按键,低表示按下
input   sw2_n; 
output  led_d1;    //发光二极管,分别由按键控制
output  led_d2;
//===========================================================================
//Wire and reg declaration
//===========================================================================

wire clk;
wire rst_n;
wire sw1_n;
wire sw2_n;
wire led_d1;
wire led_d2;
//===========================================================================
//Wire and reg in the module
//===========================================================================


//===========================================================================
//按键去抖模块
//===========================================================================
//边沿变化检测,每个CLK时钟都检测按键边沿变化
reg[1:0] key_rst;  

always @(posedge clk  or negedge rst_n)
    if (!rst_n)
       key_rst <= 2'b11;
    else 
       key_rst <= {sw2_n,sw1_n};

reg[1:0] key_rst_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n)
       key_rst_r <= 2'b11;
    else 
       key_rst_r <= key_rst;
   
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期 
wire[1:0] key_an = key_rst_r & ( ~key_rst);

//---------------------------------------------------------------------------
//计数寄存器
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[1:0] low_sw;
//每20毫秒判断检测,两次检测的时间间隔是20MS.
always @(posedge clk  or negedge rst_n)
    if (!rst_n)
       low_sw <= 2'b11;
    else if (cnt == 20'hfffff)     //只有满20ms,才会将按键值记录到寄存器中
       low_sw <= {sw2_n,sw1_n};
      
reg  [1:0] low_sw_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n)
       low_sw_r <= 2'b11;
    else
       low_sw_r <= low_sw;
   
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期 
wire[1:0] led_ctrl = low_sw_r & ( ~low_sw); //此处的检测是瞬时的
//===========================================================================
//LED显示模块
//===========================================================================
reg d1;
reg d2;
  
always @ (posedge clk or negedge rst_n)
    if (!rst_n) begin
        d1 <= 1'b0;
        d2 <= 1'b0;
      end
    else begin        //某个按键值变化时,LED将做亮灭翻转
        if ( led_ctrl[0] ) d1 <= ~d1;    
        if ( led_ctrl[1] ) d2 <= ~d2;
      end

assign led_d1 = d1 ? 1'b1 : 1'b0;
assign led_d2 = d2 ? 1'b1 : 1'b0;
  
endmodule

RTL视图 (无)

如初出现电平变化,则启动20ms延时.如果在20ms计数以内电平依旧出现变化,则计数器清零,直到电平稳定,如果电平在20ms内一直稳定,则记录下20ms后的电平,并与之前稳定的电平进行判断比较,如果是电平变化,则进行相应输出.图形表示如下:

key1

这种方式在进行双边沿检测时会多出一倍的资源量,所以在减小资源的情况下,不推荐使用.

//===========================================================================

红星开发板的按键消抖方式:

Verilog代码见key2.v

`define    UD        #1

module key2
    (
    //Input ports.
    SYSCLK,       
    RST_B,
    KEY_B,

    //Output ports.
    LED_B
    );

//===========================================================================
//Input and output declaration
//===========================================================================

input       SYSCLK;        //System clock, 27MHz.
input       RST_B;        //Global reset, low active.
input      [3:0]    KEY_B;        //Key input, low active.

output  [3:0]    LED_B;        //Led output, low active.

//===========================================================================
//Wire and reg declaration
//===========================================================================

wire           SYSCLK;
wire           RST_B;
wire      [3:0]    KEY_B;

reg      [3:0]    LED_B;


//===========================================================================
//Wire and reg in the module 
//===========================================================================

reg    [19:0]    TIME_CNT;    //Counter, count press key time.
reg    [3:0]    KEY_REG;    //Save the input one cycle every 20ms.
reg    [3:0]    LED_B_N;    //Next value of LED_B.

wire    [19:0]    TIME_CNT_N;    //Next value of TIME_CNT.
wire    [3:0]    KEY_REG_N;    //Next value of KEY_REG.
wire    [3:0]    PRESS;        //Decide which key in press.

//===========================================================================
//Logic
//===========================================================================

//Some control single.
assign    PRESS = KEY_REG & (~KEY_REG_N);

//Save the key value when some key is press.
always @ (posedge SYSCLK  or negedge RST_B)
begin
  if(!RST_B)
    KEY_REG   <= `UD 4'hF;
  else 
    KEY_REG   <= `UD KEY_REG_N;
end

assign    KEY_REG_N = (TIME_CNT == 20'h0) ? KEY_B : KEY_REG;

//Count the time the key is pressed.
always @ (posedge SYSCLK  or negedge RST_B)
begin
  if(!RST_B)
    TIME_CNT  <= `UD 20'h0;
  else 
    TIME_CNT  <= `UD TIME_CNT_N;
end

assign    TIME_CNT_N = TIME_CNT +1'h1;

//Output control.
always @ (posedge SYSCLK or negedge RST_B)
begin
  if(!RST_B)
    LED_B     <= `UD 4'hF;
  else
    LED_B     <= `UD LED_B_N;
end

always @ (*)
begin
  case(PRESS)
    4'b0001   :  LED_B_N = {LED_B[3:1]  , (~LED_B[0])             }; 
    4'b0010   :  LED_B_N = {LED_B[3:2]  , (~LED_B[1]) , LED_B[0]  }; 
    4'b0100   :  LED_B_N = {LED_B[3]    , (~LED_B[2]) , LED_B[1:0]}; 
    4'b1000   :  LED_B_N = {              (~LED_B[3]) , LED_B[2:0]}; 
    default  :  LED_B_N = LED_B;
  endcase
end

endmodule

RTL视图 (无)

该开发板的消抖方式十分简陋,就是每隔20ms把按键采样值与之前的存储值进行比较,如果产生下降沿,就认为按键按下.

这个做法的弊端是计数器属于一直工作状态.没有开关去停止.而且单纯使用一个间隔采样去判断,则对采样时间间隔有了很多的漏洞.如下图:

key2

可以发现,由于按键的时间是不能确定的,所以这个时候,间隔采样将遇到亚稳态的问题.所以该方法不可取.

//===========================================================================

国外的一个按键消抖方式(推荐)。

这套代码在关键点上和特权同学的思想有异曲同工之妙,都是通过电平的不同开始进行计数,并且在抖动期间,计数器反复清零,只有在稳定的时候才进行计数,当计数一定数值后确定电平.再进行输出.不同的是.这套代码使用了一个异步锁存器来对信号进行输出,而如果去掉这个异步锁存器,则可以看到,信号和按键是随动的.这是这个代码的灵活的地方,上下边沿都进行了判断.而且资源消耗很少.

RTL图如下.

key3

VHDL代码见(当时也是从网上下载的,只有VHDL。我对代码做了详细的注释说明)

 buttom说明

--Resource    Usage
--Total logic elements    24
---- Combinational with no register    6
---- Register only    0
---- Combinational with a register    18
--    
--Logic element usage by number of LUT inputs    
---- 4 input functions    5
---- 3 input functions    1
---- 2 input functions    16
---- 1 input functions    2
---- 0 input functions    0
--    
--Logic elements by mode    
---- normal mode    9
---- arithmetic mode    15
---- qfbk mode    0
---- register cascade mode    0
---- synchronous clear/load mode    16
---- asynchronous clear/load mode    0
--    
--Total registers    18
--Total logic cells in carry chains    16
--I/O pins    3
--Maximum fan-out node    clk
--Maximum fan-out    17
--Total fan-out    92
--Average fan-out    3.41
--这个是老外给出的一个按键消抖程序.写VHDL一定要时刻记得,这是一个硬件设计,信号来了就会生效,因为是一个电路,而不是程序逻辑.判断条件仅仅是
--触发器电路的抽象概念,一定要时刻记住,是电路,一个process就是一个电路模块,signal就是模块与模块之间的电路连线.变量则可以看做是一个临时存储单元.
--书中对于信号与变量说的很清楚: 常量与信号是全局的,而变量是局部的,变量只能在顺序代码中执行,并且它们的值是不能直接向外传递的.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all; 

entity buttom is
     Port ( btn_0 : in  STD_LOGIC;  -- 按键输入
            clk : in  STD_LOGIC;    -- 时钟
            led : out  STD_LOGIC);  -- 信号输出
 end buttom;
 
architecture Behavioral of buttom is

 --消抖延时计数值(常量)
 constant CNTR_MAX : std_logic_vector(15 downto 0) := (others => '1');
 --计数变量(使用信号是因为要用硬件的角度去看,它在一个模块中是输出,在另一个模块中是输入,是一根信号线) 
 signal btn0_cntr : std_logic_vector(15 downto 0) := (others => '0');  
 --输出连接信号线 
 signal led_r : std_logic := '0'; 
 -- 同样是一个信号连接线,连接两个模块.
 signal btn0_reg : std_logic := '0';
begin
-- 如果btn_0(按键)按下,则发生和btn0_reg的电平异号,开始计数.
-- 在抖动期间会发生btn_0经常异号的情况,所以计数器会时刻清零,直到按键稳定开始计数
 btn0_counter_process : process (CLK)
 begin
  if (rising_edge(CLK)) then
   if ((btn0_reg = '1') xor (btn_0 = '1')) then
    if (btn0_cntr = CNTR_MAX) then
     btn0_cntr <= (others => '0');
    else
     btn0_cntr <= btn0_cntr + 1;
    end if;
   else
    btn0_cntr <= (others => '0');
   end if;
  end if;
 end process;
 
 btn0_debounce_process : process (CLK)
 begin
  if (rising_edge(CLK)) then
   if (btn0_cntr = CNTR_MAX) then
    btn0_reg <= not(btn0_reg);
   end if;
  end if;
 end process;

-- 注释掉的功能是锁存器,目的是只有在btn0_reg上升沿时候才进行操作
 process(btn0_reg)
 begin
  if (rising_edge(btn0_reg)) then
    led_r <= not (led_r);
  end if;
 end process;
 led <= led_r;
 -- end of comment --
 --如果要求按键随动则注释掉上面,把下面这句打开.如果要锁存就要打开上面,注释掉下面.
 --led <= btn0_reg;
 
 end Behavioral;

 

 

然后是我自己改写的Verilog代码,功能和RTL与VHDL完全一致。

//===========================================================================
// Created by    : Casio
// Filename      : key4.v
// Author        : 
// Created On    : 2013-04-25 21:44
// Last Modified : 2013-05-13 20:37
//
//
// Revision::
//
// Description:
//
//
//===========================================================================

module key4(
   //Input  Ports
   input  clk, //时钟
   input  rst_n, //复位
   input  btn0, //按键输入
   //Output Ports
   output led //信号输出
);

parameter CNTR_MAX = 16'hFFFF; //计数器阈值
//===========================================================================
//Wire and reg in the module
//===========================================================================
wire xor_out;
reg [15:0] btn0_cntr; //16位计数器
reg [15:0] btn0_cntrN; //触发器左端输入
reg led_r; //输出寄存器
reg led_rN;
reg btn0_reg; //连接异步触发器的信号线
reg btn0_regN; //触发器左端输入线
//===========================================================================
//Logic
//===========================================================================
assign xor_out = btn0_reg ^ btn0; //用异或门判断两个触发状态是否在同一侧 
always @ (posedge clk or negedge rst_n) begin
   if (!rst_n)
      btn0_cntr <= 16'h0;
   else
      btn0_cntr <= btn0_cntrN;
end

always @ (*) begin
   if (xor_out) begin
      if (btn0_cntr == CNTR_MAX)
         btn0_cntrN = 16'h0;
      else
         btn0_cntrN = btn0_cntr + 16'h1;
   end
   else
      btn0_cntrN = 16'h0;
end

always @ (posedge clk or negedge rst_n) begin
   if (!rst_n)
      btn0_reg <= 1'b0;
   else
      btn0_reg <= btn0_regN;
end

always @ (*) begin
   if (btn0_cntr == CNTR_MAX)
      btn0_regN = ~btn0_reg; //反向
   else
      btn0_regN = btn0_reg;
end
//此部分为异步锁存器,如果去掉此部分,则按键随动,否则按键状态锁存
always @ (posedge btn0_reg or negedge rst_n) begin
   if (!rst_n)
      led_r <= 1'b0;
   else
      led_r <= led_rN;
end

always @ (*) begin
   led_rN = ~led_r;
end

assign led = led_r;
//如果要求按键随动,则打开下面这句
//assign led = btn0_reg;

endmodule
posted on 2013-05-13 21:02  casio1374633  阅读(731)  评论(0编辑  收藏  举报