按键抖动现象与解决方案(Verilog)

一、按键抖动

按键在按下时,活动触点击打固定触点会有机械振动,因而造成输出波形抖动,因按键形态和触点材料的不同,抖动的过程一般会持续数毫秒,金属触点的按键可能达到10ms,而软性触点(如导电橡胶或薄膜)则可能在1ms以内甚至没有抖动。
按键去抖动的实用做法是定时查询,定时器资源往往是极易复用的,一般10ms左右的查询间隔对于用户按键也是足够的,用户操作按键不可能达到50次每秒,按下的持续时间也不可能短于10ms。

二、解决思路

学过数字电路基础的同学应该都熟悉D触发器的特性,其输入端D和输出端Q的值相隔一个时钟周期,那么将两个信号取出来做如图所示的运算,就可以检测到按键输入端电平变化为上升还是下降:检测上升沿
检测下降沿
检测到上升沿或下降沿时,我们可以开始计时,当电平变化保持时间超过按键抖动的理论最长时间时,我们可以认为抖动结束,进入稳定的按下状态。同理,松开按键时,当电平变化保持时间超过按键抖动的理论最长时间时,可以认为进入稳定的弹开状态。由于要对抖动时间进行计时,所以代码中必定包含一个计数器。

三、状态转换图

按键未检测到电平变化时为第一个状态S0,我们命名为IDLE(空闲)。当检测到第一个下降沿到来时,开始计时并进入第二个状态S1,我们命名为FILTER0。
进入第二个状态后,我们检查计数器的值——如果计数器计满了10ms,则说明此时抖动已经结束,进入稳定的闭合状态S2,我们将其命名为DOWN;如果计数器没有计满,而是迎来了上升沿变化,则说明遇到了抖动,经历的低脉冲无效,那么状态就返回到最初的IDLE状态,重新等待下降沿的到来。
进入第三个状态 DOWN后,我们可以设置各个管脚在按键按下时应有的变化,例如点亮LED,或者发送一帧数据。同时我们等待上升沿的到来,当上升沿到来时,开始计数并进入第四个状态S3,我们将其命名为FILTER1。
进入第四个状态后,我们检查计数器的值,如果计满了10ms,则说明抖动已经结束,回到最初的IDLE状态,如果计数器没有计满,反而是迎来了下降沿,则说明发生了抖动,重新回到DOWN的按下状态,等待上升沿的到来。
状态图如下:按键消抖状态图

四、按键消抖完整代码

module key_filter(
			clk,		//50M时钟输入
			rst_n,		//模块复位
			key_in,		//按键输入
			key_flag,	//按键标志信号
			key_state,	//按键状态信号
			led			//led调试端口
		);

	input clk;
	input rst_n;
	input key_in;
	
	output reg key_flag;
	output reg key_state;

	output wire [3:0] led;
	
	localparam
		IDEL	= 4'b0001,
		FILTER0	= 4'b0010,
		DOWN	= 4'b0100,
		FILTER1 = 4'b1000;
		
	reg [3:0]state;	//状态
	reg [19:0]cnt;	//计数器
	reg en_cnt;		//使能计数寄存器

	assign led = (state == DOWN)?4'b0000:4'b1111;	//点灯调试
	
//对外部输入的异步信号进行同步处理
	reg key_in_sa,key_in_sb;
	always@(posedge clk or negedge rst_n)
	if(!rst_n)begin
		key_in_sa <= 1'b0;
		key_in_sb <= 1'b0;
	end
	else begin
		key_in_sa <= key_in;
		key_in_sb <= key_in_sa;	
	end
	
	reg key_tmpa,key_tmpb;
	wire pedge,nedge;
	reg cnt_full;//计数满标志信号
	
//使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步到系统时钟域中)的电平状态
	always@(posedge clk or negedge rst_n)
	if(!rst_n)begin
		key_tmpa <= 1'b0;
		key_tmpb <= 1'b0;
	end
	else begin
		key_tmpa <= key_in_sb;
		key_tmpb <= key_tmpa;	
	end

//产生跳变沿信号	
	assign nedge = !key_tmpa & key_tmpb;
	assign pedge = key_tmpa & (!key_tmpb);
	
	always@(posedge clk or negedge rst_n)
	if(!rst_n)begin
		en_cnt <= 1'b0;
		state <= IDEL;
		key_flag <= 1'b0;
		key_state <= 1'b1;
	end
	else begin
		case(state)
			IDEL :
				begin
					key_flag <= 1'b0;
					if(nedge)begin
						state <= FILTER0;
						en_cnt <= 1'b1;
					end
					else
						state <= IDEL;
				end
					
			FILTER0:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b0;
					en_cnt <= 1'b0;
					state <= DOWN;
				end
				else if(pedge)begin
					state <= IDEL;
					en_cnt <= 1'b0;
				end
				else
					state <= FILTER0;
					
			DOWN:
				begin
					key_flag <= 1'b0;
					if(pedge)begin
						state <= FILTER1;
						en_cnt <= 1'b1;
					end
					else
						state <= DOWN;
				end
			
			FILTER1:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b1;
					state <= IDEL;
				end
				else if(nedge)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

	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;
	
	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

五、仿真

由于抖动的波形书写起来比较麻烦,本人跳过了仿真这一步,直接进行板级验证,开发板上的LED可以按照预期的效果进行点亮。

六、小结

值得说明的是,前文说的10ms(100Hz)只是一般的情况,根据不同的机械按键特性应设置为合适的值,给出的代码设置的是20ms,即抖动频率为50Hz。在开发时应当根据应用场景来设定合适的延时值。


版权声明:本文为m0_51261356原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。