Verilog 三个状态机实现的按键消抖方案

关于按键消抖

按键消抖的作用还有原理就不科普了,网上解释和实现方案也挺多的,但在我玩FPGA时,我会遇到三种按键的需求,以点灯为例:a)是按下灯亮,松开灯灭;b)是按下松开,灯亮,再按下松开,灯灭(借鉴电气电路中的名词,我把这种方案称为“自锁”);c)每次按键发出一个时钟周期的脉冲,比如在设置时钟时会使用这种方案。

网上给的示例不全,到处找比较麻烦。另外有些通过状态机实现的方案都是一段式状态机,代码质量不高,所以我自己把这三种方案都写了一遍。


  • 以下方案均采用三段式状态机实现,初学verilog的也可以把这几段代码作为三段式状态机的一个示例;
  • 以下方案均通过参数化配置设计为可根据不同时钟频率复用的形式,我是以50MHz的频率为上限设计的,如果需要用更高的频率请修改20ms计数器的位宽;
  • 以下方案均已在FPGA上验证通过,在这里就不放testbench文件和仿真波形了。

2022.11更新:
这篇文章还是刚转到数字IC方向时写的,今天回过头来看,对原来三个按键消抖的代码也不是很满意,又重写了一遍。
直接从 VS code 粘贴过来,许多缩进有问题,就不改了。

不带自锁的方案

/****************************************************************************
 *  A basic key filter. 
 * 	
 *  Here the key is active-low. When key is pressed, output high.
*****************************************************************************/

module keyFilter #(
  parameter   SYS_CLK = 50_000_000
)(
  input       clk ,
  input       rstn,
  input       ikey,

  output reg  okey
);

// localparam CNT_MAX = SYS_CLK / 50;

/*          debug            */
localparam CNT_MAX = 50;

reg  [25:0]     cnt        ;

/*          edge detect         */
reg  key_in_r;
wire key_pos, key_neg;

always @(posedge clk or negedge rstn) begin
  if (~rstn) begin
    key_in_r    <=  1'b1;
  end else begin
    key_in_r    <=  ikey;
  end
end

assign key_pos = (ikey & ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey & key_in_r) ? 1'b1 : 1'b0;

/*          FSM         */
reg  [3:0]  nstate, cstate;
localparam  IDLE   = 4'b0001,
            SAMPLE = 4'b0010,
            PRESS  = 4'b0100,
			LOOSE  = 4'b1000;

always @(posedge clk or negedge rstn) begin
  if (~rstn)
    cstate  <=  IDLE;
  else 
    cstate  <=  nstate;
end

