FPGA实现按键消抖(任意位宽)

一、按键原理

硬件原理图

在这里插入图片描述

  • 当按键KEY1按下时,D3V3(也就是电源)通过电阻R(原理图上折线的那一段)然后再通过按键KEY1最终进入GND形成一条通路,那么这条线路的全部电压都加到了R这个电阻上,KEY1(最左边四个IO口)这个引脚就是个低电平。
  • 当松开按键后,线路断开,就不会有电流通过,那么KEY1和D3V3就应该是等电位,是一个高电平。我们就可以通过KEY1这个IO口的高低电平来判断是否有按键按下。

二、按键消抖

2.1 原理

  按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。
  所以我们在使用按键时往往需要消抖,以确保按键被按下一次只检测到一次低电平
在这里插入图片描述

2.2 解决方法

延迟采样
在这里插入图片描述

2.2.1 设计思路

  通常我们认为按键的抖动时间存在于20ms之间,因此可以设计在第一次检测到有下降沿时开始计时,到计满20ms后,我们认为此时的按键信号保持稳定,则输出一个脉冲信号表示按键确实按下。
  首先需要一个模块来检测按键是否抖动,如果抖动,→ 给计时模块一个标志位开始计时,记满20ms,→ 再给输出消抖后按键信号模块一个标志位进行采样。

2.2.2 时序图分析

在这里插入图片描述

  • clock:我们的整个程序都是在时钟的控制下运行的,所有的always模块都对时钟的上升沿敏感,本人使用的开发板时钟频率是50MHz,也就是一秒震动50_000_000次,一个周期就是20ns
  • key_in:这是按键输入信号,低电平有效
  • key_r0:同步打拍后的信号,为了滤除掉小于一个周期的抖动,对key_in推迟一个周期进行同步
  • key_r1 :这个信号是把同步信号再延时一个周期,主要是为了保存key_r0上一个周期的值(打一拍),来判断是否出现下降沿
  • key_r2:与key_r1同理,再延时一个时钟周期
  • nedge:检测key_r0是否出现下降沿,若出现,则将标志位flag设为1,也是计时器开启的条件
  • add_cnt:计时器开启条件,用flag表示
  • end_cnt:计时器结束条件,记满20ms,也是采样模块的开启条件
  • key_down:计时器记满,则开始采样,key_down拉高一个周期(脉冲信号),代表按键按下
2.2.3 代码设计

key_debounce.v

