处理按键抖动的两种思路

按键做为基本的人机输入接口,在很多电子设计中都能见到。由于它的机械特性,在按键按下或松开的时候,会伴随一段时间的抖动,通常小于10ms,我们的目标是把这段抖动的时间滤除掉,使按键按下或松开的过程只有一个跳变沿,处理后的最终的效果如下图所示:
在这里插入图片描述
很明显整个消除抖动的过程可以划分为四个阶段。

  1. 按键没有按下,高电平,IDLE
  2. 按键按下,抖动状态
  3. 按键按下后的平稳状态
  4. 按键解除后的抖动状态

状态划分好了之后,再去考虑下再每个状态都需要做哪些处理才能实现我们的目标。经过分析发现在IDLE态检测到按键按下之后需要实现一个周期大于10ms的计数器,当计数器计数到设定的值之后再去检测按键是否按下,如果检测到按键按下了,那么此时到了平稳态。在平稳态需要做什么事情呢,就是判断按键有没有松开,如果按键松开了,那就再计数到一个特定值,到了特定值之后再去检测按键是否是松开后的状态。经过这样的处理后我们就能得到滤除后的理想波形了。整个操作流程用状态机可以轻松实现。对于何时读取按键值,可以定在按键接触后回到初始态,也可以定在平稳态,这个看个人习惯。经过处理之后的确可以达到按键按下一次触发一次操作的功能,但对于自锁式按键则不行,这时候就需要产生两个vld信号来标记哪里是按键按下后的平稳态,哪里是按键解除后的初始态。但是大都数板载按键都是按一次触发一次操作,所以目前的代码就产生一个vld信号。

// 2023/5/10 17:05:09
// 模块实现对按键的消抖处理

module key_filter #(
    parameter           SIMULATION  = 1'b0					
)(
    input				    clk       ,	//50MHz
	input				    rstn      ,	//低电平有效
	input        [2:0]      key_in    ,
	
	output  reg             key_vld   ,
	output  reg  [2:0]      key_value
);

    localparam    S_IDLE         = 4'b0001  ;
    localparam    S_PRESS        = 4'b0010  ;
    localparam    S_STABLE       = 4'b0100  ;
    localparam    S_RELEASE      = 4'b1000  ;
    
    reg    [3:0]  state, nxt_state          ;
    reg    [9:0]  cnt                       ;
    //-----------------------------------------------------
    localparam   TIME_20MS = (SIMULATION == 1'b1) ? 10 : 1000000; // just for sim
    reg    [20*8-1:0]  sta_sim ;
    reg    [20*8-1:0]  nxt_sta_sim ; 
    generate if (SIMULATION == 1'b1) begin
    	always @(*) begin
    		case (state)
    			4'b0001  :  sta_sim     =  "S_IDLE    " ;
    			4'b0010  :  sta_sim     =  "S_PRESS   " ;
    			4'b0100  :  sta_sim     =  "S_STABLE  " ;
    			4'b1000  :  sta_sim     =  "S_RELEASE " ;
    		endcase
    	end
    	always @(*) begin
    		case (nxt_state)
    			4'b0001  :  nxt_sta_sim =  "S_IDLE    " ;
    			4'b0010  :  nxt_sta_sim =  "S_PRESS   " ;
    			4'b0100  :  nxt_sta_sim =  "S_STABLE  " ;
    			4'b1000  :  nxt_sta_sim =  "S_RELEASE " ;
    		endcase
    	end
    end
    endgenerate
    //-----------------------------------------------------
    
    always @(posedge clk or negedge rstn) begin
    	if (!rstn) begin
    		state  <= S_IDLE;
    	end else begin
    		state  <= nxt_state;
    	end
    end
    
    //--处理流程
    always @(*) begin  
    	nxt_state  = state;
    	case (state)
    		S_IDLE    : if (key_in != 3'b111)   nxt_state = S_PRESS;
    		
    		S_PRESS   : if ((cnt == TIME_20MS-1) && (key_in == 3'b111))
    		                nxt_state = S_IDLE;
    		            else if ((cnt == TIME_20MS-1) && (key_in != 3'b111)) 
    		                nxt_state = S_STABLE;
    		                
            S_STABLE  : if (key_in == 3'b111)   nxt_state = S_RELEASE;
           
            S_RELEASE : if ((cnt == TIME_20MS-1) && (key_in == 3'b111)) 
                            nxt_state = S_IDLE;
                            
            default   : nxt_state = S_IDLE;
        endcase
    end
    
    always @(posedge clk or negedge rstn) begin
    	if (!rstn) begin
    		cnt  <= 'd0;
    	end else if (state == S_PRESS || state == S_RELEASE) begin
    		cnt  <= cnt + 1;
    	end else if (state != S_PRESS || state != S_RELEASE) begin
    		cnt  <= 'd0;
    	end
    end
    
    always @(posedge clk or negedge rstn) begin
    	if (!rstn) begin
    		key_value    <= 3'b000;
    	end else if (state == S_PRESS && nxt_state == S_STABLE) begin
    		key_value    <= key_in;
    	end
    end
    
    always @(posedge clk or negedge rstn) begin
    	if (!rstn) begin
    		key_vld    <= 1'b0;
    	end else if (state == S_RELEASE && nxt_state == S_IDLE) begin
    		key_vld    <= 1'b1;
    	end else begin
    		key_vld    <= 1'b0;
        end
    end  
endmodule
    

代码虽然简单易懂,但这里也有一些小技巧分享给大家。在编写RTL代码时经常会使用状态机,不管使用什么编码方式在仿真中都是一串数字,尤其当状态较多时,时常看着看着自己就糊涂了,在这里我们使用generate语法,当在仿真时把对应的信号用ASCII码表示就会很容易显示处状态值,这样看着就清晰很多。另外,有时候在RTL中需要计数一个很大的值,但在仿真中直接用这个大的值会非常不方便,同样的我们也可以用示例代码中的方法,只需要在仿真文件中修改参数就可以很方便的改变相应的值。请看下面的仿真图,可以很直观的看出上面体现的两种办法。
在这里插入图片描述

由于按键的机械特性,按下或松开后会抖动一段时间,通常小于10ms,那我们是不是可以考虑实现一个10ms的计数器,当计数到10ms时去锁定此时的key_in值,以单个按键为例,可以定义一个[1:0] key_reg,如果在按键平稳期锁定的输入值,那么直接key_reg[0] key_reg[1] 根据按键的初始态和按下后的稳定态的高低电平做适当的运算可以判断按键是否被按下,然后产生一个触发信号给其他模块使用就行了;如果在按键抖动时间内锁定输入值,那么不管此时按键锁定的是高电平还是低电平,都不会影响按键判断,因为下一次锁定时一定是按键稳定后的值。只是会延时10ms。这里有一个隐含条件就是按键按下后持续的平稳时间要大于20ms,一般情况下都会达到。代码就不贴了,有兴趣的同学可以自己尝试下,然后通过仿真看看这样做是不是可行。帖一张手画的示意图可以对着理解下。

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值