最近在网上看了下 Verilog 按键消抖方面的设计,有一些个人的想法,做一些分析和记录;

电路板上,通常会提供若干按键,每个按键下赋予了不同的含义,按键的含义由具体的场景来定义;

打个比方,一组电路板上的按键定义如下所示:

在这个例子中,可以看到,硬件原理图中提供了 5 个信号:

KEY_UP

KEY_DOWN

KEY_LEFT

KEY_RIGHT

KEY_ENTER

当 S2~S6 没有被按下的时候,I/O 管脚被通过上拉电阻到 Vcc,即逻辑 1;

当 S2~S6 被按下的时候,这些 I/O 信号直接到 GND,即逻辑 0;

即,按下按键,读 I/O 状态为 0;否则为 1;

当然,这是理想情况,但是现实却很骨感,我们把这个简单的按键操作无限放大后,事实的真相是:按键的时候,可能会存在抖动因素,即需要判断是否是真的按下了按键?是否是因为抖动的因素,导致了误报?这就是按键去抖;

其实按键去抖比较常见,一个比较简易的判断抖动的算法是:如果发现按下了,那么间隔一个很短的时间,再去采集该 I/O 的状态,发现还是按下的状态,那么就认为是真的按键,否则认为是抖动;这里,间隔的这个很短的时间,一般情况可以取 20ms;那么在翻译一下,发现 I/O 状态改变后,隔 20ms,再次采样 I/O 状态,如果发现当前的状态与之前的状态一致,那么说明,I/O 状态改变是不争的事实!

对应的,如果在单片机上,发现有按键按下,那么可以起一个 20ms 的 Timer,到期后再次检测按键即可;

那么 FPGA 上,纯硬件逻辑应该如何应对呢?这就是本章需要了解的部分;

这里分析了几种实现方案:

方案一

具体实现上,有一些技巧,参考了一些设计,具体的实现如下所示(这里分了几个部分,每个部分逐步讲述):

1、定义一个 20ms 的计数器,不断的从 0 ~ 20ms 进行计数

2、20 ms 到期后,将 key 数据进行采样并缓存到 key 0,并在下一个时钟周期,将 key 0 同步到内部缓存的 key 1 中;

3、key 0 与 key 1 有一个时钟周期的差别,利用这一点,进行判断,是否 key 值变化了

Part 1

输入部分,有 4 个按键,分别控制 4 个 LED,按下其中一个,对应的 LED 亮,再次按下,LED 灭

输入时钟频率 25MHz,输入有复位;

首先实现的是 20ms 的计数器,这个不多说了,Verilog 逻辑如下:

module keyscan(input clk,input n_rst,input [3:0] key,output [3:0] led);// Input Clock is 25MHz, so in order to get 20ms // The Counter should be 500,000 - 1reg [19:0] cnt;
always @(posedge clk or negedge n_rst)if(!n_rst) cnt <= 20'd0;else if (cnt == 20'd499999) cnt <= 20'd0;else cnt <= cnt + 1'b1;

Part 2

当计数器到达 20ms 的时候,将按键的 I/O 信息进行采样,存储到内部 key_sample_new[3:0] 寄存器:

    // Sample Key input every 20msreg [3:0] key_sample_new;
always @(posedge clk or negedge n_rst)if(!n_rst) key_sample_new <= 4'b0000;else if(cnt == 20'd499999) key_sample_new <= key;

Part 3

用时钟,同步新/旧的采样数据,并且通过新旧的按键对比出到底是哪个被按下了:

    // Sync the latest one to old onereg [3:0] key_sample_last;
always @(posedge clk or negedge n_rst)if(!n_rst) key_sample_last <= 4'b0000;else if(cnt == 20'd499999) key_sample_last <= key_sample_new;// latch one clock cycle and check which key have been press downwire [3:0] key_pressed = key_sample_last[3:0] & (~key_sample_new[3:0]);

我们按照时间顺序来进行流程分析:

1、首先,clk 的 20ms 的计数器到期了,会将当前的 key 的 I/O 状态进行采样到 key_sample_new[3:0],注意,此刻的 key_sample_last[3:0] 还是上一次的值,并没发生变化,因为此刻的数据刚刚打入 key_sample_new[3:0];

