从简单的按键消抖开始

  笔者正在接受FPGA的线上培训,以接近尾声,就水平来说算是入门。设计时发现做些设计总结非常重要,可以帮助自己理清思路,同时也能得到很好的复习,便于日后回顾。之前一直在做altera FPGA的相关学习,对xilinx还不是很熟悉,借着这个契机,将比较基础常用的设计在VIVADO开发环境中过一遍,对我来说是个不错的选择。废话不多说,进入今天的正题。

  众所周知,硬件按键都存在机械抖动。所以一次人为按下的动作会触发数次按键按下的行为。所谓“按键消抖”模块的功能就是将抖动滤除掉,保证对按键状态的有效识别。单片机的设计思想比较通用,即检测到按键连接端口为低电平(低电平有效)后,延迟一段时间再次确认是否为低。若是则说明此次低电平确实为一次按键行为,否则视为抖动。按键松手检测同理。其大体设计流程如下:

  这是典型的顺序设计思想,但FPGA是并行的。所以这种时间有先后,且操作差异较大的处理过程要用到状态机进行设计。简化后可将上述过程分为四个状态:初始空闲状态、延迟并检测低电平状态、检测释放状态和延迟并检测高电平状态。以下是状态转移图:

  空闲状态下如检测到按键接口低电平进入延迟并确认低电平状态,延迟计数时间设定为10ms。若计数完成且依然为低电平则按下有效进入检测释放状态,若计数期间按键出现高电平说明为抖动回到初始状态。在检测释放状态中若出现高电平进入延迟确认状态,否则持续检测高电平。在延迟确认高电平状态若计数完成且为高电平视为有效松手行为,此时置位有效标志位,按键完成了一次按下到松手的完整有效过程回到IDLE状态再检测下一次按下。如果计数期间出现低电平同样为抖动回到检测释放状态重新检测。

  1 `timescale 1ns / 1ps
  2
  3 module key_jitter#
  4 (
  5 parameter DELAY_TIME = 2000_000  //延迟10ms
  6 )
  7 (
  8     input clk,
  9     input rst_n,
 10
 11     input key_i,
 12     output reg led_o
 13     );
 14
 15     localparam IDLE          = 4‘b0001,
 16                DELAY_LOW     = 4‘b0010,
 17                CHECK_RELEASE = 4‘b0100,
 18                DELAY_HIGH    = 4‘b1000;
 19
 20     reg [20:0] div_cnt;
 21     reg [3:0] state_c,state_n;
 22     reg key_tmp0,key_tmp1;
 23
 24     wire add_cnt,end_cnt;
 25     wire vld_flag;
 26     wire cnt_during;
 27
 28     //消除亚稳态
 29     always@(posedge clk or negedge rst_n)begin
 30         if(!rst_n)begin
 31             key_tmp0 <= 0;
 32             key_tmp1 <= 0;
 33         end
 34         else begin
 35             key_tmp0 <= key_i;
 36             key_tmp1 <= key_tmp0;
 37         end
 38     end
 39
 40     //状态机
 41     always@(posedge clk or negedge rst_n)begin
 42         if(!rst_n)
 43             state_c <= IDLE;
 44         else
 45             state_c <= state_n;
 46     end
 47
 48     always@(*)begin
 49         case(state_c)
 50             IDLE:begin  //初始状态检测是否有按键按下 //4‘b0001
 51                 if(key_tmp1 == 0)//有按键按下进入延时后再次确认低电平状态
 52                     state_n <= DELAY_LOW;
 53                 else
 54                     state_n <= state_c;
 55             end
 56
 57             DELAY_LOW:begin  //延时并再次确认低电平状态  //4‘b0010
 58                 if(end_cnt && key_tmp1 == 0)//10ms后依然是低电平则有按键按下,此时检测是否松手
 59                     state_n <= CHECK_RELEASE;
 60                 else if(cnt_during && key_tmp1 == 1)
 61                     state_n <= IDLE;//若未计数完成出现高电平则视为抖动,重新检测按下
 62                 else
 63                     state_n <= state_c;//计数未完成继续
 64             end
 65
 66             CHECK_RELEASE:begin  //4‘b0100
 67                 if(key_tmp1 == 1)//为高电平则等待并再次确认
 68                     state_n <= DELAY_HIGH;
 69                 else
 70                     state_n <= state_c;//若没有高电平则持续检测
 71             end
 72
 73             DELAY_HIGH:begin  //4‘b1000
 74                 if(vld_flag)//10ms后依然高电平则按键释放
 75                     state_n <= IDLE;//释放后回到初始状态再次检测下一次的按下
 76                 else if(cnt_during && key_tmp1 == 0)//若延时后为0则松手过程视为抖动
 77                     state_n <= CHECK_RELEASE;
 78                 else
 79                     state_n <= state_c;//继续计数
 80             end
 81
 82             default:
 83                 state_n <= IDLE;
 84         endcase
 85     end
 86
 87     assign cnt_during = add_cnt && div_cnt < DELAY_TIME;
 88
 89     //延迟计数器
 90     always@(posedge clk or negedge rst_n)begin
 91         if(!rst_n)
 92             div_cnt <= 0;
 93         else if(add_cnt)begin
 94             if(end_cnt)
 95                 div_cnt <= 0;
 96             else
 97                 div_cnt <= div_cnt + 1‘b1;
 98         end
 99         else
100             div_cnt <= 0;
101     end
102
103     assign add_cnt = state_c == DELAY_HIGH || state_c == DELAY_LOW;
104     assign end_cnt = add_cnt && div_cnt == DELAY_TIME - 1;
105     //按下一次并释放后表示一次有效的操作,此时led翻转
106     always@(posedge clk or negedge rst_n)begin
107         if(!rst_n)
108             led_o <= 0;//上电复位点亮
109         else if(state_c == DELAY_HIGH && vld_flag)//可将()内条件作为按键有效输出
110             led_o <= ~led_o;
111     end
112
113
114     assign vld_flag = end_cnt && key_tmp1 == 1;
115
116 endmodule

  需要注意的知识点是状态机的设计技巧和参数设定。采用三段式状态机设计:一个always块用同步时序方式描述状态转移,另一个模块采用组合逻辑判断状态转移条件,最后给每一个状态输出分配一个时序逻辑块。其优势在于它将同步时序和组合逻辑分别放到不同的always 程序块中实现。这样做的好处不仅仅是便于阅读、理解、维护,更重要的是利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。同时采用时序逻辑输出消除了“毛刺”现象,提高设计稳定性。

  另外,参数化设计帮助提高代码可读性和灵活性。verilog中经常使用parameter 和localparam两个关键字定义参数,两者之间有一定的区别:parameter可用作在顶层模块中例化底层模块时传递参数的接口,localparam的作用域仅仅限于当前module,不能作为参数传递的接口。所以这里将延迟时间设定为可传递参数接口,便于顶层模块修改。而状态参数不能改动,只使其作用于当前模块。

  在FPGA设计中,仿真环节必不可少,甚至占用设计周期的大半,极大提高开发效率,让问题尽量在设计前期解决。现在编写测试激励,用modelsim仿真观察按键消抖模块是否完成预期功能。

 1 `timescale 1ns / 1ps
 2
 3 module key_jitter_tb();
 4
 5     // reg sys_clk_n,sys_clk_p;
 6     reg clk;
 7     reg rst_n;
 8     reg key_i;
 9     reg [15:0] myrand;
