###### 【该随笔中部分内容转载自小梅哥】 #########

  独立按键消抖自古以来在单片机和FPGA中都是个不可避免的问题,首先,解释一下什么叫做按键抖动,如图,按键在按下和松开的那个瞬间存在大概20ms的机械抖动:

  

  下面就是本篇的第一个重点 —— 什么时候需要按键消抖设计?如果是像复位按键这样,短时间内可以多次触发,就完全不需要设计消抖,但是如果是要设计按下按键使LED状态翻转,或者按下按键计数一次的话,就必须要设计消抖模块,否则就会带来不可预知的错误,因为在按下按键的那个时刻,可能已经触发了少则几次,多则几十次,可见按键消抖的必要性;

  那么,既然按键消抖如此重要,如何来进行消抖呢?

  1、硬件消抖 —— 0.1uF电容滤波

  

    这个104的电容就是起高频滤波的作用,在按键不是很多的情况下,可以使用这种设计,但是如果要做项目,会增加大量成本,所以接下来我们讲述如何进行软件消抖;

  2、软件消抖 —— delay

if(key_in == )
{
deley();
if(key_in == )
{
//按键按下,执行相应操作
}
}

  在单片机中用C语言,可以这样设计按键消抖,同样的思路,在FPGA中,我们依然可以采用这种思想,将按键的这20ms抖动“屏蔽”,但是FPGA没有delay(2000),该如何设计呢?

  FPGA中控制延时可以采用计数器,因为工作时钟是已知的50M,所以要延时20_000_000ns(20ms),只需要对计数1_000_000个clk就可以,这样延时问题就解决了,按照之前的思路,设计如下:只需要在检测到key_in变为0,启动定时器,定时器时间到,再次检测key_in是否为0,若为0,表明按键按下稳定,关闭计数器并清零,然后等待按键释放,也就是key_in出现上升沿,再次启动定时器,时间到后检测key_in,若为1,则证明按键已释放,一次完整的按键过程结束

  大致思路有了,如何设计实现呢?貌似这是一个很复杂的设计,实则不然,FSM的本质就是对具有逻辑规律和时序逻辑的事物的描述,采用FSM设计,问题迎刃而解!

  1、从状态变量入手,分析状态变量

    IDLE:按键空闲状态(由于上拉电阻的作用,按键未被按下时保持高电平);

    FILTER_DOWN:按下滤波状态;

    DOWN:按下稳定状态;

    FILTER_UP:释放滤波状态;

  2、分析状态转移条件,绘制状态转移图(visio)

  3、照图施工,选用合适的描述方案

    在描述的时候,有两个重要问题需要解决:

    1)按键信号属于异步信号,在状态转移中需要对按键边沿敏感,所以首先采用一级D触发器将key_in与clk同步,产生pedge和nedge信号,也就是边沿检测电路,代码如下:

//边沿检测电路
always@(posedge clk)
key_temp <= key_in; //暂存上一个clk按键状态
assign key_nedge = (key_temp)&&(!key_in); //下降沿检测
assign key_pedge = (!key_temp)&&(key_in); //上升沿检测

    2)当20ms延时完毕后,应该输出一个脉冲,通知其它模块检测key_flag引脚电平;

    完整的verilog描述代码如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter
// Description: //独立按键消抖模块
//////////////////////////////////////////////////////////////////////////////////
module key_filter(
input clk, //50M时钟信号
input rst, //低电平复位
input key_in, //按键输入
output reg key_flag, //消抖完毕输出脉冲
output reg key_state //按键状态输出
);
reg [:]NS; //nextstate
reg key_temp;
wire key_pedge;
wire key_nedge;
reg en_cnt;
reg [:]cnt; //需要计数次数1_000_000 //边沿检测电路
always@(posedge clk)
key_temp <= key_in; //暂存上一个clk按键状态
assign key_nedge = (key_temp)&&(!key_in); //下降沿检测
assign key_pedge = (!key_temp)&&(key_in); //上升沿检测 //带使能端计数器,用于20ms延时
always@(posedge clk,negedge rst)
if(!rst)
cnt <= ;
else if(en_cnt)
cnt <= cnt + 'b1;
else
cnt <= ; //状态one-hot编码
localparam
IDLE = 'b0001, //空闲状态
FILTER_DOWN = 'b0010, //按下消抖状态
DOWN = 'b0100, //按下稳定状态
FILTER_UP = 'b1000; //释放消抖状态 //一段式状态机
always@(posedge clk,negedge rst)
if(!rst)begin
NS <= IDLE;
en_cnt <= ;
key_flag <= ;
key_state <= ;
end
else
case(NS)
IDLE:
begin
key_flag <= ;
key_state <= ;
if(key_nedge)begin
NS <= FILTER_DOWN;
en_cnt <= 'b1; //使能计数器
end
else
NS <= IDLE;
end
FILTER_DOWN:
if(cnt >= 'd999_999)begin
en_cnt <= ; //20ms时间到,失能计数器,进入稳定状态
key_flag <= 'b1; //key_flag输出一个clk高脉冲
NS <= DOWN;
end
else if(key_pedge)begin
en_cnt <= ; //20ms时间内发生上升沿,失能计数器,保持空闲状态
NS <= IDLE;
end
DOWN:
begin
key_flag <= ;
key_state <= ;
if(key_pedge)begin
NS <= FILTER_UP;
en_cnt <= 'b1; //使能计数器
end
else
NS <= DOWN;
end
FILTER_UP:
if(cnt >= 'd999_999)begin
en_cnt <= ;
NS <= IDLE; //20ms时间到,失能计数器,进入稳定状态
key_flag <= ;
end
else if(key_nedge)begin
en_cnt <= ; //20ms时间内发生上升沿,失能计数器,保持按下稳定状态
NS <= DOWN;
end
default:
NS <= IDLE;
endcase
endmodule

  4、编写tsetbench进行仿真测试(查看所生成的波形、状态转移图、RTL视图)

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter_tb
// Description:
//////////////////////////////////////////////////////////////////////////////////
`define clk_period //100M系统时钟 module key_filter_tb(); reg clk; //50M时钟信号
reg rst; //低电平复位
reg key_in; //按键输入
wire key_flag; //消抖完毕输出脉冲
wire key_state; //按键状态输出 //例化测试模块
key_filter key_filter_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.key_in(key_in), //按键输入
.key_flag(key_flag), //消抖完毕输出脉冲
.key_state(key_state) //按键状态输出
);
//产生100M时钟信号
initial clk = ;
always #(`clk_period / ) clk <= ~clk; //开始测试
initial begin
rst <= ; //系统复位
key_in <= ; //按键处于空闲状态
#(`clk_period * );
rst <= ;
#10_000_000; //延时10ms,方便观察按键按下现象
//开始模拟按键按下抖动
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
//产生一个稳定的低电平大于20ms,代表按键稳定
key_in <= ; #30_000_000;
//模拟释放抖动
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
key_in <= ; #;
//产生一个稳定的高电平大于20ms,代表释放稳定
key_in <= ; #30_000_000;
$stop;
end
endmodule

  测试结果如下:

  对于testbench,这个文件写的很繁琐,可以进行一下优化,首先

  1、利用$random函数产生随机延时值模拟抖动,用法如下:

reg [:]randnum;
randnum = $random % ; //产生一个-49~49内的随机数
randnum = {$random} % ; //产生一个0 ~ 50内的随机数

 2、利用task/endtask将重复代码进行封装,用法如下:

task <任务名>;
<语句1>
<语句2>
<语句3>
....
endtask

  优化后的testbench测试文件如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter_tb
// Description:
//////////////////////////////////////////////////////////////////////////////////
`define clk_period //100M系统时钟 module key_filter_tb(); reg clk; //50M时钟信号
reg rst; //低电平复位
reg key_in; //按键输入
wire key_flag; //消抖完毕输出脉冲
wire key_state; //按键状态输出 reg [:]rand_time; //按键抖动时随机时长 //例化测试模块
key_filter key_filter_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.key_in(key_in), //按键输入
.key_flag(key_flag), //消抖完毕输出脉冲
.key_state(key_state) //按键状态输出
);
//产生100M时钟信号
initial clk = ;
always #(`clk_period / ) clk <= ~clk; //开始测试
initial begin
rst <= ; //系统复位
key_in <= ; //按键处于空闲状态
#(`clk_period * );
rst <= ;
#10_000_000; //延时10ms,方便观察按键按下现象
press_key; #; //第一次按下按键
press_key; #; //第二次按下按键
press_key; #; //第三次按下按键
$stop;
end task press_key;
begin
//开始模拟按键按下抖动
repeat()begin
rand_time = {$random} % ;
#rand_time key_in = ~key_in;
end
//产生一个稳定的低电平大于20ms,代表按键稳定
key_in = ;
#30_000_000;
//模拟释放抖动
repeat()begin
rand_time = {$random} % ;
#rand_time key_in = ~key_in;
end
//产生一个稳定的高电平大于20ms,代表释放稳定
key_in = ;
#30_000_000;
end
endtask
endmodule

  测试结果如下(总共按键三次),可以看到,优化后的testbench比之前的测试更加精准,更加真实的模拟现实情况:

  这里,因为这是一个按键模型,对外提供的功能就是实际中按键的作用,这也就是仿真模型,所以可以独立写一个testbench作为按键模型,方便例化调用:

  按键模型的仿真代码如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_module
