找回密码
 注册
关于网站域名变更的通知
查看: 1851|回复: 1
打印 上一主题 下一主题

Verilog按键消抖

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-5-7 13:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
Verilog按键消抖

+ s6 u/ n( Q0 B, p0 ]' a
$ P/ A  x" l% N/ h* tFPGA按键消抖程序剖析  ) }1 [9 a) i1 K! m5 u3 t, h
对于学习  FPGA  的爱好者来说  ,  在我们做的许许多多的系统和项目中都会用到1 E/ o- E& Y' L% [
按键  ,  但是在我们所涉及的按键输入的数据  ,  是不能够直接利用的,而是要经过消
7 D3 n, W; W6 R& c; M抖,唉,为了初级菜鸟,说简单点吧  ,  什么叫消抖?消抖说白点就是消除抖动引起的8 q7 ^5 `6 U: d% r- l9 j2 p
按键不确定性!如果不消抖处理的话,就会按一次可能将相当于按几次出现的结果。相
2 [& D2 S8 C3 v信玩过单片机的人都清楚  ,  我们在读出按键的时候都会用到一个程序:
2 i4 t3 I; z, p# D$ ~: q/ \1 V$ uIf(!key)
+ t# D3 r  w. }4 H; {8 e4 u{
2 @# {, t% r- U0 lDelay(x);
$ B& o1 Y7 @3 `5 g& [If(!key) - X: {) T& U$ m$ l- k7 n  T
{...}
5 M  W4 q6 i8 ~7 }% e} ' S0 u) f5 |6 H( N
这个程序在此就不再延伸解释  ,  我所要说的是  ,  在  FPGA  里面按键的消抖与单片* _; T0 {5 \. }- z7 Y$ i' m
机的消抖原理都是一样的!或许大家都自己写过或者在网上找到过许多消抖的程序  ,  但是3 I' ?! |' A- W; ^
我所接触的当中  ,  我觉得特权前辈所写的那个是最为经典的  ,  一下就是我盗版特权前辈4 h- n" V; a6 t8 Z: L
的一个程序:
+ B5 J4 i3 ~' p0 Dmodule keyscan_module (
6 l% X) V( W2 V/ y* E9 y# c# u4 z0 _( Uinput clk, //  外部输入时钟,我选择  50M + Z0 r4 k# @# _% y3 W
input reset, //  复位  ,  但是在此不建议使用此种复位 7 Y7 `$ b& M6 S8 H$ N
//  形式,建议用异步复位同步释放
) p, O! i- F6 M, k2 |) ainput key_in, //  按键输入(  1bit  ) & G+ m5 o4 F, W9 P' j
output key_ready); //  按键值输出 4 R% H! S1 `4 f% r/ d0 A2 n0 w
/*********************************************/
  k/ X: H: R5 u/ areg key1,key2;  定义两个寄存器变量 * p3 k: W/ f6 w9 e4 X+ M
