在学习了单按键消抖方法后,按键消抖的关键点就是20ms的延时,这一点和单片机按键消抖的思路是一样的。但是FPGA的延时需要通过寄存器计数,这个是比较消耗内部资源的。如果要检测4个按键时,最简单的方法就是将单按键消抖程序例化4次,传入不同的按键接口就行。这样的话就相当于用了4个不同的寄存器对20ms计数,对 FPGA内部资源浪费比较大,那么能不能用一个20ms的寄存器同时判断四个按键呢。

可以参考单按键检测的思路,首先将按键的当前状态和上一个状态存储起来,然后比较按键状态,如果按键状态发生了变化,说明有按键按下,然后开始20ms计时,如果在20ms内按键状态又发生了变化,说明按键出现了抖动,将计数器清0,重新开始20ms计时,当20ms计时时间到了之后,输出一个按键按下标志,然后存储按键当前值。

思路比较简单,下面直接写代码:

module key_filter(input              sys_clk,input               sys_rst_n,input      [3:0] key_in,output        key_flag,       //按键按下标志output reg [3:0] key_value       //按键当前值);reg [3:0] key_now;
reg [3:0] key_last;reg key_state;
reg key_state0,key_state1;//parameter DELAY = 20'd1_000_000;          //延时时间
parameter DELAY = 20'd20;                 //测试时间reg [19:0] cnt;                               //20ms计数
reg en_cnt;                                 //计数使能

首先定义输入输出口和需要用到的相关寄存器,输入信号有3个,时钟、复位、4位按键,输出信号有2个,一个按键按下标志,一个4位按键值。

开始20ms计数

//计数  20ms
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)cnt <= 20'd0;else if(en_cnt) beginif(cnt == DELAY - 1'b1)cnt <= 20'd0;else if(cnt < DELAY -1'b1)cnt <= cnt + 1'b1;endelsecnt <= 20'd0;
end

en_cnt为计数使能信号,当en_cnt为1时,开始计数,当en_cnt为0时计数清0。

检测按键值是否发生变化方法为:将按键当前值和上一时钟值用寄存器保存下来,然后去判断这两个值是否相等。

//存储按键值
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginkey_now <= 4'b1111;key_last <= 4'b1111;endelse  beginkey_now <= key_in;key_last <= key_now;end
end

key_now为当前时钟沿时按键值,key_last为上一个时钟沿时按键值,这两个值延时了一拍。

下来就可以通过这两个寄存器的值来判断是否有按键按下

//当按键发生改变时,计数器清0,当按键状态稳定20ms后,输出按键值。
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginen_cnt <= 1'b1;key_state <= 1'b0;key_value <= 4'b1111;endelse if(key_last != key_now) begin                                    //按键发生变化计数器清0en_cnt <= 1'b0;key_state <= 1'b0;endelse if(key_last == key_now) beginen_cnt <= 1'b1;if(cnt == DELAY - 1'b1) begin    //延时时间到,并且按键按下key_value <= key_last;if(key_last != 4'b1111)key_state <= 1'b1;endend
end

en_cnt用于使能计数器开始计数,key_state用来表示是否有按键按下,有按键按下时输出高电平,否则输出低电平,这样一旦有按键按下后key_state就变为高电平,直到按键松开。key_value用于存储按键稳定后的按键值。若有按键按下并且稳定了20ms,那么就存储当前按键值。

在复位时就开始使能en_cnt,开始20ms计数。为什么一开始就要计数?而不是等到有按键按下时开始计数。因为这样操作起来比较方便。假如默认情况下en_cnt设置为0,当检测到有按键按下时en_cnt设置为1开始计数,这样的话当按键刚按下就开始计数,如果按键此时出现了抖动需要清0计数器,那么只能把en_cnt先设置为1,在设置为0,让计数器清0一次。这样让计数器清0一次需要3个时钟周期,如果刚好将en_cnt设置为0时,按键不在抖动了,那么计时器就不会计数,按键检测就会失败。所以就只能在按键第一次按下时开始计时,然后中间抖动过程中计数器依然计时,直到20ms计数时间到。这样延时也能实现,但是又违背了设计初衷。希望的是按键有抖动时计数器要清0,20ms的计时是从按键最后一次抖动开始算起,而不是从按键第一次按下算起。

