实现功能:按下一个按键控制led翻转,也就是按一下亮,再按一下灭。
特权同学视频里的讲解是用了三个按键分别来控制三个led灯翻转,为了简化更容易理解我用的是一个按键控制一位led灯.然而问题简化后还是然我理解了一上午加一下午.才终于可以无误的检测按键.其实有时候就是个自己根本没有注意的问题而导致的问题。
在特权同学的视频里面用的是边沿检测法进行消抖。总体思路就是进行一个下降沿的检测及证明有按键按下。而在按下按键的前10ms可能会检测到多次下降沿,所以有可能多次触发,当然松开按键的时候也有大约10ms的抖动时间,又会造成误触发。所以既要想办法滤除掉抖动中出现的误触发。
下面来根据程序进行分析:
module KEY(key,led,clk);
input clk,key;
//输入信号:50mhz时钟,按键输入
output led;
//输出led
reg key_to;
always@(posedge clk)
begin
key_to<=key;
end
reg key_tt;
always@(posedge clk)
key_tt<=key_to;
//数值滞后于key_to一个时钟周期
wire key_flag=(key_tt&&(~key_to));
红色部分代码的作用就是检测一个下降沿并保持一个时钟周期,原理如下:
Key_to:1
1
1
1
1
0
0
0
0
0(这是每个时钟周期key_to检测到得按键值)
~key_to:0
0
0
0
0
1
1
1
1
1
Key_tt:
1
1
1
1
1
0
0
0
0
0(key_tt滞后于key_to一个时钟周期)
Key_flag:
1
及当按键有一下降沿后key_flag则会置一
reg [19:0]cnt;
always@(posedge clk)
begin
if(key_flag)cnt<=20'b0;
else cnt<=cnt+1'b1;
end
reg key_20ms;
always@(posedge clk)
begin
if(cnt==20'h7ffff)key_20ms=key;
//计时大约20ms,key_20ms得到此时按键值
end
reg key_20ms2;
always@(posedge clk)
begin
key_20ms2<=key_20ms;
end
reg d1;
wire key_sta=(key_20ms2&&(~key_20ms));
这段红色的代码的作用就是消抖了,分析一下看它是如何起到消抖作用的:如果在key_flag不置一正常的情况下计时器计够20ms时间key_20ms就会锁定一次按键值。而在按键按下期间有抖动出现这时候key_flag就会置一,计时也会清零,所以在这抖动期间计时器会不断的清零即计时器不会计到20ms,所以key_20ms就不会锁定抖动时的按键值,而锁定的还是都抖动前的高电平。人按下按键按键时间一般都大于100ms。除去抖动的10ms时间,应该最少剩余90ms的低电平时间,这时候不存在抖动,计时器可以几斗20ms,key_20ms锁定了这个低电平,和key_flag的检测方法一样,在key_20ms有一个下降沿时,~key_20ms会和key_20ms2相与得到key_sta置一,根据这个就可以判断按键按下,在下面的代码中就可以进行led的翻转。松开的时候除去抖动key_20ms是从0到1上升沿变化的key_sta不会置一误触发。
always@(posedge clk)
begin
if(key_sta)d1=~d1;
end
assign led=d1;
endmodule
这个代码我昨天都写出来了,但是测试的时候经常在松开的时候进行误触发……让我不得其解,琢磨了今天一上午又修改了延时等等都不起作用。我就想算了,可能是按键质量不好抖动时间过长……接下来我看看这个代码生成的rtl图吧,多了解了解内部结构。就在这时我发现一个问题,如图中红色标示的原来的值
这样根本就记不到20ms。我又看了程序,终于发现了一个细节问题:我是初学者以前没怎么注意过
if(cnt==20'h7ffff)key_20ms=key;这句语句中我把20写成5了,这才明白,这里的数据位宽表示的都是2进制的……啊,一上午啊就这个小问题导致的不稳定…… |