状态机按键消抖电路设计


重新补充一下,
这里的消抖,可以看作开关的消抖,开关闭合和释放都要进行消抖处理,并且中间会保持一段时间。
而按键的消抖,按下释放是连着的,这一个过程会发出一个脉冲信号,证明按键按下过


1. 什么是按键抖动

实际的拨动开关和按键都是机械式的设备,在状态改变时可能会出现来回抖动多次导致状态不稳定的情况,通常抖动的持续时间不超过20ms。消抖电路的目的就是滤去这段时间内的抖动信号。
在这里插入图片描述

2. 消抖电路设计思路

  1. 使用有限状态机的思想,当输入信号稳定20ms后才改变抖动以后的输出值。假定使用的系统时钟频率为50HMz,要等待20ms的时间,那么就使用计数器数1_000_000个clk,解决延时问题。

  2. 按照等待延时的思路,可以这样设计:检测到按键key_in按下,启动计数器,等到20ms后检测key_in是否为0,若为0,表明按键按下状态稳定,关闭计数器,然后等待按键key_in释放;当检测到key_in释放出现上升沿,再次启动计数器,20ms后检测key_in是否为1,若为1,表明按键已释放,一次完整的按键过程结束。 每20ms检测完毕后,输出一个flag脉冲。

  3. 为了实现按键消抖,考虑到两种情况,按下需要消抖,释放需要消抖。状态机分为4个状态,两个稳定状态和两个消抖状态

    IDLE:空闲状态,默认按键不按下为稳定高电平
    FILTER_DOWN:按键按下消抖状态
    DOWN:按键按下稳定状态
    FILTER_UP:按键释放消抖状态
    在这里插入图片描述

  4. 消抖模块端口设计
    在这里插入图片描述

3. 代码实现

