问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 121 人浏览分享

开启左侧

[MILIANPAI-F01-EG4D]FPGA程序设计基础实验连载-07 FPGA按键消抖实验

[复制链接]
121 0
安路-FPGA课程
安路课程: 基础入门 » 新手入门实验
安路系列: EG4
本帖最后由 UT发布 于 2025-4-9 09:13 编辑

软件版本:TD_5.6.4_Release_97693
操作系统:WIN11 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!
1概述

按键是最为常见的电子元器件之一,在电子设计中应用广泛。按键的消抖,是指按键在闭合或松开的瞬间伴随着一连串的抖动,这样的抖动将直接影响设计系统的稳定性,降低响应灵敏度。因此,必须对抖动进行处理,即消除抖动的影响。本章节中,我们根据机械按键的构造与原理,设计并实现按键消抖。

在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:

1:完成了TD软件安装

2:完成了modelsim安装以及TD库的编译

3:掌握了TD仿真环境的设置

4:掌握了modelsim通过do文件启动仿真

1.1 按键消抖简介

按键开关,最早出现在日本,称之为敏感型开关,我们开发板上使用的机械按键是按键的一种,特点是:接触电阻小,按键按下或弹起时有滴答清脆声但由于其构造和原理,在按键闭合及断开的瞬间均伴随有一连串的抖动

image.jpg image.jpg

                                                  按键结构示意图

从按键结构示意图中可以看到按键存在一个反作用弹簧,其内部结构是靠金属弹片受力变化来实现通断向按键开关操作方向施压时开关功能闭合接通,当撤销压力时开关即断开。当按下或者松开时,由于弹簧的物理特性,不会立马闭合或者断开,往往会在闭合或断开的短时间内产生机械抖动,机械抖动便会产生电平的抖动。按键从按下再到松开的过程中,其变化如下图所示:

image.jpg

按键按下到松开的电平变化图

按键的机械特性,决定着按键的抖动时间,产生的抖动次数以及间隔时间均是不可预期的,一般抖动时间在5ms~10ms通常检测到输入信号状态为低电平时表示按键按下,当FPGA的晶振频率为25MHz,即系统时钟40ns时,以40ns的周期去采样信号的状态,由于采样时间远小于按键抖动时间,FPGA在检测信号状态时会认为按键多次被按下,这就需要通过消除抖动,以确保按键被按下时只检测到一次低电平,意味着每次在按键闭合或松开期间,跳过这段抖动时间,再检测按键的状态。实际工程中,有很多消抖方案,按键消抖可分为硬件消抖和软件消抖。硬件消抖主要使用 RS 触发器或电容等方法在硬件电路上实现消抖,一般在按键较少时使用。软件消抖可以通过简单的延时实现按键的消抖动,也可以通过状态机的方式完成。 本章利用FPGA内部来设计消抖,即采取软件消抖。

延时方法:

一般机械按键在被按下和释放时,产生的抖动不会超过20ms,因此可以通过简单的延时来检测,当检测到输入信号为低电平时,开始计时20ms,计时结束后检测此时按键信号状态,当信号状态为高电平时,表示按键抖动过程,等待下一次低电平到来后再次开始计时检测按键信号状态;当信号为低电平时,表示按键为按下状态,产生一个脉冲,表示按键按下状态。当检测到输入信号为高电平时,开始计时20ms,计时结束后检测此时按键信号状态,当信号状态为低电平时,表示按键抖动过程,等待下一次高电平到来后再次开始计时检测按键信号状态;当信号为高电平时,产生一个脉冲,表示按键释放状态。

image.jpg

             按键消抖原理图


状态机方法:

将按键按下到释放的整个过程分为4个过程:按键未被按下或释放按键后的空闲状态;按键按下前的抖动状态;按下按键并保持低电平的稳定状态;按键释放前的抖动状态。

image.jpg

·空闲状态(IDEL):该状态一直处于高电平,当检测到下降沿,状态跳转到FILTER0

·抖动状态(FILTER0):该状态下需要根据是否计满数和是否有上升沿来做出不同的反应。当低电平保持时间超过计时时间,表示按键按下,状态跳转至DOWN;当计时未满且检测到上升沿,表示此时为抖动,状态跳转回IDEL

·稳定状态(DOWN):该状态一直处于低电平,当检测到上升沿,状态跳转到FILTER1,否则保持DOWN

·释放状态(FILTER1):该状态与FILTER0类似,高电平保持时间超过计时时间,表示按键释放,状态跳转至IDLE;当计时未满且检测到下降沿,表示此时为抖动,状态跳转回DOWN

image.jpg

       按键消抖状态转换图