10
11     wire led_o;
12
13     key_jitter key_jitter
14 (
15
16     .clk(clk),
17     .rst_n(rst_n),
18
19     .key_i(key_i),
20     .led_o(led_o)
21 );
22
23     defparam key_jitter.DELAY_TIME = 50000;//参数重定义 有效时间改为50us便于仿真
24     parameter RST_TIME = 2,
25                CYCLE = 5;
26
27     initial begin
28         clk = 0;
29         forever #(CYCLE /2) clk = ~clk;
30     end
31
32     initial begin
33         rst_n = 1;
34         #1;
35         rst_n = 0;
36         #(CYCLE * RST_TIME);
37         rst_n = 1;
38     end
39
40     initial begin
41         #1;
42         key_i = 1;//初始未按下
43         #(CYCLE * RST_TIME);
44         #(CYCLE * 10);
45         press_key;
46         #10_000;
47         press_key;
48         $stop;
49     end
50
51     task press_key;
52     begin
53         repeat(20)begin//模拟抖动过程
54             myrand = {$random}%50000;
55             #myrand key_i = ~key_i;
56         end
57         key_i = 0;
58         #300_000;
59         repeat(20)begin
60             myrand = {$random}%50000;
61             #myrand key_i = ~key_i;
62         end
63         key_i = 1;
64         #300_000;
65     end
66     endtask
67
68 endmodule

  其中使用defparam实现参数重定义,让延迟时间缩短,可以在完成功能验证的前提下缩短仿真时间,提升开发效率。(仿真真的是慢!)在测试激励的编写中,task任务封装绝对是一项利器,可以非常方便地将某项功能封装后反复调用。测试文件中将按键按下释放以及中间的抖动过程作为一个task,在仿真过程中多次调用模拟多次按下释放的行为。通过观察输出波形即可得知功能是否正确。

  在仿真之前,一定要设置好仿真工具、编译库等选项。注意:如果按下Run xx simulation之后一直卡在执行仿真过程中,说明代码中有错误。此时要检查tcl console和log日志文件查看仿真相关警告和错误提示并作出修改。