wire key_en;  定义一个线性变量 " V9 [6 @/ k  ?* b) O  ]
always @ ( posedge clk or negedge reset) 6 ^6 \9 N' w7 p/ P9 e3 p' V$ v
if ( !reset )
: h2 x; r, |0 ]* |$ ?begin " Q( p4 L, ^  H' l" `& e9 w
key1 <= 1'b1; : ^- W; E' k( T9 \6 y* N
key2 <= 1'b1; 8 f  k( x5 i; g- X
end ; P3 k6 a& L- k( `# ~+ y* d
else
/ Y- I; }1 B6 \, qbegin
( p) Z: |' t! ~; `) F- S6 v$ L' zkey1 <= key_in; //  学习过非阻塞式语句的同志,应该不能理解这里 5 p. M% E# Y; N; b0 [
//  第一个时钟读取按键进入第一个寄存器  ,  第二个时钟 5 v6 }# ?3 M1 Y% B9 T5 J3 d
key2 <= key1;  将第一个寄存器中的值赋给第二个寄存器
" |  k4 H" u7 `$ y% W% a  Bend
& r9 l4 X: U7 N( ?7 T( D( Uassign key_en = key2 & ( ~key1 ); //  当检测到下降沿的时候,  key_en  保持一个时钟的高 # i3 w0 ]( G) Y& ~. V, m
电平 + N: r0 g/ f, i; M) p
/**********************************************/ 8 Q2 }' B3 p8 u* l5 l
reg[19:0] cnt;  定义一个计数器 # S/ g! Q" e# u4 K  `4 B/ w
always @ ( posedge clk or negedge reset )
( T( f! p- L% b  g: w% Y* uif ( !reset )cnt <= 20'd0;
% y' ^4 R% I$ q$ Eelse if ( key_en ) cnt <= 20'd0; else cnt <= cnt + 1'b1;
+ i$ M9 l: p& y; d. }/********************************************/
& F# j6 n0 x( ^reg key3,key4;
" C  U5 Y( w- l3 k6 @, }& nalways @ ( posedge clk or negedge reset ) ) ?% n+ E% B% X4 v( S7 H) q
if ( !reset ) key3 <= 1'b1;
/ u4 X) V- ^  s( p5 m1 v/ gelse if ( cnt == 20'hfffff) //  当计数到  20ms  的时候 2 E6 p4 V. {9 f8 E+ k
key3 <= key_in;
7 h. o- x, _& R( L, u0 @always @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按键值赋给  key4
. t6 L" g; T" E/ p: ^if(!reset)key4 <= 1'b1; " z& \2 j+ u2 q7 o6 N+ Q; @
else key4 <= key3;assign key_ready = key4 & ( ~key3); //  当有按键按下时  ,  输出有效  ,  保, i8 {1 z/ p. n5 i3 ^
持一个时钟周期  ; 1 q; o7 K; \6 W
endmodule
. C) d& ~' l: C1 |3 s# F下面我们来好好研究下这个程序: 2 \  @& Y7 @. Y' T' \
整个程序的基本思路是这样的  :  系统上电后  ,  计数器就开始计数  , $ j' p8 |% A" ^; `
(注意啊  ,  不管有没有按键按下都在计数  ,  每次计数  20MS  )  于此同 9 L* W" U4 h7 Q  d% f
时,系统也在不断采集  key_in  的电平,假设在一个时钟上升沿的时
" c; H* u# A& j  `候,检测到  key_in  为高电平  ,  (那么  key1<=1,key2<=1  )在下一个时 2 c3 @% B. w, p4 Q" N0 [- x7 C1 \
钟上升沿的时候检测到低电平  (key1<=0;key2<=1)  那么执行这个语句 3 e% ]0 Q8 ]! l4 y' i
assign key_en = key2 & ( ~key1 )  那么  key_en  就会得到一个高电
. z9 o- m! ]" k& f6 Q$ P平  ,  当第三个时钟上升沿的时候  (  key1<=0,key2<=0  )  由此可知  ,  key_e n
4 x% V, A5 R' G2 g只保持一个时钟周期的高电平  ,  而且仅是当检测到有下降沿的时候才
, W1 h4 H& e6 N/ w3 y0 O. v会变为高电平  。  说到计数器  ,  前面也说了  ,  上电之后计数器就一直在
; n  `& Z) W9 d计数,当时当  key_en  为高电平的时候,计数清零,也就是说,当前
5 o+ r$ ?- u6 m% s4 @; v面检测到下降沿之后就重新开始计数, ( A- O8 \# ?1 S( w
else if ( key_en ) cnt <= 20'd0;
, j6 V$ ]+ Y) p! b! o* Y$ v8 xelse cnt <= cnt + 1'b1; / c. ^+ K, D: r3 A
然后到这个程序
2 Q/ ~; A+ @. G* _else if ( cnt == 20'hfffff) //  当计数到  20ms  的时候 5 n" A: M6 ~- \
key3 <= key_in; % F" ?6 ?$ ?, m5 _
这个程序的作用是计数到了  20ms  之后再一次读取  key_in  的值  , . H0 F+ I+ E2 F! |% @4 s# V4 I
换句话说,就是从前面检测到下降沿之后,  20ms  后再去检测!我们
  k- a  b, U4 V+ d1 G/ A都知道,抖动所产生的毛刺都是在  us  级别的,哎呀,再怎么大也大
- Z( d' G7 @* y2 j) Z  U" V2 L, H不过  20ms  的,而我们按按键的话那肯定就不止  20ms  啦,再怎么快也要  500ms  以上吧!
" {: _3 n3 j7 ]! s再结合下段程序: 0 {! B; D' S3 n9 [2 B$ @
always @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按
2 d; Z3 X, y, k, E' O  g' F键值赋给  key4 ) @/ [2 a: E/ s
if(!reset)key4 <= 1'b1; 4 ?8 W. p: m$ V! s
else key4 <= key3;
6 D/ ~! ^$ }! N% Iassign key_ready = key4 & ( ~key3); // 1 x  n9 @3 X" M2 W/ _6 W
假设第一次检测到的下降沿是由于抖动产生的毛刺  ,  那么  20M S ! [4 v4 \" O% n* K; V! J
后  ,  过  滤  掉  毛  刺  ,  检  测  到  的  应  该  是  一  个  高  电  平
; P1 Y2 S+ K% m0 n(  key3<=1,key4<=1,key_ready=0  )  ,  假如前面检测到的下降沿不是由于 ! z7 d+ }# X7 h
毛刺,而是按键按下的,那么  20MS  后,  key_in  肯定还会是低电平  , ' ^" C' |, H% r' u, L9 ?- @
所以  (  key3<=0,key4<=1,key_ready=1  )  ,  而在  20MS  之后的下一个时钟 (  key3<=0,key4<=0,  那么  key_ready=0  )  key_read  只保持一个时钟周期的时间!
6 G" a/ m: Z* x5 P& U$ p, {为什么这个消抖程序被列为经典呢?大家神人研究后不妨从 + b3 X3 m0 K# g. k, H
时序和资源方面去考虑一下,昨天我在一本黑金的教程上面弄了一
4 k5 `; g% h/ X7 O+ j5 T个,一个按键就消耗  80  多个  lut  ,而这个只要  30  个,试想一下,  做
. d4 H1 W& R/ l; {& Z' p16  个按键都上千呢,那其他模块怎么办?还有我觉得很好的一点就 0 h# D! I9 n, g' e1 q3 t$ Y7 L2 {
是它的输出  (  key_ready  )  只保持一个时钟周期  ,  有利于上层模块采集  ! 5 c7 o' @) l  _* ^0 b9 P
哎呀  ,  没有对比  ,  看不出差距  ,  以下是我们老师教我写的一个消抖程 8 C7 p3 B& P1 `# x0 h
序:他的思路是:把系统时钟(  50M  )分频  100K  (  10ms  )后再去读
" p" k7 `2 k+ ^% O% x7 R取按键值
$ e/ I: X+ g$ IAlways @ (posedge clk_100K or negedge reset) + c+ f* \( i+ o8 o' V
If(!reset)begin key1<=1;key2<=1; end
" E: _8 s7 }1 @/ D" S; e2 w$ _Else begin key1<=key_in;key2<=key1;end 1 K$ S- n9 z+ }) x7 V
Assign key_ready=key2 & !key1; 7 o2 n; [3 B7 w  R& y- q! X" r
大家看一下这个程序  ,  原理和特权的那个差不多  ,  看似更加简单  , 5 _% [/ G- {6 b: d1 M
的确,这个程序可以消抖,但是存在一个问题,就是  key_ready  也会 8 H: C1 W& u8 l; z9 l4 P
保持  10MS  的一个高电平  ,  有些人会问  ,  高电平持续就一点不是方便 " V" m# O9 v) }) z
上层采集吗?其实不然  ,  举个例子  :  我们用一个按键  ,  控制一个二极
) U& }+ ]# i1 r, c6 c管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge )
2 S2 ?4 J% @8 v! ~7 }: k.........................................(  省略  ) 3 _) q9 X$ p  ~% g
If(key_ready) led<=~led
" C  X- Y. [# |, P5 M1 E! W4 a我们使用的系统时钟是很高的,假如  key_ready  保持不是一个时 / ?) N  G1 }3 h0 L  h4 L
钟周期  ,  而是  10ms,  那么我们按一次  ,  led  就会一亮一灭很多次  !  碰到
4 H$ |. G0 e6 ]( k; l这种情况,一般要用组合逻辑解决,比如
2 m8 g8 k3 f0 W8 \# b( xAlways @ (key_read or negedge)
- Z1 @7 r' B+ X+ @...................
& N. P: i# I0 k# f3 F- VIf(key_reaf)......
1 x5 E; G. X* }这样也可以实现按一次变化一次  ,  但是大家都知道  ,  组合逻辑往 ! \' |9 A9 e; e- F2 h% [4 n- o
往会给我们带来许多时序上的问题,能用同步就同步  !  在此也特别提 $ j8 \$ I, I, V; f
醒诸位,在  if(x)  判断电平的时候多考虑一下,免得出错! ) X& D  k- |. R1 C4 a
+ r8 b# c: a9 f8 L+ r+ a7 r
对于消抖程序忠告如下 / i- E  K% a5 S/ P
reg [3:0]key1_reg; 7 X: [/ s$ h% _
reg [3:0]key2_reg;
5 H0 O4 a. q  K& m2 oalways @(posedge clk,negedge rest)begin
. D* |3 O0 W: \# R" n  if(!rest)begin
7 d) _) d9 x$ ]+ I0 m   key1_reg<=4'b1111; 4 S# a2 I5 p2 M
   key2_reg<=4’b1111;
