具体见正点原子《达芬奇pro之FPGA开发指南v2.1》第十八章
1.为什么要消抖
因为我们通常使用的开关为机械弹性开关,当我们按下或松开按键时,由于弹片物理特性,无法立即闭合或断开,会在该时刻短时间内产生机械抖动,使我们采集到错误的按键状态。
所以要消除这种抖动,而消抖的过程就被称为按键消抖。
①硬件消抖: 主要用RS触发器、电容等方法在硬件电路上实现,一般适用于按键较少。
②软件消抖: 按键按下或松开后,由处理器偃师5-20ms,再对按键状态进行采样判断。简单说就是在规定时间内读取两次按键状态,判断两次状态是否一致。
其原理图如下所示:
2.软件部分:代码
2.1 按键消抖
module key_debounce(
input sys_clk,
input sys_rst_n,
input key, //外部输入的按键信号
output reg key_fliter
);
//parameter define
parameter CNT_MAX = 20'd100_0000; //消抖时间20ms
//parameter CNT_MAX = 20'd10;
//reg define
reg [19:0] cnt;
reg key_d0;
reg key_d1;
//***********************************
//** main code
//***********************************
//记录两次键值,key_d0记录外部输入的键值,key_d1记录前一时钟周期的键值
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
key_d0 <= 1'b1;
key_d1 <= 1'b1;
end
else begin
key_d0 <= key;
key_d1 <= key_d0;
end
end
//按键消抖
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 20'd0;
else begin
if(key_d1 != key_d0) //按键值发生变化,则该键值不作数,滤去,计数器重置
cnt <= CNT_MAX;
else begin //按键值一致,判断是否20ms后还一致,这里可以改成5-20ms
if(cnt > 20'd0)
cnt <= cnt - 1'b1;
else
cnt <= 20'd0;
end
end
end
//确认最终键值
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_fliter <= 1'b1;
else if(cnt == 20'd1) //递减到1,已确认按键最终状态
key_fliter <= key_d1;
else
key_fliter <= key_fliter;
end
endmodule
①记录的键值会在时钟上升沿发生变化。
②总体可分为:读取键值-延时-二次读取键值两部分。
2.2 按键控制蜂鸣器
module key_beep(
input sys_clk,
input sys_rst_n,
input key_fliter,
output reg beep
);
//reg define
reg key_fliter_d0;
//wire define
wire neg_key_fliter;
//***********************************
//** main code
//***********************************
/*
key_fliter 有两种状态,按下-0,未按下-1
*/
assign neg_key_fliter = (~key_fliter) & key_fliter_d0;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_fliter_d0 <= 1'b1;
else
key_fliter_d0 <= key_fliter;
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
beep <= 1'b0; //上电为关闭状态
else if(neg_key_fliter)
beep <= ~beep;
else
beep <= beep;
end
endmodule
①对于语句assign neg_key_fliter = (~key_fliter) & key_fliter_d0;
主要是为了让按键的状态得到记录。因为开发板上的按键需要压力才会有响应,导致需要按住按键才能满足需求。
由仿真效果可知,按键松开时,传递给处理器的按键状态并没有改变。
2.3 顶层代码
module top_key_beep(
input sys_clk,
input sys_rst_n,
input key,
output beep
);
//parameter define
parameter CNT_MAX = 20'd100_0000;
//parameter CNT_MAX = 20'd10;
//wire define
wire key_fliter;
//********************************
//** main code
//********************************
key_debounce #(
.CNT_MAX (CNT_MAX)
)u_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_fliter (key_fliter)
);
key_beep u_key_beep(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_fliter (key_fliter),
.beep (beep)
);
endmodule
3.测试部分:仿真
仿真代码
`timescale 1ns/1ns
module tb_top_key_keep();
//parameter define
parameter CLK_PERIOD = 20;
parameter CNT_MAX = 20'd10; //消抖时间20 * 10 = 200 ns
//reg define
reg sys_clk;
reg sys_rst_n;
reg key;
//wire define
wire beep;
//信号初始化
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
key <= 1'b1;
#200
sys_rst_n <= 1'b1;
//key信号变化,只有延迟超过300ns,其按键效果才有效。(仿真是200ns的延迟)
#20
key <= 1'b0;
#20
key <= 1'b1;
#50
key <= 1'b0;
#40
key <= 1'b1;
#20
key <= 1'b0;
#300
key <= 1'b1;
#50
key <= 1'b0;
#40
key <= 1'b1;
#300
key <= 1'b0;
end
//产生时钟
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
//特例化待测设计
top_key_beep #(
.CNT_MAX (CNT_MAX)
)u_top_key_beep(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.beep (beep)
);
endmodule
仿真结果