2、组合逻辑生效 key_pressed 信号会根据当前 20ms 拿到的最新的 I/O 状态 key_sample_new[3:0] 取反和上一次的 key_sample_last[3:0] 进行与操作,这里多说一下:

wire [3:0] key_pressed = key_sample_last[3:0] & (~key_sample_new[3:0]);

如果对应的 bit 之前为 1 (也就是 last 中为 1),新采样拿到的数据为 0(也就是 new 中为 0),那么 key_pressed 中对应的 bit 被置为 1,否则,其他任何情况,key_pressed 中对应的 bit 都为 0;换句话来说,只有检测到对应的 bit 由 1 -> 0 的过程,那么就说明被按下了,并将其记录到 key_pressed 中;

3、下一个 clk 的上升沿到来的时候,key_sample_new[3:0] 被同步到了 key_sample_last[3:0] 中,也就是说,如果有真实的按下按键的话,这两个内部寄存器的值,只有一个时钟周期是不一样的;利用这个时钟周期,将数据存储到了 key_pressed;数据完成了同步后,key_sample_last[3:0] 中就代表了上一次的采样数据了;

Part 4

既然按下按键的信息被存储到了 key_pressed 中,那么控制 LED 就靠它了:

reg [3:0] temp_led;
always @(posedge clk or negedge n_rst) if(!n_rst) temp_led <= 4'b1111;else beginif ( key_pressed[0] ) temp_led[0] <= ~temp_led[0];if ( key_pressed[1] ) temp_led[1] <= ~temp_led[1];if ( key_pressed[2] ) temp_led[2] <= ~temp_led[2];if ( key_pressed[3] ) temp_led[3] <= ~temp_led[3];endassign led[0] = temp_led[0];assign led[1] = temp_led[1];assign led[2] = temp_led[2];assign led[3] = temp_led[3];endmodule

总的来说,这种方案就是将 20ms 前获取到的 key 值(key_sample_last[3:0]) 和 20ms 拿到的新的 key 值(key_sample_new[3:0]) 进行按位来对比,判断是否有被置位的情况;

典型的情况是:

当然,还可能存在一些比较极限的情况,就是 20ms 到期的时候,保存最新的 key 值的时候,正好处于抖动期间;

极限的真实按下按键键的情况:

如果此次是真实的按键,而且正好采样时刻 B 位于抖动期间,如果采集到了 0,那么判断条件生效,将会认为有按键按下,如果采集到的是 1,那么相当于认为当前还是未按下的状态,并且会在 C 点判断出已经按键,显然,这种 Case 没有问题;

如果只有抖动,没有按键的情况,如果此次采集到的 key 是 1,那么相安无事,如果采集到的是 0,那么判断就会失效;所以方案一是存在失效风险的;

方案二

针对方案一的缺陷,可以设计成为采样 cnt 一直累加,但是一旦检测到 key 有下降沿的时候,立即重置 cnt 到 0,重新计数;

