当前位置:   article > 正文

FPGA开发——按键的使用及其消抖方法

FPGA开发——按键的使用及其消抖方法

一、概述

我们在进行各种硬件开发时通常都会实现多效果的综合和实现,而在实际的开发中,每个时刻只会显示单个效果,这就需要涉及到效果之间的切换了,而要实现状态切换最好的就是使用按键进行按键,所以按键在我们的日常硬件开发中占据很大的作用。

二、相关理论

我们在使用按键之前,首先应该考虑到的就是按键的抖动的问题,所以在使用按键的过程中按键消抖的成功与否是确保最终结果的最关键的过程。以下是按键抖动过程中波形的具体示意图:

三、实现方法

FPGA的按键使用过程中我们有两种方法对按键进行消抖,一种是直接采用计数器搭配打拍的方式进行按键的消抖,最后使用一个输出电平标志按键消抖的完成。另外一种是采用状态机的方式进行按键消抖的实现。我们这里分别从两种实现方法进行讨论。

1、计数器的方法

基本思路

最初波形图构造

其中的key_in就是按键的状态,代表按键按下与否,key_in_r是key_in打一拍之后的波形图,与key_in相比相位相差了一个周期。nedge就是下降沿触发的标志,也就是按键抖动的标志位,key_out是输出一个电平代表按键消抖成功。

2、状态机的方法

状态机的实现方法就是在计数器的转移条件之间增加了下降沿以及计数器进行搭配检测,实现状态机状态之间的转移 ,从而达到按键消抖的目的

最初波形图构造

这里就是比前面计数器多了一个现态和次态,以及上升沿标志,将整个按键从按下到释放划分成四个状态,通过各个条件转换成状态机状态转移的条件,通过状态的切换达到按键消抖。

四、代码编写

1、计数器实现

设计文件

  1. //对一位按键输入进行消抖,最终输出消抖过后的按键信号,输出一个周期高电平表示按键按下一次
  2. module key(
  3. input clk,
  4. input rst_n,
  5. input key_in,
  6. output reg key_out
  7. );
  8. //参数定义
  9. parameter TIME_20MS = 1_000_000;
  10. //内部信号定义
  11. reg key_in_r;
  12. reg [23:0] cnt;
  13. wire add_cnt;
  14. wire end_cnt;
  15. wire nedge;
  16. reg add_flag;
  17. //检测抖动,对信号打一拍
  18. always @(posedge clk or negedge rst_n )begin
  19. if(!rst_n)
  20. key_in_r<=1'b1;
  21. else
  22. key_in_r<=key_in;
  23. end
  24. assign nedge = !key_in && key_in_r;
  25. //从抖动的地方开始计数,这里实现计数标志位的书写
  26. always @(posedge clk or negedge rst_n)begin
  27. if(!rst_n)
  28. add_flag<=0;
  29. else if(nedge)
  30. add_flag<=1;
  31. else if(end_cnt)
  32. add_flag<=0;
  33. else
  34. add_flag<=add_flag;
  35. end
  36. //开始计数,在计数结束时采样点平
  37. always @(posedge clk or negedge rst_n)begin
  38. if(!rst_n)
  39. cnt<=0;
  40. else if(add_cnt)begin
  41. if(end_cnt)
  42. cnt<=0;
  43. else
  44. cnt<=cnt+1;
  45. end
  46. else
  47. cnt<=0;
  48. end
  49. assign add_cnt=add_flag;
  50. assign end_cnt= (add_cnt) && (cnt==TIME_20MS-1);
  51. //电平输出
  52. always @(posedge clk or negedge rst_n)begin
  53. if(!rst_n)
  54. key_out <= 0;
  55. else if(end_cnt)
  56. key_out <= 1'b1;
  57. else
  58. key_out <= 0;
  59. end
  60. endmodule

