2019/01/08,第一个判断是否有按键按下的操作好像有问题,有空在修改!

红色为修改部分:

问题描述:

当三个独立按键的某一个被按下后,相应的LED被点亮;再次按下后,LED熄灭,按键控制LED亮灭


下面是LED灯的原理图:

可见,LED是低电平亮,高电平灭。

事实上,控制LED等的亮灭很简单,不是问题,对应的代码段如下:

reg d1;
reg d2;
reg d3;always @ (posedge clk or negedge rst_n)if (!rst_n) begind1 <= 1'b0;d2 <= 1'b0;d3 <= 1'b0;endelse begin       //某个按键值变化时,LED将做亮灭翻转if ( led_ctrl[0] ) d1 <= ~d1;    if ( led_ctrl[1] ) d2 <= ~d2;if ( led_ctrl[2] ) d3 <= ~d3;endassign led_d3 = d1 ? 1'b1 : 1'b0;       //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;

我来做出解释:

首先,系统复位时候,先把寄存器变量d1、d2、d3清零,由下面这三天语句可知,这三个寄存器变量清零后,对应的led灯就是低电平,也就是亮。

assign led_d3 = d1 ? 1'b1 : 1'b0;        //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;

然后如果某个按键按下,对应的led_ctrl[?]就会变成高电平,此时对应的d?就会翻转电平值,也就是说原来led?是低电平,按键按下后,d?就会翻转为高电平,此时,对应的led灯自然也会由亮变灭了。继续按下按键,led的亮灭又会翻转。

(这些分析都是本人通过代码以及实验结果证明的,确实如此。)


不得不说,如果仅仅是上面的分析那么简单,就省事了,同时这个实验也没啥意思了。

事实的情况我根据理解做如下的描述:

按键按下时候,按键值有一段时间的低电平,我们必须在低电平这段时间内的某一个瞬间采样按键值来控制led灯的亮灭。

可能不能采样对是一个值得研究的问题。

先给出按键的大概电路图;

对这个电路图的一些解释:

独立按键一般有2组管脚,这2组管脚在按键未被按下时是断开的,在按键被按下时则是导通的。

基于此原理,我们一般会把按键的一个管脚接地,另一个管脚上拉到VCC,并且也连接到GPIO。这样,在按键未被按下时,GPIO的连接状态为上拉到VCC,则键值为1;按键被按下时,GPIO虽然还是上拉到VCC,但同时被导通的另一个管脚拉到地了,所以它的键值实际上是0。

再给出按键的波形图:

如下图是按键按下时候的波形图,一个是理想情况下的波形,另一个是事实情况的模拟图:

可见,按下以及释放的瞬间都有一个抖动。

在按键按下或者释放的时候都会出现一个不稳定的抖动时间,如果不处理好这个抖动时间,我们就无法正确采集到正确有效的按键值,所以我们的设计中必须有效消除按键抖动。

好了,按键消抖的问题摆在面前了,我们怎么来消除抖动呢?这是一个有意思的问题。


看了网上好多解释,通过画图来解释这个消除抖动的原理,可是总是越看越糊涂,感觉这种不伦不类的图已经对我的理解产生一些误导了,于是找到了特权同学的视频看了一遍,说实话,看的似乎明白了,但是不是太清晰,学习这东西必须花时间,自己动手动脑分析才能够理清楚。

我就通过分析代码的方式来理解这个消抖的过程。

