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