Hugo ʕ•ᴥ•ʔ Bear Blog

💡 特别鸣谢野火 FPGA 的教学与帮助!
本节相关的教程 link 📌

- 第 19 章 按键消抖模块的设计与验证:
  - 19.1 章节导读:
  - 19.2 理论学习:
    - 19.2.1 硬件消抖:
    - 19.2.2 软件消抖:
  - 19.3 实战演练:
    - 19.3.1 实验目标:
    - 19.3.2 程序设计:
      - 1. 模块框图:
      - 2. 波形图绘制:
      - 3. 代码编写:
      - 4. 仿真验证:
        - 仿真文件编写:
        - 仿真波形分析:
  - 19.4 章末总结:
  - 19.5 拓展训练:

在学习的过程中,枯燥乏味其实是难免的,系统地学习大多数知识的过程也都是这样,与人相处也是这样,反者道之动,但是我们应该庆幸的是,虽然学习知识的本身有点难受,但是这些知识实际上帮我们节省了很多时间,我们所学到的,可以说是知识的精华,唯一要做的,就是细嚼慢咽,耐下心来,好好揣摩。 前人的踩坑经历为我们规避了很多的陷阱和坑道,同时系统的文献也让我们更舒适地咀嚼知识,如果没有什么特别重要的事情, 就躲到一个角落,好好看看。

19.1 章节导读


本章节中,我们要根据机械按键的构造与原理,设计并实现按键消抖模块。以开发板上的物理按键作为输入信号,使用设计的按键消抖模块对输入的按键信号进行消抖处理, 输出能够正常使用的按键触发信号。

19.2 理论学习

如图 19-1 所示,我们所使用的按键开关为机械弹性开关,当机械触点断开、闭合时, 由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而做的措施就是按键消抖。按键抖动原理图如图 19-2 所示。

抖动时间的长短由按键的机械特性决定,一般为 5ms~10ms。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起一次按键被误读多次。为确保控制器对按键的一次闭合仅作一次处理,必须去除按键的抖动。在按键闭合稳定时读取按键的状态,并且必须判别到按键释放稳定后再作处理。 消抖是为了避免在按键按下或是抬起时电平剧烈抖动带来的影响。按键的消抖,可用硬件或软件两种方法。

19.2.1 硬件消抖


在按键个数较少时可用硬件方法消除键抖动。如图 19-3 所示的 RS 触发器为常用的硬件去抖。图中两个与非门构成一个 RS 触发器。当按键未按下时,输出为 0;当键按下时, 输出为 1。此时即使用按键的机械性能,使按键因弹性抖动而产生瞬时断开(抖动跳开 B), 只要按键不返回原始状态 A,双稳态电路的状态不改变,输出保持为 0,不会产生抖动的波形。也就是说,即使 B 点的电压波形是抖动的,但经双稳态电路之后,其输出为正规的矩形波。这一点通过分析 RS 触发器的工作过程很容易得到验证。

19.2.2 软件消抖


如果按键个数较多,常用软件方法去抖,即检测出按键闭合后执行一个延时程序,根据抖动的时间为 5ms~10ms,我们产生一个 20ms 的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。

19.3 实战演练


前面已经分析了按键抖动的机理和消除按键抖动的几种方案,我们知道硬件消抖会使用一些额外的器件占用电路板上的空间,从而在一定程度上增加了 PCB 布局布线的复杂度,所以我们用软件消抖的方式来实现去抖动的操作,去抖动后的效果是当按键按下后能够准确检测到按键被按下了一次,而不会因机械抖动发生按键重复多次按下的现象。

19.3.1 实验目标


利用所学知识,设计并实现一个按键消抖模块,将外部输入的单比特按键信号做消抖处理后输出,输出信号正常可被其他模块调用。

19.3.2 程序设计


本实验工程只涉及一个模块,就是按键消抖模块。接下来,我们将从模块框图、波形图绘制等各个进行讲解。

1. 模块框图


因为我们要计数过滤掉按键抖动的时间,所以计数器是必不可少的,所以我们设计的模块一定会用到时序电路,所以时钟 sys_clk 和复位 sys_rst_n 信号一定先加上,而且是输入信号,另外还有一个输入信号就是按键的输入 key_in,我们最终要实现的就是对输入的 key_in 信号进行去抖动,输出信号为去抖动后的稳定的按键信号 key_flag。根据上面的分析设计出的 visio 框图如图 19-4 所示。

2. 波形图绘制


首先我们从实际问题出发,分析抖动的本质,再想办法去消除抖动。我们先把波形图的三个输入信号画好,抖动我们就模拟和真实中的情况一样,即当按键被按下和按键被释放时都会有抖动,也就是有前抖动和后抖动,这两种抖动都会对我们的设计产生一定的影响,会让我们的系统误判为按键被多次按下。我们需要做的就是要准确判断出稳定的按下的那一次状态。

