按键消抖补充

文章详细介绍了如何使用Verilog硬件描述语言实现按键消抖功能,包括基于延时的方法和状态机实现。提供了4个按键的消抖模块源码,并附带了相应的仿真文件,以验证消抖效果。通过状态机,能更灵活地处理按键的按下和松开抖动,确保稳定信号的输出。
摘要由CSDN通过智能技术生成

1.补充方法原理

  • 原理:仍然是采用延时方法

  • 时序图如下
    在这里插入图片描述

  • 该时序图实现的是一位按键的情况。在使用消抖后按键信号的时候,可以在顶层调用消抖模块多次;也可以写多位按键消抖情况

2.补充方法源码及仿真

  • 上文说到我们可能会需要多位按键消抖的情况,由于笔者常用的EP4CE6F17C8器件通常是四个按键,下文展示了4个按键的消抖模块
/**************************************功能介绍***********************************
Date	:2023年7月27日10:42:24 
Author	: Alegg xy.
Version	: 2.0
Description: 按键消抖(4个按键)
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module key_debounce ( 
    input				clk		,
    input				rst_n	,
    input		[3:0]	key_in  ,
    output  reg [3:0]   key_out //输出脉冲信号
);								 
//---------<参数定义>--------------------------------------------------------- 
    parameter TIME_20MS = 20'd1000_000;//20ms

//---------<内部信号定义>-----------------------------------------------------
    reg         [3:0]   key_r0          ;//同步
    reg         [3:0]   key_r1          ;//打两拍
    reg         [3:0]   key_r2          ;
    wire                nedge           ;//下降沿
    reg                 flag            ;//计数器计数的标志信号(按键按下抖动标志)

    reg			[19:0]	cnt_20ms	   	;//20ms计数器
    wire				add_cnt_20ms	;
    wire				end_cnt_20ms	;

//****************************************************************
//--同步打拍
//****************************************************************
    //同步,将key_in信号,同步到clk时钟域下面
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r0 <= 4'b1111;
        end 
        else begin 
            key_r0 <= key_in;
        end 
    end
    
    //打两拍
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r1 <= 4'b1111;
            key_r2 <= 4'b1111;
        end 
        else begin 
            key_r1 <= key_r0;
            key_r2 <= key_r1;
        end 
    end
    
//****************************************************************
//--nedge 
//****************************************************************
    assign nedge = (~key_r1 & key_r2) != 0 ;//下降沿检测


//****************************************************************
//--cnt_20ms
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_20ms <= 'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms)begin 
                cnt_20ms <= 'd0;
            end
            else begin 
                cnt_20ms <= cnt_20ms + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_20ms = flag;
    assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
    
//****************************************************************
//--flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag <= 'd0;
        end 
        else if(nedge)begin 
            flag <= 1'b1;
        end 
        else if(end_cnt_20ms)begin
            flag <= 1'b0;
        end
    end

//****************************************************************
//--key_out
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_out <= 4'd0000;
        end 
        else if(end_cnt_20ms)begin 
            key_out <= ~key_r2;
        end 
        else begin 
            key_out <= 4'b0000;
        end 
    end

endmodule
  • 仿真文件
`timescale 1ns/1ns
    
module tb_key_debounce();

//激励信号定义 
    reg				tb_clk  	;
    reg				tb_rst_n	;
    reg		[3:0]   tb_key_in	;

//输出信号定义	 
    wire	[3:0]		tb_key_out	;

//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   

//模块例化
    key_debounce u_key_debounce(	
    .clk		(tb_clk			),
    .rst_n		(tb_rst_n		),
    .key_in		(tb_key_in		),

    .key_out	(tb_key_out		) 
    );

//产生时钟
    initial 		tb_clk = 1'b0;
    always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;

    defparam u_key_debounce.TIME_20MS = 100;
    integer i,j;
//产生激励
    initial  begin 
        tb_rst_n = 1'b1;
        tb_key_in = 4'b1111;
        #(CLOCK_CYCLE*2);
        tb_rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        tb_rst_n = 1'b1;

        //****************************************************************
        //--模拟按下key[0]
        //****************************************************************
        #1;
        tb_key_in[0] = 1'b0;
        for (j =0 ; j<8 ; j=j+1) begin//模拟抖动
            i = {$random}%500;
            #i;
            tb_key_in[0] = i;
        end
        tb_key_in[0] = 1'b0;

        wait(u_key_debounce.end_cnt_20ms);

        #1000;
        tb_key_in[0] = 1'b1;


        //****************************************************************
        //--模拟同时按下key[1]和key[2]
        //****************************************************************
        #1;
        tb_key_in = 4'b1001;
        for (j=0 ; j<8 ; j=j+1) begin
            i = {$random}%500;
            #i;
            tb_key_in[1] = i;
            tb_key_in[2] = i;
        end
        tb_key_in = 4'b1001;

        wait(u_key_debounce.end_cnt_20ms);

        #1000;
        tb_key_in = 4'b1111;
        
        #1000;
        $stop;
    end

endmodule 
  • 仿真效果
    在这里插入图片描述

3.扩展方法:状态机实现消抖

  • 状态转移图
    在这里插入图片描述

  • 代码

/**************************************功能介绍***********************************
Date	: 2023年7月27日15:52:18
Author	: Alegg xy.
Version	: 3.0
Description: 状态机实现按键消抖
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module fsm_key_debounce( 
    input				clk		,
    input				rst_n	,
    input		[3:0]	key_in	,

    output	reg	[3:0]	key_out	
);								 
//---------<参数定义>--------------------------------------------------------- 
    parameter MAX_20ms = 20'd1_000_000;
    reg         [3:0]   key_r0          ;
    reg         [3:0]   key_r1          ;
    reg         [3:0]   key_r2          ; 
    wire                nedge          ;
    wire                pedge          ;
    reg                 flag            ;     
    reg			[19:0]	cnt_20ms	   	;
    wire				add_cnt_20ms	;
    wire				end_cnt_20ms	;


//---------<内部信号定义>-----------------------------------------------------
    //同步,将key_in信号,同步到clk时钟域下面
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r0 <= 4'b1111;
        end 
        else begin 
            key_r0 <= key_in;
        end 
    end
    
    //打两拍
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r1 <= 4'b1111;
            key_r2 <= 4'b1111;
        end 
        else begin 
            key_r1 <= key_r0;
            key_r2 <= key_r1;
        end 
    end
    
//****************************************************************
//--nedge和pedge定义 
//****************************************************************
    assign nedge = (~key_r1 & key_r2) != 0;//下降沿检测
    assign pedge = (key_r1 & ~key_r2) != 0;//上升沿检测

//****************************************************************
//--cnt_20ms
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_20ms <= 19'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms)begin 
                cnt_20ms <= 19'd0;
            end
            else begin 
                cnt_20ms <= cnt_20ms + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_20ms = flag;
    assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX_20ms - 1;
    
//****************************************************************
//--flag
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag <= 'd0;
        end 
        else if(nedge || pedge)begin 
            flag <= 1'b1;
        end 
        else if(end_cnt_20ms)begin
            flag <= 1'b0;
        end
    end
    
//****************************************************************
//--状态机
//****************************************************************
    //状态机参数定义
    localparam  IDLE            = 4'b0001,//初始状态
                FILTTER_DOWN    = 4'b0010,//按下抖动
                HOLD_DOWN       = 4'b0100,//稳定信号
                FILTER_UP       = 4'b1000;//松开抖动
    
    reg 	[3:0]	cstate     ;//现态
    reg	    [3:0]	nstate     ;//次态
    
    //第一段:时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cstate <= IDLE;
        end 
        else begin 
            cstate <= nstate;
        end 
    end
    
    //第二段:组合逻辑描述状态转移规律和状态转移条件
    always @(*) begin
        case(cstate)
            IDLE            :
                if (nedge) begin
                    nstate = FILTTER_DOWN;
                end
                else begin
                    nstate = cstate;
                end 
            FILTTER_DOWN    :
                if (end_cnt_20ms) begin
                    nstate = HOLD_DOWN;
                end
                else begin
                    nstate = cstate;
                end 
            HOLD_DOWN       :
                if (pedge) begin
                    nstate = FILTER_UP;
                end
                else begin
                    nstate = cstate;
                end 
            FILTER_UP       :
                if (end_cnt_20ms) begin
                    nstate = IDLE;
                end
                else begin
                    nstate = cstate;
                end 
            default : nstate = cstate;
        endcase
    end
                
    //第三段:描述输出,时序逻辑或组合逻辑皆可
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_out <= 4'b0000;
        end 
        else begin 
            case (cstate)
                IDLE        :key_out <= 4'b0000        ;
                FILTTER_DOWN:key_out <= 4'b0000        ;
                HOLD_DOWN   :key_out <= ~key_r2 && pedge;
                FILTER_UP   :key_out <= 4'b0000        ; 
                default     :key_out <= 4'b0000        ;
            endcase
        end 
    end       
                
endmodule
  • 代码理解的关键之处

    1、4位按键信号赋值需要4位位宽的二进制数

    2、上升沿和下降沿如何求值

    3、何时计数开始

    4、状态转换条件

    5、如何赋值可以实现输出的脉冲信号

  • 仿真文件

`timescale 1ns/1ns
    
module tb_fsm_key_debounce();

//激励信号定义 
    reg				tb_clk  	;
    reg				tb_rst_n	;
    reg		[3:0]	tb_key_in	;

//输出信号定义	 
    wire	[3:0]	tb_key_out  ;

//时钟周期参数定义	
    parameter		CLOCK_CYCLE = 20;   
    defparam u_fsm_key_debounce.MAX_20ms = 10;
//模块例化
    fsm_key_debounce u_fsm_key_debounce(	
    .clk		(tb_clk			),
    .rst_n		(tb_rst_n		),
    .key_in		(tb_key_in		),

    .key_out	(tb_key_out		) 
    );

//产生时钟
    initial 		tb_clk = 1'b0;
    always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;

//产生激励
    initial  begin 
        tb_rst_n = 1'b1;
        tb_key_in = 4'b1111;
        #(CLOCK_CYCLE*2);
        tb_rst_n = 1'b0;
        #(CLOCK_CYCLE*20);
        tb_rst_n = 1'b1;
        #7;
        tb_key_in = 4'b1110;
        #13;
        tb_key_in = 4'b1111;
        #8;
        tb_key_in = 4'b1110;
        #10;
        tb_key_in = 4'b1111;
        #5;
        tb_key_in = 4'b1110;
        #1000;
        tb_key_in = 4'b1111;
        #1000;
        $stop;


    end

endmodule 
  • 仿真效果
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值