Verilog 按键消抖的一些分析和想法
最近在网上看了下 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 按键消抖的一些分析和想法相关推荐
- 基于verilog按键消抖设计
关于键盘的基础知识,我就以下面的一点资料带过,因为这个实在是再基础不过的东西了.然后我引两篇我自己的博文,都是关于按键消抖的,代码也正是同目录下project里的.这两篇博文都是ednchina的博客 ...
- Verilog中按键消抖检测的实现
Verilog按键消抖是FPGA学习时的一个入门教程,为避免眼高手低,还是再次分析与记录一下.此处着重介绍按键消抖的基本原理,对按键消抖与检测的关键技术进行分析,并进行功能仿真. 一.按键消抖基本原理 ...
- 【Verilog HDL 训练】第 09 天(按键消抖)
5月7日 按键防抖 1. 用verilog实现按键抖动消除电路,抖动小于15ms,输入时钟12MHz. 在编写Verilog代码之前,先分析下一些前提问题,首先是几个按键(1个,多个),我们以1个和三 ...
- c语言实现按键的抖动与消除,【Verilog HDL 训练】第 09 天(按键消抖)
5月7日 按键防抖 1. 用verilog实现按键抖动消除电路,抖动小于15ms,输入时钟12MHz. 在编写Verilog代码之前,先分析下一些前提问题,首先是几个按键(1个,多个),我们以1个和三 ...
- Verilog功能模块 —— 按键消抖
一. 什么是按键消抖 按键消抖_百度百科 (baidu.com) 按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开.闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断 ...
- FPGA学习-Verilog实现独立按键消抖
文章目录 前言 一.独立按键消抖原理 二.按键消抖程序实现(Verilog) 1.按键触发判断 2.计数器模块实现 3.按键状态更新 4.按键控制led亮灭 三.仿真测试文件编写 四.编译结果 前言 ...
- Verilog实现4位按键消抖,分别控制一个LED
Verilog实现4位按键消抖,分别控制一个LED 代码思路(完整代码在后) 完整代码 参考:(主要是第一篇,第二篇不严谨) <按键消抖与LED控制>实验的个人思考与总结 Verilog实 ...
- 80C51并行口结构与驱动 [附:按键消抖分析]
80C51单片机有4个8位的并行I/O接口,分别是P0.P1.P2和P3.各口都是由口锁存器.输出驱动器和输入缓冲器组成.各口编址于特殊功能寄存器中,既有字节地址又有位地址.对各口锁存器的读写,就可以 ...
- verilog常用模块1——按键消抖模块详解
按键消抖模块key_filter 1. 原理介绍 如图,按键未按下时keys信号为高电平,按下则为低电平:通过检测keys信号电平,就可以判断按键状态. 但反作用弹簧会导致抖动现象,电平信号出现一段不 ...
- verilog基础-状态机之FPGA独立按键消抖设计与验证(熟练testbench的写法)
独立按键消抖设计与验证 本实验主要是为了锻炼状态机的思维模式以及熟练掌握TB的写法 本节主要收获了:define的用法,另外就是,顶层的input在TB中是reg的真正含义,其实就是把激励当做寄存器来 ...
最新文章
- VisualStudio:WEB 性能测试和负载测试 入门
- 想知道数据表的用户或系统异动时间
- 如何调试bash脚本
- MFC DLL 的三种类型
- xfce的面板调节声音大小的按钮不见了。
- 路由器三种口令的设置
- Ubuntu 8.04和Windows Server 2008 双体系的安置与卸载记
- GitLab版本管理(转)
- 蓝牙安全与攻击案例分析
- 微信小程序项目实例——今日美食
- sir模型 python_SIR传染病模型(附Python代码)
- matlab中ones函数的使用方法详细介绍(附matlab代码)
- excel 在一列中查找某个值的出现次数 countif函数
- Python报错处理libpng warning: iCCP: cHRM chunk does not match sRGB
- 推荐系统架构与算法流程详解
- 快速搞懂C语言中exit(0)与exit(1)有什么区别??
- FE_CSS 页面布局之定位
- Atom编译器64位win版本下载汉化及前端必备插件
- 使用JDK自带的 keytool 工具生成公私钥证书库
- 压力传感器行业调研报告 - 市场现状分析与发展前景预测
热门文章
- IDEA “Cannot resolve symbol” 解决办法
- Android Studio Cannot resolve symbol 解决方法
- 【Linux】Ubuntu运行环境搭建
- React-滑条组件使用
- 1449异常 mysql_mysql异常-UncategorizedSQLException 1449
- [教程]教你如何制作彩色的3D打印Groot
- 【Mysql】Error 1826: Duplicate foreign key constraint 错误
- 【笃行】Button的选中与改变
- 行杂记之Zookeeper SessionTimeOut分析
- html语言vb怎么定义数组,vb数组的定义方法是什么