那么就换个思路,复位时就让en_cnt为1,计数器开始计数,如果没有按键按下,那么计数器计数到了20ms时,自动清0继续计数,程序不做任何动作。如果有按键状态发生了变化将en_cnt设置为0,计数器清0。等按键状态不发生变化时,再将en_cnt设置为1,开始计时。这样如果按键按下时发生了抖动,按键状态就会一直变化,这样计数器就会一直被清0,直到按键抖动结束后,按键状态不在发生变化,这时候才开始计时。这样计时到了20ms后,就能检测到真正的按键值。同样按键弹起时发生抖动,计时器也会被清0,直到按键弹起后不在抖动。这样不仅可以检测按键按下抖动,同样可以检测按键弹起抖动。

所以在复位时将en_cnt设置为1,开始20ms计数,复位时默认按键未按下,所以将key_state设置为0,同时将按键值key_value设置为4'b1111,4个按键默认都为高电平,所以按键值都设置为1。

if(!sys_rst_n) beginen_cnt <= 1'b1;key_state <= 1'b0;key_value <= 4'b1111;end

下面判断按键值是否发生了变化,如果按键值有变化,就将计数器清0。由于此时按键还未稳定,所以key_state仍然设置为0。

    else if(key_last != key_now) begin                                    //按键发生变化计数器清0en_cnt <= 1'b0;key_state <= 1'b0;end

如果按键值不在发生变化,那么就等待20ms计数结束。

else if(key_last == key_now) beginen_cnt <= 1'b1;if(cnt == DELAY - 1'b1) begin    //延时时间到,并且按键按下key_value <= key_last;if(key_last != 4'b1111)key_state <= 1'b1;endend

如果按键值没有发生变化,就让en_cnt一直为1。当计时到20ms后,将按键当前值存储到key_value中,如果此时按键值不是4'b1111,说明是按键按下,而不是按键弹起。此时将按键状态key_state拉高,代表已经有按键真正的被按下了。如果按键弹起了,按键值就会发生变化,key_state就会被清0。这样key_state就会在按键被按下的过程中一直为高电平,按键弹起后为低电平。

先看一下输出波形是不是和预想的一样。

通过波形可以看到,当按键值为发生变化时,计数器会一直计数,计时到20ms后不做任何动作。按键按下后抖动一次,计数器就会清0一次,按键按下稳定后,并且计时时间到了20ms,按键状态位就会输出高电平,同时保存按键值,按键弹起后,状态位变为低电平。同时按键值会实时输出按键稳定后的值。这样根据key_state和key_value这两个寄存器值的值就可以知道按键什么时候按下,按下的时间为多长,按键值是多少。

对上面的波形分析后,已经可以成功的滤除按键抖动,并读取按键值。那么是不是程序就完成了?当然不是,还差最后一步。模块还需要向外输出信号,通知其他模块有按键按下。那直接用key_state这个信号输出行不行呢?key_state在没有按键按下时输出电平,在有按键按时时输出高电平。但是这个高电平持续时间比较长,如果外部模块直接用这个信号的话,就会在很多个时钟周期的上升沿检测到key_state为高电平。这样就分不清楚当前高电平是新的按键按下的高电平还是上一个按键按下后持续输出的高电平?如果要让外部其他模块清晰的知道按键是否被按下,只能输出一个时钟周期的高脉冲,这样外部模块每检测到一个高电平就代表按键被按下了一次。就不会造成误判或者漏判的情况。这样就还得输出一个信号,按键按下一次就输出一个时钟周期的脉冲。

通过观察上面的信号就可以想到 key_state 这个信号每次按键按下一次后,就会从0变为1,那么就可以在 key_state 为上升沿的这一瞬间输出一个脉冲,这样就可以实现按下按下一次输出一个脉冲的功能了。

下面就检测 key_state 信号的上升沿。

//读取按键状态
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginkey_state0 <= 1'b0;key_state1 <= 1'b0;endelse  beginkey_state0 <= key_state;key_state1 <= key_state0;end
end
//检测上升沿
assign key_flag = key_state0 & (~key_state1);

同样的方法将key_state当前状态和上一次状态存储起来,如果key_state当前值为1,上一个状态值为0,说明出现了上升沿。

将当前状态和上一个状态取反后再做位与运算,当两个值同时为1时,就说明出现了上升沿,最后将这个脉冲信号输出。

看一下key_flag这个标志的仿真波形

