[X]关闭

[米联客-XILINX-H3_CZ08_7100] FPGA基础篇连载-07 FPGA按钮去抖实验

文档创建者:FPGA课程
浏览次数:383
最后更新:2024-08-22
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-FPGA部分 » 2_FPGA实验篇(仅旗舰) » 1-FPGA基础入门实验
本帖最后由 FPGA课程 于 2024-8-22 19:55 编辑

​ 软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!



1概述按键的消抖,是指按键在闭合或松开的瞬间伴随着一连串的抖动,这样的抖动将直接影响设计系统的稳定性,降低响应灵敏度。因此,必须对抖动进行处理,即消除抖动的影响。实际工程中,有很多消抖方案,如RS触发器消抖,电容充放电消抖,软件消抖。本章利用FPGA内部来设计消抖,即采取软件消抖。
按键的机械特性,决定着按键的抖动时间,一般抖动时间在5ms~10ms。消抖,也意味着,每次在按键闭合或松开期间,跳过这段抖动时间,再检测按键的状态。只要通过简单的延时就可实现按键的消抖动。
a50c30cab84145bba89d57074827fe75.jpg
2硬件电路分析硬件接口和子卡模块请阅读“附录 1”
配套工程的 FPGA PIN 脚定义路径为 fpga_prj/uisrc/04_pin/ fpga_pin.xdc。
3系统框图10ms定时器模块用于触发去抖状态机每间隔10ms判断一次按键状态。
寄存去亚稳态模块是对key进行异步多次寄存,消除亚稳态
去抖状态机每间隔10ms判断一次按键状态,分别判断按键的按下,按键的弹开
之后根据去抖状态机的状态切换判断按键是有效按下和有效弹开。
47e2aca7e3814eb4adf494f81df81fe2.jpg
4状态机流程图状态机每间隔10ms完成一次按键检测,首先判断按键是否按下,如果按下再次判断按键是否按下,如果按下,检测按键是否松开,也是判断两次。如果按键按下后第二次判断按键没有按下,那么就代表按键按下的不稳定,继续回到开始状态机。
8aa1344146cf4b9691f4db6e43d86593.jpg
5 key模块的设计由于按键滤波是比较比较通用的一个程序,因此我们可以把一个通用的程序设置为一个模块,方便后面重复使用。
  1. `timescale 1ns / 1ps

  2. module key #
  3. (
  4.     parameter CLK_FREQ = 100000000
  5. )
  6. (
  7. input clk_i,
  8. input key_i,
  9. output key_cap
  10. );
  11. //10ms
  12. parameter CNT_10MS = (CLK_FREQ/100 - 1'b1);
  13. parameter KEY_S0 = 2'd0;
  14. parameter KEY_S1 = 2'd1;
  15. parameter KEY_S2 = 2'd2;
  16. parameter KEY_S3 = 2'd3;

  17. reg [24:0] cnt10ms = 25'd0;
  18. (*mark_debug = "true"*) reg [1:0] key_s = 2'b0;
  19. (*mark_debug = "true"*) reg [1:0] key_s_r = 2'b0;
  20. (*mark_debug = "true"*) wire en_10ms ;

  21. assign en_10ms = (cnt10ms == CNT_10MS);
  22. assign key_cap = (key_s==KEY_S2)&&(key_s_r==KEY_S1);

  23. always @(posedge clk_i)begin
  24.     if(cnt10ms < CNT_10MS)
  25.         cnt10ms <= cnt10ms + 1'b1;
  26.     else
  27.         cnt10ms <= 25'd0;
  28. end

  29. always @(posedge clk_i)begin
  30.     key_s_r <= key_s;
  31. end

  32. always @(posedge clk_i)begin
  33.     if(en_10ms)begin
  34.         case(key_s)
  35.         KEY_S0:begin
  36.            if(!key_i)
  37.                key_s <= KEY_S1;
  38.         end  
  39.         KEY_S1:begin
  40.            if(!key_i)
  41.                key_s <= KEY_S2;
  42.             else
  43.                key_s <= KEY_S0;
  44.         end
  45.         KEY_S2:begin
  46.            if(key_i)
  47.                key_s <= KEY_S3;
  48.         end  
  49.         KEY_S3:begin
  50.            if(key_i)
  51.               key_s <= KEY_S0;
  52.             else   
  53.               key_s <= KEY_S2;
  54.         end
  55.         endcase                  
  56.     end
  57. end

  58. endmodule

复制代码


以上代码中,首先把系统时钟做分频,产生10ms 的分配时钟使能信号。在设计的状态机中,分4个状态
KEY_S0:判断按键是否按下,如果是,转移到状态KEY_S1;
KEY_S1:10ms后再次判断按键是否按下,如果是,转移状态到KEY_S2,否则继续回到KEY_S0;
KEY_S2:判断按键是否抬起,如果是,转移状态到KEY_S3;
KEY_S3:10ms后再次判断按键是否抬起,如果是,转移状态到KEY_S0,否则继续回到KEY_S2;
当状态从KEY_S1 转到KEY_S2代表依次按钮按下key_cap输出一次高电平。
6调用key模块以下代码中调用了key模块,并且通过两个按钮来分别控制LED等的向左跟向右偏移。
  1. `timescale 1ns / 1ns

  2. module key_top #
  3. (
  4. parameter REF_CLK = 32'd100_000_000                                     //设置时间参数,方便上层调用
  5. )
  6. (
  7. input  I_sysclk_p,
  8. input  I_sysclk_n,
  9. input  I_rstn,
  10. input  I_key1,
  11. input  I_key2,
  12. output [4:0]O_led
  13. );
  14. wire I_clk;
  15. IBUFGDS CLK_U(
  16. .I(I_sysclk_p),
  17. .IB(I_sysclk_n),
  18. .O(I_clk)
  19. );
  20. reg [4:0] led_r;                                                       //存储LED灯的状态
  21. wire key1_down,key2_down;
  22. assign O_led = led_r;                                                 //将存储的LED灯的状态输出
  23. always @(posedge I_clk or negedge I_rstn)begin
  24.     if(I_rstn == 1'b0)
  25.        led_r <= 5'b00001;                                         //设置LED的初始状态
  26.     else if(key1_down)
  27.        led_r<={led_r[3:0],led_r[4]};                              //将LED灯西向上移动
  28.     else if(key2_down)
  29.        led_r<={led_r[0],led_r[4:1]};                                 //将LED灯向下移动
  30. end
  31. key#(                                //例化了两个KEY按键,一个负责上移一个负责下移
  32. .REF_CLK(REF_CLK)
  33. )
  34. key_u1
  35. (
  36. .I_clk(I_clk),
  37. .I_rstn(I_rstn),
  38. .I_key(I_key1),
  39. .O_key_down(key1_down),
  40. .O_key_up()
  41. );
  42. key#(
  43. .REF_CLK(REF_CLK)
  44. )
  45. key_u2
  46. (
  47. .I_clk(I_clk),
  48. .I_rstn(I_rstn),
  49. .I_key(I_key2),
  50. .O_key_down(key2_down),
  51. .O_key_up()
  52. );

  53. endmodule
