[X]关闭

[米联派-安路飞龙DR1-FPSOC] FPGA基础篇连载-07 FPGA按键消抖实验

文档创建者:FPGA课程
浏览次数:281
最后更新:2024-09-09
文档课程分类-安路-DR1
安路-DR1: FPSOC-DR1-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
​ 软件版本:Anlogic -TD5.9.1-DR1_ES1.1
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
实验平台:米联客-MLKPAI-SF01-DR1M90M开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

1概述
按键是最为常见的电子元器件之一,在电子设计中应用广泛。按键的消抖,是指按键在闭合或松开的瞬间伴随着一连串的抖动,这样的抖动将直接影响设计系统的稳定性,降低响应灵敏度。因此,必须对抖动进行处理,即消除抖动的影响。本章节中,我们根据机械按键的构造与原理,设计并实现按键消抖。
在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:
1:完成了TD软件安装
2:完成了modelsim安装以及TD库的编译
3:掌握了TD仿真环境的设置
4:掌握了modesim通过do文件启动仿真
1.1 按键消抖简介
按键开关,最早出现在日本,称之为敏感型开关,我们开发板上使用的机械按键是按键的一种,特点是:接触电阻小,按键按下或弹起时有滴答的清脆声。但由于其构造和原理,在按键闭合及断开的瞬间均伴随有一连串的抖动。
629b3aad11e04c01b65408711179c222.jpg
e94336aabfc2495ea3de06d2ab90e0a9.jpg
按键结构示意图
从按键结构示意图中可以看到按键存在一个反作用弹簧,其内部结构是靠金属弹片受力变化来实现通断向按键开关操作方向施压时开关功能闭合接通,当撤销压力时开关即断开。当按下或者松开时,由于弹簧的物理特性,不会立马闭合或者断开,往往会在闭合或断开的短时间内产生机械抖动,机械抖动便会产生电平的抖动。按键从按下再到松开的过程中,其变化如下图所示:
0f81bce4157c41d0b4234f64adc85d7c.jpg
按键按下到松开的电平变化图
按键的机械特性,决定着按键的抖动时间,产生的抖动次数以及间隔时间均是不可预期的,一般抖动时间在5ms~10ms。通常检测到输入信号状态为低电平时表示按键按下,当FPGA的晶振频率为100MHz,即系统时钟10ns时,以10ns的周期去采样信号的状态,由于采样时间远小于按键抖动时间,FPGA在检测信号状态时会认为按键多次被按下,这就需要通过消除抖动,以确保按键被按下时只检测到一次低电平,意味着每次在按键闭合或松开期间,跳过这段抖动时间,再检测按键的状态。实际工程中,有很多消抖方案,按键消抖可分为硬件消抖和软件消抖。硬件消抖主要使用 RS 触发器或电容等方法在硬件电路上实现消抖,一般在按键较少时使用。软件消抖可以通过简单的延时实现按键的消抖动,也可以通过状态机的方式完成。 本章利用FPGA内部来设计消抖,即采取软件消抖。
延时方法:
一般机械按键在被按下和释放时,产生的抖动不会超过20ms,因此可以通过简单的延时来检测,当检测到输入信号为低电平时,开始计时20ms,计时结束后检测此时按键信号状态,当信号状态为高电平时,表示按键抖动过程,等待下一次低电平到来后再次开始计时检测按键信号状态;当信号为低电平时,表示按键为按下状态,产生一个脉冲,表示按键按下状态。当检测到输入信号为高电平时,开始计时20ms,计时结束后检测此时按键信号状态,当信号状态为低电平时,表示按键抖动过程,等待下一次高电平到来后再次开始计时检测按键信号状态;当信号为高电平时,产生一个脉冲,表示按键释放状态。
6fa95735a0654ec4b79a29f42b478c02.jpg
按键消抖原理图
状态机方法:
将按键按下到释放的整个过程分为4个过程:按键未被按下或释放按键后的空闲状态;按键按下前的抖动状态;按下按键并保持低电平的稳定状态;按键释放前的抖动状态。
271d107c8328476fa86fa652dc9a9b39.jpg
·空闲状态(IDEL):该状态一直处于高电平,当检测到下降沿,状态跳转到FILTER0。
·抖动状态(FILTER0):该状态下需要根据是否计满数和是否有上升沿来做出不同的反应。当低电平保持时间超过计时时间,表示按键按下,状态跳转至DOWN;当计时未满且检测到上升沿,表示此时为抖动,状态跳转回IDEL。
·稳定状态(DOWN):该状态一直处于低电平,当检测到上升沿,状态跳转到FILTER1,否则保持DOWN。
·释放状态(FILTER1):该状态与FILTER0类似,高电平保持时间超过计时时间,表示按键释放,状态跳转至IDLE;当计时未满且检测到下降沿,表示此时为抖动,状态跳转回DOWN。
3977e83c43344bc9bb813bba1f1bc07b.jpg
按键消抖状态转换图
触发器内部的数据形成需要一定的时间,在同步系统中,输入信号总与系统时钟同步,能达到寄存器的时序要求,亚稳态就不会发生。只要系统中有异步元件,亚稳态就无法避免,亚稳态主要发生在异步信号检测、跨时钟域信号传输以及复位电路等常用设计中。
8e167d3770bc4a439e3f45782968206d.jpg
亚稳态产生示意图
建立时间Tsu:上升沿到来之前,数据要维持一定时间的稳定状态,数据稳定不变的一个最小时间。
保持时间Th:触发器时钟上升沿到来以后,在一定的时间内,数据也要保持不变,数据保持不变的最小时间。
寄存器延迟(Tco):上升沿以后到产生震荡的过程叫做寄存器延迟。
决断时间(Tmet):输出信号稳定前,信号震荡的时间,这段时间是不确定的。
亚稳态就是不稳定的状态,是指触发器无法在某个规定时间段内达到一个可确认的状态。如上图,在CLK的Tus和Th时间内,输入数据D发生改变,不是稳定在0或者1,这会导致采集数据时在0和1之间振荡,这段时间称为决断时间,经过这段时间后,采集的数据将随机稳定到0或1上,输出与输入没有必然的关系,会导致对后续电路判断不准,因此在FPGA设计中要降低亚稳态发生的概率。
亚稳态产生的概率计算:
概率 = (建立时间 + 保持时间)/ 采集时钟周期
当系统采用100M时钟对一个外部信号进行采集,采集时钟周期为10ns,亚稳态产生的概率为: 1ns/10ns = 10%
有亚稳态产生,我们就要对亚稳态进行消除,常用对亚稳态消除有三种方式:
(1)对异步信号进行同步处理;
(2)采用FIFO对跨时钟域数据通信进行缓冲设计;
(3)对复位电路采用异步复位、同步释放方式处理。
1.2 硬件电路分析
5个按键,其中1路硬件复位按键,按下按键就可以给系统实现一个复位,系统部分恢复默认状态,需要重新加载FPGA。1路PS按键,3路PL按键,通过软件给系统一个高/低电平信号来实现复位操作,程序部分状态恢复到默认。由原理图可以看出,按键未按下时 ,线路断开,没有电流通过按键,那么IO和VCC_ADJ是等电位,此时IO为高电平;按键按下时,VCC_ADJ经过电阻和按键,接到GND,形成一条通路,此时IO为低电平,因此系统即可通过检测 IO 的电平来判断按键的状态。
23313af88aed4ad6a9f5a3a19c75779f.jpg
bff3776e446b49f596571dbb7c52e403.jpg
按键未按下时的等效电路
326d3ed4ce2f4de58e70d9cbe7c574d5.jpg
按键按下时的等效电路
2按键消抖程序设计
2.1系统框图
本次实验通过按键实现LED左移的效果,采用延时方法消除按键抖动,该方法代码简单使用方便,不占用其他资源。本实验共有两个模块,分别为顶层模块key_top和按键消抖模块key,通过调用key模块实现按键去抖动的功能。顶层模块key_top需要三个输入的端口,分别为系统时钟、系统复位、按键,效果输出为4位的 LED 端口。
4c52829df52a4084ab39d41c5ad5d81e.jpg
按键消抖模块需要三个输入的端口,分别为系统时钟、系统复位、按键,输出两个端口,分别为按键按下和释放。判断按键状态是否变化,需要使用计时器模块对前后信号进行对比,计时器计时需要基于系统时钟I_sysclk来计数,除此之外,添加系统复位使程序恢复至默认状态,按键输入信号I_key消抖后的输出结果表示为O_key_down(按键按下)和O_key_up(按键释放)。
5eb1326a1a64409ba22b2decda4d0901.jpg
2.2状态机流程图
10ms定时器模块:FPGA开发板上晶振产生的系统时钟频率是25MHz,周期为40ns,10ms则需要计数(10ms/40ns=250_000-1=249_999)次,计数249_999时,产生一个脉冲信号并置1。
消抖状态机模块:状态机共有四个状态,使用10ms计时模块产生的脉冲信号来触发消抖状态机模块,每间隔10ms判断一次按键状态。当检测到按键信号为低电平,下一个10ms检测到按键信号不为低电平时表示此时为按键抖动;再次检测到按键信号为低电平,下一个10ms检测到按键信号为低电平时判断此时按键按下;当检测到按键信号为高电平,下一个10ms检测到按键信号不为高电平时表示此时为按键抖动;再次检测到按键信号为高电平,下一个10ms检测到按键信号为高电平时判断此时按键释放。
e82119627fb7474dafd2064ddfb54648.jpg
寄存去亚稳态模块我们采用对异步信号进行同步处理的方式去除亚稳态的影响,对key进行异步多次寄存,让数据在下一个时钟沿之后保持稳定,从而消除亚稳态。寄存器具有消除亚稳态功能,使用多级寄存器消除亚稳态的危害,单Bit数据从低速到高速,一般采取打两拍进行对亚稳态的消除,打一拍的话,如果决断时间过长导致下一个时钟沿时数据还处于决断时间,此时我们数据正常输出的概率是70-80%。因此可以通过再打一拍的方式降低亚稳态发生的概率,第二拍以后数据正常输出的概率是99%以上。
2.3程序源码
按键去抖源码
  1. `timescale 1ns / 1ns
  2. module key #
  3. (
  4. parameter REF_CLK = 64'd25_000_000        //设置时钟为参数,方便上层调用修改
  5. )
  6. (
  7. input  I_sysclk,
  8. input  I_rstn,
  9. input  I_key,
  10. output O_key_down,
  11. output O_key_up
  12. );

  13. parameter  T10MS = (REF_CLK/50 - 1'b1);                      //设置10MS的时钟分频计数
  14. parameter  KEY_S0 = 2'd0;                                      //设置按键状态机的状态
  15. parameter  KEY_S1 = 2'd1;
  16. parameter  KEY_S2 = 2'd2;
  17. parameter  KEY_S3 = 2'd3;

  18. reg [32:0] t10ms_cnt = 25'd0;
  19. reg [3:0] key_r = 4'd0;
  20. reg [1:0] key_s = 2'b0;
  21. reg [1:0] key_s_r = 2'b0;
  22. wire t10ms_done ;

  23. assign t10ms_done = (t10ms_cnt == T10MS);
  24. assign O_key_down   = (key_s == KEY_S2)&&( key_s_r == KEY_S1);    //设置判断按键按下时的条件
  25. assign O_key_up     = (key_s == KEY_S0)&&( key_s_r == KEY_S3);     //设置判断按键松开时的条件

  26. //10ms timer counter
  27. always @(posedge I_sysclk or negedge I_rstn)begin                 //系统时钟的上升沿以及复位的下降沿触发
  28.     if(I_rstn == 1'b0)begin
  29.         t10ms_cnt <= 25'd0;                                          //系统复位
  30.     end
  31.     else if(t10ms_cnt < T10MS)                                        //10ms计数,目标值是 T10MS
  32.         t10ms_cnt <= t10ms_cnt + 1'b1;                                //未达到目标t10ms_cnt+1
  33.     else
  34.         t10ms_cnt <= 25'd0;                                             //达到目标值复位
  35. end
  36. always @(posedge I_sysclk)begin                                     //将key_s的状态缓存一拍
  37.     key_s_r <= key_s;
  38. end
  39. always @(posedge I_sysclk)begin                                     //将I_key的状态缓存一拍
  40.     key_r <= {key_r[2:0],I_key};
  41. end
  42. always @(posedge I_sysclk or negedge I_rstn)begin                //设置状态机,设定按键的4种状态
  43.     if(I_rstn == 1'b0)begin
  44.         key_s <= KEY_S0;
  45.     end
  46.     else if(t10ms_done)begin                                           //触发条件为t10ms_done,说明下列所有的状态转移都是每10ms
  47.         case(key_s)                                                     //触发一次
  48.         KEY_S0:begin
  49.            if(!key_r[3])                                                //收到第一个按键的低电平信号,不能判断是否为毛刺
  50.                key_s <= KEY_S1;                                        //转到状态S1
  51.         end  
  52.         KEY_S1:begin//recheck key done                               //第二次判断按键是否按下
  53.            if(!key_r[3])
  54.                key_s <= KEY_S2;                                        //按下转入S2状态
  55.             else
  56.                key_s <= KEY_S0;                                        //没按下,判断为毛刺,转入S0状态,等待触发
  57.         end
  58.         KEY_S2:begin//wait key up                                     //确定按键按下后
  59.            if(key_r[3])                                                //等待按键松开,接收到按键的高电平信号,
  60.                key_s <= KEY_S3;                                        //不能确定是否为毛刺
  61.         end                                                              //转入状态S3
  62.         KEY_S3:begin//recheck key up                                   
  63.            if(key_r[3])                                                 //第二次判断案件是否松开
  64.               key_s <= KEY_S0;                                           //依然检测到按键是松开的状态,转入S0状态,等待触发
  65.         end
  66.         endcase                  
  67.     end
  68. end
  69. 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. `timescale 1ns / 1ns

  2. module key_top #
  3. (
  4. parameter REF_CLK = 32'd25                                    //设置时间MHz参数,方便上层调用
  5. )
  6. (
  7. input  I_sysclk,//系统时钟信号
  8. input  I_rstn,//全局复位
  9. input  I_key1,//按键
  10. input  I_key2,
  11. output [7:0]O_led   //LED灯输出
  12. );

  13. reg [7:0] led_r;                                                       //存储LED灯的状态

  14. wire key1_down;
  15. wire key2_down;

  16. assign O_led = led_r;                                                 //将存储的LED灯的状态输出

  17. always @(posedge I_sysclk or negedge I_rstn)begin
  18.     if(I_rstn == 1'b0)
  19.        led_r <= 8'b01111111;                                           //设置LED的初始状态
  20.     else if(key1_down)
  21.        led_r<={led_r[6:0],led_r[7]};                              //将LED灯左移动
  22.     else if(key2_down)
  23.        led_r<={led_r[0],led_r[7:1]};                                 //将LED灯右移动
  24. end

  25. key#(                                                                   //例化了两个KEY按键,一个负责左移一个负责右移
  26. .REF_CLK(REF_CLK)                                                       //底板按键资源不够,本次实验仅展示左移
  27. )
  28. key_u1
  29. (
  30. .I_sysclk(I_sysclk),
  31. .I_rstn(I_rstn),
  32. .I_key(I_key1),
  33. .O_key_down(key1_down),
  34. .O_key_up()
  35. );
  36. key#(
  37. .REF_CLK(REF_CLK)
  38. )
  39. key_u2
  40. (
  41. .I_sysclk(I_sysclk),
  42. .I_rstn(I_rstn),
  43. .I_key(I_key2),
  44. .O_key_down(key2_down),
  45. .O_key_up()
  46. );
  47. endmodule
