利用按键控制LED的要求为:按一下按键,改变一下LED的状态。按键按一次,LED由熄灭变为点亮,按键再按一次,LED由点亮变为熄灭。
$ @5 K n/ X- F3 Z1 m3 M
- 硬件介绍
. S, \9 {7 L9 U, E8 \7 Y0 P F
# K. v F1 C. \1 ]$ v) w8 x开发板上面有四个按键,当按键按下时,将对应的网络置成低电平;当按键释放时,将对应的网络置成高电平。
, ~7 e$ W( Q- }& i: i开发板上面有四个LED发光二极管,FPGA输出高电平时,LED点亮;FPGA输出低电平时,LED熄灭。
+ P1 g: d; W: I1 Z. s
- 设计原理! w6 y: m0 L. J# P) n6 Q( {& A+ H
* I! F2 O6 f( y# V' n
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。
; u- Q" R6 U& g! o( a
按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
* j6 \" d+ J1 f5 V9 A" b抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。
* y; G: k5 {4 M6 v
我们可以在按键和主控设备之间加入消抖电路(消抖芯片、电容等),此种方法会增大PCB面积和花费一定的物料费用。大多数的板子直接将按键和主控设备相连接,将带有抖动的波形输入到主控设备内部,由内部进行消抖处理。
) h1 h' O& Q0 l
单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为20ms),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
. N' _8 z: c" v" C/ d+ Z$ H8 e
在FPGA设计时,笔者推荐另外一种方式:持续采样。当检测到信号持续为低10ms,认为按键按下;当检测到信号持续为高10ms,认为按键释放。
n9 `8 Z0 P. Z- n- P- b2 x. o8 H在设计时,需要考虑到外部的按键信号为异步信号,需要进行同步处理。具体请参考附录2 FPGA中的同步信号、异步信号和亚稳态。
6 Q8 d- g4 n; }9 k% N每次按键按下的时间的长短不一,经过消抖后,低电平的持续长度长短也不一样。此长度远远大于一个时钟周期的长度。要求每次按下只能够切换一次LED的状态,所以不能够直接用此电平当做输出翻转的使能。
) Q1 ~5 \9 c: R$ N经过消抖的波形,每次按下只有一个下降沿(按键按下时)、只有一个上升沿(按键释放时)。所以通过检测下降沿(上升沿)的变化,产生一个新的信号------脉冲(一个时钟周期的脉冲),利用此脉冲作为翻转的使能即可。利用检测到下降沿的脉冲翻转时,LED的状态会在按下时就会改变;利用检测到上升沿的脉冲翻转时,LED的状态会在释放时发生改变。本设计中采用检测到下降沿的脉冲进行翻转。
% F: d% B. C I: Q, s; I/ U
/ }) X; S* y1 m# Q7 W本设计模块命名为key_led。
* i A3 q8 I5 w
* K( c _- z$ Y* K3 d5 K; V
- [: w, A( i* L$ ^; x- R
/ D, d. [0 u* q/ l8 C4 t% a在设计中,共分为三个模块。
. `* G/ n# E+ H `! \key_filter(按键消抖模块):将外部输入的带有抖动的波形进行消抖。
2 }$ x7 `2 w8 l3 S
edge_check(边沿检测模块):将消抖后的波形进行下降沿检测,并产生对应的脉冲。
( O; r: B$ @7 f
% N+ X2 a1 w7 f0 S
led_ctrl(led控制模块):利用脉冲,翻转led的输出状态。
3 h3 O! b# p5 \% z0 W7 t1 A, a( {# U0 G2 w8 J6 z2 A- s9 S$ E
$ H9 c& Q% P- ^
+ I7 f6 O# R# Y2 x; ]2 n2 @6 h7 ?
% l& ~+ y' e; X- J, K4 q$ ?5 n- key_filter设计实现
& j( {+ P3 k- T( D3 a. `0 p' I6 \
3 k* y; i+ W s" |% _) c本设计采用状态机实现,状态机的具体原理请参看相关文章。
( k4 c1 D, Q& G2 @; L: M对key_n信号为异步信号,需要进行同步两拍,命名为key_n_r和key_n_rr。状态机的判断信号为key_n_rr信号。
, H: q$ D h2 I
本设计共分为四个状态,KEY_OFF(按键释放状态),SHAKE_ON(按键按下时抖动判断状态),KEY_ON(按键按下状态),SHAKE_OFF(按键释放时抖动判断状态)。
8 _- g" F, M/ @( s
按键没有按下时,一直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状态。
' P! a7 a3 f3 q2 W2 H
在KEY_OFF和SHAKE_ON状态,认为按键没有按下;在KEY_ON和SHAKE_OFF状态,认为按键为按下;
: z8 o; V8 R! v2 X6 x
& G8 L& m2 j: E$ @8 X: R5 B: A状态转移图如下:
- V$ h% f( l' w
0 O, X. d: z7 ]; C7 _% U
$ r0 q- K. N3 z9 v1 ~" E. g, a0 S: ~) W* m8 W0 }
% w$ q! |' ?! ^
, J0 w8 e) ]" L/ Y' E设计代码为:
: w; C- i/ w; _ c3 e: v6 b. d1 C6 F" E3 d9 s. w* V+ I4 k! _4 ^) Y) L
+ M# I7 e) O- V8 Z5 _! o q! x% a7 W9 V" o P& h
0 }, G: ~- o g5 qlocalparam可以定义参数,与parameter的区别在于,parameter定义的参数可以在例化时进行参数修改,而localparam定义的参数在例化时则不能够修改。定义状态机状态时,一般采用localparam的定义方式。在不希望别人修改参数时,也可以定义为localparam。
4 U ]# `' P& @
- edge_check设计实现
8 r+ f" N, J2 v
! v. ^8 s( W- P, m# J
在一个波形中,如果当前时刻为低电平,上一个时刻为高电平,则认为波形中有一个下降沿;如果当前时刻为高电平,上一个时刻为低电平,则认为波形中有一个上升沿。
- N5 ]/ E* k8 \; b' n- t
@- n$ ^/ X" H4 u8 F在数字电路设计时,可以采用寄存器来存储上一个时刻的值。
% p% g& P S/ }" V
8 T& I/ J) @. E$ G: D
) ?1 z; k" J5 N! L+ d
( ~5 X0 n2 I8 w x. ^2 b+ }) P+ e
7 H( x: O) |1 S8 }7 [在寄存器电路中,Q的值,永远是上一个CLK的有效边沿所采样的D值。因此Q为上一时刻值,而D为当前时刻的值。
3 M1 P$ f8 s, j: \' e: H
" d$ i( Q/ m6 S/ Y, b" X设计代码为:
. I0 y5 d/ F! e& u/ u: o# ]. T4 \
8 ^3 N: @5 u. H% o& c' `4 g, P; g
" t1 e3 |2 `+ f+ ~9 E2 ]: q6 W: ` e0 k. o8 Z1 k1 v0 Y- [/ K
在设计中,注释掉的两行代码和其下方的一行代码的功能是相同的。例:对于上升沿脉冲来说,现在为1,过去为0即为上升沿。由于寄存器每个时钟周期都刷新,满足这个要求的只会存在一个时钟周期,所以flag_pos为一个时钟周期的脉冲。
3 C7 ~# g. u, d& J' q- led_ctrl设计实现
. h3 L% H, F% e
* \: c5 H, f+ [9 W本模块中,利用脉冲进行led状态的翻转即可。
* d9 `7 R0 a+ N" O
@5 t6 v. O% M( |
设计代码为:
2 ?& X, r& I& S5 `5 e
8 T; |0 K4 P! c2 W0 c/ U R
2 A* [6 ]: g& o* r; \ o' s5 o. ~2 z; J3 v1 U6 e+ x7 l8 s
6 a5 t& T3 D/ f) @( J
- key_led设计实现
$ u, C! v8 c) [) X- M
/ c" r6 s( ]% x! t
本模块只是负责将上述的三个模块按照架构图的方式进行连接,形成最终的设计。
" k5 H+ F# D, m7 t9 y, v) `8 P0 j
. ]* T; c. b+ Y4 ^" P0 Z设计代码为:
! _% \& x7 ~+ d( r7 k5 ~
4 y) i/ {) C4 D- Y- y1 A
$ ^$ h+ `7 ]: b- v. l% }, v" p* A
3 y: P ~* s& _0 F1 U. o; m, Q
+ ^) S5 P' a8 h0 }2 ^
n# W' l. a6 R; B: }8 ~在设计中,采用了按键按下时的脉冲(检测到下降沿的脉冲),按键按下时led的状态即可进行翻转。
& k6 B- L, N5 R8 g
5 c: C5 X5 Z. g, J
" c/ @7 m2 S' k! ?1 R9 d
4 {4 U) |' r& q7 F- O$ r) U$ u' j( b" z5 W3 X. H) w: F& L1 O7 Q
6 x9 \& q. }1 `$ `* b! P7 x1 ?2 {2 i8 B* K7 T5 J% \% l
$ r7 W: b* i) N1 h& I* Y0 l
6 d N& w' _: j5 l. D5 n4 V
' F8 t% j" Z9 i) t$ d
0 j( j& ~+ a4 p, }% P" [
! s, V; F$ {7 |3 W- 功能仿真
* T/ k# {$ t+ I; p O
1 e+ D7 p1 v6 x2 Q& ~
% v+ K4 }% _: v在仿真时,将按键消抖中的T_10ms的参数修改为20,即持续时间不超过400ns都不认为是有效按下或者抬起。
* K1 O' j& U( f8 S* @, U仿真代码如下:
. o s7 s8 B: v
& z( c9 K5 W# N5 m0 s/ w) K2 {8 d
% k$ f! j1 ^# M, g4 c
5 i9 _* `; w9 V4 r- T/ P, ^) {, ^
! n" d: H' B; i: N6 w将okey_n、flag信号添加出来。
5 i0 r# Z/ l" z& a
2 u q9 O7 u. b7 ~! g
J( G* P: C) ~6 @. P
$ z0 u+ G8 H2 c) J$ V5 x" U* @4 W+ q' t3 C& I+ I; y: o
通过RTL仿真图,可以清晰的看到okey_n信号将key_n的抖动滤除掉;flag信号为okey_n信号的下降沿时所产生的脉冲;led在flag信号为高时,反正翻转。
7 S) ]$ A$ A$ F! n
分配管脚、下板测试之前,应该将按键消抖里面的T_10ms参数重新改为500_000,否则下板后可能会达不到消抖的效果。
! l0 ]! z/ g, q, Z7 [6 v) @' y8 C$ ~2 j. R5 o* D5 }* C
下板观察现象:
# g) ?6 h3 X9 c0 ~& ]
8 ^2 M8 r$ k0 P4 ]/ |6 f
7 K7 l/ A0 d6 }( Z& E R4 W/ U$ ~) V' B, I1 w( W* o
下板成功后,可以修改在设计中使用上升沿的脉冲,得到的现象应该是按键释放时,LED的状态发生反转。
# c3 L( }# B' i" @! O n! E0 s: H+ T& U3 S
切记:每次修改代码,一定要进行重新编译,否则更改将不会生效。