// Description: 仿真按键模型
////////////////////////////////////////////////////////////////////////////////// module key_module(
output reg key //对外输出按键信号
); reg [:]rand_time; //按键抖动时随机时长 initial begin
key <= ; //按键处于空闲状态
#10_000_000; //延时10ms,方便观察按键按下现象
press_key; #; //第一次按下按键
press_key; #; //第二次按下按键
press_key; #; //第三次按下按键
$stop;
end task press_key;
begin
//开始模拟按键按下抖动
repeat()begin
rand_time = {$random} % ;
#rand_time key = ~key;
end
//产生一个稳定的低电平大于20ms,代表按键稳定
key = ;
#30_000_000;
//模拟释放抖动
repeat()begin
rand_time = {$random} % ;
#rand_time key = ~key;
end
//产生一个稳定的高电平大于20ms,代表释放稳定
key = ;
#30_000_000;
end
endtask
endmodule

  这样一来,在testebench中测试就可以直接调用该仿真模型,优化到最后的testbench如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter_tb
// Description: 基于按键模型进行测试
//////////////////////////////////////////////////////////////////////////////////
`define clk_period //100M系统时钟 module key_filter_tb(); reg clk; //50M时钟信号
reg rst; //低电平复位
wire key_in; //按键输入
wire key_flag; //消抖完毕输出脉冲
wire key_state; //按键状态输出 //例化测试模块
key_filter key_filter_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.key_in(key_in), //按键输入
.key_flag(key_flag), //消抖完毕输出脉冲
.key_state(key_state) //按键状态输出
);
//例化按键模型
key_module key1(
.key(key_in) //对外输出按键信号
);
//产生100M时钟信号
initial clk = ;
always #(`clk_period / ) clk <= ~clk; //开始测试
initial begin
rst = ; //系统复位
#(`clk_period * );
rst = ;
end endmodule

  测试结果和之前完全一样,但testcench更加简洁,按键仿真也更加方便以后调用,至此,按键消抖模块就设计测试完毕,如有兴趣,可以进行进一步的设计,控制led或数码管计数。

  