触发器内部的数据形成需要一定的时间,在同步系统中,输入信号总系统时钟同步,能达到寄存器的时序要求,亚稳态不会发生。只要系统中有异步元件,亚稳态就无法避免,亚稳态主要发生在异步信号检测、跨时钟域信号传输以及复位电路等常用设计中。

image.jpg
       亚稳态产生示意


建立时间Tsu:上升沿到来之前,数据要维持一定时间的稳定状态,数据稳定不变的一个最小时间。


保持时间Th:触发器时钟上升沿到来以后,在一定的时间内,数据也要保持不变,数据保持不变的最小时间。

寄存器延迟(Tco)上升沿以后到产生震荡的过程叫做寄存器延迟。

决断时间(Tmet)输出信号稳定前,信号震荡的时间,这段时间是不确定的。


亚稳态就是不稳定的状态,是指触发器无法在某个规定时间段内达到一个可确认的状态。如上图,在CLKTusTh时间内,输入数据D发生改变,不是稳定在0或者1,这会导致采集数据时在01之间振荡,这段时间称为决断时间,经过这段时间后,采集的数据将随机稳定到01上,输出与输入没有必然的关系,会导致对后续电路判断不准因此在FPGA设计中要降低亚稳态发生的概率。

亚稳态产生的概率计算:


概率 = (建立时间 + 保持时间)/ 采集时钟周期

当系统采用25M时钟对一个外部信号进行采集,采集时钟周期为40ns,亚稳态产生的概率为: 1ns/40ns = 2.5%。这一概率会随时钟频率升高而升高。若有亚稳态产生,我们就要对进行消除,常用对亚稳态消除有三种方式:

1)对异步信号进行同步处理;

2)采用FIFO对跨时钟域数据通信进行缓冲设计;

3)对复位电路采用异步复位、同步释放方式处理。

1.2 硬件电路分析

4个按键,其中1路硬件复位按键,按下按键就可以给系统实现一个复位,系统部分恢复默认状态,需要重新加载FPGA3PL按键,通过软件给系统一个高/低电平信号来实现复位操作,程序部分状态恢复到默认。由原理图可以看出,按键未按下时 ,线路断开,没有电流通过按键,那么IOVCC_ADJ是等电位,此时IO为高电平;按键按下时,VCC_ADJ经过电阻和按键,接到GND,形成一条通路,此时IO为低电平,因此系统即可通过检测 IO 的电平来判断按键的状态。


image.jpg
image.jpg image.jpg
按键未按下时的等效电路             按键按下时的等效电路

2按键消抖程序设计2.1 系统框图

本次实验通过按键实现LED左移的效果,采用延时方法消除按键抖动,该方法代码简单使用方便,不占用其他资源。本实验共有两个模块,分别为顶层模块key_top和按键消抖模块key,通过调用key模块实现按键去抖动的功能。顶层模块key_top需要三个输入的端口,分别为系统时钟、系统复位、按键,效果输出为4位的 LED 端口。

image.jpg

按键消抖模块需要三个输入的端口,分别为系统时钟、系统复位、按键,输出两个端口,分别为按键按下和释放。判断按键状态是否变化,需要使用计时器模块对前后信号进行对比,计时器计时需要基于系统时钟I_sysclk来计数,除此之外,添加系统复位使程序恢复至默认状态,按键输入信号I_key消抖后的输出结果表示为O_key_down(按键按下)和O_key_up(按键释放)。

image.jpg
2.2 状态机流程图

10ms定时器模块:MLK-S200 FPGA开发板上晶振产生的系统时钟频率是25MHz,周期为40ns10ms则需要计数(10ms/40ns=250_000-1=249_999)次,计数249_999时,产生一个脉冲信号并置1

消抖状态机模块:状态机共有四个状态,使用10ms时模块产生的脉冲信号来触发抖状态机模块,每间隔10ms判断一次按键状态。当检测到按键信号为低电平,下一个10ms检测到按键信号不为低电平时表示此时为按键抖动;再次检测到按键信号为低电平,下一个10ms检测到按键信号为低电平时判断此时按键按下;当检测到按键信号为高电平,下一个10ms检测到按键信号不为高电平时表示此时为按键抖动;再次检测到按键信号为高电平,下一个10ms检测到按键信号为高电平时判断此时按键释放。

image.jpg

寄存亚稳态模块我们采用对异步信号进行同步处理的方式去除亚稳态的影响,key进行异步多次寄存,让数据下一个时钟沿之后保持稳定,从而消除亚稳态寄存器具有消除亚稳态功能,使用多级寄存器消除亚稳态的危害,单Bit数据从低速到高速,一般采取打两拍进行对亚稳态的消除,打一拍的话,如果决断时间过长导致下一个时钟沿时数据还处于决断时间,此时我们数据正常输出的概率是70-80%因此可以通过再打一拍的方式降低亚稳态发生的概率,第二拍以后数据正常输出的概率是99%以上

