|
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 |
|