FPGA-状态机实现按键消抖

状态图

首先看这样一个按键消抖的时序图:

粗略画了一下按键时抖动(20ms)的情况,最初为高电平,按键按下变成低电平有一个抖动的过程,同样由低到高有抖动,按键消抖就是在时序图上反应滤除抖动的结果。

可以将其分成几个状态,并分别用独热码表示:

1,5,7:4'b0001,IDLE保持高电平;

2,6 :4'b0010,按下/由高电平作为开始的抖动;

3 :4'b0100,保持低电平;

4 :4'b1000,释放时出现的抖动.

由此可画出状态图:

开始为空闲状态IDLE(高电平),当按键按下,经过滤除抖动进入低电平状态DOWN(形容从高到低的一个趋势,即2,6状态,并不是指低电平),然后保持低电平状态HOLD,最后释放按键滤除抖动进入UP状态(由低到高的趋势,即4状态)。另外,由于7状态是直接经过抖动滤除后变成的高电平(未经过低电平保持状态),所以在经过DOWN状态时需要进行判断,判断其是否会有低电平的保持态,若检测到低电平则进入HOLD状态,若未检测到低电平而是上升沿pos,则回到高电平状态IDLE。

状态机自带两个信号,分别是state_c,state_n,即现态和次态。

时序图

画出时序图

分别进行上升沿和下降沿检测:

上升沿检测:r0为高电平,r1为低电平时,检测测到上升沿

下降沿检测:r0为低电平,r1为高电平时,检测到下降沿

(1)开头的时序图,首先检测到了下降沿,状态机次态state_n由起始的空闲状态0001变为DOWN状态(由状态1到状态3要经历状态2),即有下降沿的状态,并且次态是用组合逻辑描述的,只要条件满足就立即变化,所以次态变为0010.此时的现态state_c需要更新,由于现态用时序逻辑描述,因此,现态在下一个时钟上升沿进行变化,更新到0010.

(2)计数器从检测到第一个下降沿开始计数,有20ms的抖动,计数到20ms变为低电平(HOLD状态),次态变为0100,现态在下一个时钟周期上升沿变为0100。

(3)保持低电平状态(HOLD)直到上升沿(UP)出现则由转为高电平状态,即IDLE(0001),计数器在上升沿时开始计抖动延时20ms,结束后次态变为0001,现态在下一个时钟周期变为0001.

综上,总体时序图绘制如下:

代码

顶层key_fliter

module key_fliter (
    input           clk       ,
    input           rst_n     ,
    input           key_in    ,

    output  reg     key_out    //时序输出减少毛刺

);
//参数定义
parameter     DELAY = 1000_000;//20ms

//状态机参数
parameter     IDLE = 4'b0001      ,//空闲状态,保持高电平
              DOWN = 4'b0010      ,//按键按下抖动状态
              HOLD = 4'b0100      ,//保持状态 保持低电平状态
              F_UP = 4'b1000      ;//按键释放抖动状态

//信号定义
//状态机信号
reg      [3:0]     state_c        ;//现态
reg      [3:0]     state_n        ;//次态

reg                key_r0         ;//按键同步
reg                key_r1         ;//打拍
wire               nedge          ;//下降沿检测
wire               pegde          ;//上升沿检测

reg      [19:0]    cnt            ;//延时计数器
wire               add_cnt        ;
wire               end_cnt        ;

wire               idle2down      ;
wire               down2hold      ;
wire               down2idle      ;
wire               hold2f_up      ;
wire               f_up2idle      ;

//第一段 时序逻辑 状态的转移
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        state_c <= IDLE ;
    end
    else begin
        state_c <= state_n;
    end
end