/**************************************功能介绍***********************************
Date	: 2023年7月27日11:07:19
Author	: Ruminating.
Version	: 1.1
Description: 按键消抖模块(任意位宽实现)
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module key_debounce #(parameter WIDTH = 4,//WIDTH为按键位宽(以4位宽为例)
                                TIME_20MS = 20'd1000_000//20ms计数器
)( 
    input	wire    		        clk		,//时钟信号
    input	wire    		        rst_n	,//复位信号
    input	wire    [WIDTH-1:0]		key_in  ,//输入按键信号
    output  wire    [WIDTH-1:0]     key_out  //输出脉冲信号
);								 
//---------<参数定义>--------------------------------------------------------- 
    // parameter TIME_20MS = 1000_000;//20ms

//---------<内部信号定义>-----------------------------------------------------
    reg     [WIDTH-1:0]     key_down;//输出信号寄存器
    reg     [WIDTH-1:0]     key_r0  ;//同步打拍
    reg     [WIDTH-1:0]     key_r1  ;//打一拍
    reg     [WIDTH-1:0]     key_r2  ;//打两拍
    wire    [WIDTH-1:0]     n_edge  ;//下降沿
    reg                     flag    ;//计数器计数的标志信号(按键按下抖动标志)

    reg		[19:0]	        cnt_20ms	;//20ms计数器
    wire				    add_cnt_20ms;//开始计数条件
    wire				    end_cnt_20ms;//结束计数条件

//****************************************************************
//--同步打拍
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r0 <= {WIDTH{1'b1}};
            key_r1 <= {WIDTH{1'b1}};
            key_r2 <= {WIDTH{1'b1}};
        end 
        else begin 
            key_r0 <= key_in;
            key_r1 <= key_r0;
            key_r2 <= key_r1;
        end 
    end
    
//****************************************************************
//--n_edge 
//****************************************************************
    assign n_edge = ~key_r1 & key_r2;//下降沿检测
    // assign p_edge = key_r1 & ~key_r2;//上升沿检测

//****************************************************************
//--cnt_20ms计数器
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_20ms <= 20'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms)begin 
                cnt_20ms <= 20'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(检测到下降沿时开始计数,直到计满20ms后,停止计数)
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            flag <= 1'b0;
        end 
        else if(n_edge)begin 
            flag <= 1'b1;
        end 
        else if(end_cnt_20ms)begin
            flag <= 1'b0;
        end
    end

//****************************************************************
//--key_down(输出脉冲信号)
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_down <= 4'b0000;
        end 
        else if(end_cnt_20ms)begin 
            key_down <= ~key_r2;
        end 
        else begin 
            key_down <= 4'b0000;
        end 
    end

//****************************************************************
//--key_out
//****************************************************************
    assign key_out = key_down;


endmodule

状态机实现:
  将按键的按下分为以下过程
在这里插入图片描述
增加了对上升沿的检测,因此可以选择在按键稳定按下或者在按键释放后生成脉冲信号
fsm_key_debounce.v

/**************************************功能介绍***********************************
Date	: 2023年7月27日11:07:19
Author	: Ruminating.
Version	: 1.1
Description: 按键消抖模块状态机实现(任意位宽实现)
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module fsm_key_debounce #(parameter WIDTH = 4,//WIDTH为按键位宽
                                TIME_20MS = 20'd1000_000//20ms计数器
)( 
    input	wire    		        clk		,//时钟信号
    input	wire    		        rst_n	,//复位信号
    input	wire    [WIDTH-1:0]		key_in  ,//输入按键信号
    output  wire    [WIDTH-1:0]     key_out  //输出脉冲信号
);								 
//---------<参数定义>--------------------------------------------------------- 
    // parameter TIME_20MS = 1000_000;//20ms

//---------<内部信号定义>-----------------------------------------------------
//状态机参数定义
    localparam  IDLE        = 4'b0001,//按键空闲状态
                FILTER_DOWN = 4'b0010,//按键按下状态
                HOLD_DOWN   = 4'b0100,//按键稳定状态
                FILTER_UP   = 4'b1000;//按键释放状态

    reg 	[WIDTH-1:0]	    state_c     ;//现态
    reg	    [WIDTH-1:0]	    state_n     ;//次态
    reg     [WIDTH-1:0]     key_down;//输出信号寄存器
    reg     [WIDTH-1:0]     key_r0  ;//同步打拍
    reg     [WIDTH-1:0]     key_r1  ;//打一拍
    reg     [WIDTH-1:0]     key_r2  ;//打两拍
    wire    [WIDTH-1:0]     n_edge  ;//下降沿
    wire    [WIDTH-1:0]     p_edge   ;//上升沿
    
    reg		[19:0]	        cnt_20ms	;//20ms计数器
    wire				    add_cnt_20ms;//开始计时条件
    wire				    end_cnt_20ms;//结束计时条件

    wire            idle2filter_down        ;
    wire            filter_down2hold_down   ;
    wire            hold_down2filter_up     ;
    wire            filter_up2idle          ;//状态转换条件

//****************************************************************
//--同步打拍
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            key_r0 <= {WIDTH{1'b1}};
            key_r1 <= {WIDTH{1'b1}};
            key_r2 <= {WIDTH{1'b1}};
        end 
        else begin 
            key_r0 <= key_in;
            key_r1 <= key_r0;
            key_r2 <= key_r1;
        end 
    end
    
//****************************************************************
//--n_edge 和 p_edge
//****************************************************************
    assign n_edge = ~key_r1 & key_r2;//下降沿检测
    assign p_edge = key_r1 & ~key_r2;//上升沿检测
//****************************************************************
//--cnt_20ms
//****************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_20ms <= 20'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms)begin 
                cnt_20ms <= 20'd0;
            end
            else begin 
                cnt_20ms <= cnt_20ms + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_20ms = state_c == FILTER_DOWN || state_c == FILTER_UP;
    assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
    
//****************************************************************
//--状态机实现
//****************************************************************
    //第一段:时序逻辑描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end

    //第二段:组合逻辑描述状态转移规律和状态转移条件
    always @(*) begin
        case(state_c)
            IDLE        : begin
                if(idle2filter_down)begin
                    state_n = FILTER_DOWN;
                end
                else begin
                    state_n = state_c;
                end
            end
            FILTER_DOWN : begin
                if(filter_down2hold_down)begin
                    state_n = HOLD_DOWN;
                end
                else begin
                    state_n = state_c;
                end
            end
            HOLD_DOWN   : begin
                if(hold_down2filter_up)begin
                    state_n = FILTER_UP;
                end
                else begin
                    state_n = state_c;
                end
            end 
            FILTER_UP   : begin
                if(filter_up2idle)begin
                    state_n = IDLE;
                end
                else begin
                    state_n = state_c;
                end
            end 
            default:state_n = IDLE ;
        endcase
    end

    assign idle2filter_down      = state_c == IDLE        && n_edge;
    assign filter_down2hold_down = state_c == FILTER_DOWN && end_cnt_20ms;
    assign hold_down2filter_up   = state_c == HOLD_DOWN   && p_edge;
    assign filter_up2idle        = state_c == FILTER_UP   && end_cnt_20ms;

    //第三段:描述输出,时序逻辑或组合逻辑皆可
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_down <= 4'b0000;
        end
        else if(filter_down2hold_down)begin
            key_down <= ~key_r2;
        end
        else begin
            key_down <= 4'b0000;
        end
    end

    assign key_out = key_down;

    

endmodule
2.2.4 代码分析

1.下降沿或上升沿的检测

    assign n_edge = ~key_r1 & key_r2;//下降沿检测
    //assign p_edge = key_r1 & ~key_r2;//上升沿检测

在这里插入图片描述
2.脉冲信号生成

key_down <= ~key_r2;
  • 生成脉冲信号时的赋值没有直接取1而是取key_r2信号的反能确保此脉冲是在按键稳定后生成(也可取~key_r1,只相差一个时钟周期差距不大)

三、总结

  此按键消抖模块可以对任意位数的按键信号进行消抖,实现了按键消抖模块并使其参数化,在调用该模块时只需将参数WIDTH的值赋于自己所需要的位宽即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值