2.3 程序源码

按键去抖源码

  1. module key #
  2. (
  3. parameter REF_CLK = 32'd25000000    //设置时钟为参数,方便上层调用和修改
  4. )
  5. (
  6. input  I_sysclk,
  7. input  I_rstn,
  8. input  I_key,
  9. output O_key_down,
  10. output O_key_up
  11. );
  12. //设置10MS的时钟分频计数
  13. localparam  T10MS = (REF_CLK/100 - 1'b1);
  14. //设置按键状态机的状态
  15. localparam  KEY_S0 = 2'd0;
  16. localparam  KEY_S1 = 2'd1;
  17. localparam  KEY_S2 = 2'd2;
  18. localparam  KEY_S3 = 2'd3;
  19. reg [24:0] t10ms_cnt = 25'd0;
  20. reg [3:0] key_r = 4'd0;
  21. reg [1:0] key_s = 2'b0;
  22. reg [1:0] key_s_r = 2'b0;
  23. wire t10ms_done ;
  24. assign t10ms_done = (t10ms_cnt == T10MS);//10ms计数是否完成
  25. assign O_key_down   = (key_s == KEY_S2)&&( key_s_r == KEY_S1);//是否按下
  26. assign O_key_up     = (key_s == KEY_S0)&&( key_s_r == KEY_S3);//是否弹起
  27. //10ms timer counter
  28. always @(posedge I_sysclk or negedge I_rstn)begin
  29.     if(I_rstn == 1'b0)begin
  30.         t10ms_cnt <= 25'd0;//复位
  31.     end
  32.     else if(t10ms_cnt < T10MS)
  33.         t10ms_cnt <= t10ms_cnt + 1'b1;//未计满则+1
  34.     else
  35.         t10ms_cnt <= 25'd0;//计满则置0,开始新一轮计数
  36. end
  37. always @(posedge I_sysclk)begin
  38.     key_s_r <= key_s;//将key_s的状态缓存一拍
  39. end
  40. always @(posedge I_sysclk)begin
  41.     key_r <= {key_r[2:0],I_key};//将I_key的状态缓存一拍
  42. end
  43. always @(posedge I_sysclk or negedge I_rstn)begin
  44.     if(I_rstn == 1'b0)begin
  45.         key_s <= KEY_S0;
  46.     end
  47.     else if(t10ms_done)begin    //每10ms进行一次状态判断和转移
  48.         case(key_s)
  49.         KEY_S0:begin
  50.            if(!key_r[3])    //收到第一个按键的低电平信号,不能判断是否为毛刺
  51.                key_s <= KEY_S1;     //转到状态S1
  52.         end  
  53.         KEY_S1:begin//recheck key done  //第二次判断按键是否按下
  54.            if(!key_r[3])
  55.                key_s <= KEY_S2; //按下转入S2状态
  56.             else
  57.                key_s <= KEY_S0; //没按下,判断为毛刺,转入S0状态,重新等待触发
  58.         end
  59.         KEY_S2:begin//wait key up   //确定按键按下后
  60.            if(key_r[3])   //等待按键松开,接收到按键的高电平信号
  61.                key_s <= KEY_S3;  //不能确定是否为毛刺,转状态S3
  62.         end  
  63.         KEY_S3:begin//recheck key up
  64.            if(key_r[3])  //第二次判断按键是否松开
  65.               key_s <= KEY_S0; //依然检测到按键是松开的状态,转入S0状态,等待触发
  66.         end
  67.         endcase                  
  68.     end
  69. end
  70. endmodule
复制代码

以上代码中,首先把系统时钟做分频,产生10ms 的分配时钟使能信号。在设计的状态机中,分4个状态

KEY_S0:判断按键是否按下,如果是,转移到状态KEY_S1

KEY_S110ms后再次判断按键是否按下,如果是,转移状态到KEY_S2,否则继续回到KEY_S0

KEY_S2:判断按键是否抬起,如果是,转移状态到KEY_S3

KEY_S310ms后再次判断按键是否抬起,如果是,转移状态到KEY_S0,否则继续回到KEY_S2

当状态从KEY_S1 转到KEY_S2代表依次按钮按下key输出一次高电平。


顶层调用按键去抖模块演示代码:

演示代码中通过按键按下调整LED的点亮顺序,确认按键工作正常

  1. module key_top #
  2. (
  3. parameter REF_CLK = 32'd25000000
  4. )
  5. (
  6. input  I_sysclk,
  7. input  I_rstn,
  8. input  I_key1,
  9. input  I_key2,
  10. output [3:0]O_LED
  11. );
  12. reg [3:0] LED_r;//存储LED灯的状态
  13. wire key1_down,key2_down;
  14. wire lock,clk0;
  15. assign O_LED = LED_r;//将存储的LED灯的状态输出
  16. pll pll_inst(
  17.   .refclk(I_sysclk),
  18.   .reset(~I_rstn),
  19.   .extlock(lock),
  20.   .clk0_out (clk0)
  21. );
  22. always @(posedge I_sysclk or negedge I_rstn)begin
  23.     if(I_rstn == 1'b0)
  24.        LED_r <= 4'b0111; //设置LED的初始状态,低电平有效
  25.     else if(key1_down)
  26.        LED_r<={LED_r[2:0],LED_r[3]};//LED灯右移
  27.     else if(key2_down)
  28.        LED_r<={LED_r[0],LED_r[3:1]};//LED灯左移  
  29. end
  30.          
  31. //例化两个按键,分别控制LED左右移动
  32. key#(
  33. .REF_CLK(REF_CLK)
  34. )
  35. key_u1
  36. (
  37. .I_sysclk(I_sysclk),
  38. .I_rstn(I_rstn),
  39. .I_key(I_key1),
  40. .O_key_down(key1_down),
  41. .O_key_up()
  42. );
  43. key#(
  44. .REF_CLK(REF_CLK)
  45. )
  46. key_u2
  47. (
  48. .I_sysclk(I_sysclk),
  49. .I_rstn(I_rstn),
  50. .I_key(I_key2),
  51. .O_key_down(key2_down),
  52. .O_key_up()
  53. );
  54. endmodule
复制代码
3 FPGA工程

fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的FPGA型号以对应的开发板上芯片为准

image.jpg

米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹

01_rtl:放用户编写的rtl代码

02_sim:仿真文件或者工程

03_ip:放使用到的ip文件

04_pin:fpgapin脚约束文件或者时序约束文件

05_boot:放编译好的bit或者bin文件(一般为空)

06_doc:放本一些相关文档(一般为空)

image.jpg
4 Modelsim仿真4.1 准备工作

Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验

仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了:

  1. module tb_key();
  2. reg I_sysclk,I_rstn,I_key1,I_key2;
  3. wire  [3:0]O_LED;
  4. key_top#
  5. (
  6. .REF_CLK(25000000)
  7. )
  8. key_top_inst
  9. (
  10. .I_sysclk(I_sysclk),
  11. .I_rstn(I_rstn),
  12. .I_key1(I_key1),
  13. .I_key2(I_key2),
  14. .O_LED(O_LED)
  15. );
  16. initial
  17.         begin
  18.                 // Initialize Inputs
  19.                 I_sysclk = 0;
  20.                 I_rstn = 0;
  21.                 #100;
  22.                 I_rstn =1;
  23.                 I_key1 = 1;
  24.                 I_key2 = 1;
  25.                 #10000;
  26.                 forever
  27.                         begin
  28.                                 I_key1 = 0;
  29.                                 // Wait 100 ns for global reset to finish
  30.                                 #100;
  31.                                 I_key1=1; #1000;
  32.                                 I_key1=0; #1000;
  33.                                 I_key1=1; #2000;
  34.                                 I_key1=0; #5000;
  35.                                 #20000000;
  36.                                 I_key1=1;
  37.                                 I_key1=0; #1000;
  38.                 I_key1=1; #2000;
  39.                 I_key1=0; #1000;
  40.                 I_key1=1; #2000;
  41.                                 #20000000;
  42.                                 I_key2=1; #1000;
  43.                                 I_key2=0; #1000;
  44.                                 I_key2=1; #2000;
  45.                                 I_key2=0; #5000;
  46.                                 #20000000;
  47.                                 I_key2=1;
  48.                                 I_key2=0; #1000;
  49.                 I_key2=1; #2000;
  50.                 I_key2=0; #1000;
  51.                 I_key2=1; #2000;
  52.                                 I_key2=0; #1000;        
  53.                                 #20000000;               
  54.                                 I_key2=1; #1000;        
  55.                         end
  56.         end
  57. always #20 I_sysclk=~I_sysclk;
  58. endmodule
复制代码
4.2 启动modelsim仿真

启动后,右击需要观察的信号,添加到波形窗口

image.jpg

设置restart

image.jpg

设置运行100ms(如果运行时间太长可以修改小一些)

image.jpg

可以看到按键状态,key_downkey_up的情况,以及成功滤除了按键的抖动

image.jpg
5下载演示

下载程序前,先确保FPGA工程已经编译。

5.1 硬件连接

(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)

请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)


image.jpg
5.2 运行结果

(该教程为通用型教程,教程中仅展示一款示例开发板的上板现象,具体现象以所购买的开发板型号以及配套代码上板现象为准。)

每按一次底板上的KEY2按键,LED灯会依次右移;按KEY3键,LED灯依次左移

image.jpg
















您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

294

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版