基于状态机的按键消抖

一、按键相关原理介绍

在这里插入图片描述
按键的结构
按键按下,对应的端口进入低电平。在这里插入图片描述
左下角就是经典的按键电路,按键按下、进入低电平,按键释放回到高电平。这个过程就会产生抖动。
就像人去敲击一个不锈钢的脸盆,脸盆会产生多次的振动。

实际上由于释放的时候,触点已经拉的很远了,抖动几乎不可能将其拉到一起。所以释放的抖动要比按下的抖动小的多。甚至不出现抖动波形。

在这里插入图片描述
会产生的影响:
抖动的时间肯定是远远大于我们FPGA的时钟的周期的(20ns)。

在抖动的时候不认为按键是按下的,等到抖动停止,判断按键是按下的。

在这里插入图片描述
可以将抖动的按键变为理想的按键。

1、按键的工作状态

1、按键未按下时,空闲态,电路输出高电平
2、按键按下抖动,电路输出在高低电平间跳转。
3、按下抖动停止,静止态,电路输出低电平
4、按键松开抖动,电路输出在高低电平间跳转。
5、松开抖动停止,电路输出高电平。

在这里插入图片描述
按下的抖动和松开的抖动都不会超过20ms.

2、如何滤除按键抖动,正确判断按键的按下与释放?

1.在单片机中,最常用的是延时消抖。延时一段时间,等待抖动的时间过去。

在这里插入图片描述
可能在采样波形的时候,正好是低电平,虽然暂时对了但是会导致错误。
在这里插入图片描述
思路二的实现情况。
在这里插入图片描述
消抖的时序状态分析。

3、状态转移图的绘制

在这里插入图片描述
绘制的状态转移图如上。

在这里插入图片描述
模块的示意图。

先用两级D触发器进行一个同步,避免出现亚稳态的情况。
在这里插入图片描述
前两个D触发器对输入信号Key,进行一个打拍同步,第三个存储前一拍的值,然后通过一个与门与当前时刻的值进行一个比较,从而完成按键的一个边沿检测。
用来同步的D触发器通常命名为sync。
在这里插入图片描述
所以有如上命名。

有一个映射关系,
在这里插入图片描述
为了方便表示,可以用状态名代替数字的值。这里可以使用parameter:
parameter:用于定义一个标识符代表常量,可以通过defparam或顶层例化时进行参数传递。
localparam:本地参数定义,用于定义一个标识符代表常量,无法进行参数传递。

4、时钟周期频率值

很多的时候时钟周期的频率值并不一定是一个整数。
在这里插入图片描述
在verilog语法工具中,没有小数点的计算parameter是软件提前的计算,会忽略小数点后面。最好的方式是用电脑上带小数的运算方式得到一个精确的值之后,用这个精确地值作为计数的最大值。

在这里插入图片描述

这一块想表达的是遇到非整数周期的情况,如何让计算周期和实际周期尽可能接近

5、基于状态机思维的按键消抖功能verilog实现思路

1.对异步输入信号打拍消除亚稳态。
2.寄存器打拍后信号。对比得出上升沿与下降沿的标志信号。
3.列出所有状态,编写状态机整体框架。
4.编写各状态间跳转方向及对应跳转条件。
5.编写状态机输出逻辑,在正确时刻为输出信号赋值。

二、按键消抖的具体实现

1.模块构建

构建模块如下

module key_filter(
    Clk,
    Reset_n,
    Key,
    Key_P_Flag,
    Key_R_Flag
    );
    input Clk;
    input Reset_n;
    input Key;
    output reg Key_P_Flag;
    output reg Key_R_Flag;
    
    reg [1:0]state;        //根据的state的值不同进行不同的操作
    
    localparam IDLE = 0;   //空闲态
    localparam P_FILTER = 0;
    localparam WAIT_R = 0;
    localparam R_FILTER = 0;
    
    parameter MCNT = 1000_000 - 1;
    reg [29:0]cnt;
        
    
    reg sync_d0_Key;
    reg sync_d1_Key;
    reg r_Key;
    
    wire pedge_key;
    wire nedge_key;
    
    wire time_20ms_reached;
    assign time_20ms_reached = cnt >= MCNT;
    
    
    always@(posedge Clk)
        sync_d0_Key <=Key;
    
    always@(posedge Clk)
        sync_d1_Key <= sync_d0_Key;
    
    always@(posedge Clk)  //用来检测边沿的
        r_Key <= sync_d1_Key;
    
    assign nedge_key = (sync_d1_Key == 0)&& (r_Key == 1);   //表示下降沿的情况
    assign pedge_key = (sync_d1_Key == 1)&& (r_Key == 0);
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        state <= IDLE;
        Key_R_Flag <= 1'd0;
        Key_P_Flag <= 1'd0;
    end
    else begin    //实现状态的跳转
        case(state)
            IDLE:
                begin
                    Key_R_Flag <= 1'd0;
                    if(nedge_key)
                        state <= P_FILTER;       
                end    
            P_FILTER:           //按下消抖状态
                if(time_20ms_reached)begin
                    state <= WAIT_R;
                    Key_P_Flag <= 1'd1;
                end  
                else if(pedge_key)
                    state <= IDLE;
                else
                    state <= state;
            WAIT_R:
                begin
                Key_P_Flag <= 1'd0;  //一进入等待信号就立刻将其拉低,表明不是按下状态了
                if(pedge_key)
                    state <= R_FILTER;
                end
            R_FILTER:               //释放消抖状态
                if(time_20ms_reached)begin
                    state <= IDLE;
                Key_R_Flag <= 1'd1;       //释放的标志信号为1
                end
                else if(nedge_key)  //在等待新的信号时又检测到了下降沿
                    state <= WAIT_R;
                else
                    state <= state;  //接着等
        endcase
    end    
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        cnt <= 0;
    else if((state == P_FILTER) || (state == R_FILTER))    
        cnt <= cnt + 1'd1;
    else    
        cnt <=0;