2、状态机实现

  1. //状态机实现
  2. module key (
  3. input clk ,
  4. input rst_n ,
  5. input [ key_in , //输入原始的按键信号
  6. output reg key_out //输出处理之后的按键信号
  7. );
  8. //参数定义
  9. localparam IDLE = 4'b0001,//空闲
  10. JITTLE0 = 4'b0010,//滤除第一次抖动
  11. DOWN = 4'b0100,//稳定
  12. JITTLE1 = 4'b1000;//滤除第二次抖动
  13. parameter TIME_20MS = 1_000_000;//需要计数的值,20ms
  14. //内部信号
  15. reg [3:0] state_c;//现态
  16. reg [3:0] state_n;//次态
  17. reg [19:0] cnt_20ms;//计数20ms
  18. wire add_cnt_20ms;
  19. wire end_cnt_20ms;
  20. wire nedge ;//下降沿信号
  21. wire pedge ;//上升沿信号
  22. reg key_in_r1 ;//打两拍 同步打拍
  23. reg key_in_r2 ;
  24. //状态转移 同步时序逻辑描述状态转移
  25. always @(posedge clk or negedge rst_n) begin
  26. if(!rst_n)
  27. state_c <= IDLE;
  28. else
  29. state_c <= state_n;
  30. end
  31. //状态转移条件 组合逻辑
  32. always @(*) begin
  33. case (state_c)//一定是case 现态
  34. IDLE:begin
  35. if(nedge)
  36. state_n = JITTLE0;
  37. else
  38. state_n = state_c;
  39. end
  40. JITTLE0:begin
  41. if(end_cnt_20ms)
  42. state_n = DOWN;
  43. else
  44. state_n = state_c;
  45. end
  46. DOWN:begin
  47. if(pedge)
  48. state_n = JITTLE1;
  49. else
  50. state_n = state_c;
  51. end
  52. JITTLE1 :begin
  53. if(end_cnt_20ms)
  54. state_n = IDLE;
  55. else
  56. state_n = state_c;
  57. end
  58. default: state_n = IDLE;
  59. endcase
  60. end
  61. //20ms计数器
  62. always @(posedge clk or negedge rst_n) begin
  63. if(!rst_n)
  64. cnt_20ms <= 0;
  65. else if(add_cnt_20ms)begin
  66. if(end_cnt_20ms)
  67. cnt_20ms <= 0;
  68. else
  69. cnt_20ms <= cnt_20ms + 1;
  70. end
  71. end
  72. assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
  73. assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
  74. //下降沿 上升沿
  75. //同步 打拍
  76. always @(posedge clk or negedge rst_n)begin
  77. if(!rst_n)begin
  78. key_in_r1 <= 1'b1;
  79. key_in_r2 <= 1'b1;
  80. end
  81. else begin
  82. key_in_r1 <= key_in; //同步按键输入信号
  83. key_in_r2 <= key_in_r1; //打拍
  84. end
  85. end
  86. //r1当前状态,r2上一个状态
  87. assign nedge = ~key_in_r1 && key_in_r2;
  88. assign pedge = key_in_r1 && ~key_in_r2;
  89. //key_out
  90. always @(posedge clk or negedge rst_n) begin
  91. if(!rst_n)
  92. key_out <= 0;
  93. else if(end_cnt_20ms &&(state_c== JITTLE1))
  94. key_out <= 1'b1;//有效脉冲 20ns
  95. else
  96. key_out <= 0;
  97. end
  98. endmodule

3、测试文件

因为两种方法所需要实现的效果是一致的,所以这里我们采用同一个测试文件。

  1. `timescale 1ns/1ns
  2. module key_tb ;
  3. reg clk ;
  4. reg rst_n ;
  5. reg key_in ;
  6. wire key_out ;
  7. defparam key_inst.TIME_20MS = 1000;
  8. key key_inst(
  9. .clk (clk ),
  10. .rst_n (rst_n ),
  11. .key_in (key_in ), //输入原始的按键信号
  12. .key_out (key_out ) //输出处理之后的按键信号
  13. );
  14. //激励信号产生
  15. parameter CLK_CLY = 20;
  16. //时钟
  17. initial clk=1;
  18. always #(CLK_CLY/2)clk=~clk;
  19. //复位
  20. initial begin
  21. rst_n= 1'b0;
  22. #(CLK_CLY*3);
  23. #5;//复位结束避开时钟上升沿
  24. rst_n= 1'b1;
  25. end
  26. //激励
  27. integer i;
  28. initial repeat(5)begin
  29. key_in = 1;//模拟按键未按下
  30. i ={$random}%6;//给i赋值0-5
  31. #(CLK_CLY*500);//等待复位时间结束
  32. #3;
  33. repeat (3)begin
  34. key_in = 0;//前按键抖动开始
  35. #(CLK_CLY*1);
  36. //一个5-10ms的抖动时间
  37. repeat ((i+5)*50)begin
  38. key_in = $random;
  39. #(CLK_CLY*1);
  40. end
  41. key_in = 0;//按键稳定
  42. #(CLK_CLY*100*50);
  43. //后抖动开始
  44. key_in = 1;
  45. #(CLK_CLY*1);
  46. repeat ((i+5)*50)begin
  47. key_in = $random;
  48. #(CLK_CLY*1);
  49. end
  50. key_in = 1;//按键稳定
  51. #(CLK_CLY*10*500);
  52. end
  53. //模拟意外抖动
  54. repeat (3)begin
  55. repeat ((i+5)*50)begin
  56. key_in = $random;
  57. #(CLK_CLY*1);
  58. end
  59. key_in = 1;//按键稳定
  60. #(CLK_CLY*500);
  61. end
  62. $stop;
  63. end
  64. endmodule

五、仿真波形图

1、计数器方法

2、状态机方法

通过仿真结果得知两种方法实现的波形图其实都是差不多的,就是第二种使用状态机的缘故导致波形图看着比第一种的负责一点,其实两种方法基本的实现原理都是一致的,只不过就是换了一种实现方法。在图中我们可以看到两种方法的key_out输出位置不一致,这是我们设置的输出条件有点小差别,这个不会影响最终结果。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号