[Verilog HDL] 按键消抖

[复制链接]
10332|2
 楼主| gaochy1126 发表于 2023-5-29 15:09 | 显示全部楼层 |阅读模式

方案:在按下阶段计时,当按下时间大于规定时间时,记为一次按键。

一般情况下,我们从按下按键到松开基本需要大于几十毫秒的时间,系统时钟的周期处于纳秒级,因此我们按下一次按键会被大于十万个时钟的上升沿采集到,然而我们希望的是按下一次按键只被一次上升沿采集到,不然会被认为按了多次按键,所以我们需要对我们的按键进行处理。假设按键在没被按下时为高电平,被按下时处于低电平,如图 2 所示的波形图。


         图 2 按键波形图

由图 2 分析可知在 key 被按下时有且仅有一个 key 的上升沿和一个 key 的下降沿,我们可以通过检测 key 的上升沿或者下降沿来确定按键被按下一次, 这就涉及到边缘检测,具体方法如图 3 所示,可以用 clk 的上升沿将 key 延时一 个周期产生 key_reg,通过 key 和 key_reg 的值同时判断 key 的上升沿或下降 沿。由图 3 可知,当用 clk 的上升沿检测到 key 等于 1 的同时 key_reg 等于 0, 此时则为 key 的上升沿,若 key 等于 0 的同时 key_reg 等于 1 则为 key 的下降 沿。
         图 3 边缘检测
 由图 3 可知根据 key 的上升沿或下降沿可确定按键按了一次,然而事实却不允许我们这么做。图 4 给出了一个按键的模型,当不按下按键 s 时,a、b 两点 是断开的,按下按键 s 时线路才接通,然而当按下按键时,会存在物理上的抖动现象,此时 a、b 两点会在断开和接通之间反复的一段时间,就会出现图 5 所 示的抖动、稳定的波形。
         图 4 按键模型

         图 5 按键抖动