按键的抖动会产生如图 19-5 所示的毛刺,毛刺中会有低电平的情况,但是因为机械抖动的原因很快又回拉高了,如果我们把其中的每次的低电平和高电平都采集到,那么相当于是按键被按下了好多次,而不是我们想要的一次,所以我们一定要把这段抖动给滤除掉,这段抖动的时间我们通过前面的分析是已知的,抖动的时间是小于 10ms 的,而当有 20ms 的时间内都没有抖动就说明按键已经处于稳定状态了,也就是说我们可以做一个计数器来进行计数,计数 20ms 的时间,也就是说只要 20ms 的时间内都没有抖动产生,那结果是什么电平就是什么,我们需要做的是找到最后一次抖动的时间是在什么时候,才能够开启这 20ms 的计数,否则这 20ms 内不能够保证都是我们的安全时间。 当然有的同学可能会说我在单片机的设计中都是检测第一次按键为低电平了就开始计数,然后延时一段大于 30ms 的时间后再检测得到的按键电平就是稳定的按键信号,难道这种方式不可以吗?这种方式虽然也是可以的,但不是最好的,因为这会浪费我们的不必要时间,也就是说虽然抖动的时间理论上不会大于 10ms,但是具体是多少可能每次按键实验的结果都不相同,如果我们每次都按照最大的抖动时间 10ms 来计算无疑会“多”考虑了一些时间,所以我们采用一种更“节约”时间的方法,我们添加一个名为 cnt_20ms 用于计数 20ms 时间的计数器,每当系统检测到按键输入信号为低电平时 cnt_20ms 计数器就开始计数,在 cnt_20ms 计数器计数期间内,如果再次检测到按键为高电平则说明上次检测到的低电平一定是个抖动,那么我们就将这个计数器清零,总结为简单的一句话就是:当系统检测到按键为低电平时 cnt_20ms 计数器就计数,当检测到按键为高电平时 cnt_20ms 计数器就清零。讲到这里主要问题我们就已经解决了,然后要考虑 cnt_20ms 计数器计数个数和计数满了后该怎么处理以及滤除抖动后的输出信号 key_flag 什么时候拉高、拉低的问题。 首先是考虑计数器的问题,根据我们使用的 50Mhz 的晶振来计算,cnt_20ms 计数器计数 20ms 时间所需要计数的个数为 999_999,计数器计数满后我们习惯性先清零,如果有问题我们根据分析再进行修改。而 key_flag 信号则是一个脉冲信号,也就是只有一个时钟周期的高电平,且当 cnt_20ms 计数器计数到 999_999 时才拉高,而这个高电平只能存在一个。按照 cnt_20ms 计数器计数到 999_999 时清零来分析,其波形图如图 19-6 所示,按键会因为低电平的时间太久,会存在多个 20ms 的间,cnt_20ms 计数器计数满清零多次, 这样就会有多个计数值为 999_999 的情况,从而导致 key_flag 信号产生多次脉冲,这显然是我们不想要的结果。那我们需要分析是 cnt_20ms 计数器清零的问题还是 key_flag 信号拉高时间的问题。经分析 key_flag 信号即使不是在 cnt_20ms 计数器计数到 999_999 时拉高而在其他时间拉高也会出现同样的问题,所以那只能怀疑是 cnt_20ms 计数器清零的条件不对了。刚开始的时候 cnt_20ms 计数器清零已经有一个条件了,那就是当输入信号 key_in 只要为高电平就将 cnt_20ms 计数器清零,那这里我们就让 cnt_20ms 计数器计数满后保持为 999_999 而不清零,等待输入信号 key_in 为高电平的时候再清零。

修改 cnt_20ms 计数器清零后的结果如图 19-7 所示,我们可以发现 key_flag 信号确实不会产生多个了,而是出现了新的问题,key_flag 信号也不是脉冲了,是一个长长的电平信号,这也不是我们想要的结果,其根本原因是 cnt_20ms 计数器计数到 999_999 后保持在 999_999 的时间太久导致的。

针对上面的探索,我们最终灵机一动,发现 cnt_20ms 计数器计数到 999_998 的次数只有一个,而且最接近 999_999,在既保证去抖动时间的前提下使 key_flag 信号只产生一个脉冲信号。最终的波形结果如图 19-8 所示。

3. 代码编写


在波形图绘制小节,我们结合相关理论知识,讲解并绘制了按键消抖模块波形图。我们参照波形图,编写模块参考代码。参考代码编写较为简单,且有详细注释,此处不再过多讲解,按键消抖模块参考代码,具体见代码清单 19-1。

4. 仿真验证


仿真文件编写

按键消模块参考代码编写完毕,为验证代码正确性,对模块参考代码进行仿真验证。 编写模块仿真代码,具体见代码清单 19-2。仿真参考代码中有有详细注释,此处不再过多介绍。

仿真波形分析


模块仿真波形如下,图 19-9 为按键消抖模块整体仿真波形图;图 19-10、图 19-11 和图 19-12 为按键消抖模块局部仿真波形图,分别为前抖动部分、稳定部分和后抖动部分的仿真波形。由整体和局部仿真波形可以看出,模块仿真波形和绘制波形图,各信号波形变化一致,模块通过仿真验证。

19.4 章末总结


通过本例我们可以发现在画波形图时不一定可以保证 100%正确,根据分析,我们可以适当的调整,这也是设计之前画波形图的意义所在,而不是一抹黑的、漫无目的的调试代码。 其实在项目的设计过程中并不是一帆风顺的,往往会遇到各种各样的问题,通过经过思考与探索尝试使我们最终得出正确的结果。如果总是站在上帝视角去考虑问题,而抛弃分析真理的过程,这对于学习来说将是巨大的损失。 希望学习者能深入体会设计中遇到的问题并掌握设计分析的方法,养成一个善于思考敢于尝试的习惯,学会通过分析绘制波形图来提前预判潜在的设计问题。

19.5 拓展训练


本例我们实现的是一个按键的消抖操作,如果我们有很多按键都需要消抖的话,理论上需要实例化多次按键消抖模块,那有什么方法可以更方便的实现这种操作吗?大家可以想一想。

#野火FPGA