利用按键控制LED的要求为:按一下按键,改变一下LED的状态。按键按一次,LED由熄灭变为点亮,按键再按一次,LED由点亮变为熄灭。
. w N$ k6 C# V! ^* d7 x6 |9 ]- 硬件介绍
6 R; K, E" Q# H+ o4 W
5 a8 ~' m E: Q8 i( V8 j1 j( H
开发板上面有四个按键,当按键按下时,将对应的网络置成低电平;当按键释放时,将对应的网络置成高电平。
3 k+ B6 f; D" E/ j6 m开发板上面有四个LED发光二极管,FPGA输出高电平时,LED点亮;FPGA输出低电平时,LED熄灭。
9 J; k" I, n6 O' h+ B- 设计原理" X+ q+ [' l5 R; R1 Y; {7 \" G9 ]" B
5 a# l: L' {) U* ]2 d; |- z8 o通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
2 i0 \; V# F2 @$ Q/ B; Z
按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
$ b$ P% M5 Y7 M: @) h, Q1 u: G抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。
\# p, v- z3 ]3 `) J0 O我们可以在按键和主控设备之间加入消抖电路(消抖芯片、电容等),此种方法会增大PCB面积和花费一定的物料费用。大多数的板子直接将按键和主控设备相连接,将带有抖动的波形输入到主控设备内部,由内部进行消抖处理。
1 o7 M" G& L( u
单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为20ms),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
% V0 M+ r8 M& g- U4 E8 m8 Z* H在FPGA设计时,笔者推荐另外一种方式:持续采样。当检测到信号持续为低10ms,认为按键按下;当检测到信号持续为高10ms,认为按键释放。
9 C+ r8 ]5 s: W0 b
在设计时,需要考虑到外部的按键信号为异步信号,需要进行同步处理。具体请参考附录2 FPGA中的同步信号、异步信号和亚稳态。
0 b* i5 }. u& H) J# G3 y
每次按键按下的时间的长短不一,经过消抖后,低电平的持续长度长短也不一样。此长度远远大于一个时钟周期的长度。要求每次按下只能够切换一次LED的状态,所以不能够直接用此电平当做输出翻转的使能。
7 c7 D4 B& B, S经过消抖的波形,每次按下只有一个下降沿(按键按下时)、只有一个上升沿(按键释放时)。所以通过检测下降沿(上升沿)的变化,产生一个新的信号------脉冲(一个时钟周期的脉冲),利用此脉冲作为翻转的使能即可。利用检测到下降沿的脉冲翻转时,LED的状态会在按下时就会改变;利用检测到上升沿的脉冲翻转时,LED的状态会在释放时发生改变。本设计中采用检测到下降沿的脉冲进行翻转。
7 K# T4 {0 V! j( C* o+ B6 w- 设计架构和信号说明
1 x* ~5 x/ A$ T/ h2 C4 S( [8 N
. C! V* ^: K' g本设计模块命名为key_led。
9 F; H, \/ S+ l% I: b
$ ^) j8 d5 D: K
2 I2 v. w+ {, u$ j( k
+ B$ c7 j3 p+ D% M- z! u在设计中,共分为三个模块。
& U0 O+ i2 d2 W1 }key_filter(按键消抖模块):将外部输入的带有抖动的波形进行消抖。
( w, f8 b4 g8 r: Oedge_check(边沿检测模块):将消抖后的波形进行下降沿检测,并产生对应的脉冲。
2 p% ~8 b! |: k+ Q4 e( v7 I# d) w9 n4 m/ e
led_ctrl(led控制模块):利用脉冲,翻转led的输出状态。
) ]% U6 }! z# r0 r. W( [
! g6 c5 G( h' Y0 {+ a
7 ]( j1 g' a! z3 T! x6 J0 _: C
; k+ y: k9 J$ w2 l9 p1 A* g/ |
" H$ ]( ~- {, W- P g- key_filter设计实现6 i7 H9 e% d7 a
+ ~/ p- ?; y# F( Q+ [( H% L本设计采用状态机实现,状态机的具体原理请参看相关文章。
9 e$ H5 F. { g4 |( ]对key_n信号为异步信号,需要进行同步两拍,命名为key_n_r和key_n_rr。状态机的判断信号为key_n_rr信号。
7 c' A2 s+ W! ~8 c' b9 o本设计共分为四个状态,KEY_OFF(按键释放状态),SHAKE_ON(按键按下时抖动判断状态),KEY_ON(按键按下状态),SHAKE_OFF(按键释放时抖动判断状态)。
K) v& G0 ~) `9 g' f
按键没有按下时,一直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状态。
# a" v7 f! G2 ?9 w! s& i
在KEY_OFF和SHAKE_ON状态,认为按键没有按下;在KEY_ON和SHAKE_OFF状态,认为按键为按下;
; `% W( v0 ~5 Y1 \" _5 |! Z, m1 S3 m2 d0 r7 X
状态转移图如下:
6 q( _8 h; }% o. Y# H* M5 ?
4 J1 G* p- s; U! }2 Q; a
" p \4 l4 F. q$ k4 E0 L1 | ?9 t k# i7 ~# w
5 ]- u: d$ s8 X$ o9 I
) v) K# A+ K: s
设计代码为:
- A" P9 h# x0 w/ N9 w- T ?# h
( f2 a! C; I1 ^ ]* V7 T
4 t: t6 |4 ] `, ]7 q$ Q
! n5 j# Q3 n7 D! C( s
/ u2 u7 H7 r3 `0 _9 S) W; alocalparam可以定义参数,与parameter的区别在于,parameter定义的参数可以在例化时进行参数修改,而localparam定义的参数在例化时则不能够修改。定义状态机状态时,一般采用localparam的定义方式。在不希望别人修改参数时,也可以定义为localparam。
' w2 Z$ s+ E" ?$ A4 D
- edge_check设计实现
`# I# n# j" ~3 K/ ]
$ Q) x+ @4 C) N) n& Z, Q& d2 q# y
在一个波形中,如果当前时刻为低电平,上一个时刻为高电平,则认为波形中有一个下降沿;如果当前时刻为高电平,上一个时刻为低电平,则认为波形中有一个上升沿。
; L+ x, v% O& y6 L ?4 ~4 P; X" a& [- e6 P8 J8 {, _: p
在数字电路设计时,可以采用寄存器来存储上一个时刻的值。
7 U' t9 I: t) f" q7 e }' b/ ]* Y# Y/ W, a$ v
' c1 d; a- v' [
7 f1 d9 h! h' _3 R4 I0 \& m9 x6 J$ D
1 y( y7 [+ h6 c& A: p7 j
在寄存器电路中,Q的值,永远是上一个CLK的有效边沿所采样的D值。因此Q为上一时刻值,而D为当前时刻的值。
* W; F6 {& Y# t: a/ y
: ]" \- ] b7 F
设计代码为:
9 |; M* [( R0 B+ F( k+ n. _5 b; p+ I. h
; E; f) n& H+ m) P! U
/ ^! D( r3 z( Y6 \" i; [! A在设计中,注释掉的两行代码和其下方的一行代码的功能是相同的。例:对于上升沿脉冲来说,现在为1,过去为0即为上升沿。由于寄存器每个时钟周期都刷新,满足这个要求的只会存在一个时钟周期,所以flag_pos为一个时钟周期的脉冲。
$ T" x x' }0 c" M+ O' V- led_ctrl设计实现
+ T% G4 M1 p+ B b- ]
0 |6 y" f1 v# ~本模块中,利用脉冲进行led状态的翻转即可。
: m+ {" C% c2 g& Y' p t* G6 r
( i8 X+ a. `- `/ Z( Y* R
设计代码为:
( ^- _2 I% p+ S
9 K, N$ w' T2 n' H
! `% u' t* J' j% q
! O& g( H {0 T7 h! }/ I7 h8 q2 S
/ {; f+ X$ d: v! e- key_led设计实现
5 |. T8 J7 p, M
9 j! A5 Q3 z& `# K, h% z+ L本模块只是负责将上述的三个模块按照架构图的方式进行连接,形成最终的设计。
) T% s& O: m3 {) Z& D g! Y2 v! `/ Q0 U3 q0 P1 N
设计代码为:
# Z8 G3 h% Z+ f, F, G7 Z- g
! u8 ^* P4 Z! O+ G
& q6 L3 q' T+ \5 u" |7 x7 ^
# f1 y: a7 e. r: b: J/ I# Q' s. d) ^5 |. Z! R+ G R
/ W9 U' d7 Q, d! I2 B1 O* J
在设计中,采用了按键按下时的脉冲(检测到下降沿的脉冲),按键按下时led的状态即可进行翻转。
7 `- H3 O N4 s
" V+ |! U+ N7 @0 o
2 l% y0 a. P! ?; q' b/ e
9 @5 L* I$ n0 B1 Y5 T; K7 r) ~
; i6 ^ V1 A3 A5 A9 M o. b) b% o5 _: p" }8 Y5 O( q( Z, g9 M& e
; O9 b& \+ p3 W4 }8 U9 g5 O- e) H. y# O9 G
: a0 |5 P. M* g3 _% i" g0 X1 s9 I, f7 n9 _2 |+ K( U& G6 G4 R% U
( @$ t! p5 K3 f3 c
9 A8 n* X) C! w
- 功能仿真- b$ Z! O& A% [/ _" B8 n
" G- U& V$ H: f3 s- H* \
1 u7 e. |, ^, m
在仿真时,将按键消抖中的T_10ms的参数修改为20,即持续时间不超过400ns都不认为是有效按下或者抬起。
0 a0 I' D) N; U" K8 a仿真代码如下:
- N4 l9 M% R) _6 i
9 ^; r& d, ]- t& @! Y8 }& M# B
* [ J; o4 z) k9 v9 e
8 W- P/ A) |7 _
3 ~7 ?0 L* [: H( V/ j将okey_n、flag信号添加出来。
1 }" g" l i8 U* F+ j Q
5 G3 H2 g) q8 G& M% L% ?' A
: C6 S; c/ v3 W5 k m& }/ X
* }% i ~) W8 v# w
$ v8 }, Q2 n* _( z7 ~通过RTL仿真图,可以清晰的看到okey_n信号将key_n的抖动滤除掉;flag信号为okey_n信号的下降沿时所产生的脉冲;led在flag信号为高时,反正翻转。
& o# r9 a( ~0 y/ m! C3 ~分配管脚、下板测试之前,应该将按键消抖里面的T_10ms参数重新改为500_000,否则下板后可能会达不到消抖的效果。
; v; u2 v0 Q" o6 V! ~7 ?7 |0 q6 Q. ?
下板观察现象:
! F$ q2 D8 v! z
7 L U( Z- [ i" W9 V
1 H5 g8 k4 s* I4 D1 N$ V3 W' u
2 p0 |6 q1 E/ i$ p6 F3 u! Q下板成功后,可以修改在设计中使用上升沿的脉冲,得到的现象应该是按键释放时,LED的状态发生反转。
; N i" @ J; A
6 f) b0 ^( y* Z2 h4 Y- ]2 v* E
切记:每次修改代码,一定要进行重新编译,否则更改将不会生效。