always @(*) begin
  nstate = cstate;
  case (cstate)
    IDLE :
      if (key_neg)
		nstate = SAMPLE;
    SAMPLE: 
      begin
        if (okey == 1'b0 && key_pos) begin
          nstate = IDLE;
        end else if (okey == 1'b1 && key_neg) begin
          nstate = PRESS;
        end else if (cnt == CNT_MAX - 1 && okey == 1'b0) begin
		  nstate = PRESS;
	 	end else if (cnt == CNT_MAX - 1 && okey == 1'b1)
		  nstate = IDLE;
      end
    PRESS:
      if (key_pos)
		nstate = SAMPLE;
    default: nstate = cstate;
  endcase
end

always @(posedge clk or negedge rstn) begin
  if(~rstn)
    okey  <=  1'b0;
  else begin
    case (nstate)
      IDLE : 
    	okey   <= 1'b0;
      SAMPLE: 
	  	okey   <= okey;
      PRESS: 
     	okey   <= 1'b1;
      default:
	  	okey   <= okey;
    endcase
  end
end

/*          20ms counter         */
always @(posedge clk or negedge rstn) begin
  if (~rstn) 
    cnt     <=  26'd0;
  else if (nstate == SAMPLE) 
    cnt     <=  cnt + 1'b1;
  else
    cnt     <=  26'd0;
end

endmodule

带自锁的方案

实际上和不带自锁的方案只有一点点改动。

/****************************************************************************
*   Change to the opposite side and hold after pressing the key, 
*   behavouring like a self-lock button.
*
*		Here the key is active-low. When key is pressed, output high.
*****************************************************************************/

module keyHold #(
  parameter   SYS_CLK = 50_000_000
)(
  input       clk ,
  input       rstn,
  input       ikey,

  output reg  okey
);

localparam CNT_MAX = SYS_CLK / 50;

/*          debug            */
// localparam CNT_MAX = 50;

reg  [25:0]     cnt        ;

/*          edge detect         */
reg  key_in_r;
wire key_pos, key_neg;

always @(posedge clk or negedge rstn) begin
  if (~rstn) begin
    key_in_r    <=  1'b1;
  end else begin
    key_in_r    <=  ikey;
  end
end

assign key_pos = (ikey & ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey & key_in_r) ? 1'b1 : 1'b0;

/*          FSM         */
reg  [3:0]  nstate, cstate;
localparam  IDLE   = 4'b0001,
            SAMPLE = 4'b0010,
            PRESS  = 4'b0100,
			LOOSE  = 4'b1000;

always @(posedge clk or negedge rstn) begin
  if (~rstn)
    cstate  <=  IDLE;
  else 
    cstate  <=  nstate;
end

always @(*) begin
  nstate = cstate;
  case (cstate)
    IDLE :
      if (key_neg)
				nstate = SAMPLE;
    SAMPLE: 
      begin
        if (key_pos) begin
          if (okey == 1'b0)
            nstate = LOOSE;
          else
            nstate = PRESS;
        end else if (cnt == CNT_MAX - 1 && okey == 1'b0)
			nstate = PRESS;
		else if (cnt == CNT_MAX - 1 && okey == 1'b1)
			nstate = LOOSE;
      end
    PRESS:
      if (key_neg)
		nstate = SAMPLE;
	LOOSE:
      if (key_neg)
				nstate = SAMPLE;
    default: nstate = cstate;
  endcase
end

always @(posedge clk or negedge rstn) begin
  if(~rstn)
    okey  <=  1'b0;
  else begin
    case (nstate)
      IDLE : 
    	okey   <= 1'b0;
      SAMPLE: 
	  	okey   <= okey;
      PRESS: 
     	 okey   <= 1'b1;
	  LOOSE: 
	  	okey	 <= 1'b0;
      default:
	  	okey   <= okey;
    endcase
  end
end

/*          20ms counter         */
always @(posedge clk or negedge rstn) begin
  if (~rstn) 
    cnt     <=  26'd0;
  else if (nstate == SAMPLE) 
    cnt     <=  cnt + 1'b1;
  else
    cnt     <=  26'd0;
end

endmodule

输出一个时钟周期的方案

/******************************************************************************
*		Output a pulse everytime pressing the key.
*******************************************************************************/

module keyPulse #(
  parameter   SYS_CLK = 50_000_000
)(
  input       clk ,
  input       rstn,
  input       ikey,

  output reg  okey
);

localparam CNT_MAX = SYS_CLK / 50;

/*    debug   */
// localparam CNT_MAX = 50;

reg  [25:0]     cnt_20ms ;

/*          detect edge         */
reg  key_in_r;
wire key_pos, key_neg;

always @(posedge clk or negedge rstn) begin
  if (~rstn) begin
    key_in_r    <=  1'b1;
  end else begin
    key_in_r    <=  ikey;
  end
end

assign key_pos = (ikey && ~key_in_r) ? 1'b1 : 1'b0;
assign key_neg = (~ikey && key_in_r) ? 1'b1 : 1'b0;

/*          FSM         */
reg  [2:0]  nstate, cstate;
localparam  IDLE    = 3'b001,
            SAMPLE  = 3'b010,
			CONFIRM = 3'b100;

always @(posedge clk or negedge rstn) begin
  if (~rstn)
    cstate  <=  IDLE;
  else 
    cstate  <=  nstate;
end

always @(*) begin
  nstate = cstate;
  case (cstate)
    IDLE:
      if (key_neg)
				nstate = SAMPLE;
    SAMPLE: 
			begin
      	if (key_pos)    
        	nstate = IDLE;
				else if (cnt_20ms == CNT_MAX - 1)
					nstate = CONFIRM;
			end
		CONFIRM:
			nstate = IDLE;
    default: nstate = cstate;
  endcase
end

always @(posedge clk or negedge rstn) begin
  if(~rstn)
    okey  <=  1'b0;
  else begin
    case (nstate)
      IDLE: 
     	  okey   <= 1'b0;
      SAMPLE: 
     	  okey   <= 1'b0;
	  	CONFIRM:
	  		okey 	 <= 1'b1;
      default:
	  		okey	 <= okey;
    endcase
  end
end

/*          cnt_20ms            */
always @(posedge clk or negedge rstn) begin
  if (~rstn) 
    cnt_20ms    <=  26'd0;
  else if (nstate == SAMPLE) 
    cnt_20ms    <=  cnt_20ms + 1'b1;
  else
    cnt_20ms    <=  26'd0;
end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值