modelsim仿真波形:

   可以看到两处红圈处为一次按下释放的有效动作,使led输出翻转。以上便是FPGA实现按键消抖模块的全部设计过程。由于设计比较简单,此处略去上板验证并在线调试的过程,在之后的设计中将给出此处的具体操作流程。这是笔者第一次撰写技术博文,希望大家给出宝贵建议,相互交流学习!

时间: 2024-08-28 22:12:27

从简单的按键消抖开始的相关文章

09B-独立按键消抖实验02——小梅哥FPGA设计思想与验证方法视频教程配套文档

芯航线--普利斯队长精心奉献 ? 实验目的: 1.复习按键的设计 2.用模块化设计的方式实现每次按下按键0,4个LED显示状态以二进制加法格式加1,每次按下按键1,4个LED显示状态以二进制加法格式减1 实验平台:芯航线FPGA核心板 实验原理:???? ????在上一讲中设计并验证了独立按键的消抖,这里基于上一讲的按键消抖模块来实现一个加减法计数器,并以此学习模块化的设计方式. ????在设计过程中,相对大一点的工程经常通常不会写在一个设计文件中,通常会针对不同的功能设计出不同的子文件,最后在

按键消抖之终极解决方案

1.按键消抖的原理 图1.按键抖动示意图 我们平常所用的按键为机械弹性开关,由于触点的弹性作用,按键在闭合时不会马上稳定的接通,而是有一段时间的抖动,在断开时也不会立即断开.抖动时间由按键的机械特性所决定,一般为5ms~10ms.所以我们在做按键检测时都要加一个消抖的过程. 按键消抖主要有两种方案: 一是延时重采样:二是持续采样. 从理论上来说,延时(如10ms)重采样的准确率肯定低于持续采样. 2.按键消抖的方法 (1)延时重采样 延时重采样的意思是,当第一次检测到键值由'1'变为'0'时,再

jQueryAjax模拟按键消抖(可设置抖动延迟时间)

