关于FPGA设计中的机械按键消抖问题

文章介绍了在FPGA应用中,如何通过软件方法实现机械按键的消抖,主要采用了状态机设计,包括IDLE、FILTER0、DOWN和FILTER1四个状态,以消除按键20ms的抖动。在设计中,利用了one-hot编码优化状态机,两级寄存器捕获按键电平变化,并通过20ms计数器判断按键是否稳定。仿真结果显示,该方法能有效滤除按键抖动,产生稳定的key_flag和key_state信号。
摘要由CSDN通过智能技术生成

在实际应用中,用按键控制一些操作的场景十分常见,但由于机械按键的物理特性导致按下和释放瞬间会有大约20ms的抖动,这就会给应用时带来一些不方便的地方。当然硬件上可以使用RS触发器合理设计消抖电路,但这里记录的是一种软件消抖方法。由于是基于FPGA的应用,延时是不可综合的,故采用一种状态机电路对机械按键进行消抖。模块图:

  • key_in是按键信号的输入。

  • key_flag信号在滤波稳定后拉高一个时钟周期,合理设计的话,一次按键事件,该信号应被分别拉高一个时钟周期。

  • key_state是按键稳定后,为0或者为1指示按键的所处状态。

  • 如assign key = !key_state & key_flag 可作为按键按下的标志。

采用状态机进行设计,状态转移图以及相关条件如下图。

分为四个状态:

  • IDLE:空闲态,复位后,没有任何按键行为,按键值为高电平。在检测到下降沿后,进入按下滤波状态FILTER0,否则一直等待下降沿的到来。

  • FILTER0:按下滤波态,进入该态后,启动20ms的计数器,如在计满期间检测到上升沿,则判定为是在抖动中,按键并未稳定为低电平,则返回IDLE态。如果计满时还未检测到上升沿,则认为按键稳定在低电平。进入DOWN态

  • DOWN:按键按下稳定态,是类似于IDLE态的孪生态,在该态中检测到上升沿后,进入释放滤波态FILTER1,否则一直等待上升沿的到来。

  • FILTER1:释放滤波态,进入该态后,启动20ms的计数器,如在计满期间检测到下降沿,则判定为是在抖动中,按键并未稳定为高电平,则返回DOWN态。如果计满时还未检测到下降沿,则认为按键稳定在高电平。进入IDLE态。

一、逻辑设计及仿真

两个要注意的点:

  • 在表示状态机的状态时,由于状态较少条件相对较多,采用one-hot编码,虽然多使用了几个bit的寄存器,但由于one-hot编码实际上已经是译码后的状态,减少综合后的组合逻辑,提高电路的运行效率。由于不涉及外部传参,故采用localparam进行编码定义。

  • 采用两级寄存器对按键输入信号进行寄存,在20ns的时钟速度下,足以捕捉到按键按下的瞬间前后的电平状态,再用一级组合电路对边沿进行判断即可。

  1. 逻辑设计

module key_filter
(   
    input   wire                clk         ,
    input   wire                rst_n       ,
    input   wire                key_in      ,
        
    output  reg                 key_flag    ,
    output  reg                 key_state   
);

//状态机参数定义
localparam  IDLE    = 4'b0001;
localparam  FILTER0 = 4'b0010;
localparam  DOWN    = 4'b0100;
localparam  FILTER1 = 4'b1000;

reg [3:0] state;


//20ms受控计数器参数定义
reg cnt_en;
reg [19:0] cnt;

//计数满20ms信号定义
reg cnt_full;



//边沿捕获参数定义
wire nedge;
wire pedge;
reg  key_reg0;
reg  key_reg1;



//两级寄存器寄存key_in状态
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        key_reg0 <= 1'b0;
    else
        key_reg0 <= key_in;

always@(posedge clk or negedge rst_n)
    if(!rst_n)
        key_reg1 <= 1'b0;
    else
        key_reg1 <= key_reg0;

//边沿判断
assign nedge = (key_reg1) & (!key_reg0);
assign pedge = (!key_reg1) & (key_reg0);

//20ms受控计数器
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        cnt <= 20'd0;
    else if(cnt_en)begin
        if(cnt == 20'd999_999)
            cnt <= 20'd0;
        else
            cnt <= cnt + 1'b1;
    end
    else
        cnt <= 20'd0;


//cnt_full信号赋值
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        cnt_full <= 1'b0;
    else if(cnt == 20'd999_999)
        cnt_full <= 1'b1;
    else
        cnt_full <= 1'b0;


//状态机部分
always@(posedge clk or negedge rst_n)
    if(!rst_n)begin
        state <= IDLE;
        cnt_en <= 1'b0;
        key_flag <= 1'b0;
        key_state <= 1'b1;
    end
    else begin
        case(state)
            IDLE : begin
                key_flag <= 1'b0;
                if(nedge)begin
                    state <= FILTER0;
                    cnt_en = 1;
                end
                else
                    state <= IDLE;
            end
        
        
        
            FILTER0 : begin
                if(cnt_full)begin
                    state <= DOWN;
                    cnt_en = 1'b0;
                    key_flag = 1;
                    key_state = 0;
                end
                else if(pedge) begin
                    state <= IDLE;
                    cnt_en <= 1'b0;
                end
                else
                    state <= FILTER0;
            end
        
        
        
            DOWN : begin
                key_flag <= 1'b0;
                if(pedge)begin
                    state <= FILTER1;
                    cnt_en = 1;
                end
                else
                    state <= DOWN;
            end
        
        
        
        
            FILTER1 : begin
                if(cnt_full) begin
                    state <= IDLE;
                    cnt_en <= 1'b0;
                    key_flag = 1;
                    key_state = 1;
                end
                else if(nedge)begin
                    state <= DOWN;
                    cnt_en = 1'b0;
                end
                else
                    state <= FILTER1;
            end
            
            
            default : begin
                state <= IDLE;
                cnt_en <= 1'b0;
                key_flag <= 1'b0;
                key_state <= 1'b1;
            end
        
        endcase
    end


endmodule
  1. tb

这里使用了一个仿真模型,如果直接在tb中写代码进行按键行为的模拟,代码非常臃肿。写一个单独的的“模块”模拟几次按键行为,也方便以后调用。

`timescale 1ns/1ns
module key_model
(
    output  reg        key
);

reg [15:0] rand_value;

    task press_key; begin
        repeat(50) begin
            rand_value = {$random} % 65536;
            #rand_value;
            key = !key;
        end
        key = 0;
        #50_000_000;
        
        repeat(50) begin
            rand_value = {$random} % 65536;
            #rand_value;
            key = !key;
        end
        key = 1;
        #50_000_000;
    end
    endtask
    
    initial begin
        key = 1;
        press_key;
        #200;
        press_key;
        #200;
        press_key;
        #200;
        $stop;
    end


endmodule

这里涉及到task语法和系统函数random的使用。

  • task类似函数打包调用,不赘述。

  • random函数实际上生成一个范围内的随机数,适合用来模拟抖动每个高低电平的持续时间。贴出官方文档截图。

  1. modelsim仿真

填坑:设计到仿真模型时要注意simulation的设置,在原有的tb选项中添加key_model文件,而不是创建一个key_model为仿真文件的test bench。

可以看到经历一串抖动之后,分别合理地产生了key_flag信号和key_state信号。利用好这两个信号便从功能上滤除了机械按键地抖动。

二、总结

  • 又一次熟悉了状态机的操作。

  • 关于状态机的状态定义是使用格雷码还是独热码,更多理解可以看另一位博主的文章,下面给出链接。

格雷码?独热码?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李亚有鸭梨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值