//第二段 组合逻辑 描述状态转移的规律
always @(*) begin
    case (state_c)
        IDLE: begin
            if(idle2down)     //如果从idle状态到down状态,这里_2_理解为像个状态转换的过程
              state_n = DOWN ;//则将down状态赋给次态
            else 
              state_n = state_c;//否则将现态赋给次态 
        end
        DOWN: begin
            if(down2hold)
              state_n = HOLD ;
            else if(down2idle)
              state_n = IDLE ;
            else 
              state_n = state_c ;
        end
        HOLD: begin
            if(hold2f_up)
              state_n = F_UP ;
            else 
              state_n = state_c ;
        end
        F_UP: begin
            if(f_up2idle)
              state_n = IDLE ;
            else 
              state_n = state_c ; 
        end
        default: state_n = IDLE;
    endcase
end

//状态之间的条件(状态之间由多少箭头就有多少条件)
  assign   idle2down = state_c == IDLE && (nedge);//出现dile转为down的条件是出现下降沿
  assign   down2hold = state_c == DOWN && (end_cnt && (key_r1 == 1'b0)) ;//由down转为保持前提是在下降沿消除抖动(20ms)后变得平稳的输出,观察时序图r0和r1的情况
  assign   down2idle = state_c == DOWN && (end_cnt && (key_r1 == 1'b1)) ;//由down转为idle也需要消除抖动,并且观察时序图此时的r1为高电平
  assign   hold2f_up = state_c == HOLD && (pegde)  ; //释放抖动              
  assign   f_up2idle = state_c == F_UP && (end_cnt);                  

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_r0 <= 1'b1 ;
        key_r1 <= 1'b1 ;
    end
    else begin
        key_r0 <= key_in ;//同步
        key_r1 <= key_r0 ;//打拍 
end
end

assign nedge = key_r1 && ~key_r0 ; //下降沿r0为低电平,r1为高电平
assign pegde = ~key_r1 && key_r0 ; //上升沿r0为高电平,r1低电平

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt <= 'd0;
    end
    else if (add_cnt) begin
        if (end_cnt) begin
            cnt <= 'd0 ;     
        end
        else begin
            cnt <= cnt + 1'b1;
        end
    end
end  

assign add_cnt = (state_c == DOWN) || (state_c == F_UP) ;
assign end_cnt = add_cnt && (cnt == DELAY - 1);

// key_out时序输出减少毛刺
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_out <= 'd0;
    end
    else if (down2hold) begin
        key_out <= 1'b1;
    end
    else begin
        key_out <= 1'b0;
end
end

endmodule

注意,key_out输出的0或者1表示按键是否按下的标志,所以最终的输出只有在输入为电平时期输出才有有一个拉高的标志,表示按键按下,而后当输入高电平时,输出为低电平表示按键未按下。

rtl视图:

分析状态机的正确性:

仿真

测试文件

`timescale 1ns/1ps
module key_fliter_tb();
reg           clk      ;
reg           rst_n    ;
reg           key   ;

wire          key_out  ;

//参数
parameter    CYCLE = 20 ;//50M时钟 
defparam     u_key_fliter.DELAY = 20 ;

//模块例化
key_fliter u_key_fliter(
    .clk                (clk    ),
    .rst_n              (rst_n  ),
    .key_in             (key    ),
    .key_out            (key_out)  
);

//激励
initial begin  //产生时钟
    clk = 1'b1 ;
    forever begin
        #(CYCLE/2);
        clk = ~clk ;
    end
end

initial begin
    rst_n = 1'b1 ;
    #(CYCLE);
    rst_n = 1'b0 ;//上电复位

    key = 1'b1 ;
    #(CYCLE*2);
    #2;
    rst_n = 1'b1 ;
    #(CYCLE*4);
//模拟顶层文件按键信号(key_in)输入
//模拟按键按下
    key = 1'b0 ;
    #(CYCLE*30);
    key = 1'b1 ;

    #(CYCLE*5);//加延时避免出现毛刺
//模拟按键出现抖动
    key = 1'b0 ;
    #(CYCLE*17);//需要小于DELYA
    key = 1'b1 ;
    
    #(CYCLE*10);
    $stop;
end

endmodule

仿真图:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值