endmodule

2.激励编写

我们的激励主要是针对按键信号。
在这里插入图片描述

`timescale 1ns / 1ps

module key_filter_tb;
    reg Clk;
    reg Reset_n;
    reg Key;
    wire Key_P_Flag;
    wire Key_R_Flag;
    wire Key_State;
    
    
    
    key_filter key_filter_inst(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Key(Key),
        .Key_State(Key_State),
        .Key_P_Flag(Key_P_Flag),
        .Key_R_Flag(Key_R_Flag)
    );
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Reset_n = 0;
        Key = 1;
        #201;
        Reset_n = 1;
        
        //第一次按下测试
        //空闲稳定
        Key = 1;#100000000;  //空闲 稳定 100ms
        
        //按下抖动
        Key = 0; #18000000; //按下 抖动 低18ms
        Key = 1; #2000000;  //按下 抖动 高2ms
        Key = 0; #1000000;  //按下 抖动 低1ms
        Key = 1; #200000;  //按下 抖动 高0.2ms
        Key = 0; #20000000;  //按下 稳定 低20ms
        
        
        
        //按下稳定
        Key = 0; #50000000;  //按下 稳定 继续50ms
        
        //释放抖动
        Key = 1; #2000000;  //释放 抖动 高2ms
        Key = 0; #1000000;  //释放 抖动 低1ms
        Key = 0; #20000000;  //释放 稳定 低20ms
        
        //释放稳定
         Key = 1; #50000000;  //按下 稳定 继续50ms
        
        //空闲稳定
        Key = 1;#100000000;  //空闲 稳定 100ms
        
        //按下抖动
        Key = 0; #18000000; //按下 抖动 低18ms
        Key = 1; #2000000;  //按下 抖动 高2ms
        Key = 0; #1000000;  //按下 抖动 低1ms
        Key = 1; #200000;  //按下 抖动 高0.2ms
        Key = 0; #20000000;  //按下 稳定 低20ms
        
        
        
        //按下稳定
        Key = 0; #50000000;  //按下 稳定 继续50ms
        
        //释放抖动
        Key = 1; #2000000;  //释放 抖动 高2ms
        Key = 0; #1000000;  //释放 抖动 低1ms
        Key = 1; #20000000;  //释放 稳定 高20ms
        
        //释放稳定
         Key = 1; #50000000;  //释放 稳定 继续50ms
        $stop;
    end    
        
        
endmodule

这条新的代码行 assign time_20ms_reached = cnt >= MCNT; 改变了之前逻辑判断的方向。
具体来说:
cnt >= MCNT 是一个比较表达式,它会评估为true(如果cnt的值大于或等于MCNT)或false(如果cnt的值小于MCNT)。
time_20ms_reached = cnt >= MCNT; 将这个比较结果(true或false)赋值给time_20ms_reached变量。
从新的代码来看,现在time_20ms_reached变量被设置为true的条件是cnt的值大于或等于MCNT。如果cnt是一个递增的计数器,这通常意味着time_20ms_reached会在cnt超过或等于某个预设的阈值MCNT时变为true,这个阈值可能代表20毫秒的时间点。
与之前的cnt <= MCNT条件相比,这个新的条件要求cnt达到或超过MCNT,而不是小于或等于它。这意味着time_20ms_reached现在表示的是“是否已经超过或等于20毫秒”,而不是“是否已经达到或还未超过20毫秒”。
同样,这里依然只是逻辑上的判断,并不涉及实际的计时功能。实际的计时和cnt的更新需要由其他部分的代码或硬件来完成。

3.仿真结果

在这里插入图片描述
这里有一个
由并行赋值逻辑可以知道:上一时刻state=1,故下一个上升沿到来,尽管state=2,但计数器仍会计数
计数器仍多计数了一个周期的问题。

always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        cnt <= 0;
    else if((state == P_FILTER) || (state == R_FILTER))    
        cnt <= cnt + 1'd1;
    else    
        cnt <=0;

这里的state判断,判断的是上一个时钟周期的值。
在这里插入图片描述
对.v文件修改后的结果。

总结

对按键消抖的verilog进行了学习和仿真,相关的代码可以直接用于后续的按键消抖的工作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值