// Solution 2parameter SAMPLE_RATE=6'd10;
// ------------- Key negedge detect Logic Start -------------reg [3:0]key_0;always @(posedge clk or negedge n_rst)if(!n_rst) key_0 <= 4'b1111;else key_0 <= key;reg [3:0]key_1;always @(posedge clk or negedge n_rst)if(!n_rst) key_1 <= 4'b1111;else key_1 <= key_0;// Check key negedgewire [3:0]key_neg = key_1[3:0] & (~key_0[3:0]);
// ------------- Key negedge detect Logic End -------------// ------------- 4 cnt Logic Start -------------reg [3:0] cnt_k0;always @(posedge clk or negedge n_rst)if(!n_rst) cnt_k0 <= 4'd0;else if(key_neg[0]) cnt_k0 <= 4'd0;else if(cnt_k0 == SAMPLE_RATE) cnt_k0 <= 4'd0;else cnt_k0 <= cnt_k0 + 1'b1;reg [3:0] cnt_k1;always @(posedge clk or negedge n_rst)if(!n_rst) cnt_k1 <= 4'd0;else if(key_neg[1]) cnt_k1 <= 4'd0;else if(cnt_k1 == SAMPLE_RATE) cnt_k1 <= 4'd0;else cnt_k1 <= cnt_k1 + 1'b1;reg [3:0] cnt_k2;always @(posedge clk or negedge n_rst)if(!n_rst) cnt_k2 <= 4'd0;else if(key_neg[2]) cnt_k2 <= 4'd0;else if(cnt_k2 == SAMPLE_RATE) cnt_k2 <= 4'd0;else cnt_k2 <= cnt_k2 + 1'b1;reg [3:0] cnt_k3;always @(posedge clk or negedge n_rst)if(!n_rst) cnt_k3 <= 4'd0;else if(key_neg[3]) cnt_k3 <= 4'd0;else if(cnt_k3 == SAMPLE_RATE) cnt_k3 <= 4'd0;else cnt_k3 <= cnt_k3 + 1'b1;
// ------------- 4 cnt Logic End -------------// Sample Key input every SAMPLE_RATEreg [3:0] key_sample_new;
always @(posedge clk or negedge n_rst)if(!n_rst) key_sample_new <= 4'b1111;else beginif(cnt_k0 == SAMPLE_RATE) key_sample_new[0] <= key[0];if(cnt_k1 == SAMPLE_RATE) key_sample_new[1] <= key[1];if(cnt_k2 == SAMPLE_RATE) key_sample_new[2] <= key[2];if(cnt_k3 == SAMPLE_RATE) key_sample_new[3] <= key[3];end// Sync the latest one to old onereg [3:0] key_sample_last;
always @(posedge clk or negedge n_rst)if(!n_rst) key_sample_last <= 4'b1111;else key_sample_last <= key_sample_new;// latch one clock cycle and check which key have been press downwire [3:0] key_pressed = key_sample_last[3:0] & (~key_sample_new[3:0]);reg [3:0] temp_led;
always @(posedge clk or negedge n_rst) if(!n_rst) temp_led <= 4'b0000;else beginif ( key_pressed[0] ) temp_led[0] <= ~temp_led[0];if ( key_pressed[1] ) temp_led[1] <= ~temp_led[1];if ( key_pressed[2] ) temp_led[2] <= ~temp_led[2];if ( key_pressed[3] ) temp_led[3] <= ~temp_led[3];endassign led[0] = temp_led[0];assign led[1] = temp_led[1];assign led[2] = temp_led[2];assign led[3] = temp_led[3];

首先使用一个边缘检测电路,检测到 key 的下降沿,一旦发现下降沿,那么 cnt 立马置 0,这样就万无一失了吧?

这种方案的确是比方案一稳一些了,但是在还是有问题:

当 cnt 计数正好计数满的时候,此刻来下降沿了,此刻判断失效!

方案三

针对方案一和方案二的问题,那么是否可以这样设计,计数器开始就别工作了,等到下降沿检测好了,在开始进入工作状态,在计数的过程中如果发现有上升沿,那么重新计数,如果一直计数到满(20ms)为止,那么判定,此次是真的按下了!

上述想法可以使用状态机进行设计,刚刚开始,处于 IDLE 状态,通过边沿检测电路来检测 key 的下降沿;

当检测到 key 下降沿后,进入 SAMPLING 采样状态,一旦发现采样过程有上升沿,那么返回到 IDLE,如果计数器满足后,判定为真实按下,进入 DOWN 状态,并输出相应的输出!

这样就能够解决上面两个方案中遇到的问题啦,Verilog 如下(为了仿真,采样时间定在了 10 个 clk,并且只针对了一个 key):

