利用按键控制LED的要求为:按一下按键,改变一下LED的状态。按键按一次,LED由熄灭变为点亮,按键再按一次,LED由点亮变为熄灭。
4 [+ _; w1 H* }1 q0 l# x7 E( A$ ^# C
开发板上面有四个按键,当按键按下时,将对应的网络置成低电平;当按键释放时,将对应的网络置成高电平。
6 D3 l0 s, j* H/ E6 ?0 x" K
开发板上面有四个LED发光二极管,FPGA输出高电平时,LED点亮;FPGA输出低电平时,LED熄灭。
: h+ q$ v0 ~0 x8 a; z1 w- t V
- 设计原理6 L5 I6 g8 b5 }% x G( G
9 e6 t% a& q& ?& o
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
7 I& Y7 J9 w/ c3 c9 g/ D- o$ ~按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
( V$ Q# f# E# w2 S4 x抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。
1 |5 M% R9 ^8 x我们可以在按键和主控设备之间加入消抖电路(消抖芯片、电容等),此种方法会增大PCB面积和花费一定的物料费用。大多数的板子直接将按键和主控设备相连接,将带有抖动的波形输入到主控设备内部,由内部进行消抖处理。
7 V" l: d5 Q0 E; r; _6 y单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为20ms),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
9 H/ |1 T+ n: u# c9 i在FPGA设计时,笔者推荐另外一种方式:持续采样。当检测到信号持续为低10ms,认为按键按下;当检测到信号持续为高10ms,认为按键释放。
* K: A. R& W% Y8 u. }
在设计时,需要考虑到外部的按键信号为异步信号,需要进行同步处理。具体请参考附录2 FPGA中的同步信号、异步信号和亚稳态。
" g8 g- s( B2 y# }5 ^3 @ \每次按键按下的时间的长短不一,经过消抖后,低电平的持续长度长短也不一样。此长度远远大于一个时钟周期的长度。要求每次按下只能够切换一次LED的状态,所以不能够直接用此电平当做输出翻转的使能。
' t# T9 @4 S9 R5 N- T7 l经过消抖的波形,每次按下只有一个下降沿(按键按下时)、只有一个上升沿(按键释放时)。所以通过检测下降沿(上升沿)的变化,产生一个新的信号------脉冲(一个时钟周期的脉冲),利用此脉冲作为翻转的使能即可。利用检测到下降沿的脉冲翻转时,LED的状态会在按下时就会改变;利用检测到上升沿的脉冲翻转时,LED的状态会在释放时发生改变。本设计中采用检测到下降沿的脉冲进行翻转。
( L4 t: ]. w$ P+ @% ~$ i- g- 设计架构和信号说明
( g5 u! T; Z% B6 X$ ?
! k2 L$ ]) s- }# n/ s$ T
本设计模块命名为key_led。
3 \( e [+ N/ B ^7 L$ R2 N' o8 c, J D: I% w5 I6 }: i# P5 @5 ^: z+ C
" T2 o# B( X) E: ^7 \) r3 b5 e. W8 s
在设计中,共分为三个模块。
% j* B' E9 F1 A3 Ukey_filter(按键消抖模块):将外部输入的带有抖动的波形进行消抖。
8 \$ i4 ^( u- }edge_check(边沿检测模块):将消抖后的波形进行下降沿检测,并产生对应的脉冲。
8 Z* [) \" g$ B6 S: P
% h' C0 d9 z* k3 @! r: Iled_ctrl(led控制模块):利用脉冲,翻转led的输出状态。
, D6 h6 ?- L9 B; x2 C# M7 H" G! T( @1 s
; [ h! h7 R2 O5 Y
" s' ~" f* {6 W2 Q2 l' p4 B6 B
3 U- i% O4 f+ S! j P- key_filter设计实现
: s4 s& c1 r8 P( k+ H1 S) ~% @' {
' J3 Y* _! h& a) b- B
本设计采用状态机实现,状态机的具体原理请参看相关文章。
; P0 B* y0 e# Z% h" n对key_n信号为异步信号,需要进行同步两拍,命名为key_n_r和key_n_rr。状态机的判断信号为key_n_rr信号。
" p: Z/ D* Z! O6 _
本设计共分为四个状态,KEY_OFF(按键释放状态),SHAKE_ON(按键按下时抖动判断状态),KEY_ON(按键按下状态),SHAKE_OFF(按键释放时抖动判断状态)。
4 Z' I8 L7 Y6 `0 e, q按键没有按下时,一直KEY_OFF状态,当按键信号变为低电平时,就转入SHAKE_ON状态,检测低电平的持续时间。如果持续时间没有达到T_10ms就变为高电平,则清零计数器并返回KEY_OFF状态;如果持续时间没有达到T_10ms并且也一直为低电平,则继续在SHAKE_ON状态计数;如果持续时间达到T_10ms并且为低电平,则清零计数器并进入KEY_ON状态。在KEY_ON状态,外部输入为低电平时,则继续在KEY_ON状态;如果外部输出为高电平,则转入SHAKE_OFF状态。在SHAKE_OFF状态,如果持续时间没有到达T_10ms就变为低电平,则清零计数器并返回KEY_ON状态;如果持续时间没有达到T_10ms并且一直为高电平,则继续在SHAKE_OFF状态计数;如果持续时间达到T_10ms并且一直为高电平,则清零计数器并转入KEY_OFF状态。
" S Z3 A: H! f8 n( p' B7 g& Z8 U
在KEY_OFF和SHAKE_ON状态,认为按键没有按下;在KEY_ON和SHAKE_OFF状态,认为按键为按下;
' n; w9 q- h0 }% |
, b% U# r' p( n# y# c5 K1 T" U& d状态转移图如下:
6 y/ T5 L5 ]8 C! U4 s! c
3 `# H5 s" T+ @6 E5 |- @
. Q6 X2 M+ L( k y$ F/ V* r8 B+ j9 {+ Y" B, F
5 l' S4 q8 P% D4 L k
5 O; `: n" W+ D% Q3 A. K
设计代码为:
% M& O/ ]1 o, ^
% t- a+ E" k# m) c4 t, T
0 }' t+ A" t; ?" I
( m0 j8 l% F6 I$ K/ `: s [8 ?+ q
' v+ p' x. h- olocalparam可以定义参数,与parameter的区别在于,parameter定义的参数可以在例化时进行参数修改,而localparam定义的参数在例化时则不能够修改。定义状态机状态时,一般采用localparam的定义方式。在不希望别人修改参数时,也可以定义为localparam。
. N `7 b0 E4 m8 q2 O7 w, \- edge_check设计实现5 O8 O3 @4 l# P0 M5 H
& k( d" u. q* E) ` g# ?0 z, U. Z
在一个波形中,如果当前时刻为低电平,上一个时刻为高电平,则认为波形中有一个下降沿;如果当前时刻为高电平,上一个时刻为低电平,则认为波形中有一个上升沿。
: v+ c1 M: u. H% R2 ^( v! M: Y2 S& p- Q8 y5 Q
在数字电路设计时,可以采用寄存器来存储上一个时刻的值。
+ \$ M( f( ]/ A9 K- a# y& ?- u t1 D. ~8 F
, q$ w. @0 |9 q8 {+ V o8 F I' b* y/ Q6 ?# Q r1 b
) l6 E: L. t+ s7 O" e在寄存器电路中,Q的值,永远是上一个CLK的有效边沿所采样的D值。因此Q为上一时刻值,而D为当前时刻的值。
0 h0 E' w# u' L9 ~! G9 l
7 O+ t* Y1 l# s% r- S5 ^) H
设计代码为:
6 ?4 \4 Y- c' ?- {% Q% c
! b; E( G- L2 t& _ b
; M6 O# T" W( {$ f; F9 r$ C
! J- {, O0 J- S, }0 F) R4 C
在设计中,注释掉的两行代码和其下方的一行代码的功能是相同的。例:对于上升沿脉冲来说,现在为1,过去为0即为上升沿。由于寄存器每个时钟周期都刷新,满足这个要求的只会存在一个时钟周期,所以flag_pos为一个时钟周期的脉冲。
5 S. k- d# }4 z ~2 a8 [) A
- led_ctrl设计实现
; S) _: _% z% p2 \) c6 e/ _# z
( z$ o9 o$ a/ L% ^1 @& l. n& I本模块中,利用脉冲进行led状态的翻转即可。
) S2 D) L$ Y8 f1 I; y. g6 N$ j+ r A/ a" ]7 ^; l \" f* J4 {) N
设计代码为:
( ^1 d8 B6 T$ }0 U( Z1 e$ v- }3 e R7 H; G
2 x, v! l! ]0 ` R c1 J6 L/ X4 S7 s! Z- Y# h; f
0 I/ I3 T6 f: C4 v: N% l% C* W+ J- key_led设计实现; p' ?$ C) z h' e
, T- Q; x7 p2 Q2 t! R本模块只是负责将上述的三个模块按照架构图的方式进行连接,形成最终的设计。
' W5 o) m% |9 }: ]3 G
6 f4 ^* E6 g; {/ L$ o" D" k设计代码为:
& U2 I/ e) |4 h3 i) ?# e
$ ?6 d# @) `4 w( K4 ]6 M- ?
% U* d6 C6 {$ N7 ^, w0 w! J
% Z$ H2 Y) _4 v/ |! M. O/ H$ Y8 w
4 Y8 l' k' g" d0 G: X7 u) p8 L
t% ~) `) P* @* W+ C) ~在设计中,采用了按键按下时的脉冲(检测到下降沿的脉冲),按键按下时led的状态即可进行翻转。
( w' W, o, T+ Z% v3 F; D2 ]$ z2 L
/ f) U0 ^. o" I- P7 `/ m$ k1 a9 {% F% _ _# O% A$ I
8 A- e) N Y1 ^2 z
2 F& A2 U9 ?1 W+ Z4 R+ z# C0 o
" @; L7 K8 j6 Q3 H- p1 j8 G. o, I4 w/ N- P$ |; L* Y
# \$ _) j/ H0 ?5 J* r$ ?$ h( B
0 f) u) D( G: g& j( g
' t# R6 C/ T. I, ~. Z! R
% L& |5 o- w( p, T9 c" ?: r+ T3 P/ }/ Z5 N, Y- j
- 功能仿真
O* P% S6 u2 Q6 @( R& H4 ^4 S( v/ Q$ k
1 G W: N1 M, X/ }+ D- d
6 A8 k; H% s" M7 s, t- E* p" {在仿真时,将按键消抖中的T_10ms的参数修改为20,即持续时间不超过400ns都不认为是有效按下或者抬起。
4 D( O. M2 U' C" C, \) Z( c1 Z; r
仿真代码如下:
* V6 @' S% g I: z1 }* ^& g1 j2 D4 G
. u: M$ Z# Z) V
7 Q9 U7 m+ G: [- Y" P7 D
) U+ ]' p2 V+ Z2 i, ?9 k- J4 g& R
2 k; n8 G- N6 J) [0 N5 g; p7 H4 ]
将okey_n、flag信号添加出来。
+ f \( `: K, \2 L3 J+ h0 b* y" Z( U6 o" w* ^ [
9 S3 i( f" C: D" s) Q9 y' ] r& Z7 n" U& _& a* n3 L5 `; \6 d
* M+ j! p+ I" l) O% y
通过RTL仿真图,可以清晰的看到okey_n信号将key_n的抖动滤除掉;flag信号为okey_n信号的下降沿时所产生的脉冲;led在flag信号为高时,反正翻转。
; b! f, g& M9 D
分配管脚、下板测试之前,应该将按键消抖里面的T_10ms参数重新改为500_000,否则下板后可能会达不到消抖的效果。
! H" w' q; z8 z
* l/ ~" s2 `: B8 e
下板观察现象:
- L% V3 P) a* G, C2 I0 M6 @
4 o1 |$ N6 }7 G6 i7 y6 l
4 d& G- g2 M# o- X* i/ M4 \
' H$ P( Y6 b1 t t& }! n2 }" D下板成功后,可以修改在设计中使用上升沿的脉冲,得到的现象应该是按键释放时,LED的状态发生反转。
4 J- [$ K" O. d
9 p* {, s9 V& F切记:每次修改代码,一定要进行重新编译,否则更改将不会生效。