FPGA-11-状态机的实现实例(按键的消抖)

大致思路有了,如何设计实现呢?貌似这是一个很复杂的设计,实则不然,FSM的本质就是对具有逻辑规律和时序逻辑的事物的描述,采用FSM设计,问题迎刃而解!

  1、从状态变量入手,分析状态变量

    IDLE:按键空闲状态(由于上拉电阻的作用,按键未被按下时保持高电平);

    FILTER_DOWN:按下滤波状态;

    DOWN:按下稳定状态;

    FILTER_UP:释放滤波状态;

  2、分析状态转移条件,绘制状态转移图(visio)

  3、照图施工,选用合适的描述方案

    在描述的时候,有两个重要问题需要解决:

    1)按键信号属于异步信号,在状态转移中需要对按键边沿敏感,所以首先采用一级D触发器将key_in与clk同步,产生pedge和nedge信号,也就是边沿检测电路,代码如下:

//边沿检测电路
    always@(posedge clk)
        key_temp <= key_in;                                 //暂存上一个clk按键状态
    assign key_nedge = (key_temp)&&(!key_in);        //下降沿检测
    assign key_pedge = (!key_temp)&&(key_in);        //上升沿检测

    2)当20ms延时完毕后,应该输出一个脉冲,通知其它模块检测key_flag引脚电平;

    完整的verilog描述代码如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter
// Description: //独立按键消抖模块
//////////////////////////////////////////////////////////////////////////////////
module key_filter(
    input clk,                    //50M时钟信号
    input rst,                    //低电平复位
    input key_in,                 //按键输入
    output reg key_flag,          //消抖完毕输出脉冲
    output reg key_state          //按键状态输出
);
    reg  [3:0]NS;            //nextstate
    reg  key_temp;
    wire key_pedge;    
    wire key_nedge;
    reg en_cnt;
    reg [19:0]cnt;            //需要计数次数1_000_000
    
    //边沿检测电路
    always@(posedge clk)
        key_temp <= key_in;                          //暂存上一个clk按键状态
    assign key_nedge = (key_temp)&&(!key_in);        //下降沿检测
    assign key_pedge = (!key_temp)&&(key_in);        //上升沿检测
    
    //带使能端计数器,用于20ms延时
    always@(posedge clk,negedge rst)
        if(!rst)
            cnt <= 0;
        else if(en_cnt)
            cnt <= cnt + 1'b1;
        else
            cnt <= 0;
    
    //状态one-hot编码
    localparam 
        IDLE        = 4'b0001,        //空闲状态
        FILTER_DOWN = 4'b0010,        //按下消抖状态
        DOWN        = 4'b0100,        //按下稳定状态
        FILTER_UP   = 4'b1000;        //释放消抖状态
    
    //一段式状态机
    always@(posedge clk,negedge rst)
    if(!rst)begin
        NS     <= IDLE;
        en_cnt <= 0;
        key_flag <= 0;
        key_state <= 1;
    end
    else
        case(NS)
            IDLE:
                begin
                    key_flag <= 0;
                    key_state <= 1;
                    if(key_nedge)begin
                        NS <= FILTER_DOWN;
                        en_cnt <= 1'b1;        //使能计数器
                    end
                    else
                        NS <= IDLE;
                end
            FILTER_DOWN:
                if(cnt >= 20'd999_999)begin
                    en_cnt <= 0;               //20ms时间到,失能计数器,进入稳定状态
                    key_flag <= 1'b1;          //key_flag输出一个clk高脉冲
                    NS <= DOWN;
                end
                else if(key_pedge)begin    
                    en_cnt <= 0;                //20ms时间内发生上升沿,失能计数器,保持空闲状态
                    NS <= IDLE;
                end
            DOWN:
                begin
                    key_flag <= 0;
                    key_state <= 0;
                    if(key_pedge)begin
                        NS <= FILTER_UP;
                        en_cnt <= 1'b1;         //使能计数器
                    end
                    else
                        NS <= DOWN;
                end
            FILTER_UP:
                if(cnt >= 20'd999_999)begin
                    en_cnt <= 0;
                    NS <= IDLE;                 //20ms时间到,失能计数器,进入稳定状态
                    key_flag <= 1;
                end
                else if(key_nedge)begin
                    en_cnt <= 0;                //20ms时间内发生上升沿,失能计数器,保持按下稳定状态
                    NS <= DOWN;            
                end
            default:
                    NS <= IDLE;
        endcase
endmodule

猜你喜欢

转载自blog.csdn.net/weixin_41445387/article/details/83218732