`timescale 1ns / 1ps
//
// Company:
// Engineer:       StephenZhou
//
// Create Date:    10:02:46 11/25/2019
// Design Name:
// Module Name:    keyscan
// Project Name:
// Target Devices: SP6
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision: 0.01
// Revision 0.01 - File Created
// Additional Comments:
//
//module keyscan(input clk,input n_rst,input key,output reg led);// Key posedge and negedge detective logicreg key_in_0;reg key_in_1;always @(posedge clk or negedge n_rst)if(!n_rst) beginkey_in_0 <= 1'b1;key_in_1 <= 1'b1;endelse beginkey_in_0 <= key;key_in_1 <= key_in_0;end    wire key_posedge;wire key_negedge;assign key_posedge = key_in_0 & (~key_in_1);assign key_negedge = key_in_1 & (~key_in_0);// State Machinereg [2:0] state;parameter IDLE =     3'b001;parameter SAMPLING = 3'b010;parameter DOWN =     3'b100;reg en_cnt;reg led_pressed;always @(posedge clk or negedge n_rst)if(!n_rst) beginstate  <= IDLE;en_cnt <= 1'b0;//led    <= 1'b0;led_pressed    <= 1'b0;endelse begincase(state)IDLE : beginif(key_negedge) beginstate  <= SAMPLING;en_cnt <= 1'b1;//led    <= 1'b0;led_pressed <= 1'b0;endelse beginstate  <= IDLE;en_cnt <= 1'b0;//led    <= 1'b0;led_pressed <= 1'b0;endendSAMPLING : beginif(key_posedge) beginstate  <= IDLE;en_cnt <= 1'b0;led_pressed <= 1'b0;endelse beginif(cnt_full) beginstate  <= DOWN;en_cnt <= 1'b0;led_pressed <= 1'b0;endelse beginstate  <= SAMPLING;en_cnt <= 1'b1;led_pressed <= 1'b0;endendendDOWN : beginstate  <= IDLE;en_cnt <= 1'b0;led_pressed <= 1'b1;enddefault : beginstate  <= IDLE;en_cnt <= 1'b0;led_pressed <= 1'b0;                endendcaseend// Counterreg [3:0] cnt;always @(posedge clk or negedge n_rst)if(!n_rst) cnt <= 4'b0000;else if (en_cnt) cnt <= cnt + 1'b1;else cnt <= 4'b0000;// Counter Full Logicreg cnt_full;parameter SAMP_CNT = 4'd10;always @(posedge clk or negedge n_rst)if(!n_rst) cnt_full <= 1'b0;else if(cnt == SAMP_CNT) cnt_full <= 1'b1;else cnt_full <= 1'b0;// Check posedge of led and keep outputalways @(posedge clk or negedge n_rst)if(!n_rst) led <= 1'b0;else if(led_pressed) led <= ~led;//else led <= 1'b0;endmodule

代码中均有注释,还是来解释一下:

1、首先使用 key_in_0 和 key_in_1 进行下降沿和上升沿检测电路(Verilog 边沿检测电路)

2、定义状态机,独热码,三个状态 IDLE、SAMPLING、DOWN,检测下降沿状态为 IDLE,一旦有下降沿,则进入 SAMPLING,在此状态下,如果有上升沿,那么认为是抖动,返回 IDLE,继续检测下降沿,同时计数器停止计时

3、在 SAMPLING 状态下计时器到期,那么认为,稳定时间到,则认为有按键按下,并走到 DOWN 状态;

4、DOWN 状态,认为已经检测 OK,那么 led_pressed 赋值为高,led 亮

testbench 为:

`timescale 1ns / 1ps// Company:
// Engineer:    StephenZhou
//
// Create Date:   13:44:47 11/22/2019
// Design Name:   keyscan
// Module Name:   D:/Xlinx_ISE_Projects/keyscan/tb/key_scan_tb.v
// Project Name:  test
// Target Device:
// Tool versions:
// Description:
//
// Verilog Test Fixture created by ISE for module: keyscan
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// module key_scan_tb;// Inputsreg clk;reg n_rst;reg key;// Outputswire led;// Instantiate the Unit Under Test (UUT)keyscan uut (.clk(clk), .n_rst(n_rst), .key(key), .led(led));// Clock Generator freq @20always #5 clk = ~clk;initial begin// Initialize Inputsclk = 0;n_rst = 0;key = 1;// Wait 100 ns for global reset to finish#100;// Add stimulus here// Release Reset signaln_rst = 1;// Wait 100#100;// Key Input Signal Jitter#20 key = 1'b0;#15 key = 1'b1;#20 key = 1'b0;#5  key = 1'b1;#15 key = 1'b0;#15 key = 1'b1;#10 key = 1'b0;#5  key = 1'b1;#400;// Key Input Signal Jitter#18 key = 1'b0;//#30 key[0] = 1'b0;#15 key = 1'b1;#20 key = 1'b0;#5  key = 1'b1;#15 key = 1'b0;#15 key = 1'b1;#10 key = 1'b0;#5  key = 1'b1;// Real Push down#10 key = 1'b0;// Up key#200 key = 1'b1;endendmodule

