Table of Contents
1. 回顾...............................................4
2. 按键消抖........................................4
2.1 按键输入原理.......................4
2.2 何为按键消抖.......................4
2.3 按键消抖思路.......................5
2.4 按键消抖程序.......................5
2.5 程序分析...............................6
2.6 整体程序...............................8
3. 实验结果.......................................10
4. 总结...............................................11
1. 回顾
这次我们继续给玩转LED加入些新元素,使用按键控制LED。点亮LED是利用了FPGA输出电平,这次对按键进行操作则是对FPGA进行输入了。
2. 按键消抖
2.1 按键输入原理
首先,我们得打开EE_FPGA的硬件手册,找到按键部分的原理图。
如下图所示,这会大家就可以利用在LED中学到的知识进行分析了,当按键没有被按下的时候,管脚连接的是VDD3.3V的高电平;当按键被按下时,管脚接地。
所以我们只要检测这几个管脚是否是低电平,就可以判断是否有按键被按下啦。
2.2 何为按键消抖
如果仅仅是按上面所说,那这个是否太简单了一点呢?是滴,你一定会想到按键消抖的问题。似乎不管是学单片机还是DSP的时候,凡是涉及到按键的都会提到按键消抖。正好,网上找到一张关于按键抖动的图。
抖动时间t1、t3一般在20ms左右。从理论上讲,在抖动时间内,会产生多个脉冲信号,如果不进行任何处理,则按一次按键,程序会认为按了多次,从而产生错误。
那不消抖可不可以的呢,也许有些地方是没什么问题的。上次还在论坛上看到一位朋友一定要找出一种能说明按键不消抖有问题的例子。我想,这样没必要,设计的时候根据实际情况自然就知道需不需要消抖了。这里,我们是学习这个知识点。
2.3 按键消抖思路
关于FPGA的按键消抖,我在网上找了一个经典的程序,稍加修改,便于大家学习和理解。
程序设计的基本思路是:
1、 检测管脚电平是否拉低
2、 若检测到低电平,启动计数器,延时20ms左右的时间
3、 再次检测管脚是否低电平
4、 若还是低电平,确定按键被按下。输出控制信号
2.4 按键消抖程序
input clk; //主时钟信号,50MHz
input rst_n; //复位信号,低有效
input key1; //按键1
//---------------------------------------------------------------------------
reg reg0_key;
reg reg1_key;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
reg0_key <= 1'b1;
reg1_key <= 1'b1;
end
else begin
reg0_key <= key1;
reg1_key <= reg0_key;
end
end
//当寄存器key1由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an;
assign key_an = reg1_key & ( ~reg0_key);
//-------------------------------启动延时--------------------------------------------
reg[19:0] cnt_key; //计数寄存器
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) cnt_key <= 20'd0; //复位
else if(key_an) cnt_key <=20'd0;
else cnt_key <= cnt_key + 1'b1;
end
reg reg_low;
reg reg1_low;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
reg_low <= 1'b1;
reg1_low <= 1'b1;
end
else if(cnt_key == 20'hfffff) begin
reg_low <= key1; // cnt == 20'hfffff 约20ms
reg1_low <= reg_low;
end
end
//---------------------------------------------------------------------------
//当寄存器reg_low由1变为0时,key_low的值变为高,维持一个时钟周期
wire key_low = reg1_low & ( ~reg_low);
我们画一张时序图来解释这个问题就非常好理解了。我们假设按键key1输入上图这样一段时序序列。经过reg0_key和reg1_key的移位操作,以及reg0_key的取反。最后寄存器key_an被拉高一个时钟周期,清楚地显示了下降沿的位置。
这段程序是用来检测下降沿的典型程序。这里,我提醒下,我们只要把取反的寄存器换一下,改成assign key_an = reg0_key & ( ~reg1_key);就变成了一段检测上升沿的典型语句。不信你画个时序图看看。顺便多说一句,做数字电路,画时序图是解决问题的一个很好的方法哦。
理解了以上两个知识点,那这个按键消抖的程序就很好懂了。如果管脚检测到下降沿,我们用key_an作为标志信号启动计数器,当计数器计到20’hfffff的时候,(即约10万个clk周期,20ms)。再次存入键值,
module led (
clk,rst_n,key1,
led
);
input clk;
input rst_n;
input key1;
output[3:0] led;
//------------------------键盘消抖程序---------------------------------------------------
reg reg0_key;
reg reg1_key;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
reg0_key <= 1'b1;
reg1_key <= 1'b1;
end
else begin
reg0_key <= key1;
reg1_key <= reg0_key; //根据非阻塞赋值的原理,reg1_key存储的值是reg0_key上一个时钟的值
end
end
//当寄存器key1由1变为0时,led_an的值变为高,维持一个时钟周期
wire key_an;
assign key_an = reg1_key & ( ~reg0_key);
//---------------------------------------------------------------------------
reg[19:0] cnt_key; //计数寄存器
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
cnt_key <= 20'd0; //异步复位
else if(key_an)
cnt_key <=20'd0;
else
cnt_key <= cnt_key + 1'b1;
end
reg reg_low;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
reg_low <= 1'b1;
end
else if(cnt_key == 20'hfffff)
begin
reg_low <= key1; //cnt == 20'hfffff,20ms
end
end
//-------------------------------------------------------------------
reg reg1_low;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
reg1_low <= 1'b1;
end
else
begin
reg1_low <= reg_low;
end
end
//当寄存器reg_low由1变为0时,key_low的值变为高,维持一个时钟周期
wire key_low = reg1_low & ( ~reg_low);
//===============LED控制==================================
reg[21:0] cnt; //
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 22'b0;
else
cnt <= cnt + 1'b1;
end
reg enable_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
enable_r <= 1'b0;
else
if (cnt == 22'h3fffff) enable_r <= 1'b1;
else
enable_r <= 1'b0;
end
wire enable;
assign enable = enable_r;
reg[3:0] led_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
led_r <= 4'b0111;
else if(key_low)
led_r <= 4'b0;
else if(enable && !key_low)
led_r <= {led_r[0],led_r[3:1]};
else ;
end
wire[3:0] led;
assign led = led_r;
endmodule
3. 实验结果
我们把按键消抖的程序结合到之前点亮LED的程序中。另外我们分配管脚的时候把按键Key2连接到rst_n信号,key1连接到key1信号。最终的结果是:当按下key2键的时候,系统复位,只有一个LED点亮。松开key2,没有键按下的时候,四个LED交替两灭,流水灯操作。当按下key1键时,执行下面语句else if(key_low) led_r <= 4'b0;四个灯全亮。这时,如果不按复位按键,系统会一直停留在这个状态。
是不是迫不及待想试试了呢?
4. 总结
这次不仅学习了按键消抖的程序,更重要的是理解了非阻塞赋值,脉冲边沿检测这两个重要的概念。
附学习文档PDF版本:
(下载次数: 1941, 2011-6-27 22:16 上传)
[ 本帖最后由 xieqiang 于 2011-6-27 22:16 编辑 ]
谢谢楼主分享