module key_filter(
    output  reg     key_flag,   // 消抖完毕输出脉冲
    output  reg     key_state,  // 按键状态输出
    
    input   wire    clk,       // 50MHz时钟
    input   wire    rst,
    input   wire    key_in
    );
    
    // 状态声明
    parameter   IDLE        =   4'b0001;
    parameter   FILTER_DOWN =   4'b0010; 
    parameter   DOWN        =   4'b0100;
    parameter   FILTER_UP   =   4'b1000;
    
    reg     [3:0]   next_state;
    reg             key_temp;   // 
    wire            key_nedge;  //
    wire            key_pedge;  //
    reg             en_counter; // 计数器使能信号
    reg     [19:0]  counter;    // 计数1_000_000次
    
    // 边沿检测电路
    // 按键信号属于异步信号,在状态转移中需要对按键边沿敏感,所以先采用一级D触发器将key_in与clk同步,产生pedge和nedge信号
    always @ (posedge clk)
        key_temp    <=  key_in;                         // 暂存上一个按键状态
     assign key_nedge   =   (key_temp) && (!key_in);    // 下降沿检测
     assign key_pedge   =   (!key_temp) && (key_in);    // 上升沿检测
    
    // 带使能的计数器,实现20ms延时
    always @ (posedge clk)      begin
        if (rst == 1'b0)    begin
            counter <=  0;
        end else if (en_counter == 1'b1)    begin
            counter <=  counter + 1;
        end else    begin
            counter <=  0;
        end
    end
    
    // 状态机模块
    always @ (posedge clk or negedge rst)   begin
        if (rst == 1'b0)    begin
            next_state  <=  IDLE;
            en_counter  <=  1'b0;
            key_flag    <=  1'b0;
            key_state   <=  1'b1;
        end else    begin
            case (next_state)
                IDLE:           begin
                    key_flag    <=  1'b0;
                    key_state   <=  1'b1;
                    if (key_nedge)  begin
                        next_state  <=  FILTER_DOWN;
                        en_counter  <=  1'b1;
                    end else    begin
                        next_state  <=  IDLE;
                    end  
                end
                FILTER_DOWN:    begin
                    if (counter >= 20'd999_999) begin   // 20ms时间到
                        key_flag    <=  1'b1;       // flag输出一个高脉冲,进入稳定状态
                        en_counter  <=  1'b0;       // 计数器不使能
                        next_state  <=  DOWN;
                    end else if (key_pedge)     begin   // 20ms内检测到有上升沿
                        en_counter  <=  1'b0;       //计数器不使能,保持空闲状态
                        next_state  <=  IDLE;
                    end
                end
                DOWN:           begin
                    key_flag    <=  1'b0;
                    key_state    <=  1'b0;
                    if (key_pedge)  begin
                        next_state  <=  FILTER_UP;
                        en_counter  <=  1'b1;
                    end else    begin
                        next_state  <=  DOWN;
                    end                
                end
                FILTER_UP:      begin
                    if (counter >= 20'd999_999) begin   // 20ms时间到
                        key_flag    <=  1'b1;       // flag输出一个高脉冲,进入稳定状态
                        en_counter  <=  1'b0;       // 计数器不使能
                        next_state  <=  IDLE;
                    end else if (key_nedge) begin       // 20ms内检测到有下降沿
                        en_counter  <=  1'b0;       // 计数器不使能,保持按下稳定状态
                        next_state  <=  DOWN;
                    end
                end
                default:        begin
                    next_state  <=  IDLE;
                end
            endcase
        end
    end
    
endmodule

4. 仿真测试

4.1. initial语句块赋值

`define clk_period 20        //100M系统时钟

module key_filter_tb();
    
    reg clk;                   //50M时钟信号
    reg rst;                   //低电平复位
    reg key_in;                //按键输入
    wire key_flag;             //消抖完毕输出脉冲
    wire key_state;            //按键状态输出
    
    //例化测试模块
    key_filter key_filter_test(
        .clk(clk),             //50M时钟信号
        .rst(rst),             //低电平复位
        .key_in(key_in),       //按键输入
        .key_flag(key_flag),   //消抖完毕输出脉冲
        .key_state(key_state)  //按键状态输出
);
    //产生100M时钟信号
    initial clk = 1;
    always #(`clk_period / 2) clk <= ~clk;
    
    //开始测试
    initial begin
        rst <= 0;        //系统复位
        key_in <= 1;     //按键处于空闲状态
    #(`clk_period * 2);
        rst <= 1;
     #10_000_000;        //延时10ms,方便观察按键按下现象
    //开始模拟按键按下抖动
    key_in <= 0;    #1000;
    key_in <= 1;    #2000;
    key_in <= 0;    #1400;
    key_in <= 1;    #2600;
    key_in <= 0;    #1300;
    key_in <= 1;    #200;
    //产生一个稳定的低电平大于20ms,代表按键稳定
    key_in <= 0;   #30_000_000;
    //模拟释放抖动
    key_in <= 1;    #2000;
    key_in <= 0;    #1000;
    key_in <= 1;    #2600;
    key_in <= 0;    #1400;
    key_in <= 1;    #200;
    key_in <= 0;    #1300;
    //产生一个稳定的高电平大于20ms,代表释放稳定
    key_in <= 1;   #30_000_000;
    $stop;
    end
    
endmodule

在这里插入图片描述
局部抖动
在这里插入图片描述

4.2. 调用task任务封装赋值并调用$random函数

  1. 利用$randon函数产生随机延时置模拟抖动
reg [15:0]randnum;
randnum = $random % 50;        //产生一个-49~49内的随机数
randnum = {$random} %  50;     //产生一个0 ~ 50内的随机数
  1. 利用 task / endtask 将重复代码进行封装
task <任务名>;
    <语句1>
    <语句2>
    <语句3>
    ....
endtask
`define clk_period 20        //100M系统时钟

module key_filter_2_tb();
    
    reg clk;                   //50M时钟信号
    reg rst;                   //低电平复位
    reg key_in;                //按键输入
    wire key_flag;             //消抖完毕输出脉冲
    wire key_state;            //按键状态输出
    
    reg [15:0]rand_time;       //按键抖动时随机时长
    
    //例化测试模块
    key_filter key_filter_test(
        .clk(clk),                    //50M时钟信号
        .rst(rst),                    //低电平复位
        .key_in(key_in),              //按键输入
        .key_flag(key_flag),          //消抖完毕输出脉冲
        .key_state(key_state)         //按键状态输出
);
    //产生100M时钟信号
    initial clk = 1;
    always #(`clk_period / 2) clk <= ~clk;
    
    //开始测试
    initial begin
        rst <= 0;        //系统复位
        key_in <= 1;     //按键处于空闲状态
    #(`clk_period * 2);
        rst <= 1;
     #10_000_000;          //延时10ms,方便观察按键按下现象
     press_key; #10000;    //第一次按下按键
     press_key; #10000;    //第二次按下按键
     press_key; #10000;    //第三次按下按键
    $stop;
    end
    
    task press_key;
       begin
           //开始模拟按键按下抖动
           repeat(50)begin
              rand_time = {$random} % 65536;
              #rand_time key_in = ~key_in; 
           end 
           //产生一个稳定的低电平大于20ms,代表按键稳定
           key_in = 0;     
           #30_000_000;
           //模拟释放抖动
           repeat(50)begin
               rand_time = {$random} % 65536;
               #rand_time key_in = ~key_in;  
           end
           //产生一个稳定的高电平大于20ms,代表释放稳定
           key_in = 1;
           #30_000_000;
       end
    endtask
    
endmodule

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值