利用按键控制LED的要求为:按一下按键,改变一下LED的状态。按键按一次,LED由熄灭变为点亮,按键再按一次,LED由点亮变为熄灭。
0 I& G* }5 C" F
- 硬件介绍
) F( {4 }" w) i" @% X7 o% |
( { C& A% \9 c) x4 L, m# x7 w开发板上面有四个按键,当按键按下时,将对应的网络置成低电平;当按键释放时,将对应的网络置成高电平。
& [" {) {9 L9 T; X8 i/ W5 o开发板上面有四个LED发光二极管,FPGA输出高电平时,LED点亮;FPGA输出低电平时,LED熄灭。
4 I: u1 {( }$ q7 ~ p- u# F! [2 p9 t$ J: z( l
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
8 E! m4 x/ O& M' {) a% u: @/ S# d按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
% i/ D" o0 o% x) I( \5 |( _+ T/ K抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。
0 P$ F9 P4 v( p! k" v
我们可以在按键和主控设备之间加入消抖电路(消抖芯片、电容等),此种方法会增大PCB面积和花费一定的物料费用。大多数的板子直接将按键和主控设备相连接,将带有抖动的波形输入到主控设备内部,由内部进行消抖处理。
7 Y/ i6 L6 { X- `+ Z
单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为20ms),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
8 o. e+ b' {% P
在FPGA设计时,笔者推荐另外一种方式:持续采样。当检测到信号持续为低10ms,认为按键按下;当检测到信号持续为高10ms,认为按键释放。
$ k- h- _6 I' D, L2 p
在设计时,需要考虑到外部的按键信号为异步信号,需要进行同步处理。具体请参考附录2 FPGA中的同步信号、异步信号和亚稳态。
" }1 E9 W! K# r/ H- Z
每次按键按下的时间的长短不一,经过消抖后,低电平的持续长度长短也不一样。此长度远远大于一个时钟周期的长度。要求每次按下只能够切换一次LED的状态,所以不能够直接用此电平当做输出翻转的使能。
' m8 q! z5 x2 M: b! Y7 f" z4 R
经过消抖的波形,每次按下只有一个下降沿(按键按下时)、只有一个上升沿(按键释放时)。所以通过检测下降沿(上升沿)的变化,产生一个新的信号------脉冲(一个时钟周期的脉冲),利用此脉冲作为翻转的使能即可。利用检测到下降沿的脉冲翻转时,LED的状态会在按下时就会改变;利用检测到上升沿的脉冲翻转时,LED的状态会在释放时发生改变。本设计中采用检测到下降沿的脉冲进行翻转。
) B( A7 m i. E' E- 设计架构和信号说明" E# t& k T8 D) z+ C& c0 c# [) S% S
9 d5 Y9 W+ l" r) q9 `, M
本设计模块命名为key_led。
) k7 m8 N0 |4 Q5 M
! t/ g& y4 t8 X: x9 j4 T; U
6 e: n. D5 N( c: G! E2 r8 z \
* X. q. d" _, m) ]" B' {
在设计中,共分为三个模块。
$ ?, K2 r+ L8 L3 Wkey_filter(按键消抖模块):将外部输入的带有抖动的波形进行消抖。
& D, E1 X `0 Z" b7 |% jedge_check(边沿检测模块):将消抖后的波形进行下降沿检测,并产生对应的脉冲。
0 L3 ~8 T8 I0 i- g( o2 k6 @: U7 n# z; X) Q" `
led_ctrl(led控制模块):利用脉冲,翻转led的输出状态。
& z! C8 B& F( p0 K" ?8 J. G, V8 a" _: E" P
1 x& c4 _+ _: H1 |+ l0 V
* _0 u) r3 W* v
5 u" c$ Z# Q# O/ D0 D/ N; ~8 A1 X+ h: X+ F( d
- key_filter设计实现
4 s$ M, S) [ G. c
) s! m% X x/ i0 |6 }本设计采用状态机实现,状态机的具体原理请参看相关文章。
0 S7 O3 f0 a ?3 `) w
对key_n信号为异步信号,需要进行同步两拍,命名为key_n_r和key_n_rr。状态机的判断信号为key_n_rr信号。
; G- w: m) }8 C$ ~; j" L) r" j
本设计共分为四个状态,KEY_OFF(按键释放状态),SHAKE_ON(按键按下时抖动判断状态),KEY_ON(按键按下状态),SHAKE_OFF(按键释放时抖动判断状态)。
6 b* E+ Y2 c$ U
按键没有按下时,一直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状态。
- ]1 K7 c5 k1 w3 B/ Z0 e在KEY_OFF和SHAKE_ON状态,认为按键没有按下;在KEY_ON和SHAKE_OFF状态,认为按键为按下;
! R- i6 p8 U8 M$ G/ F$ M% D8 W H$ [! W
状态转移图如下:
: v/ C; t9 \' A; }7 `$ \0 R4 b& `; S- P T" F9 T
+ F* `" m6 t. ]/ v) }, U: t: Z( Q$ C" O( Q, b
3 W2 a: k. i {! Q O
2 y4 p2 Z) C/ J ]% g. @设计代码为:
; t& p& e- m# x% `7 g. z' U
% _( S: M: X4 ]6 {0 A
$ I3 c- u9 Y4 F7 S* ~4 a
* i9 u2 H1 Y& e1 K4 R1 r
6 m. S- _0 ]% Plocalparam可以定义参数,与parameter的区别在于,parameter定义的参数可以在例化时进行参数修改,而localparam定义的参数在例化时则不能够修改。定义状态机状态时,一般采用localparam的定义方式。在不希望别人修改参数时,也可以定义为localparam。
$ A, R7 \: W+ J6 n# @2 p( @8 \) G- edge_check设计实现
6 u8 Q/ q7 y: I7 I* w( `
7 T; [* A" a+ P0 j$ T2 Z: `" F* \. {
在一个波形中,如果当前时刻为低电平,上一个时刻为高电平,则认为波形中有一个下降沿;如果当前时刻为高电平,上一个时刻为低电平,则认为波形中有一个上升沿。
) O; w D' s# x3 u) Z8 E
- ~; Q& \, b; ?8 J3 M- A8 ^. \ N
在数字电路设计时,可以采用寄存器来存储上一个时刻的值。
$ [1 _ T2 N. w4 s( K
7 G4 W7 p! ], o8 N; r
- R8 g" d/ B+ ?5 N- c6 |3 E
- k! u. } m# J$ R; P$ }
+ {7 c, ~1 O; E# G8 v2 V2 T在寄存器电路中,Q的值,永远是上一个CLK的有效边沿所采样的D值。因此Q为上一时刻值,而D为当前时刻的值。
' E% S( J( X% n; \/ w# H( d, Z6 S6 ] a0 a7 ~* j0 J! I0 @5 V
设计代码为:
; p+ V: R3 `9 R) j. `7 ~
4 p/ w4 W. @2 p) ~) I7 E& u
7 L+ c1 o5 r: @" k5 d
* O4 B% @4 f; d' f7 I% ?在设计中,注释掉的两行代码和其下方的一行代码的功能是相同的。例:对于上升沿脉冲来说,现在为1,过去为0即为上升沿。由于寄存器每个时钟周期都刷新,满足这个要求的只会存在一个时钟周期,所以flag_pos为一个时钟周期的脉冲。
8 I' g; e& `7 ]( p4 Q
- led_ctrl设计实现! X! B* i& t" w( [5 H
/ x# M7 @ E2 V( _2 }8 A4 b
本模块中,利用脉冲进行led状态的翻转即可。
# m6 M0 E# J$ u A" a9 I/ u0 q
& r* J1 h' l0 O4 J/ T设计代码为:
7 f: R- l9 D# @0 G& U" v- m
+ `$ W' V7 L2 O+ G8 O/ f2 [
4 k6 \) V2 L' _$ M6 w: t% G5 s& P8 x6 |0 y1 H& Z
. e' G4 X2 ~$ ^9 ^. r- key_led设计实现
6 z5 x1 w! h0 L" \6 Q! t2 X! o
1 j) `) A: |- y4 T
本模块只是负责将上述的三个模块按照架构图的方式进行连接,形成最终的设计。
c; D) e& I' I- V0 `- @ d6 ^# b
f( c. L' o. ^3 m: ]
设计代码为:
" b% o& B; y8 I$ ?. b# e& ^
/ v: M7 D, A, @) h
. o0 O N7 C' |, L- Q9 s2 y# J+ \- z+ G
0 J) `& M, E- ]/ A. H
* Z |7 Q7 l- B& {在设计中,采用了按键按下时的脉冲(检测到下降沿的脉冲),按键按下时led的状态即可进行翻转。
6 N% E% {" w0 V c! i M
$ N( d$ G/ @7 y" U& R p8 y5 ?3 {( C/ s/ P5 Q$ M$ u2 S
X5 C2 Y& l8 S& o. I# m9 l
* }1 c; `+ Y Q$ L1 W @8 h
; v# Q/ f/ h7 W( t y8 h# C! {
: |1 U! I# E" Z# V9 ]/ x, q0 i1 u8 i
: F0 f; j* l' s# O: N
: a, V7 ^' b$ Y8 W% J; w: J) J' J2 A0 p o8 P
( k, b% |* O' T) X9 S w6 k; t0 b& l: {2 ^. ~
' b/ @) U+ a8 r4 G3 ?' }
" Q; i6 H; j8 d在仿真时,将按键消抖中的T_10ms的参数修改为20,即持续时间不超过400ns都不认为是有效按下或者抬起。
$ ]& e( b* a, j9 @ N! B. I
仿真代码如下:
" |! T8 G9 y. ^& H: u
8 B+ T! O5 `8 B
# v9 b, B' i, F3 `0 Y. R
! U6 B( b/ |' w4 b/ Y# V, P' a- c/ r6 V8 i1 ]
将okey_n、flag信号添加出来。
( f- A1 b8 d' C) u) l0 M* B M% A7 j2 V) ^5 l' s
6 D0 x/ ^$ n; d2 z& ~5 z2 I) H1 e$ E0 j
3 ~, _. I# T6 A5 e/ L! m; l
通过RTL仿真图,可以清晰的看到okey_n信号将key_n的抖动滤除掉;flag信号为okey_n信号的下降沿时所产生的脉冲;led在flag信号为高时,反正翻转。
$ k7 W$ S' a4 U2 y" R/ s3 |. b分配管脚、下板测试之前,应该将按键消抖里面的T_10ms参数重新改为500_000,否则下板后可能会达不到消抖的效果。
z7 F- i8 ]2 P3 W+ x
# o5 u8 ^0 S& W下板观察现象:
8 e; L3 b1 V3 |, k7 I' V+ M9 H/ l: Z+ ?9 h: N, S' E
9 `/ [) g( e" X% z% y
' F) G& C' b1 X下板成功后,可以修改在设计中使用上升沿的脉冲,得到的现象应该是按键释放时,LED的状态发生反转。
" b8 V5 E" z5 y4 ?1 _
1 S' E8 }! V o! ^% {) d切记:每次修改代码,一定要进行重新编译,否则更改将不会生效。