图 5 所示前抖动为按下时产生的抖动,后抖动为按键松开时造成的抖动, 前、后抖动持续时间一般均为 5~10ms,在有抖动的情况下,key 会被 clk 上升沿采集到很多的上升沿和下降沿,因此用 key 的上升沿和下降沿判断按键一次就不成立了,我们需要寻找新的方法。
   我们知道按键被按下时 key 值为低电平(0),在抖动期间 key 既有高电平也有低电平,我们可以使用 clk 的上升沿计算 key 连续为低电平的时间,期间当检测到 key 为高电平时,则从头开始计数,当计数超过 5~10ms 时,我们可以认定按键有被按下的时候,此时我们可以产生一个 clk 周期为高电平的标志,当该标志位高电平认为有一次按键即可,具体波形如图 6 所示。
         图 6 按键消抖波形图
  此处我们认为 key 值有连续性的 5ms 时为按键被按下,时钟周期为 50MHz, 即 20ns,可以算出 5ms 占 250000 个 clk 周期,也就是说 cnt 从 0 计数到 249999 为 5ms,当 cnt 等于 249999 时可以将 po_key_flag 拉高一个周期,由于按键时间长短无法确定,因此 cnt 有可能多次达到 249999 这个值,会造成 po_key_flag 多次被拉高,这样又会出现按下一次键被当成按下多次,所以在 图 6 中出现了 cnt_flag 变量,在遇到 cnt 等于 249999 时,cnt_flag 就会被置高,直到遇到 key 等于高电平时才会被拉低,这样我们就可以将第一个 cnt 等 于 249999 和后面的区分开,那么我们也可以用 cnt 和 cnt_flag 共同控制在一次按键中,保证 po_key_flag 有且仅有一个时钟周期的高电平。根据波形图可得到如下所示的代码。


 楼主| gaochy1126 发表于 2023-5-29 15:10 | 显示全部楼层


  1. module aa(input clk,rst_n,key,output reg key_flag);
  2. reg [17:0] cnt;
  3. reg cnt_full;
  4. always@(posedge clk or negedge rst_n)begin
  5. if(!rst_n) cnt<=18'b0;
  6. else if (key==0) cnt<=cnt+1'b1;
  7. else cnt<=18'b0;
  8. end

  9. always@(posedge clk or negedge rst_n)begin
  10. if(!rst_n) cnt_full<=1'b0;
  11. else if(cnt==24999)
  12. cnt_full<=1'b1;
  13. else if (key==1) cnt_full<=1'b0;
  14. else cnt_full<=cnt_full;
  15. end

  16. always@(posedge clk or negedge rst_n)begin
  17. if(!rst_n) key_flag<=1'b0;
  18. else if (cnt==24999&&cnt_full==1'b0)
  19. key_flag=1'b1;
  20. else key_flag=1'b0;
  21. end
  22. endmodule

代码解析:

①第 4 行的 always 块实现了一个计数器,当 key 键按下(值为 0)的时候开始计数,当检测到按键松开(值为 1)的时候计数器归零,这样在抖动时候,该计数器会出现计数、归零循环的一个过程,直到 key 值一直 为 0,即按键持续被按下并且抖动过程结束,计数器才会持续计数到 5ms;

②第 10 行的 always 块产生一个标志,当检测到 cnt 第一次等于 24999的下一个clk时,该标志则被置为高电平,这样就可以将 cnt 第一次等于 24999 和之后 的 cnt 等于24999 区分开,直到 key 松开(值为 1)时,该标志才被置为低电平;具体 第一次cnt=24999时,cnt_full=0,之后的cnt=24999时,cnt_full=1.

③第 18 行的 always 块实现产生整个按键过程中的一个时钟周期高电平的标志,当 cnt 计数到 5ms 时,为了确保只有一次的 key_flag 有效, 因此此处条件中不仅需要 cnt=5ms,而且需要 cnt_flag==0。至此,实现了按键的消抖处理,在此基础上就可以很好地实现最初的要求了。

 楼主| gaochy1126 发表于 2023-5-29 15:10 | 显示全部楼层
  1. `timescale 1ns/1ns

  2. `define clk_period 20

  3. module tb;

  4.         reg Clk;
  5.         reg Rst_n;
  6.         reg key_in;
  7.        
  8.         wire key_flag;
  9.         wire key_state;
  10.        
  11.         aa aa(
  12.                 .clk(Clk),
  13.                 .rst_n(Rst_n),
  14.                 .key(key_in),
  15.                 .key_flag(key_flag)
  16.         );
  17.        
  18.         initial Clk= 1;
  19.         always#(`clk_period/2) Clk = ~Clk;
  20.        
  21.         initial begin
  22.                 Rst_n = 1'b0;
  23.                 key_in = 1'b1;
  24.                 #(`clk_period*10) Rst_n = 1'b1;
  25.                 #(`clk_period*10 + 1);
  26.                 key_in = 1'b0;#1000;
  27.                 key_in = 1'b1;#2000;
  28.                 key_in = 1'b0;#1400;
  29.                 key_in = 1'b1;#2600;
  30.                 key_in = 1'b0;#1300;
  31.                 key_in = 1'b1;#200;
  32.                 key_in = 1'b0;#2000100;
  33.                 #50000100;
  34.                
  35.                 key_in = 1'b1;#2000;
  36.                 key_in = 1'b0;#1000;
  37.                 key_in = 1'b1;#2000;
  38.                 key_in = 1'b0;#1400;
  39.                 key_in = 1'b1;#2600;
  40.                 key_in = 1'b0;#1300;
  41.                 key_in = 1'b1;#2000100;
  42.                 #50000100;
  43.                
  44.                 key_in = 1'b0;#1000;
  45.                 key_in = 1'b1;#2000;
  46.                 key_in = 1'b0;#1400;
  47.                 key_in = 1'b1;#2600;
  48.                 key_in = 1'b0;#1300;
  49.                 key_in = 1'b1;#200;
  50.                 key_in = 1'b0;#2000100;
  51.                 #50000100;
  52.                
  53.                 key_in = 1'b1;#2000;
  54.                 key_in = 1'b0;#1000;
  55.                 key_in = 1'b1;#2000;
  56.                 key_in = 1'b0;#1400;
  57.                 key_in = 1'b1;#2600;
  58.                 key_in = 1'b0;#1300;
  59.                 key_in = 1'b1;#2000100;
  60.                 #50000100;
  61.                 $stop;               
  62.         end
  63.        
  64. endmodule


您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:这个社会混好的两种人:一是有权有势,二是没脸没皮的。

1205

主题

11937

帖子

26

粉丝
快速回复 在线客服 返回列表 返回顶部