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

Verilog按键消抖

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
Verilog按键消抖

% Q' Y" N. c0 V" v+ y. b
  v6 K' s9 r, lFPGA按键消抖程序剖析  7 S2 |! W. |6 ^. ]3 Q
对于学习  FPGA  的爱好者来说  ,  在我们做的许许多多的系统和项目中都会用到
4 A, H2 a3 u3 S  [按键  ,  但是在我们所涉及的按键输入的数据  ,  是不能够直接利用的,而是要经过消3 u" ^+ [) l, o) P3 m
抖,唉,为了初级菜鸟,说简单点吧  ,  什么叫消抖?消抖说白点就是消除抖动引起的1 o4 A: d1 V8 ?& C) X/ m$ m# J
按键不确定性!如果不消抖处理的话,就会按一次可能将相当于按几次出现的结果。相
, S2 Y# U2 t3 q" g2 V信玩过单片机的人都清楚  ,  我们在读出按键的时候都会用到一个程序: 2 F' C  E: t2 J+ J9 b" \/ L1 I
If(!key)
3 U/ T: [# M7 _, i7 D{
6 w, N" \' f. PDelay(x);
) t: L+ Z6 D1 Y9 f/ nIf(!key)   t: ?4 y6 _- s/ ?) \
{...}
4 ?; s* {3 t5 Z} ' T; T7 ~8 H+ h& F- ~9 f9 V
这个程序在此就不再延伸解释  ,  我所要说的是  ,  在  FPGA  里面按键的消抖与单片
7 R& ~6 r3 J; e4 p7 m. t机的消抖原理都是一样的!或许大家都自己写过或者在网上找到过许多消抖的程序  ,  但是
9 Y% U. _( l% q$ A. m5 Y我所接触的当中  ,  我觉得特权前辈所写的那个是最为经典的  ,  一下就是我盗版特权前辈6 i" S+ U; m4 u* c8 L
的一个程序: 2 V. ~8 I- }& W3 v/ b
module keyscan_module (
* ]6 p& u# t! Y+ |# \' finput clk, //  外部输入时钟,我选择  50M % i  a3 [: f0 H
input reset, //  复位  ,  但是在此不建议使用此种复位
% R- F( |! w' f& j0 U! W9 W( m//  形式,建议用异步复位同步释放
) `) |" p: `5 W5 u0 Winput key_in, //  按键输入(  1bit  ) 2 m) p( o0 _- A! b4 S* `$ K2 Y3 h
output key_ready); //  按键值输出 4 m% }1 j9 M/ _$ r
/*********************************************/
2 m& {  q' a3 ]2 t6 a7 _. rreg key1,key2;  定义两个寄存器变量 2 h7 `. D# B1 t
wire key_en;  定义一个线性变量
6 c" w. _$ r. L  H5 talways @ ( posedge clk or negedge reset)
- ~9 t! d# X* D% X/ i$ f! ~if ( !reset )
8 J$ Y' G2 r; [$ y$ ]8 Ubegin % z& h" A3 J* u
key1 <= 1'b1;
( G8 [& C) K' ^9 A+ F* ?( q  Tkey2 <= 1'b1; / i6 x' Y9 g/ `; d# \/ X, a
end
  P5 ^/ Q- p  G- M% d, f1 Yelse ' \/ X8 Q  s# B0 A
begin - t- |% N0 S) X8 E
key1 <= key_in; //  学习过非阻塞式语句的同志,应该不能理解这里
5 M& n4 b0 Q5 Y0 U, |  [6 u//  第一个时钟读取按键进入第一个寄存器  ,  第二个时钟 ) i5 i! x% T4 J* [- w. H9 {- g
key2 <= key1;  将第一个寄存器中的值赋给第二个寄存器
  D: X' f1 J% I* U5 q: E5 U6 X3 fend
3 ~3 L) y# Y2 f/ u" e# \7 Rassign key_en = key2 & ( ~key1 ); //  当检测到下降沿的时候,  key_en  保持一个时钟的高
: d* W$ W* @; I" g8 \; f4 b" r' y电平 ; F. I, V/ _/ v& ]3 t
/**********************************************/ & H; o. ]# W' x( U8 Z( m
reg[19:0] cnt;  定义一个计数器
: h9 D+ L& k6 e+ d5 @" ialways @ ( posedge clk or negedge reset ) 8 a9 U& _8 [0 B- ^9 [/ n
if ( !reset )cnt <= 20'd0;
  Z" o9 M5 V+ e$ N: y* D5 Relse if ( key_en ) cnt <= 20'd0; else cnt <= cnt + 1'b1; / y* Y; Y$ c7 l/ M* K
