由于金属弹性形变的原因,按键/开关在状态切换过程中总是会有抖动情况,有时这种抖动会导致电路错误运行。比如在进行按键加法时,按一下加 1可能会加2个数甚至更多。因此,在很多时候,输入电路的信号需要经过防抖动处理。
一、实验分析与设计
实验使用有限状态机实现,需要将输入的按键信号经过防抖动处理后用于计数器,同时将计数器的值显示在数码管上。
所以有输入按键信号以及处理后的输出按键信号。
状态机有四种状态:低电平状态、由低到高的抖动状态、高电平状态、由高到低的抖动状态。
当输入按键信号为低电平时,处于低电平状态,输出按键信号也为低电平;
当输入按键信号由低到高抖动时,处于抖动状态,输出按键信号保持低电平;
当输入按键信号为高电平时,处于高电平状态,输出按键信号也为高电平;
当输入按键信号由高到低抖动时,处于抖动状态,输出按键信号保持高电平;
通过状态机的转换,输出按键信号就消除了抖动部分。
所以,本实验的关键在于如何进行状态间的转换。
方法一:
在低电平状态时,检测到输入信号有上升沿,就转入由低到高的抖动状态;
在由低到高的抖动状态,等待对应时间(假定大于抖动时间),转入高电平状态。
在高电平状态时,检测到输入信号有下降沿,就转入由高到低的抖动状态;
在由高到低的抖动状态,等待对应时间(假定大于抖动时间),转入低电平状态。
方法二:
在低电平状态时,检测到输入信号有上升沿,就转入由低到高的抖动状态;
在低到高的抖动状态,检测到高电平状态维持一定时间(小于高电平总时长),转入高电平状态。
在高电平状态时,检测到输入信号有下降沿,就转入由高到低的抖动状态;
在高到低的抖动状态,检测到低电平状态维持一定时间(小于低电平总时长),转入低电平状态。
此时需要引入时间信号,用于记录时间。
二、程序代码
采用层次化设计,包括按键防抖动部分,计数器部分、七段译码器部分。
方法一与方法二的区别只在按键防抖动部分,方法二比方法一多4行代码,在方法二中标出。
顶层文件
module Test(clk,key,key_out,Data,codeout);
input clk; //50MHZ 计时
input key; //按键输入
output key_out;//按键经过处理后的输出
output [3:0]Data; //计数器输出
output [6:0]codeout; //七段译码器
Test_1 t1(clk,key,key_out); //按键防抖动
Test_2 t2(key_out,Data); //计数器
Test_3 t3(codeout,Data); //译码器
endmodule
按键防抖动部分
方法一:等待时长取10ms。(假设抖动时长小于10ms,如果大于10ms会出现问题)
module Test_1(clk,key,key_out); //按键防抖动
input clk; //50MHZ 计时
input key; //按键输入
output reg key_out = 1'b0;//按键经过处理后的输出
//状态机四种状态
parameter Down = 2'b00,Filter1 = 2'b01,Filter2 = 2'b10,Up = 2'b11;
reg [1:0] state = Down; //实时状态
//按键输入的上升下降沿
reg keyT1=1'b0,keyT2=1'b0;
always@(posedge clk)
begin
keyT1 <= key; //当前按键输入值
keyT2 <= keyT1;//前一按键输入值
end
wire keyPos,keyNeg;
assign keyPos = keyT1 & (!keyT2); //上升沿为:前一0 当前1。
assign keyNeg = (!keyT1) & keyT2; //下降沿为:前一1 当前0。
//计时10ms
reg TimeSwitch = 1'b0; //计时开关
reg TimeFull = 1'b0; //计时10ms是否到
integer n = 1; //计时
always@(posedge clk) //计时模块10ms
begin
if(TimeSwitch) n <= n + 1;
else n <= 1;
if(n >= 500000) TimeFull <= 1'b1;
else TimeFull <= 1'b0;
end
//有限状态机模块
always@(posedge clk)
begin
case(state)
Down: //低电平状态
begin
key_out <= 1'b0; //输出按键信号为低电平
if(keyPos) //按键由0到1的第一个上升沿
begin
state <= Filter1; //进入抖动状态
TimeSwitch <= 1'b1; //开始计时
end
else state = state;
end
Filter1: //低到高抖动状态
begin
if(TimeFull) //计时10ms结束
begin
state <= Up; //进入高电平状态
TimeSwitch <= 1'b0; //计时结束
end
else state <= state;
end
Up: //高电平状态
begin
key_out <= 1'b1; //输出按键信号为高电平
if(keyNeg) //由1到0的第一个下降沿
begin
state <= Filter2; //进入抖动状态
TimeSwitch <= 1'b1; //开始计时
end
else state <= state;
end
Filter2: //高到低抖动状态
begin
if(TimeFull) //计时10ms结束
begin
state <= Down; //进入低电平状态
TimeSwitch <= 1'b0; //计时结束
end
else state <= state;
end
default: state <= Down;
endcase
end
endmodule
方法二:维持时长取10ms。(假设高低电平维持时长大于10ms,如果小于10ms会出现问题)
module gwl_2535_7_1(clk,key,key_out); //按键防抖动
input clk; //50MHZ 计时
input key; //按键输入
output reg key_out = 1'b0;//按键经过处理后的输出
//状态机四种状态
parameter Down = 2'b00,Filter1 = 2'b01,Filter2 = 2'b10,Up = 2'b11;
reg [1:0] state = Down; //实时状态
//按键输入的上升下降沿
reg keyT1=1'b0,keyT2=1'b0;
always@(posedge clk)
begin
keyT1 <= key; //当前按键输入值
keyT2 <= keyT1;//前一按键输入值
end
wire keyPos,keyNeg;
assign keyPos = keyT1 & (!keyT2); //上升沿为:前一0 当前1。
assign keyNeg = (!keyT1) & keyT2; //下降沿为:前一1 当前0。
//计时10ms,key一直保持不变(即key不抖动10ms)
reg TimeSwitch = 1'b0; //计时开关
reg TimeFull = 1'b0; //计时是否到
reg flag = 1'b0; //反应key的值 //增加变量flag
integer n = 1; //计时
always@(posedge clk) //计时模块10ms
begin
if(TimeSwitch && flag) n <= n + 1; //计时开关打开且flag为1 //判定条件被修改
else n <= 1;
if(n >= 500000) TimeFull <= 1'b1;
else TimeFull <= 1'b0;
end
//有限状态机模块
always@(posedge clk)
begin
case(state)
Down: //低电平状态
begin
key_out <= 1'b0; //输出按键信号为低电平
if(keyPos) //按键由0到1的第一个上升沿
begin
state <= Filter1; //进入抖动状态
TimeSwitch <= 1'b1; //开始计时
end
else state = state;
end
Filter1: //低到高抖动状态
begin
flag <= key; //key保持在高电平,flag为1 //增加将key赋值给flag
if(TimeFull) //计时10ms结束
begin
state <= Up; //进入高电平状态
TimeSwitch <= 1'b0; //计时结束
end
else state <= state;
end
Up: //高电平状态
begin
key_out <= 1'b1; //输出按键信号为高电平
if(keyNeg) //由1到0的第一个下降沿
begin
state <= Filter2; //进入抖动状态
TimeSwitch <= 1'b1; //开始计时
end
else state <= state;
end
Filter2: //高到低抖动状态
begin
flag <= ~key; //key保持在低电平,flag为1 //增加将~key赋值给flag
if(TimeFull) //计时10ms结束
begin
state <= Down; //进入低电平状态
TimeSwitch <= 1'b0; //计时结束
end
else state <= state;
end
default: state <= Down;
endcase
end
endmodule
用输出按键信号的计数器
module Test_2(key_out,Data); //输出计数器
input key_out;
output reg[4:0]Data = 4'b0000;
always@(posedge key_out) //上升沿
begin
if(Data < 4'b1001) //小于9递增
Data <= Data + 1'b1;
else
Data <= 1'b0;
end
endmodule
七段译码器
module Test_3(codeout,Data); //七段译码器
input[3:0] Data;//4位输入信号
output reg[6:0] codeout = 7'b1111110;//7位输出信号
always @(Data)//用always块语句描述逻辑
begin
case(Data)//case多分支条件语句,按输入显示对应输出
4'd0 : codeout = 7'b1111110;
4'd1 : codeout = 7'b0110000;
4'd2 : codeout = 7'b1101101;
4'd3 : codeout = 7'b1111001;
4'd4 : codeout = 7'b0110011;
4'd5 : codeout = 7'b1011011;
4'd6 : codeout = 7'b1011111;
4'd7 : codeout = 7'b1110000;
4'd8 : codeout = 7'b1111111;
4'd9 : codeout = 7'b1111011;
default : codeout = 7'bx;//其他输入情况
endcase
end
endmodule
三、ModelSim仿真
一、Test Bench文件
此处输入波形key,为循环输入:
低电平40ms->抖动10ms(每1ms翻转一次)->高电平40ms->抖动10ms(每1ms翻转一次)->低电平
`timescale 1 ns/ 1 ns
module Test_vlg_tst();
reg clk;
reg key;
wire [3:0] Data;
wire [6:0] codeout;
wire key_out;
Test i1 (
.Data(Data),
.clk(clk),
.codeout(codeout),
.key(key),
.key_out(key_out)
);
initial
begin
clk = 1'b0;
key = 1'b0;
$display("Running testbench");
end
always //循环输入
begin
#40000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#40000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
#1000000
key = 1'b1;
#1000000
key = 1'b0;
end
always #10 clk = ~clk;
endmodule
二、波形图
方法一:
方法二:
注:为了使波形图中显示时间相对准却,可以修改计时部分中的500000为499999(方法一)或499998(方法二)。这是由于非阻塞赋值导致的。