$ a  @5 d4 H/ m4 ~% F  end 9 w* l; U1 W! ?7 R$ C! y
  else if(counter==5'hfffff)begin 4 w1 w8 B- ~3 `9 ~3 u; H, v/ ?* X3 H9 B
   key1_reg<=key; " P5 ?+ ^+ v( n" R, k9 n# V
   key2_reg<=key1_reg 7 S2 d; {: J" R( t7 s" W
end
, T* O. ^+ I; H" s$ rassign key_ctr=key2_reg&(~key1_reg); $ J5 h+ N  k3 k- w" i+ }! u; G
这段程序,20ms后采样 key的值给 key1_reg,而 key2_reg值为 key1_reg前一时刻的值,有按键按下时 key_ctr 为 1,可是程序中 key2_reg 值 20ms 才刷新一次,如果在做我们用一个按1 n5 r% }& Y/ x9 H' D; C9 @" p
键  ,  控制一个二极
% g; s5 i( O( J2 A8 `9 a* c  J+ ~8 L. y管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge ) & J+ s" s/ F9 B6 ]$ T7 e2 Y
.........................................(  省略  ) 8 J) o% A: N$ t+ k! `
If(key_ready) led<=~led ! p- i2 ~" {3 i6 z
这段 20ms 时间内每一晶振脉冲下 led 都翻转,这样灯就一闪一闪,不能达到要求,而我们7 v2 N/ y/ a. R
程序改成这样:
7 r# b! Q2 N; ereg [3:0]key1_reg; 2 k! j! n6 f8 ?  q) a% i
reg [3:0]key2_reg;
) h/ ?- A% G6 a" a5 a: Ealways @(posedge clk,negedge rest)begin
9 Z* |4 m. T2 u  if(!rest)
& b/ j7 W' ]9 t   key1_reg<=4'b1111; # P" T* Z! J  Q- ^( f! `" c" B
  else if(counter==5'hfffff)
, L0 x+ \: \1 f( P. Q+ N: J   key1_reg<=key;
& Q0 w1 z+ X3 ~4 c/ e0 Kend 3 z/ P3 G( J; K# l9 Y
always @(posedge clk,negedge rest)begin ( D% P' M( @  Z1 {4 }& e2 e
  if(!rest) % N* ]9 r2 o& Z0 |' n
   key2_reg<=4'b1111;
; J9 W7 D  M8 u# @  else  
  Q3 _. g6 Y6 |$ y$ [   key2_reg<=key1_reg;
8 ~; c$ u2 e+ \+ ~1 g* _" d* Zend 0 C% Q+ p$ R+ T! t& I. u/ d9 s
assign key_ctr=key2_reg&(~key1_reg);
! y" v, i6 B1 x: Q) fAlways @ (posedge clk or negedge rest ) ) B/ \; _9 L. C2 `9 E1 k
.........................................(  省略  ) & F4 ?; ^9 Y* q) n+ @( V3 Z
If(key_ready) led<=~led;
" u6 S: G! O* }3 P这样 led 就只是翻转一次才能达到控制效果。 4 V6 m' ?. O* E& X

) @( K4 H' ?2 q/ y6 l- F' ]: K- `
. ^" F/ G& B; {. k' s4 b& y
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-8-2 22:48 , Processed in 0.109375 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表