/********************************************/
, X+ r& A3 g* Areg key3,key4;
! h% k" [" m; M& P) t# e$ galways @ ( posedge clk or negedge reset ) & d6 e+ |$ }1 }/ E
if ( !reset ) key3 <= 1'b1; . j% h$ |# ^; M3 ]
else if ( cnt == 20'hfffff) //  当计数到  20ms  的时候
. X+ O, _0 y0 B1 v, ~key3 <= key_in; - y0 [( M/ i1 q5 W6 @$ Q, ?0 Q( F
always @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按键值赋给  key4
4 T" Z9 x8 U6 q1 m1 ~7 }if(!reset)key4 <= 1'b1; 4 s* c0 X" t+ o6 U" u
else key4 <= key3;assign key_ready = key4 & ( ~key3); //  当有按键按下时  ,  输出有效  ,  保/ e( N) Z! R" n0 w9 n
持一个时钟周期  ; / O8 I) {- A6 l( m8 r
endmodule
& _  M/ T/ l+ a. o' }! J7 Y下面我们来好好研究下这个程序: 7 S7 e4 z# R* u/ \
整个程序的基本思路是这样的  :  系统上电后  ,  计数器就开始计数  ,
' z$ ?1 U# O; b7 Z, Q; Q1 ](注意啊  ,  不管有没有按键按下都在计数  ,  每次计数  20MS  )  于此同 ' v$ i, a& [. \$ u4 c- c" v* J1 D+ v
时,系统也在不断采集  key_in  的电平,假设在一个时钟上升沿的时
$ y( Y& @2 G% e候,检测到  key_in  为高电平  ,  (那么  key1<=1,key2<=1  )在下一个时 ! p! A. P% i9 v! {9 U% ^3 X4 @
钟上升沿的时候检测到低电平  (key1<=0;key2<=1)  那么执行这个语句 ) @; j  D3 R4 x" h- }  ^! t- h
assign key_en = key2 & ( ~key1 )  那么  key_en  就会得到一个高电
* z- v. n4 u! ?5 e9 W平  ,  当第三个时钟上升沿的时候  (  key1<=0,key2<=0  )  由此可知  ,  key_e n
) J6 ?2 K, p* F. j1 L只保持一个时钟周期的高电平  ,  而且仅是当检测到有下降沿的时候才 . k3 ]0 x: h, F* S
会变为高电平  。  说到计数器  ,  前面也说了  ,  上电之后计数器就一直在
3 n  k2 d3 b) y& G' B2 H& t计数,当时当  key_en  为高电平的时候,计数清零,也就是说,当前
+ Q1 e0 A* N6 m0 E# H$ Q: h7 }& j面检测到下降沿之后就重新开始计数,
  d' p# v5 l* U9 d. g7 Uelse if ( key_en ) cnt <= 20'd0; ( O! |2 R( l6 }: F
else cnt <= cnt + 1'b1;
/ {( v9 F1 R# p然后到这个程序 3 J! t/ m8 n! \( ]! y7 n, D6 c
else if ( cnt == 20'hfffff) //  当计数到  20ms  的时候
# C. x" b9 e* ^- Xkey3 <= key_in;
$ d) ^# E* d1 |. R7 g这个程序的作用是计数到了  20ms  之后再一次读取  key_in  的值  , 5 z3 r* t2 E/ b, j9 t
换句话说,就是从前面检测到下降沿之后,  20ms  后再去检测!我们
7 ]. G6 |( A4 \7 K6 x5 F都知道,抖动所产生的毛刺都是在  us  级别的,哎呀,再怎么大也大
( e5 }- ?6 }4 H) w  H2 c不过  20ms  的,而我们按按键的话那肯定就不止  20ms  啦,再怎么快也要  500ms  以上吧!
( K5 F+ e3 ?8 f, P再结合下段程序:
0 Q3 k5 Y  s+ I! D* calways @ (posedge clk or negedge reset) //  下一个时钟把  key3  的按
! g+ M- h/ `) Q9 o键值赋给  key4
' g! Y0 |) Q6 fif(!reset)key4 <= 1'b1; ' ~- |8 W1 w5 p
else key4 <= key3; ) t; L5 S$ u$ Y) ~, _
assign key_ready = key4 & ( ~key3); // 2 u" b5 E3 W1 U
假设第一次检测到的下降沿是由于抖动产生的毛刺  ,  那么  20M S $ s6 n2 |4 U2 b+ t# t
后  ,  过  滤  掉  毛  刺  ,  检  测  到  的  应  该  是  一  个  高  电  平 ' x+ @5 Q. @% M' S3 W
(  key3<=1,key4<=1,key_ready=0  )  ,  假如前面检测到的下降沿不是由于
- o% E* B7 Y; x% _3 s; C/ ~毛刺,而是按键按下的,那么  20MS  后,  key_in  肯定还会是低电平  ,
7 b) v9 n2 S. p/ |- t0 P所以  (  key3<=0,key4<=1,key_ready=1  )  ,  而在  20MS  之后的下一个时钟 (  key3<=0,key4<=0,  那么  key_ready=0  )  key_read  只保持一个时钟周期的时间! 1 Z# B. \& E8 q5 `. ~' \
为什么这个消抖程序被列为经典呢?大家神人研究后不妨从
6 M: Q7 L2 F  v/ v时序和资源方面去考虑一下,昨天我在一本黑金的教程上面弄了一 7 w: a" `) K$ p
个,一个按键就消耗  80  多个  lut  ,而这个只要  30  个,试想一下,  做 4 P; y4 h" B, {* m
16  个按键都上千呢,那其他模块怎么办?还有我觉得很好的一点就
( G9 J: X, p" I$ X8 P2 J4 f% z% L9 z是它的输出  (  key_ready  )  只保持一个时钟周期  ,  有利于上层模块采集  !
0 t# |9 {) G& o/ j) W8 o5 ]哎呀  ,  没有对比  ,  看不出差距  ,  以下是我们老师教我写的一个消抖程
3 F5 M) J8 B& V  f# E  _5 N8 {% i序:他的思路是:把系统时钟(  50M  )分频  100K  (  10ms  )后再去读
* {1 o2 E) c, o# J取按键值 8 [) T( [% x# L
Always @ (posedge clk_100K or negedge reset)
" U5 }! W# d2 [" jIf(!reset)begin key1<=1;key2<=1; end
& g) F% R+ F* N8 K5 z5 PElse begin key1<=key_in;key2<=key1;end * ]/ N" I- }& V- U3 h9 c
Assign key_ready=key2 & !key1;
* N' i; K4 \! @# ?7 n8 m# _- X+ e大家看一下这个程序  ,  原理和特权的那个差不多  ,  看似更加简单  , 6 W6 S! ~5 t! X& a3 U
的确,这个程序可以消抖,但是存在一个问题,就是  key_ready  也会 : S( i; L) u* z+ {% [
保持  10MS  的一个高电平  ,  有些人会问  ,  高电平持续就一点不是方便
4 a0 T/ G( Z+ ^2 B上层采集吗?其实不然  ,  举个例子  :  我们用一个按键  ,  控制一个二极 2 W9 u$ p. ~) {0 p
管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge ) 6 ~3 T9 A, T2 i/ E+ L6 Q  ^
.........................................(  省略  ) , F9 O7 W4 E* F5 G; G. U, [
If(key_ready) led<=~led $ o! K. i9 H& _" r# g1 X& G
我们使用的系统时钟是很高的,假如  key_ready  保持不是一个时
6 N8 H) o& S3 o* q+ A" x钟周期  ,  而是  10ms,  那么我们按一次  ,  led  就会一亮一灭很多次  !  碰到
# g  W7 T# \! ^; L这种情况,一般要用组合逻辑解决,比如 $ o% i' |* s! B
Always @ (key_read or negedge) . X4 D) M" ^9 f) O, q
................... # {# Y, n* k. }7 j* @( j
If(key_reaf)...... ; ]* a/ f1 N/ C
这样也可以实现按一次变化一次  ,  但是大家都知道  ,  组合逻辑往
* O& `7 T( L3 x往会给我们带来许多时序上的问题,能用同步就同步  !  在此也特别提 & @7 p- F  [4 T9 j5 }
醒诸位,在  if(x)  判断电平的时候多考虑一下,免得出错!
% \% k* `) ]5 |) w1 a. D2 V: H5 V) Z& r
对于消抖程序忠告如下
" {& V4 B1 Q1 u9 u- H' Ureg [3:0]key1_reg; $ H& f6 i, |0 {' {) ~, I
reg [3:0]key2_reg;
& d& J# j* U4 I$ |! h# Galways @(posedge clk,negedge rest)begin 9 P* V& }0 W* C) b( r
  if(!rest)begin / i3 S: K5 g# x/ ]3 Y
   key1_reg<=4'b1111; % @8 E. f3 d9 c4 y2 K0 m
   key2_reg<=4’b1111; 3 d1 K( w. q- B) ]" h% g* `1 t& j
  end ; j! U( J$ f6 [# Z
  else if(counter==5'hfffff)begin + w% x; @, [2 B; ]) {1 J$ Y# C1 R
   key1_reg<=key; 5 R5 H) y  ~, Q% @/ ?* C
   key2_reg<=key1_reg 6 [9 G& r* N" C3 C
end 1 g% C7 v$ ?1 N, ^; h$ _& m
assign key_ctr=key2_reg&(~key1_reg); 7 V! n( F# W1 s7 q* R+ L5 O1 }
这段程序,20ms后采样 key的值给 key1_reg,而 key2_reg值为 key1_reg前一时刻的值,有按键按下时 key_ctr 为 1,可是程序中 key2_reg 值 20ms 才刷新一次,如果在做我们用一个按
; N$ i7 u+ \) ?' n) Z& }$ b键  ,  控制一个二极 . ^" S* Q  I: G" W
管,按一下亮,再按一下灭,程序大概这样:Always @ (posedge clk or negedge ) ; Y9 d5 T/ N$ D
.........................................(  省略  )
$ B- U! W# |* SIf(key_ready) led<=~led
- U5 }8 X: w$ j4 s/ D% F7 Q这段 20ms 时间内每一晶振脉冲下 led 都翻转,这样灯就一闪一闪,不能达到要求,而我们9 T/ d/ H6 N2 ^$ {
程序改成这样: 0 e8 E. t3 |) x
reg [3:0]key1_reg;
  [' P* W* \( u, ]9 y: X- A6 U% ]reg [3:0]key2_reg; , b3 I$ n2 y3 h/ g
always @(posedge clk,negedge rest)begin " O7 A+ V- A' Y2 O
  if(!rest)
6 J! b4 [! z" V   key1_reg<=4'b1111; . R1 f9 O0 q8 _' j1 K8 k) N: J* ^
  else if(counter==5'hfffff) $ k5 P, X6 [2 H$ K1 D
   key1_reg<=key; ; N0 @7 k6 R& A1 j1 g" Q
end 0 B! a! z) G& p4 ^/ S. ?( Y. u
always @(posedge clk,negedge rest)begin 2 O9 {5 K, {  G* X
  if(!rest) 5 E$ Z0 Q! Q4 G1 O8 C2 H2 I
   key2_reg<=4'b1111;
, E, D0 k) h. W; V  else  
! s& ]! K0 Q! |- N% {3 K- n5 X9 ~   key2_reg<=key1_reg; ( ]6 U$ @6 H* ]: ^. u
end
8 X* x( m* J+ @4 J# y' Lassign key_ctr=key2_reg&(~key1_reg);
2 ]2 P' \$ X" F( Z8 s& A$ aAlways @ (posedge clk or negedge rest )
7 N2 A4 `! S" v/ D.........................................(  省略  )
' L! ~4 f# r2 v, e# ]If(key_ready) led<=~led;
+ n9 m3 l8 T4 ^. ]- f! v这样 led 就只是翻转一次才能达到控制效果。 % I% V( {1 ^& f+ j

+ v! c0 x" w& r2 w8 @+ j' Z/ x! U& J# @/ i  |1 C) L
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-8-1 23:33 , Processed in 0.125000 second(s), 23 queries , Gzip On.

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

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

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