消抖缘由:
为什么需要按键消抖呢,???
原因在于按键按下时,实际的按键信号并不像人们以为的那样直接按下就发生转变且稳定输出,实际上由于物理构造等原因,期间会经过多次快速的电平抖动,最终达到稳定,弹起时:the same! 这对于一些敏感的元器件或者特殊的场合(哈哈我也不知道实际是哪些场合~)肯定是不适合的.(上图?)
这里我们就需要用到战术消抖!
消抖步骤:
这里我们可以假设若输出信号稳定的时间达到某一时间段(如本例中我们认为达到20ms)即为稳定输出.有了这个前提我们只需忽略电平沿持续时间达不到20ms的电平变化即可! (是不是很简单
代码如下: (有很详细的注释哦,仔细看都能看懂.这里我们就已经达到了战术消抖的目的了!)
module keyIn_filter(
Clk, //系统时钟,频率为50M
Rst_n, //复位信号低电平有效
key_in, //低电平有效
key_flag, //记录完成一次变化(消抖完成)的信号
key_state //实际所需的稳定(消抖后)输出
);
input Clk;
input Rst_n;
input key_in;
output reg key_flag;
output reg key_state;
reg[3:0] state; //用来保存状态机的几种状态
localparam //状态名分别为无动作、滤波器0、低电平、滤波器1
IDEL = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
reg[19:0]cnt; //计数器寄存器(20位)
reg en_cnt; //计数器使能信号
reg cnt_full; //计数满信号
reg key_reg0, key_reg1;
wire pog, neg;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
key_reg0 <= 1'b0;
key_reg1 <= 1'b0;
end
else begin
key_reg0 <= key_in;
key_reg1 <= key_reg0;
end
//高低电平沿的定义
assign pog = !key_reg0 & key_reg1;
assign neg = key_reg0 & (!key_reg1);
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
state <= IDEL;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDEL:
begin
key_flag <= 1'b0;
if(neg)begin
state <= FILTER0;
en_cnt <= 1'b1;
end
else
state <= IDEL;
end
FILTER0:
if(cnt_full)begin
state <= DOWN;
en_cnt <= 1'b0;
key_flag <= 1'b1;
key_state <= 1'b0;
end
else if(pog)begin
state <= IDEL;
en_cnt <= 1'b0;
end
else
state <= FILTER0;
DOWN:
begin
key_flag <= 1'b0;
if(pog)begin
state <= FILTER1;
en_cnt <= 1'b1;
end
else
state <= DOWN;
end
FILTER1:
if(cnt_full)begin
state <= IDEL;
key_flag <= 1'b1;
key_state <= 1'b1;
end
else if(neg)begin
en_cnt <= 1'b0;
state <= DOWN;
end
else
state <= FILTER1;
default:
begin
state <= IDEL;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
//计数器为一个时钟周期(20ns)计数一次
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
//则计数满一次为20ms(预设保持20ms以上的时长为有效)需
//20ms = 20_000_000ns /20ns = 1000_000次
//也是由此得出计数器寄存器cnt的位数为20位(二进制)
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
这里检查代码状态部分是否正确可以看一下State Machine Viewer:
Testbench: (这里引出了task用法和($random)的一个用法)
`timescale 1ns/1ns //testbench精度
`define clock_period 20 //时钟周期为20ns
module keyIn_filter_tb();
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
keyIn_filter keyIn_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
//系统时钟产生
initial Clk = 1;
always #(`clock_period/2) Clk = ~Clk;
//方法1.每行手写产生输出信号
// initial begin
// Rst_n = 0;
// key_in = 1;
// #(`clock_period*50) Rst_n = 1;
// #(`clock_period*50 + 1); //老技巧,+1使波形便于观察
//
// //进入滤波0状态消抖
// #10000 key_in = 0;
// #2000 key_in = 1;
// #3000 key_in = 0;
// #4000_0000; //状态转换为DOWN
// //进入滤波1状态消抖
// #1000 key_in = 1;
// #2000 key_in = 0;
// #3400 key_in = 1;
// #3424 key_in = 0;
// #45345 key_in = 1;
// #4500_0000; //状态转为常态..
//
// //复制粘贴
//
// //进入滤波0状态消抖
// #10000 key_in = 0;
// #2000 key_in = 1;
// #3000 key_in = 0;
// #4000_0000; //状态转换为DOWN
// //进入滤波1状态消抖
// #1000 key_in = 1;
// #2000 key_in = 0;
// #3400 key_in = 1;
// #3424 key_in = 0;
// #45345 key_in = 1;
// #4500_0000; //状态转为常态..
//
// //进入滤波0状态消抖
// #10000 key_in = 0;
// #2000 key_in = 1;
// #3000 key_in = 0;
// #4000_0000; //状态转换为DOWN
// //进入滤波1状态消抖
// #1000 key_in = 1;
// #2000 key_in = 0;
// #3400 key_in = 1;
// #3424 key_in = 0;
// #45345 key_in = 1;
// #4500_0000; //状态转为常态..
//
// //进入滤波0状态消抖
// #10000 key_in = 0;
// #2000 key_in = 1;
// #3000 key_in = 0;
// #4000_0000; //状态转换为DOWN
// //进入滤波1状态消抖
// #1000 key_in = 1;
// #2000 key_in = 0;
// #3400 key_in = 1;
// #3424 key_in = 0;
// #45345 key_in = 1;
// #4500_0000; //状态转为常态..
//
// $stop; //不要忘了暂停!
// end
//方法2:使用task功能创建一次的输入信号,之后反复调用
reg [15:0]myrand;
task press_key;
begin
repeat(50)begin
myrand = ($random)%458433;
#myrand key_in = ~key_in;
end
key_in = 0;
#4500_0000;
repeat(50)begin
myrand = ($random)%458433;
#myrand key_in = ~key_in;
end
key_in = 1;
#4500_0000;
end
endtask
initial begin
Rst_n = 0;
key_in = 1;
#(`clock_period*50) Rst_n = 1;
#(`clock_period*50 + 1); //老技巧,+1使波形便于观察
press_key;
#1000;
press_key;
#1000;
press_key;
#1000;
press_key;
#1000;
$stop;
end
endmodule
WAVE: (好像时间设置的不太好啊~
没关系,放大(50倍~)看:
成功达到战术消抖!
今日份达成~加油!
and another one is : test_model (like this picture)
蓝色箭头为激励和输出.
New code: (1.test_model 2.testbench)
1.
`timescale 1ns/1ns //testbench精度
module key_model(key); //测试模块中不用关心时间只关心激励,增加了灵活性!
output reg key; //???
reg [15:0]myrand;
task press_key;
begin
repeat(50)begin
myrand = ($random)%458433;
#myrand key = ~key;
end
key = 0;
#4500_0000;
repeat(50)begin
myrand = ($random)%458433;
#myrand key = ~key;
end
key = 1;
#4500_0000;
end
endtask
//只用产生激励信号
initial begin
key = 1'b1;
press_key;
#1000;
press_key;
#1000;
press_key;
#1000;
press_key;
#1000;
$stop;
end
endmodule
2.
`timescale 1ns/1ns //testbench精度
`define clock_period 20 //时钟周期为20ns
module keyIn_filter_tb();
reg Clk;
reg Rst_n;
//reg key_in;
wire key_in;
wire key_flag;
wire key_state;
keyIn_filter keyIn_filter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
//引入测试模块进行灵活测试
key_model key_model(
.key(key_in)
);
//系统时钟产生
initial Clk = 1;
always #(`clock_period/2) Clk = ~Clk;
//产生复位信号
initial begin
Rst_n = 0;
#(`clock_period*50) Rst_n = 1;
#(`clock_period*50 + 1); //老技巧,+1使波形便于观察
end
endmodule
this new one do make testbench more flexible !