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

Verilog按键消抖

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
Verilog按键消抖

3 L, v  C  W& b: L8 s8 k9 D. w* A9 s& o# F# b" ~1 w
FPGA按键消抖程序剖析  
) {% C# @9 J, ?0 D' V- s' e4 q. S; D2 u9 K对于学习  FPGA  的爱好者来说  ,  在我们做的许许多多的系统和项目中都会用到
8 ^2 b( }. w1 S, F4 v" @按键  ,  但是在我们所涉及的按键输入的数据  ,  是不能够直接利用的,而是要经过消& D/ j8 p2 t% e9 d
抖,唉,为了初级菜鸟,说简单点吧  ,  什么叫消抖?消抖说白点就是消除抖动引起的( u& m3 \% e6 n/ i% W- w- V
按键不确定性!如果不消抖处理的话,就会按一次可能将相当于按几次出现的结果。相1 f: ~! G3 _+ T& y$ w# ~
信玩过单片机的人都清楚  ,  我们在读出按键的时候都会用到一个程序: ! ^/ P0 `- k- X1 R! ?" ?2 C) a
If(!key) % L- b( r; N; V; B1 B6 B
{
# z3 g/ z. s, D; gDelay(x); - V4 q4 r7 X/ x4 ?* U' e
If(!key)
9 r+ u: S9 q$ r* o{...} 6 k4 Z9 U/ e: q5 N. Q  P; G
}
& a' V& R- \& h! u- I. W8 j6 R这个程序在此就不再延伸解释  ,  我所要说的是  ,  在  FPGA  里面按键的消抖与单片/ v, S. i3 @% _- X# G
机的消抖原理都是一样的!或许大家都自己写过或者在网上找到过许多消抖的程序  ,  但是' W* C' z+ f, c/ E4 G
我所接触的当中  ,  我觉得特权前辈所写的那个是最为经典的  ,  一下就是我盗版特权前辈( Y, v# q$ g' y& Z
的一个程序:   n( g9 m1 n8 ?; O6 Z
module keyscan_module (
) q4 G9 y6 Q1 h; O, C- t" G+ x  hinput clk, //  外部输入时钟,我选择  50M ; X0 L: c; J8 P* q5 S# o
input reset, //  复位  ,  但是在此不建议使用此种复位
1 B7 C) }* z6 K  k$ ]% \! N1 u//  形式,建议用异步复位同步释放 4 L7 ?* T8 b0 ~) o+ ^9 o  f6 f( l' z
input key_in, //  按键输入(  1bit  ) ' L$ i/ Y# |: v' \+ y( h8 W
output key_ready); //  按键值输出
! r9 ]9 B! @. @( X6 [9 @  Y/*********************************************/ ; y2 N: W6 P' D& J0 t
reg key1,key2;  定义两个寄存器变量
) v8 M$ K0 f0 Gwire key_en;  定义一个线性变量   T" ?" e* v+ [0 Y8 ^. P; g( H' _
always @ ( posedge clk or negedge reset) & J9 ^3 _( i. w) u/ Z/ I6 X8 v( p, n
if ( !reset )   e' _- @4 [0 Q
begin
8 N$ K1 {, d5 w0 C* @  [key1 <= 1'b1;
% n/ ~% T6 A8 s$ kkey2 <= 1'b1;
' G7 R' i. T: H4 Q- O) iend 5 `4 ?& H1 e7 k( W
else
$ ~3 ?8 p5 W4 a9 P  r/ R6 Abegin 2 n& Y! U6 v, X1 d5 F; x
key1 <= key_in; //  学习过非阻塞式语句的同志,应该不能理解这里
: X% `8 t1 c- y: {$ p//  第一个时钟读取按键进入第一个寄存器  ,  第二个时钟
% p/ B9 n( W$ t$ i2 h$ okey2 <= key1;  将第一个寄存器中的值赋给第二个寄存器 , L$ |- ?: N' N: b+ o1 ^$ x% R3 S
end
# |* \1 v0 F7 a' [2 Oassign key_en = key2 & ( ~key1 ); //  当检测到下降沿的时候,  key_en  保持一个时钟的高 . g* T( a+ M+ |: s" Y
电平
# M7 I, h4 ?. C5 `* e8 c/**********************************************/
% ]  f0 x* Y0 w% U" u7 A  ]- E, V6 |6 freg[19:0] cnt;  定义一个计数器 ( r9 m, Y' ^- |
always @ ( posedge clk or negedge reset ) . N- g, V2 O( o
if ( !reset )cnt <= 20'd0; & W% n0 C, R0 b! `' W( C# _
else if ( key_en ) cnt <= 20'd0; else cnt <= cnt + 1'b1;
4 T% ^9 j. v* {8 n: e0 W: M) m/********************************************/ * H" f  f0 C) V; S* R  E
reg key3,key4; 8 K4 \' s2 f0 }8 c2 Z
always @ ( posedge clk or negedge reset ) + u( `+ n& T- Y+ M6 n, N
if ( !reset ) key3 <= 1'b1; 7 _; U/ v( [% z) X
else if ( cnt == 20'hfffff) //  当计数到  20ms  的时候
2 P6 @) C2 N3 [7 g% T8 b3 f* Ykey3 <= key_in; ! R5 s* Y- S/ ~1 y" y
always @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按键值赋给  key4
* K8 y( p8 D2 ]" }/ O0 {if(!reset)key4 <= 1'b1;   W! E8 i1 w9 H! t  z6 M
else key4 <= key3;assign key_ready = key4 & ( ~key3); //  当有按键按下时  ,  输出有效  ,  保( y, r0 E% T2 [
持一个时钟周期  ;
/ U. K2 D1 \+ r2 Dendmodule 4 C- V* G  ?* L( q' e8 P" _) U
下面我们来好好研究下这个程序:
+ w' G9 I2 n. b+ B: J整个程序的基本思路是这样的  :  系统上电后  ,  计数器就开始计数  ,
* z3 d) @- x7 `: h, t0 y(注意啊  ,  不管有没有按键按下都在计数  ,  每次计数  20MS  )  于此同 0 ]% K; C$ r" g. ?2 \
时,系统也在不断采集  key_in  的电平,假设在一个时钟上升沿的时
, X* }; v1 F  E候,检测到  key_in  为高电平  ,  (那么  key1<=1,key2<=1  )在下一个时 : E  ?& D9 g9 r# l) h/ O+ i& d
钟上升沿的时候检测到低电平  (key1<=0;key2<=1)  那么执行这个语句 2 E% d- ^8 r' x9 }2 C
assign key_en = key2 & ( ~key1 )  那么  key_en  就会得到一个高电
6 _& J/ [  E" S9 z# c$ b平  ,  当第三个时钟上升沿的时候  (  key1<=0,key2<=0  )  由此可知  ,  key_e n 4 d. u4 j5 I! r; {& I6 n8 g
只保持一个时钟周期的高电平  ,  而且仅是当检测到有下降沿的时候才 - X% h* @- d2 Y# k+ j
会变为高电平  。  说到计数器  ,  前面也说了  ,  上电之后计数器就一直在 : }( R* Q: j& i- |
计数,当时当  key_en  为高电平的时候,计数清零,也就是说,当前
. z! p9 o* n0 E4 E面检测到下降沿之后就重新开始计数, 3 J3 W- |1 X$ H. H/ k  L
else if ( key_en ) cnt <= 20'd0;
- O1 E0 q: r- H! m0 h3 Eelse cnt <= cnt + 1'b1;
5 J) y3 ~, s( K: @8 N5 b8 m' K然后到这个程序
. G0 l1 X9 X2 celse if ( cnt == 20'hfffff) //  当计数到  20ms  的时候
; |1 R( M# O9 `. m0 }- ^& lkey3 <= key_in;
+ V  |, S3 U! |7 k" _- h4 @- U这个程序的作用是计数到了  20ms  之后再一次读取  key_in  的值  ,
5 r  m% y) \2 s1 o! x; X3 d% v换句话说,就是从前面检测到下降沿之后,  20ms  后再去检测!我们
( Y6 o8 N7 i% h1 Z都知道,抖动所产生的毛刺都是在  us  级别的,哎呀,再怎么大也大
0 V/ b2 I7 q" ?$ N不过  20ms  的,而我们按按键的话那肯定就不止  20ms  啦,再怎么快也要  500ms  以上吧!
( _2 g: @  A( L9 g/ m3 m再结合下段程序: 5 }" N# j( n. d+ ]. S! k5 z1 D; y
always @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按
' P* J* f. ^  ?7 U键值赋给  key4
; t/ W# E9 h% i9 D6 v7 Uif(!reset)key4 <= 1'b1;   L8 z9 N$ ?+ v0 m: L; Q7 D
else key4 <= key3;
' G5 k/ G4 k0 ]2 S/ v) @assign key_ready = key4 & ( ~key3); //
9 g& V  u% t: A6 D+ x假设第一次检测到的下降沿是由于抖动产生的毛刺  ,  那么  20M S 9 l$ ~7 M; f4 o% J5 g
后  ,  过  滤  掉  毛  刺  ,  检  测  到  的  应  该  是  一  个  高  电  平
* N; e( O( j+ G. N) L+ G(  key3<=1,key4<=1,key_ready=0  )  ,  假如前面检测到的下降沿不是由于
4 Z( W4 _% F; E: e' r毛刺,而是按键按下的,那么  20MS  后,  key_in  肯定还会是低电平  , 2 ^: M; L. Z+ A
所以  (  key3<=0,key4<=1,key_ready=1  )  ,  而在  20MS  之后的下一个时钟 (  key3<=0,key4<=0,  那么  key_ready=0  )  key_read  只保持一个时钟周期的时间!
: Z, A6 h7 L8 L: Q" I$ Y8 R: Z为什么这个消抖程序被列为经典呢?大家神人研究后不妨从 / [  W" A- u0 Y, K
时序和资源方面去考虑一下,昨天我在一本黑金的教程上面弄了一
8 ]2 x  ^! z( ^$ P* M个,一个按键就消耗  80  多个  lut  ,而这个只要  30  个,试想一下,  做
% d1 t9 I' N" ^0 b  p16  个按键都上千呢,那其他模块怎么办?还有我觉得很好的一点就 ) F% z- [  [/ m7 _6 s
是它的输出  (  key_ready  )  只保持一个时钟周期  ,  有利于上层模块采集  !
$ M. P/ w" f2 g$ m! C# }哎呀  ,  没有对比  ,  看不出差距  ,  以下是我们老师教我写的一个消抖程
% C. S- f! O& |$ K序:他的思路是:把系统时钟(  50M  )分频  100K  (  10ms  )后再去读 / K1 N. j( d, F. t: [$ H; L9 u- i
取按键值 % j9 g, Z6 M4 P2 W
Always @ (posedge clk_100K or negedge reset)
8 F% n3 T. C0 qIf(!reset)begin key1<=1;key2<=1; end   R, _7 D5 }- b9 B# _8 p
Else begin key1<=key_in;key2<=key1;end
5 ?# B# A8 _0 y8 G( u; ], L9 B( A# p7 hAssign key_ready=key2 & !key1;   S: @9 l% T$ Q# b- `0 N7 P/ z
大家看一下这个程序  ,  原理和特权的那个差不多  ,  看似更加简单  ,
; r' H7 h, S# o  Q+ ?+ ?" {4 a$ W的确,这个程序可以消抖,但是存在一个问题,就是  key_ready  也会 7 F0 z) U8 Z, P% v, A% p3 j
保持  10MS  的一个高电平  ,  有些人会问  ,  高电平持续就一点不是方便 5 ~9 y% V. t/ X& G/ T
上层采集吗?其实不然  ,  举个例子  :  我们用一个按键  ,  控制一个二极
3 n  y9 \- G* l) ~4 e* \- A" w. @管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge ) ; p3 D3 i! V( q7 X9 m' B. x# v
.........................................(  省略  )
9 S/ V* x5 q1 k7 XIf(key_ready) led<=~led
" o7 {0 O& I3 d4 \* j我们使用的系统时钟是很高的,假如  key_ready  保持不是一个时 3 n  m; i8 n) z# z
钟周期  ,  而是  10ms,  那么我们按一次  ,  led  就会一亮一灭很多次  !  碰到 ! U( |1 N3 k. s) i, I' B+ ?
这种情况,一般要用组合逻辑解决,比如
# W$ [) z" B$ h' M( E. Q/ b1 h3 fAlways @ (key_read or negedge)
5 O6 ^$ h0 }/ _* X* [................... % u6 e" w3 V% M; `7 V4 W$ R
If(key_reaf)......
0 Q: e# u8 m: r# k5 E: F这样也可以实现按一次变化一次  ,  但是大家都知道  ,  组合逻辑往
$ v. H* R0 M5 M% p6 i! [2 l往会给我们带来许多时序上的问题,能用同步就同步  !  在此也特别提 ; L/ r8 c2 e9 N- I( b- {
醒诸位,在  if(x)  判断电平的时候多考虑一下,免得出错!
. }2 L& K* j/ O  H( B& w5 w
* q8 `. @, b; i: ~% g" P对于消抖程序忠告如下
, [8 J7 z* D6 Z$ {; Ireg [3:0]key1_reg;
0 Y4 m/ u8 Q( Y: D. U2 J3 \0 wreg [3:0]key2_reg;
/ O: [8 h( Y1 ]always @(posedge clk,negedge rest)begin
% g5 x2 y: r7 D- N' M, c/ f+ r' k  if(!rest)begin
! J* E( @" U9 b, f2 u4 w# S) N   key1_reg<=4'b1111;   B: A! i& S* G
   key2_reg<=4’b1111;
% B, N6 d7 h8 _0 {3 W9 s) _: g  end
9 n  s6 Q' S) J5 Y% M+ \  else if(counter==5'hfffff)begin
6 B# e* J1 V9 L% ^/ a- }. \   key1_reg<=key;
' O" `# |! |) T1 J& ~, m6 K! {- |# r   key2_reg<=key1_reg   [; \) E3 K, w2 W2 m7 C8 p
end
1 z8 t$ o  v5 |- n0 M! W& y6 h  nassign key_ctr=key2_reg&(~key1_reg); ) R  Y0 y- @, \' a# y8 P2 [
这段程序,20ms后采样 key的值给 key1_reg,而 key2_reg值为 key1_reg前一时刻的值,有按键按下时 key_ctr 为 1,可是程序中 key2_reg 值 20ms 才刷新一次,如果在做我们用一个按1 R2 d0 c3 }5 _$ O
键  ,  控制一个二极 6 R+ u% N7 ]( ]: r( ]- U/ S( u- j
管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge ) & I$ Z, `* v% V7 A
.........................................(  省略  ) ( |4 k& z# C2 a; j( Z1 Y3 L1 q
If(key_ready) led<=~led
0 c6 p2 m! c* u. r) S; a7 m这段 20ms 时间内每一晶振脉冲下 led 都翻转,这样灯就一闪一闪,不能达到要求,而我们) [5 m  y7 \+ g$ }% E
程序改成这样: # f9 D1 z/ B0 W; ^
reg [3:0]key1_reg; 8 S  F/ y' p; z6 U1 ~/ H  ?8 m
reg [3:0]key2_reg;
( l7 A) B* N" X2 ]9 O9 Galways @(posedge clk,negedge rest)begin 1 s9 A/ e- s9 u. L: y
  if(!rest)
- Z; C  W% q. K* Q3 J/ z7 z. {: S   key1_reg<=4'b1111;
- ?* E% R+ i! w" h" F. w3 c  else if(counter==5'hfffff) ; L7 I; b* k1 i: ~9 O- E( {
   key1_reg<=key; $ O: [5 {, h0 a
end * |- t0 n% l# m& U4 s0 U
always @(posedge clk,negedge rest)begin
7 N( v+ E! ^. D* t  k, l  if(!rest)
0 m# R- T. v1 d6 D' o2 \   key2_reg<=4'b1111;
4 k& B! l+ l" t4 m7 G7 J& c6 c  else  ! q% {, l- F/ M( E9 w
   key2_reg<=key1_reg; 8 Q6 J. c) V2 d" e& o
end 5 D# ~3 j- S+ O" i$ Y* R% e" U
assign key_ctr=key2_reg&(~key1_reg); 5 f$ i! J) w" B# J+ ~- T% ^
Always @ (posedge clk or negedge rest )
; ~; d" o; ]8 O" I1 e+ W.........................................(  省略  )
) Z: V: c" k; \9 b. [" Y8 d; b/ ?If(key_ready) led<=~led; ' U0 v. R- k2 k! M* j: {1 Y
这样 led 就只是翻转一次才能达到控制效果。
3 I% Y6 D  T! K' c6 L# o, v6 W* G3 t- z  H# T+ s
( Y& S/ X# U. z: _( {, N6 L
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-10-9 09:08 , Processed in 0.156250 second(s), 23 queries , Gzip On.

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

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

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