仿真波形全貌为:

第一个抖动,并没有认为是按键,

放大第一个逻辑为:

第二个是的确按下了,所以逻辑正确,放大第二段逻辑:

Verilog 按键消抖的一些分析和想法相关推荐

  1. 基于verilog按键消抖设计

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

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

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

  3. 【Verilog HDL 训练】第 09 天(按键消抖)

    5月7日 按键防抖 1. 用verilog实现按键抖动消除电路,抖动小于15ms,输入时钟12MHz. 在编写Verilog代码之前,先分析下一些前提问题,首先是几个按键(1个,多个),我们以1个和三 ...

  4. c语言实现按键的抖动与消除,【Verilog HDL 训练】第 09 天(按键消抖)

    5月7日 按键防抖 1. 用verilog实现按键抖动消除电路,抖动小于15ms,输入时钟12MHz. 在编写Verilog代码之前,先分析下一些前提问题,首先是几个按键(1个,多个),我们以1个和三 ...

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

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

  6. FPGA学习-Verilog实现独立按键消抖

    文章目录 前言 一.独立按键消抖原理 二.按键消抖程序实现(Verilog) 1.按键触发判断 2.计数器模块实现 3.按键状态更新 4.按键控制led亮灭 三.仿真测试文件编写 四.编译结果 前言 ...

  7. Verilog实现4位按键消抖,分别控制一个LED

    Verilog实现4位按键消抖,分别控制一个LED 代码思路(完整代码在后) 完整代码 参考:(主要是第一篇,第二篇不严谨) <按键消抖与LED控制>实验的个人思考与总结 Verilog实 ...

  8. 80C51并行口结构与驱动 [附:按键消抖分析]

    80C51单片机有4个8位的并行I/O接口,分别是P0.P1.P2和P3.各口都是由口锁存器.输出驱动器和输入缓冲器组成.各口编址于特殊功能寄存器中,既有字节地址又有位地址.对各口锁存器的读写,就可以 ...

  9. verilog常用模块1——按键消抖模块详解

    按键消抖模块key_filter 1. 原理介绍 如图,按键未按下时keys信号为高电平,按下则为低电平:通过检测keys信号电平,就可以判断按键状态. 但反作用弹簧会导致抖动现象,电平信号出现一段不 ...

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

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

最新文章

  1. VisualStudio:WEB 性能测试和负载测试 入门
  2. 想知道数据表的用户或系统异动时间
  3. 如何调试bash脚本
  4. MFC DLL 的三种类型
  5. xfce的面板调节声音大小的按钮不见了。
  6. 路由器三种口令的设置
  7. Ubuntu 8.04和Windows Server 2008 双体系的安置与卸载记
  8. GitLab版本管理(转)
  9. 蓝牙安全与攻击案例分析
  10. 微信小程序项目实例——今日美食
  11. sir模型 python_SIR传染病模型(附Python代码)
  12. matlab中ones函数的使用方法详细介绍(附matlab代码)
  13. excel 在一列中查找某个值的出现次数 countif函数
  14. Python报错处理libpng warning: iCCP: cHRM chunk does not match sRGB
  15. 推荐系统架构与算法流程详解
  16. 快速搞懂C语言中exit(0)与exit(1)有什么区别??
  17. FE_CSS 页面布局之定位
  18. Atom编译器64位win版本下载汉化及前端必备插件
  19. 使用JDK自带的 keytool 工具生成公私钥证书库
  20. 压力传感器行业调研报告 - 市场现状分析与发展前景预测

热门文章

  1. IDEA “Cannot resolve symbol” 解决办法
  2. Android Studio Cannot resolve symbol 解决方法
  3. 【Linux】Ubuntu运行环境搭建
  4. React-滑条组件使用
  5. 1449异常 mysql_mysql异常-UncategorizedSQLException 1449
  6. [教程]教你如何制作彩色的3D打印Groot
  7. 【Mysql】Error 1826: Duplicate foreign key constraint 错误
  8. 【笃行】Button的选中与改变
  9. 行杂记之Zookeeper SessionTimeOut分析
  10. html语言vb怎么定义数组,vb数组的定义方法是什么