文章目录
- 原理简介
- 软件仿真
- 板上验证
原理简介
按键作为基本的人机输入接口,在很多电子设计中都是比较常见的,但是由于其机械特性,在按键按下或者松开的时候,按键的输入值是有抖动的。按键的抖动是其固有特性,因此无论按键按下或松开的多么平稳,按键的抖动也是难以消除的。本文通过FPGA计数(计时)来达到按键消抖的目的。
计数的基本原理是:当按键的输入发生变化时,计数器就清零,判断按键的输入是否发生变化是通过两级D触发器的异或实现的。如果按键的输入没有发生变化,那么计数器就累加,直至累加到一个预定值,这个预定值是通过时间和时钟的频率确定的。计数器累加到这个预定值之后,就认为按键的输入已经稳定,可以输出,因此就得到了没有抖动的按键值。
按键消抖的示意图如下图所示。
软件仿真
在进行板上验证之前,先在Vivado软件中编写代码进行仿真,因为仿真的速度比较快,这样可以提高我们的效率。
仿真用到的设计源代码如下。
module key_anti_shake(
input clk,
input rst,
input key_in,
output reg key_out
);
reg [3:0] count;
reg [3:0] count_next;
reg D1,D2; //两级D触发器
wire add_flag; //计数标志位
wire q_reset;
reg key_out_d;
assign q_reset = (D1^D2); //通过异或两级D触发器的值判断按键的变化
assign add_flag = ~(count == 4'd10); //为1时,count可以继续加1;为0时,按键稳定,可输出
always@(q_reset,add_flag,count)
begin
case({q_reset,add_flag})
2'b00: count_next <= count;
2'b01: count_next <= count + 1;
default: count_next <= 4'b0000; //如果q_reset=1就说明两级D触发器结果不一样,肯定不稳定
endcase
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
D1 <= 1'b0;
D2 <= 1'b0;
count <= 4'b0000;
end
else
begin
D1 <= key_in;
D2 <= D1;
count <= count_next;
end
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
key_out <= 1'b1;
else if(count == 4'd10)
key_out <= D2;
else
key_out <= key_out;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
key_out_d <= 1'b1;
end
else
begin
key_out_d <= key_out;
end
end
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module key_anti_shake_sim();
reg clk;
reg rst;
reg key_in;
wire key_out;
initial
begin
clk = 0;
rst = 0;
key_in = 1;
#100
key_in = 0;
#100
key_in = 1;
#100
key_in = 0;
#600
key_in = 1;
end
always #10 clk =~clk;
key_anti_shake uut_key_anti_shake(
.clk(clk),
.rst(rst),
.key_in(key_in),
.key_out(key_out)
);
endmodule
仿真输出结果如下图所示。
可以看到,首先是两级D触发器的不同使得q_reset变化,因此激活了重新计数,当计数到指定的数时,如果按键输入还没有发生变化,则认为按键的输入已经稳定,可以将其值输出。按键消抖实验的重点其实就是两级D触发器的输出值,如果在计数没到指定值时,两级D触发器输出有变化会使得 q_reset=1,而 q_reset=1的结果就是计数要从0重新开始,也就是说,要输出稳定的按键输入值,按键的值必须稳定持续指定的时间长度才可以。
板上验证
上面的代码中为了仿真方便,计数设计的比较小,在开发板上验证时,为了使按键消抖的效果明显,我在程序中将稳定的时间设置为1秒,这样就能够更清楚的看到,LED在按键触发1秒之后进行响应,也就是LED动作滞后按键1秒钟。
在开发板上验证使用的代码如下。
`timescale 1ns / 1ps
module key_anti_shake(
input clk,
input rst,
input key_in,
output key_out
);
reg [25:0] count;
reg [25:0] count_next;
reg D1,D2;
wire add_flag;
wire q_reset;
reg key_out_d;
assign q_reset = (D1^D2); //judge whether the level change through xor
assign add_flag = ~(count == 26'd50_000_000); //延迟1s
always@(q_reset,add_flag,count)
begin
case({q_reset,add_flag})
2'b00: count_next <= count;
2'b01: count_next <= count + 1;
default: count_next <= 26'd0; //如果q_reset=1就说明两级D触发器结果不一样,肯定不稳定
endcase
end
always@(posedge clk or negedge rst)
begin
if(rst == 1'b0)
begin
D1 <= 1'b0;
D2 <= 1'b0;
count <= 26'd0;
end
else
begin
D1 <= key_in;
D2 <= D1;
count <= count_next;
end
end
always@(posedge clk or negedge rst)
begin
if(rst == 1'b0)
key_out_d <= 1'b1;
else if(count == 26'd50_000_000)
key_out_d <= D2;
else
key_out_d <= key_out_d;
end
assign key_out = key_out_d;
endmodule
引脚分配如下图所示。
把比特流文件下载到开发板上验证时结果一直不正确,后来才发现我是在仿真代码的基础上修改的,而仿真代码中的复位是高电平有效,而我正好也给复位分配了一个按键引脚,而按键在不按下时就是高电平,因此,开发板上电后就一直在复位状态,除非按下复位按键,所以我把代码中的复位改为了低电平有效,重新生成比特流文件,下载到开发板验证。
按键消抖实验的开发板验证结果如下面动图所示。
可以看到,持续按下开发板上的 PL KEY1 按键1秒后,对应的 PL LED1 亮,松开按键1秒后,PL LED1 灭,因此,按键消抖的功能实现了!在实际应用中,按键消抖用不了这么长时间,我这里是为了演示的结果更加明显,一般延时大概几十毫秒就可以,其中,要延时的时间(以秒为单位)和计数之间的关系为:计数值 = 要延时的时间 × 频率。
比如在 50MHz 系统中延时 20ms,
计数值
=
20
×
1
0
−
3
×
50
×
1
0
6
=
1
0
6
计数值 = 20 × 10^{-3} × 50 × 10^6 = 10^6
计数值=20×10−3×50×106=106 。
以上就是ZYNQ——按键消抖实验的全部内容了!
参考资料:
ZYNQ 开发平台 FPGA 教程 AX7020