复制代码

7综合布线前仿真时序1、新建仿真文件,仿真文件源码如下所示。
  1. `timescale 1ns / 1ns

  2. module tb_key();

  3. reg I_sysclk_p,I_sysclk_n,I_rstn,I_key1,I_key2;
  4. wire  [4:0]O_led;

  5. key_top#
  6. (
  7. .REF_CLK(100_000)
  8. )
  9. key_top_inst
  10. (
  11. .I_sysclk_p(I_sysclk_p),
  12. .I_sysclk_n(I_sysclk_n),
  13. .I_rstn(I_rstn),
  14. .I_key1(I_key1),
  15. .I_key2(I_key2),
  16. .O_led(O_led)
  17. );


  18. initial
  19.    begin
  20.       // Initialize Inputs
  21.       I_sysclk_p = 0;
  22.       I_sysclk_n = 1;
  23.       I_rstn = 0;
  24.       #100;
  25.       I_rstn =1;
  26.       I_key1 = 1;
  27.       I_key2 = 1;
  28.       #10000;
  29.       forever
  30.          begin
  31.             I_key1 = 0;
  32.             // Wait 100 ns for global reset to finish
  33.             #100;
  34.             I_key1=1; #1000;            //100个时钟周期,频繁的翻转,模拟的是毛刺状态
  35.             I_key1=0; #1000;
  36.             I_key1=1; #2000;
  37.             I_key1=0; #5000;
  38.             #20000000;                //20000000个时钟周期的KEY变化,确认为真实的按键操作
  39.             I_key1=1;
  40.             I_key1=0; #1000;
  41.             I_key1=1; #2000;
  42.             I_key1=0; #1000;
  43.             I_key1=1; #2000;      
  44.             #20000000;                         //20000000个时钟周期的KEY变化,确认为真实的按键操作
  45.             I_key2=1; #1000;
  46.             I_key2=0; #1000;
  47.             I_key2=1; #2000;
  48.             I_key2=0; #5000;
  49.             #20000000;                            //20000000个时钟周期的KEY变化,确认为真实的按键操作
  50.             I_key2=1;
  51.             I_key2=0; #1000;
  52.             I_key2=1; #2000;
  53.             I_key2=0; #1000;
  54.             I_key2=1; #2000;
  55.             I_key2=0; #1000;  
  56.             #20000000;                                  //20000000个时钟周期的KEY变化,确认为真实的按键操作
  57.             I_key2=1; #1000;  
  58.          end
  59.    end
  60. always #5 I_sysclk_p=~I_sysclk_p;
  61. always #5 I_sysclk_n=~I_sysclk_n;
  62. endmodule
复制代码

2、进入仿真界面:SIMULATION->单击 Run Simulation->单击Run Behavioral Simulation。
38c108659e5145deaf94f18e10b29c09.jpg
Setp1:设置断点
2adcaeda04324a748d37e05ed39d8bd3.jpg

之后再点击下图箭头所指
cebe2faf62e74ff496d70dceae63b462.jpg

读者可以以这种方法去观察自己想看的内部信号
Setp2:取消断点,添加想观察的信号
70335c6e396542cdb352e1638866eaa3.jpg

Setp3:观察仿真波形
可以看到每次key2的按下,key_down信号都会拉高表示捕捉到key2的按下,并且led灯进行偏移给我们反馈。
0412331728e44659a8cc0c52938109e4.jpg
Setp4:放大信号观察毛刺
c8e92e0e255144d98af6aceae4f20e0a.jpg
可以看到我们仿真模拟的按键抖动并不会触发我们的led灯的偏移,同时我们的key_down信号也不会拉高反馈,说明按键抖动被很好的滤除。
8 Chipscope在线逻辑分析仪仿真很多时候软件仿真后的代码也不一定完全执行正确,这个时候我们可以通过XILINX 自带的在线逻辑分析,在板子上运行并且查看关键信号。
1、将(*mark_debug = "true"*) 添加到需要观察的信号前面。
  1. (*mark_debug = "true"*) reg [1:0] key_s = 2'b0;

  2. (*mark_debug = "true"*) reg [1:0] key_s_r = 2'b0;

  3. (*mark_debug = "true"*) wire t10ms_done ;
复制代码

2、为了观察到信号,先点击Run Synthesis
c660a9302f75499a9abdd68449aedacb.jpg

点击ok
c7b083c7596c4834ae2bf86e2db61e47.jpg
3、单击Set Up Debug 设置需要观察的信号
df4dae66aaa346b7b25f613993c73d7c.jpg
332932b8498a495eb124355c281321ab.jpg
以下是我们要观察的信号
1d5a536ee1684f7eb73a55af3d718424.jpg
以下是这只在线逻辑分析仪的采样深度,使用的是FPGA的 BRAM,以及设置Captrue control,对于这种超慢信号,XILINX 的在线逻辑分析低于20M采样速度的,波形窗口就不会显示波形,这个XILINX也没有特别说明过,但是通过设置Captrue control,可以用我们这里的t10ms_done来作为扑捉控制,而采样时钟依然用系统时钟。
0385f5a2090042eaaeddc379f2287a1b.jpg


0939c201fbf04075ad349e2fac7451ee.jpg
单击Finish后会出来下面的原理图设计,可以看到FPGA编程的本质还是回归电路设计。现在我们使用代码去设计电路。记得保存,否则无法观察到调试信号。下面的小蚂蚁,就是已经添加调试标记的信号。

0ec7cd87d98c4a1984b1b862d4ac1387.jpg
4、编译程序
97e7c68a467b460395248e9960da5023.jpg
5、下载程序
6、设置触发,以及设置Capture 信号
Captrue mode 一定要设置为BASIC
Window data depth 为采样深度设置为2048最大
Trigger position inwindow 设置为1024
以上参数都可以根据需要用户自行设置
ddbde3c9cb0a4344a37a4759ce65d115.jpg
以信号t10ms_done作为Capture信号
befab710e5a04b42b3cb2109e7292604.jpg
以信号key_s以及key_s_r作为触发信号,条件是key_s == KEY_S2以及 key_s_r == KEY_S1,也就是key_s =2且key_s_r=1。因为我们代码中定义:
  1. assign key_down   = (key_s == KEY_S2)&&( key_s_r == KEY_S1);
复制代码

540ecd6c5617429682cd9471618a6996.jpg

7、启动采集并且按下按键,可以连续多次按下按键
32bbdef365c74d1e9ee2e0774e55597a.jpg
可以看到正确观察到了按键程序的状态机信号。
da78bee3192347cd9440362cc4e877d0.jpg
9输出结果(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,另外需要把核心板上的2P模式开关设置到JTAG模式,即ON ON,并且开发板已经上电。(注意JTAG端子不支持热插拔,而USB接口支持,所以在不通电的情况下接通好JTAG后,再插入USB到电脑,之后再上电,以免造成JTAG IO损坏)

将程序下载。按键BUT_2每按一次,LED灯进行向上偏移,按键BUT_3每按一次,LED灯进行向下偏移,LED灯响应无差错,按键BUT_1按下,LED灯复位。为清晰的表示消抖的效果,可将延时参数设置很小,可以发现,按键有时候明明已经按下去了,LED却无响应。
01fc62ed4c6348cc974851c5956a5baf.jpg


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

本版积分规则