`timescale 1ns / 1ps//说明:当三个独立按键的某一个被按下后,相应的LED被点亮;
//      再次按下后,LED熄灭,按键控制LED亮灭module sw_debounce(clk,rst_n,sw1_n,sw2_n,sw3_n,led_d1,led_d2,led_d3);input   clk;    //主时钟信号,25MHz
input   rst_n;  //复位信号,低有效
input   sw1_n,sw2_n,sw3_n;  //三个独立按键,低表示按下
output  led_d1,led_d2,led_d3;   //发光二极管,分别由按键控制//---------------------------------------------------------------------------
reg key_rst;  always @(posedge clk  or negedge rst_n)if (!rst_n) key_rst <= 1'b1;else key_rst <= sw3_n&sw2_n&sw1_n;reg key_rst_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中always @ ( posedge clk  or negedge rst_n )if (!rst_n) key_rst_r <= 1'b1;else key_rst_r <= key_rst;//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an = key_rst_r & (~key_rst);
/*
key_rst     1 1 1 0 0 1
~key_rst    0 0 0 1 1 0
key_rst_r     1 1 1 0 0 1
key_an        0 0 1 0 0
*/
//---------------------------------------------------------------------------
reg[19:0]  cnt; //计数寄存器always @ (posedge clk  or negedge rst_n)if (!rst_n) cnt <= 20'd0;  //异步复位else if(key_an) cnt <=20'd0;else cnt <= cnt + 1'b1;reg[2:0] low_sw;always @(posedge clk  or negedge rst_n)if (!rst_n) low_sw <= 3'b111;else if (cnt == 20'hfffff)     //满20ms,将按键值锁存到寄存器low_sw中     cnt == 20'hffffflow_sw <= {sw3_n,sw2_n,sw1_n};//---------------------------------------------------------------------------
reg  [2:0] low_sw_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中always @ ( posedge clk  or negedge rst_n )if (!rst_n) low_sw_r <= 3'b111;else low_sw_r <= low_sw;
/*
low_sw      111 111 111 110 110 110
~low_sw     000 000 000 001 001 001
low_sw_r        111 111 111 110 110 110led_ctrl 000 000 000 001 000 000 */
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);reg d1;
reg d2;
reg d3;always @ (posedge clk or negedge rst_n)if (!rst_n) begind1 <= 1'b0;d2 <= 1'b0;d3 <= 1'b0;endelse begin       //某个按键值变化时,LED将做亮灭翻转if ( led_ctrl[0] ) d1 <= ~d1;    if ( led_ctrl[1] ) d2 <= ~d2;if ( led_ctrl[2] ) d3 <= ~d3;endassign led_d3 = d1 ? 1'b1 : 1'b0;       //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;endmodule

首先点名输入输出:

input   clk;    //主时钟信号,25MHz
input   rst_n;    //复位信号,低有效
input   sw1_n,sw2_n,sw3_n;     //三个独立按键,低表示按下
output  led_d1,led_d2,led_d3;    //发光二极管,分别由按键控制

这里需要提出一点的是,输入输出在开头都声明好,至于中间需要用到的一些网线变量(wire)以及寄存器变量(reg),我们在用到的位置声明,不失为一种便于理解的代码格式。


reg key_rst;

always @(posedge clk  or negedge rst_n)
    if (!rst_n) key_rst <= 1'b1;
    else key_rst <= sw3_n&sw2_n&sw1_n;

reg key_rst_r;       //每个时钟周期的上升沿将key_rst信号锁存到key_rst_r中

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n) key_rst_r <= 1'b1;
    else key_rst_r <= key_rst;
   
//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期 
wire key_an = key_rst_r & (~key_rst);
/*
key_rst     1 1 1 0 0 1
~key_rst   0 0 0 1 1 0
key_rst_r     1 1 1 0 0 1
key_an        0 0 1 0 0
*/
//---------------------------------------------------------------------------

这里定义了一个1位的寄存器变量(key_rst),用来存放三个按键状态值的与值,由于按键值在没有按下的时候,就是拉高的状态,所以当复位信号有效时,就把key_rst赋值为1,也就是每一位都是高电平。

没有复位的时候,就在每个时钟的上升沿到来时,把三个按键值的与状态赋值给key_rst;

这两句的解释对应的代码为:

reg key_rst;

always @(posedge clk  or negedge rst_n)
    if (!rst_n) key_rst <= 1'b1;
    else key_rst <= sw3_n&sw2_n&sw1_n};


紧接着,我们讲下面这一小块代码:

reg key_rst_r;

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n) key_rst_r <= 1'b1;
    else key_rst_r <= key_rst;

又定义了一个1位的寄存器变量key_rst_r,这个寄存器变量在复位信号有效时,也都赋值为1;

否则,在每个时钟上升沿都会将key_rst的值赋值给key_rst_r;

这就意味这什么呢?

意味着这两个变量之间增加了一个触发器或寄存器,key_rst_r延时一拍(延迟了一个时钟周期)

也就是相当于:

key_rst     1 1 1 0 0 1
key_rst_r     1 1 1 0 0 1

为什么要这么处理呢?

继续看下去,就会知道这有多么的巧妙?


//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期 
wire key_an = key_rst_r & (~key_rst);

我不太喜欢这么写这句语法,更喜欢如下写法:

wire key_an;

assign key_an = key_rst_r & (~key_rst);

定了一个线网型变量key_an,它的值是上面的寄存器key_rst取反后和key_rst_r的与。

为什么相与呢?

key_rst     1 1 1 0 0 1
~key_rst    0 0 0 1 1 0
key_rst_r      1 1 1 0 0 1
key_an         0 0 1 0 0

这样处理的目的是边沿检测也就是key_rst当中如果某一位由1变为0之后,这种处理就会在跳变时产生一个高电平脉冲,持续一个时钟周期,如果这样看还不算直观的话,我就勉为其难,画个图给你看看:

看到了吗?检测到了key_rst的一个下降沿,这个下降沿到来的时候key_an就会出现一个高电平脉冲,持续一个时钟周期。

(如果有小伙伴想知道我这个波形图如何画的,就得看我的这篇博文喽:对如何使用WaveDrom画波形图的研究(案例分解分析),也就是一个工具,可以画波形图,用来写文章使用,我只开发了一点功能,其他功能有兴趣的可以自己开发哦。)


接着对这段代码分解分析:

reg[19:0]  cnt;    //计数寄存器

always @ (posedge clk  or negedge rst_n)
    if (!rst_n) cnt <= 20'd0;    //异步复位
    else if(key_an) cnt <=20'd0;
    else cnt <= cnt + 1'b1;
  
reg[2:0] low_sw;

always @(posedge clk  or negedge rst_n)
    if (!rst_n) low_sw <= 3'b111;
    else if (cnt == 20'hfffff)     //满20ms,将按键值锁存到寄存器low_sw中     cnt == 20'hfffff
      low_sw <= {sw3_n,sw2_n,sw1_n};
      
//---------------------------------------------------------------------------
reg  [2:0] low_sw_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n) low_sw_r <= 3'b111;
    else low_sw_r <= low_sw;
/*
low_sw        111 111 111 110 110 110  
~low_sw      000 000 000 001 001 001
low_sw_r        111 111 111 110 110 110

led_ctrl    000 000 000 001 000 000 
   */
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期 
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);

这里说明一下,就是按键抖动的时间,公认的大约是20ms左右,而按键按下的那段时间,至少也是几百ms。


reg[19:0]  cnt;    //计数寄存器

always @ (posedge clk  or negedge rst_n)
    if (!rst_n) cnt <= 20'd0;    //异步复位
    else if(key_an) cnt <=20'd0;
    else cnt <= cnt + 1'b1;

又定义了一个20位计时计数器寄存器变量cnt,最大计数1048575次,大约1000_000次。

时钟频率是50MHz,那么时钟周期为20ns,那么计数1000_000次,大概是20ms。

好了,继续分析上面一小段代码,系统复位时,计数器清零,如果检测到按键值跳变(有可能是抖动引起),也就是key_an有高电平脉冲,计数器也清零,否则就一直计数。


reg[2:0] low_sw;

always @(posedge clk  or negedge rst_n)
    if (!rst_n) low_sw <= 3'b111;
    else if (cnt == 20'hfffff)     //满20ms,将按键值锁存到寄存器low_sw中     cnt == 20'hfffff
      low_sw <= {sw3_n,sw2_n,sw1_n};

这里定义了一个3位的寄存器变量low_sw,如果系统复位,则low_sw赋值为111,否则如果计数器计数到最大,也就是大概计数到20ms时,将按键值赋值给low_sw;

上面的意思就是20ms锁存一次按键值。


reg  [2:0] low_sw_r;       //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中

always @ ( posedge clk  or negedge rst_n )
    if (!rst_n) low_sw_r <= 3'b111;
    else low_sw_r <= low_sw;

再次定义一个寄存器变量low_sw_r,作用是存放延迟一拍的low_sw。

复位时候,low_sw_r赋值为111;

否则将low_sw的值赋值为low_sw_r,这不就相当于延迟了一拍吗?

意义呢?

继续看呗:

//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期 
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);

这和上面的分析一样,也是一个边沿检测的做法;

同样做出形象解释:

low_sw 1 1 1 1 0 1
~low_sw 0 0 0 0 1 0
low_sw_r       1 1 1
led_ctrl        0 1 0

这说明在led_ctrl的第二位出现了一个高电平值,说明了它代表的按键按下了。

low_sw        111 111 111 110 110 110                //这一行的意思是某一个按键按下时的情况
~low_sw      000 000 000 001 001 001              //取反
low_sw_r            111 111 111 110 110 110            //延迟一拍

led_ctrl                000 000 001 000 000 000                 //相与,在相应的位置出现一个高电平脉冲


上面这段代码讲完了,之后就到了控制led灯亮灭的模块了,这部分内容我在开头就讲了,所以不再重复。接下来,我就是要总览全局的时刻了,从总体来认识下,第一次边沿检测的意义在哪里?计数20ms锁存一下键值的意义?第二次边沿检测的意义?

第一次边沿检测,检测到了按键状态的跳变,如果有跳变,则key_an出现一个高电平脉冲,这个跳变是什么引起的呢?有可能是抖动引起的。

如果是抖动引起的话,那么就到达解释计数器的意义了。

从代码可以看出,如果key_an出现高电平,则计数器清零,重新计数,由于抖动也只有20ms左右而已,在抖动器件,计数器肯定计数不到20ms就会被清零,直到抖动期过了,才能计数到20ms,这样锁定按键值,得到的才是非抖动的按键值,根据按键值的状态控制led灯的亮灭就可以了。

岂不完美!

这样就到达了滤除抖动的效果。

至于第二次边沿检测,当然就是检验按键是否按下,如果某个按键按下了,就会出现一个高电平脉冲,led_ctrl的某一位是高电平,并且持续一个周期的时间,通过此来控制led灯的亮灭。


感觉如果仔细看的话,应该会很明白了,很开心,知识是需要动手动脑才能够理解的。

这种代码的设计形式是最简单的,也很有效,实践证明,是可以的。

《按键消抖与LED控制》实验的个人思考与总结相关推荐

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

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

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

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

  3. 关于按键消抖以及LED灯控制的一个实例

    要求: 1.未按建则所有LED全黑: 2.按K1按钮,则用前8个LED灯二进制显示25: 3.按K2按钮,则12只LED合并显示流水灯效果,3个LED点亮并向右流水. 注:是HR-240B FPGA  ...

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

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

  5. ARM(I.MX6ULL) EPIT定时器中断实验、定时器按键消抖

    参考:Linux之ARM (I.MX6ULL) EPIT定时器详解 作者:一只青木呀 发布时间: 2020-09-20 10:03:37 网址:https://blog.csdn.net/weixin ...

  6. Linux驱动中按键消抖原理

    为什么要用定时器来做按键消抖? 用到按键就要处理因为机械结构带来的按键抖动问题,也就是按键消抖.前面的实验中都是直接使用了延时函数来实现消抖,因为简单,但是直接用延时函数来实现消抖会浪费 CPU 性能 ...

  7. ARM(IMX6U)裸机按键输入实验(BSP+SDK、GPIO输入与输出、按键消抖)

    参考:Linux之ARM(IMX6U)裸机按键输入实验(GPIO的输出与输入) 作者:一只青木呀 发布时间: 2020-08-17 21:43:37 网址:https://blog.csdn.net/ ...

  8. [FPGA入门笔记](十):按键消抖实验

    简介 今天购买了AXLINX AX7020的开发板,从今天开始每一个例程都要做文档记录,为自己加油. 本实验,基于ALINX AX7020开发板,芯片为xc7z020clg400-2.开发板输入时钟为 ...

  9. 可编程逻辑器件之按键消抖实验

    一.实验目标 能够熟练的进行可编程逻辑器件开发,能够通过具体工程需求进行需求分析.模块划分.代码编写.功能仿真.综合分析.板级验证,能够独立正确的进行实验操作,培养学生的工程实践研究能力和动手实践能力 ...

最新文章

  1. 【Servlet】request对象获取请求头数据和用户数据
  2. cut\grep\sort\tr
  3. Linux各个版本防火墙操作(CentOS Ubuntu)
  4. 4000字超干货!《统计学习方法》啃书指南
  5. 数据结构与算法之-----图(搜索算法)
  6. jmeter 中的Parameters 和Body Data的区别
  7. mysql order by if函数_mysql order by
  8. 会计从业人员管理系统_湖南省会计从业人员网上服务大厅
  9. 鬼压床到底是怎么回事?
  10. springboot整合任务安全
  11. 怎么避免论文查重率过高的情况?
  12. SVG互动排版公众号图文 『两次物体移动与展开长图』 模板代码
  13. MyBatis 入门级配置文件
  14. 应用在电视触摸屏中的十四通道智能触摸芯片
  15. 使用snap安装mosquitto并且进行初步配置
  16. 一款简单好用的数字温度传感器芯片介绍
  17. DHTML中重要的属性方法 (献给DHTML初学者)
  18. 聊聊Redis的数据热点问题
  19. 编译3.0的linux内核,1-3-编译Linux内核
  20. 我看2008—IT之最

热门文章

  1. 【Vegas2006】自我介绍for校青春风采大赛
  2. 300英雄服务器维护多久,300英雄7月19日停机更新公告
  3. ajax提交到mysql_利用ajax的方式来提交数据到后台数据库及交互功能
  4. python server page_python web-server
  5. Android判断CPU是否为x86,如何判断. NET 程序集是否编译为 x86,x64或者任何 CPU_visual-studio_开发99编程知识库...
  6. oracle votedisk 参数,11g r2 rac votedisk 及 ocr 磁盘破坏后,基于ocr备份的恢复步骤
  7. mysql 范围内日期列表,mysql – 将日期列表条件中的日期转换为日期范围列表
  8. Paddle 环境中 使用LeNet在MNIST数据集实现图像分类
  9. 周末都花费在智能车实验室,结果......
  10. 2021春季学期-创新设计与实践-Lesson3