通过波形可以看出key_in、key_state0、key_state1依次延迟一个时钟周期,所以当key_state0第一个高电平时,key_state1刚好是最后一个低电平,key_state1取反然后和key_state0相与的结果在这个时钟周期内是1,在其余时间都为0。通过这个方法就将key_state的上升沿提取出来了。最后将这个上升沿输出给外部其他模块,这个外部模块直接检测到一个高电平就认为有按键按下,然后去读取按键值就行。

这样代码才算真正的写完了,来看看完整代码。

module key_filter(input              sys_clk,input               sys_rst_n,input      [3:0] key_in,output        key_flag,       //按键按下标志output reg [3:0] key_value       //按键当前值);reg [3:0] key_now;
reg [3:0] key_last;reg key_state;
reg key_state0,key_state1;//parameter DELAY = 20'd1_000_000;          //延时时间
parameter DELAY = 20'd20;                 //测试时间reg [19:0] cnt;                               //20ms计数
reg en_cnt;                                 //计数使能//读取按键状态
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginkey_state0 <= 1'b0;key_state1 <= 1'b0;endelse  beginkey_state0 <= key_state;key_state1 <= key_state0;end
end
//检测上升沿
assign key_flag = key_state0 & (~key_state1);//读取按键值
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginkey_now <= 4'b1111;key_last <= 4'b1111;endelse  beginkey_now <= key_in;key_last <= key_now;end
end//当按键发生改变时,计数器清0,当按键状态稳定20ms后,输出按键值。
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginen_cnt <= 1'b1;key_state <= 1'b0;key_value <= 4'b1111;endelse if(key_last != key_now) begin                                    //按键发生变化计数器清0en_cnt <= 1'b0;key_state <= 1'b0;endelse if(key_last == key_now) beginen_cnt <= 1'b1;if(cnt == DELAY - 1'b1) begin    //延时时间到,并且按键按下key_value <= key_last;if(key_last != 4'b1111)key_state <= 1'b1;endend
end//计数 1_000_000次 20ms
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)cnt <= 20'd0;else if(en_cnt) beginif(cnt == DELAY - 1'b1)cnt <= 20'd0;else if(cnt < DELAY -1'b1)cnt <= cnt + 1'b1;endelsecnt <= 20'd0;
endendmodule

下面编写测试代码

