有限状态机实现按键防抖动电路
简要
由于金属弹性形变的原因,按键/开关在状态切换过程中总是会有或多或少的抖动情况,有时这种抖动会导致电路误动作,甚至无法正常工作。比如在设置参数时,按一下“加 1”可能会加 4~5 个数;生活中常见的情况是鼠标单击变成了双击等。因此,在很多时候,输入电路的信号需要经过防抖动处理之后才会送到后级电路。
按键抖动的电压波形如图 1 如示,tjitter 是抖动时间,通常在 1ms~30ms 之间。
设计要求
用 Verilog HDL 设计一个按键防抖动电路,要求用有限状态机实现。防抖动电路的输入接开发板的拨动开关,输出接 1 位十进制计数译码显示电路的时钟输入,实现每拨动一次开关计数器加 1,多次测试不出现抖动现象。
状态定义
parameter IDLE = 4'b0001;//闲置状态
parameter key1 = 4'b0010;//按下按键存在抖动的状态
parameter key2 = 4'b0100;//按下按键的稳定状态
parameter key3 = 4'b1000;//松开按键存在抖动的状态
状态转化图
程序代码
顶层模块:
module TYXS(
input clk,//clk时钟信号,rst清零信号,低电平清零,key为输入信号
input rst,
input key,
output key_out,//输出的防抖动之后的波形
output [6:0]codeout
);
digital a(
key_out,
rst,
codeout);
key_sh b(
clk,
rst,
key,
key_out
);
endmodule
十进制译码器显示模块
module digital(key_out,rst,codeout);
input key_out,rst;
output reg[6:0]codeout;
reg[3:0] keyout10;//寄存器用于十进制译码器显示,每按一次按键计数器加一
always@(posedge key_out,negedge rst)//十进制译码器显示,原有波形上升沿触发,即每按一次按键计数器加一
begin
if(!rst) keyout10 <= 4'd0;
else begin
if(keyout10==4'd9) keyout10 <= 4'd0;//置零
else keyout10 <= keyout10 + 4'd1;
end
end
always@(*)
begin
case(keyout10)
4'b0000: codeout<= 7'b1000000; //0
4'b0001: codeout<= 7'b1111001; //1
4'b0010: codeout<= 7'b0100100; //2
4'b0011: codeout<= 7'b0110000; //3
4'b0100: codeout<= 7'b0011001; //4
4'b0101: codeout<= 7'b0010010; //5
4'b0110: codeout<= 7'b0000010; //6
4'b0111: codeout<= 7'b1111000; //7
4'b1000: codeout<= 7'b0000000; //8
4'b1001: codeout<= 7'b0010000; //9
endcase
end
endmodule
按键防抖动模块
module key_sh(
input clk,//clk时钟信号,rst清零信号,低电平清零,key为输入信号
input rst,
input key,
output reg key_out//输出的防抖动之后的波形
);
reg cnt_en; //控制计数信号工作状态的使能信号
reg [18:0] cnt;//设置一个计数信号
reg [3:0] ns; //nextstate,状态信号
parameter IDLE = 4'b0001;//闲置状态
parameter key1 = 4'b0010;//按下按键存在抖动的状态
parameter key2 = 4'b0100;//按下按键的稳定状态
parameter key3 = 4'b1000;//松开按键存在抖动的状态
always@(posedge clk,negedge rst)//计数器
begin
if(!rst) cnt <= 0;
else if(cnt_en) cnt <= cnt + 1'b1;//计数器使能为1时,开始计数,否则清零
else cnt <= 0;
end
always@(posedge clk,negedge rst)
begin
if(!rst)
begin
ns <= IDLE;
cnt_en <= 0;
key_out <= 0;
end
else
begin
case(ns)
IDLE:begin
key_out <= 1'b0;//初始输出波形为低电平
if(key==1&&cnt_en==0)
begin
ns <= key1;//满足条件后进入下一状态key1
cnt_en <= 1'b1;//使能信号变为1,计数器开始工作
end
else
ns <= IDLE;//不满足条件,回归初始状态
end
key1:begin
key_out <= 1'b0;//按下按键时输出波形为低电平
if(cnt >= 19'd50_0000&&key==1) //因为testbench中设置了时间单位为10ns时钟周期为20ns,而设置的抖动波形不超过10ms,所以这里计数50万次
begin
ns <= key2;//满足条件后进入下一状态key2,即延时了10ms
cnt_en <= 1'b0;//使能信号变为0,计数器停止工作且清零
end
else if (cnt >= 19'd50_0000&&key==0)//防止出现一按下又松开的情况
begin
ns <= IDLE;//回归初始状态
cnt_en <= 1'b0;//使能信号变为0,计数器停止工作且清零
end
else
ns <= key1;//不满足计数条件则保持当前状态
end
key2:begin
key_out <= 1'b1;
if(key==0&&cnt_en==0)
begin
ns <= key3;//满足条件后进入下一状态key3
cnt_en <= 1'b1;//使能信号变为1,计数器开始工作
end
else
ns <= key2;//不满足计数条件则保持当前状态
end
key3:begin
key_out <= 1'b1;
if(cnt >= 19'd50_0000&&key==0)
begin
ns <= IDLE;//处理完成,回归初始状态
cnt_en <= 1'b0;//使能信号变为0,计数器停止工作且清零
end
else if (cnt >= 19'd50_0000&&key==1)//防止出现一松开又按下的情况
begin
ns <= key2;//回归上一状态
cnt_en <= 1'b0;//使能信号变为0,计数器停止工作且清零
end
else
ns <= key3;//不满足计数条件则保持当前状态
end
default :ns <= IDLE;
endcase
end
end
endmodule
仿真结果