在硬件中,按键等都会有抖动现象,如何消除抖动,不重复触发事件呢,这就要用到消抖机制了. 这是我用jQuery模拟硬件消抖原理,额,可能是吧...又不对的地方,希望有高手指点指点. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

关于按键消抖实验

对于特权同学按键消抖程序的理解:(程序源码见<深入浅出玩转FPGA>P191) 第一个always块中,在每个时钟周期(clk)对按键值进行采样 第二个always块中,利用边沿脉冲检测法,当key_rst有下降沿时,key-an将输出一个周期的高脉冲 第三个always块中,利用cnt进行循环计数(计数时间周期大约20ms),当keg-an为1时,cnt将清零,然后从零开始计数(由此产生一个20ms,从而消除抖动部分的影响) 第四个always块中,每当cnt从0计数到20'hfffff(即

按键消抖学习

KevinChen的博客——KevinChen's Blog [博客大赛]按键消抖之终极解决方案 http://bbs.ednchina.com/BLOG_ARTICLE_3020402.HTM EE_FPGA基础教程系列 -- 按键消抖  http://wenku.baidu.com/link?url=CGJkd0CRwzW-dreuF5FSiNWbUDHGE6HQIO3A8kWmERPkJmkr9vh2YOPLq3vKC8wvkaLxOa4iwSVM-ESqiODsvJwOrOpNTS24_

Verilog HDL那些事_建模篇笔记(实验三:按键消抖)

实验三:按键消抖 首先将按键消抖功能分成了两个模块,电平检查模块和10ms延迟模块.电平检测模块用来检测按键信号的变化(是否被按下),10ms延迟模块用来稳定电平检查模块的输入,进而稳定按键信号,防止其抖动而产生的信号跳变而影响输出. 设计思路:     1.当电平检测模块检查到按键被按下(输入由高电平变为低电平),则拉高H2L_Sig电平,然后拉低. 2.10ms延迟模块,检测到H2L_Sig高电平,则对其进行10ms过滤,拉高输出. 3.当按键被释放,电平检测模块会拉高L2H_Sig电平,然

FPGA培训专家 V3学院带你学习 按键消抖 和 边缘检测

FPGA培训专家 V3学院 一般情况下,我们从按下按键到松开基本需要大于几十毫秒的时间,系统时钟的周期处于纳秒级,因此我们按下一次按键会被大于十万个时钟的上升沿采集到,然而我们希望的是按下一次按键只被一次上升沿采集到,不然会被认为按了多次按键,所以我们需要对我们的按键进行处理.假设按键在没被按下时为高电平,被按下时处于低电平,如图1所示的波形图. 图1 按键波形图 由图1 分析可知在key被按下时有且仅有一个key的上升沿和一个key的下降沿,我们可以通过检测key的上升沿或者下降沿来确定按键被

手动按键复位程序(包含按键消抖)

1 //这是一个按键复位程序 2 module stable_key( 3 i_clkin, 4 i_inKey, 5 o_outKey 6 ); 7 8 input i_clkin; 9 input i_inKey; 10 output o_outKey; 11 12 reg key=1; 13 reg key_get = 1; //key输出指示信号 14 reg [20:0] cntK = 0; 15 reg o_outKey_r = 1; 16 17 always@(posedge i_

09A-独立按键消抖实验01——小梅哥FPGA设计思想与验证方法视频教程配套文档

芯航线--普利斯队长精心奉献 ? 实验目的: 1.复习状态机的设计思想并以此为基础实现按键消抖 2.单bit异步信号同步化以及边沿检测 3.在激励文件中学会使用随机数发生函数$random 4.仿真模型的概念 实验平台:芯航线FPGA核心板 实验原理: ????按键在电子设计中使用的最多,从复位到控制设置均可以看到其身影.现在按键的功能也种类也越来越多,例如多向按键.自锁按键.薄膜按键等.普通按键其硬件示意图如图9-1所示. 图9-1 按键示意图 芯航线开发板所载的为两脚贴片按键,分别位于开发板