`timescale 1ns/1ns
module key_filter_tb;
parameter T = 20;
reg sys_clk;
reg sys_rst_n;
reg [3:0] key_in;wire key_flag;
wire [3:0] key_value;key_filter key_filter(.sys_clk         (sys_clk),.sys_rst_n       (sys_rst_n),.key_in          (key_in),.key_flag        (key_flag),       //按键按下标志.key_value       (key_value)       //按键当前状态
);initial begin    sys_rst_n <= 1'b0;key_in = 4'b1111;    #(20*T);sys_rst_n <= 1'b1;#(20*T + 3);//按键默认为1 按下为0keypress(4'b1110);      //key0按下keypress(4'b1101);      //key1按下keypress(4'b1011);      //key2按下keypress(4'b0111);      //key3按下keypress(4'b1100);      //key0 key1 按下keypress(4'b1000);      //key0 key1 key2 按下keypress(4'b0000);      //全部按下$stop;
endinitial sys_clk <= 1'b0;
always #(T/2) sys_clk = !sys_clk;//模拟按键抖动
task keypress;input [3:0] keyValue;begin#(T*10+1);key_in = keyValue;#(T*3);key_in = 4'b1111;    #(T*5);key_in = keyValue;#(T*3);key_in = 4'b1111;#(T*5);key_in = keyValue;#(T*100);#(T*6+1);key_in = 4'b1111;#(T*2);key_in = keyValue;#(T*8);key_in = 4'b1111;#(T*60);end
endtask
endmodule

在测试代码中将模拟按键过程放在一个任务中,调用任务时传入按键值就行。模拟单个按键按下和多个按键同时按下。

输出整体波形如下

通过波形可以看出,按键按下一次,就会输出一个脉冲。同时输出当前按键值。

源码下载地址  https://download.csdn.net/download/qq_20222919/12687987

FPGA---多按键消抖检测相关推荐

  1. Verilog中按键消抖检测的实现

    Verilog按键消抖是FPGA学习时的一个入门教程,为避免眼高手低,还是再次分析与记录一下.此处着重介绍按键消抖的基本原理,对按键消抖与检测的关键技术进行分析,并进行功能仿真. 一.按键消抖基本原理 ...

  2. 【 FPGA 】按键消抖与LED灯流动小实验

    记录一个小实验吧,实验的目的是仅仅是塞塞牙缝而已,没其他意思,很简单. 功能:拨码开关控制led灯工作与否,拨码开关为on,led灯工作,否则不工作:导航按键up和down,也就是独立按键而已,控制l ...

  3. verilog基础-状态机之FPGA独立按键消抖设计与验证(熟练testbench的写法)

    独立按键消抖设计与验证 本实验主要是为了锻炼状态机的思维模式以及熟练掌握TB的写法 本节主要收获了:define的用法,另外就是,顶层的input在TB中是reg的真正含义,其实就是把激励当做寄存器来 ...

  4. FPGA编程按键消抖

    FPGA按键消抖 编写思路 1.按键消抖的基本原理 2.代码原理 3.代码部分 4.仿真代码编写 5.仿真 6.总结 一.按键消抖的基本原理 按键消抖,按下,松开存在毛刺,主要为了消除毛刺.通过稳定后 ...

  5. FPGA实现按键消抖及短时间按键和长时间按键不同动作

    module key_test2(clk, //时钟信号:50Mhzrst, //按键复位key, //用户按键led //LED0~LED2);//端口定义input clk;input rst;i ...

  6. 【按键消抖】基于FPGA的按键消抖模块开发

    1.软件版本 QUARTUSII8.1 Modelsim6.5d 2.系统源码 module tops(i_clk, //100Mi_rst, //系统复位功能,高电平复位,如果不使用这个角,那么一直 ...

  7. 基于verilog按键消抖设计

    关于键盘的基础知识,我就以下面的一点资料带过,因为这个实在是再基础不过的东西了.然后我引两篇我自己的博文,都是关于按键消抖的,代码也正是同目录下project里的.这两篇博文都是ednchina的博客 ...

  8. Verilog功能模块 —— 按键消抖

    一. 什么是按键消抖 按键消抖_百度百科 (baidu.com) 按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开.闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断 ...

  9. 按键消抖及原理(硬件和软件方法详解)

    在设计单片机按键输入的时候,进行按键消抖是防止按键输入被CPU误读多次的必要手段. 一.按键抖动 按键接法 抖动时间的长短由按键的机械特性决定,一般为5ms-10ms.这是一个很重要的时间参数,在很多 ...

最新文章

  1. android gdb 命令大全,ndk-gdb  |  Android NDK  |  Android Developers
  2. 大话数据结构书籍及配套源码
  3. python速度比较_Python和C运算速度对比实测
  4. 用Tableau画改进版幂函数柱状图
  5. Qualcomm QXDM工具简介和log抓取
  6. [XML-Jsoup]Jsoup_对象的使用(Jsoup工具类,Document,Elements,Element,Node)
  7. Basic Calculator II
  8. \pset 、\x命令
  9. html的table效果,html的table用法(让网页的视觉效果显示出来)
  10. 手机安全修改IMEI的方法
  11. 计算机常见故障有那些,电脑有哪些常见故障?如何排除?
  12. APP开发多少钱多少人和哪些注意事项
  13. VeiwPager、Gallery、ViewFlipper区别
  14. 逆波兰式 java_Java 实现《编译原理》中间代码生成 -逆波兰式生成与计算 - 程序解析...
  15. java503错误是什么_打开网页后出现503 service unavailable等字样,什么意思
  16. ArcGIS制作矢量动图
  17. 《薛兆丰的经济学课》课程总结4--相互依赖
  18. Nginx代理——正向、反向代理,动静分离和负载均衡
  19. 和吴昊一起玩推理(第二季首映式)Round 11 —— 从无有到无穷
  20. SharePoint BI

热门文章

  1. 国内三大PT(Private Tracker)站分析
  2. Android和iOS智能机去年出货超7亿 同比增长46%
  3. Mysql for Linux安装配置之——二进制安装
  4. Android使用自定义字体(自定义view)
  5. [Android实例] 有关spinner 的item问题 谁能给解答下??
  6. Ext.Net 最新版(2011-06-24)License 问题
  7. iPhone SDK开发基础之iPhone程序框架
  8. (转)OO设计初次见面
  9. 熟悉网络层IP协议和数据链路层
  10. JAVA中BigDecimal的字符化输出