复制代码
3FPGA工程
fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的FPGA型号以对应的开发板上芯片为准
fd1cb4b1e64447049152014b1a334c57.jpg
米联客的代码管理规范,在对应的FPGA工程路径下创建uisrc路径,并且创建以下文件夹
01_rtl:放用户编写的rtl代码
02_sim:仿真文件或者工程
03_ip:放使用到的ip文件
04_pin:放fpga的pin脚约束文件或者时序约束文件
05_boot:放编译好的bit或者bin文件(一般为空)
06_doc:放本一些相关文档(一般为空)
f1c40b78763b4e34991e036a0f3eb955.jpg
4Modelsim仿真4.1准备工作
Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了:
  1. `timescale 1ns / 1ns

  2. module key_top #
  3. (
  4. parameter REF_CLK = 32'd25                                    //设置时间MHz参数,方便上层调用
  5. )
  6. (
  7. input  I_sysclk,//系统时钟信号
  8. input  I_rstn,//全局复位
  9. input  I_key1,//按键
  10. input  I_key2,
  11. output [7:0]O_led   //LED灯输出
  12. );

  13. reg [7:0] led_r;                                                       //存储LED灯的状态

  14. wire key1_down;
  15. wire key2_down;

  16. assign O_led = led_r;                                                 //将存储的LED灯的状态输出

  17. always @(posedge I_sysclk or negedge I_rstn)begin
  18.     if(I_rstn == 1'b0)
  19.        led_r <= 8'b01111111;                                           //设置LED的初始状态
  20.     else if(key1_down)
  21.        led_r<={led_r[6:0],led_r[7]};                              //将LED灯左移动
  22.     else if(key2_down)
  23.        led_r<={led_r[0],led_r[7:1]};                                 //将LED灯右移动
  24. end

  25. key#(                                                                   //例化了两个KEY按键,一个负责左移一个负责右移
  26. .REF_CLK(REF_CLK)                                                       //底板按键资源不够,本次实验仅展示左移
  27. )
  28. key_u1
  29. (
  30. .I_sysclk(I_sysclk),
  31. .I_rstn(I_rstn),
  32. .I_key(I_key1),
  33. .O_key_down(key1_down),
  34. .O_key_up()
  35. );
  36. key#(
  37. .REF_CLK(REF_CLK)
  38. )
  39. key_u2
  40. (
  41. .I_sysclk(I_sysclk),
  42. .I_rstn(I_rstn),
  43. .I_key(I_key2),
  44. .O_key_down(key2_down),
  45. .O_key_up()
  46. );
  47. endmodule
复制代码
4.2启动modelsim仿真
启动后,右击需要观察的信号,添加到波形窗口
f97d310f45934b339dcae9c1025f59ed.jpg
设置restart
5c9067f407774beaa1ef9e1ac03a7dce.jpg
设置运行100ms(如果运行时间太长可以修改小一些)
316a6aed5a0840769051b8441cfdaf14.jpg
可以看到按键状态,key_down和key_up的情况,以及成功滤除了按键的抖动
cc9c84c425e44bd7b195e66776a5738f.jpg

5下载演示
下载程序前,先确保FPGA工程已经编译。
5.1硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

430ce14740e04e1ea8a5cb0a594711c8.jpg
5.2运行结果
(该教程为通用型教程,教程中仅展示一款示例开发板的上板现象,具体现象以所购买的开发板型号以及配套代码上板现象为准。)
每按一次底板上的KEY1/KEY2按键,LED灯会依次发生移位,KEY3按键复位灯到初始状态
08a3799cf35b4038910857f421a5dfd1.jpg


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

本版积分规则