FPGA学习笔记(八)—— 状态机设计实例之独立按键消抖的更多相关文章

  1. Java IO学习笔记八:Netty入门

    作者:Grey 原文地址:Java IO学习笔记八:Netty入门 多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用. Netty+ ...

  2. python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑

    python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件?当我们点开下载页时, 一般 ...

  3. Go语言学习笔记八: 数组

    Go语言学习笔记八: 数组 数组地球人都知道.所以只说说Go语言的特殊(奇葩)写法. 我一直在想一个人参与了两种语言的设计,但是最后两种语言的语法差异这么大.这是自己否定自己么,为什么不与之前统一一下 ...

  4. 【opencv学习笔记八】创建TrackBar轨迹条

    createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便.首先大家要记住,它往往会和一个回调函数配合起来使用.先看下他的函数 ...

  5. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  6. Redis学习笔记八:集群模式

    作者:Grey 原文地址:Redis学习笔记八:集群模式 前面提到的Redis学习笔记七:主从复制和哨兵只能解决Redis的单点压力大和单点故障问题,接下来要讲的Redis Cluster模式,主要是 ...

  7. python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取政府网新闻内容

    python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ...

  8. thinkphp学习笔记5—模块化设计

    原文:thinkphp学习笔记5-模块化设计 1.模块结构 完整的ThinkPHP用用围绕模块/控制器/操作设计,并支持多个入口文件盒多级控制.ThinkPHP默认PATHINFO模式,如下: htt ...

  9. Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

随机推荐

  1. Spring + MyBaits 日志初始化两遍的问题

    偶然发现一个问题,记录一下以备查询. 问题:系统启动时发现日志初始化了两次 14:28:04.798 [main] DEBUG org.apache.ibatis.logging.LogFactory ...

  2. 《Windows核心编程系列》十谈谈同步设备IO与异步设备IO之异步IO

    同步设备IO与异步设备IO之异步IO介绍 设备IO与cpu速度甚至是内存访问相比较都是比较慢的,而且更不可预测.虽然如此,通过使用异步设备IO我们仍然能够创造出更高效的程序. 同步IO时,发出IO请求 ...

  3. XML 基础学习

    在w3school看到了XML的教程,想到以前工作学习中也接触到了XML,但只是简单搜索了解了下,没有认真去学习XML的基础,所以现在认真看下其基础部分,并写篇博客作为笔记记录下. XML 简介 XM ...

  4. Java标识符的习惯命名规范

    1 常量标识符:全部用大写字母和下划线表示.如SALES_MAX 2 类名或接口名:标识符用大写字母开头.如CreditCard 3 变量名和方法名:以小写字母开头,单词之间不要有分隔符,第二 及后面 ...

  5. 四大开源协议比较:BSD、Apache、GPL、LGPL【转载】

    四大开源协议原文链接 本文参考文献:http://www.fsf.org/licensing/licenses/ 现今存在的开源协议很多,而经过Open Source Initiative组织通过批准 ...

  6. 转 11g RAC R2 体系结构---Grid

    基于agent的管理方式 从oracle 11.2开始出现了多用户的概念,oracle开始使用一组多线程的daemon来同时支持多个用户的使用.管理资源,这些daemon叫做Agent.这些Agent ...

  7. 转 php中$_request与$_post、$_get的区别

    php中有$_REQUEST与$_POST.$_GET用于接受表单数据,当时他们有何种区别,什么时候用那种最好. 一.$_REQUEST与$_POST.$_GET的区别和特点 $_REQUEST[]具 ...

  8. vijos P1412多人背包 DP的前k优解

    https://vijos.org/p/1412 把dp设成,dp[i][v][k]表示在前i项中,拥有v这个背包,的第k大解是什么. 那么dp[i][v][1...k]就是在dp[i - 1][v] ...

  9. 移动端UI自动化Appium测试——Windows系统Appium环境配置

    1.安装JDK,官网下载即可,这里用的1.8,环境变量配置 2.安装Android sdk,API >= 17,环境变量配置 3.安装Nodejs,官网http://nodejs.org/dow ...

  10. Java实现三角形计数

    题: 解: 这道题考的是穷举的算法. 一开始看到这道题的时候,本能的想到用递归实现.但使用递归的话数据少没问题,数据多了之后会抛栈溢出的异常.我查了一下,原因是使用递归创建了太多的变量, 每个变量创建 ...