按键消抖(有/无状态机)

news2024/9/22 21:37:29

一,理论概念

  1. 按键抖动
    按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。
  2. 按键消抖的目的
    消除抖动对程序的影响
  3. 解决方案一
    延迟采样:一般的抖动在20ms之内,所以我们可以通过检测下降沿后,延迟20ms再进行采样
  4. 解决方案二
    信号平稳变化后延迟20ms后再进行采样,等到检测最后一个下降沿结束后再开始采样

二,项目代码

这里简单做了一个按键控制led用来简单检测按键是否消抖成功

  1. 按键消抖模块
    第一个模块是不适用状态机来实现的,只需要考虑按键按下时的抖动,不考虑按键弹起时的抖动
module key_debounce (
    input   wire            clk     ,
    input   wire            rst_n   ,
    input   wire    [3:0]   key_in  ,
    output  wire    [3:0]   key_out 
);

parameter MAX20ms = 20'd999_999;

wire            add_cnt;//倒计时开始使能
wire            end_cnt;//倒计时结束使能
reg    [19:0]   cnt_20ms;//20ms计数寄存器
reg    [3:0]    key_r0;//同步
reg    [3:0]    key_r1;//打拍
reg             start;//下降沿检测寄存器
reg    [3:0]    flag;
reg    [3:0]    key_out_r;//输出按键信号寄存器
wire            nedge;



//下降沿检测
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_r0 <= 4'b1111;
        key_r1 <= 4'b1111;
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
    end
end

assign nedge = (~key_r0[0]&key_r1[0])||(~key_r0[1]&key_r1[1])||(~key_r0[2]&key_r1[2])||(~key_r0[3]&key_r1[3]);

//20ms计时器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt_20ms <= 20'd0;
    end
    else if (nedge) begin
        cnt_20ms <= 20'd0;
    end
    else if (add_cnt) begin
        if (end_cnt) begin
            cnt_20ms <= 20'd0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'b1;
        end
    end
    else begin
        cnt_20ms <= 20'd0;
    end
end

assign add_cnt = start;
assign end_cnt = add_cnt && (cnt_20ms == MAX20ms);

//约束start
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        start <= 1'b0;
    end
    else if (nedge) begin
        start <= 1'b1;
    end
    else if (end_cnt) begin
        start <= 1'b0;
    end
    else begin
        start <= start ;
    end
end

//约束flag
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag <= 4'b1111;
    end
    else if (nedge) begin
        flag <= 4'b1111;
    end
    else if (end_cnt) begin
        flag <= key_r0;
    end
    else begin
        flag <= 4'b1111 ;
    end
end
// //脉冲信号
// always @(posedge clk or negedge rst_n) begin
//     if (!rst_n) begin
//         key_out_r <= 4'b1111;
//     end
//     else if (!flag[0]) begin
//         key_out_r <= 4'b1110;
//     end
//     else if (!flag[1]) begin
//         key_out_r <= 4'b1101;
//     end
//     else if (!flag[2]) begin
//         key_out_r <= 4'b1011;
//     end
//     else if (!flag[3]) begin
//         key_out_r <= 4'b0111;
//     end
//     else begin
//         key_out_r <= 4'b1111;
//     end
// end

//持续信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_out_r <= 4'b1111;
    end
    else if (!flag[0]) begin
        key_out_r <= 4'b1110;
    end
    else if (!flag[1]) begin
        key_out_r <= 4'b1101;
    end
    else if (!flag[2]) begin
        key_out_r <= 4'b1011;
    end
    else if (!flag[3]) begin
        key_out_r <= 4'b0111;
    end
    else begin
        key_out_r <= key_out_r;
    end
end

assign key_out = key_out_r;
endmodule

这里可以通过控制key_out_r的状态来达到是脉冲还是持续信号

第二个是使用状态机来实现消抖,这个方法需要完整的考虑整个按键按下和弹起的全过程

module fsm_key_debounce # (parameter KEY_W = 4,TIME_20MS = 1_000_000)(
    input 			            clk		,
    input 			            rst_n	,
    input 		[KEY_W - 1:0]	key_in	,

    output 		[KEY_W - 1:0]	key_out	 
);
//参数定义
localparam IDLE  = 4'b0001;//初始状态 
localparam DOWN  = 4'b0010;//按键按下抖动
localparam HOLD  = 4'b0100;//按键按下后稳定
localparam UP    = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态

//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up  ;
wire up2idle  ;

reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿

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

reg [KEY_W - 1:0] key_out_r;//输出寄存

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(idle2down)begin
                state_n = DOWN;
            end
            else begin
                state_n = state_c;
            end
        end
        DOWN:begin
            if(down2idle)begin
                state_n = IDLE;
            end
            else if(down2hold)begin
                state_n = HOLD;
            end
            else begin
                state_n = state_c;
            end
        end
        HOLD:begin
            if(hold2up)begin
                state_n = UP;
            end
            else begin
                state_n = state_c;
            end
        end
        UP:begin
            if(up2idle)begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        default:state_n = state_c;
    endcase
end

assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up   = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle   = (state_c == UP)   && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_20ms <= 0;
    end
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms)begin
            cnt_20ms <= 0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'b1;
        end
    end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零



//同步打拍
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_r0 <= {KEY_W{1'b1}};
        key_r1 <= {KEY_W{1'b1}};
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
    end
end

assign nedge = ~key_r0 &  key_r1;//检测下降沿
assign pedge =  key_r0 & ~key_r1;//检测上升沿

//按键赋值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_out_r <= {KEY_W{1'b1}};
    end
    else if(state_c == HOLD && hold2up)begin
        key_out_r <= key_r1;
    end
    else begin
        key_out_r <= {KEY_W{1'b1}};
    end
end
assign key_out = key_out_r;

endmodule

  1. led灯
    这里只是写了一个小的测试led不过多陈述
module fsm_key_debounce # (parameter KEY_W = 4,TIME_20MS = 1_000_000)(
    input 			            clk		,
    input 			            rst_n	,
    input 		[KEY_W - 1:0]	key_in	,

    output 		[KEY_W - 1:0]	key_out	 
);
//参数定义
localparam IDLE  = 4'b0001;//初始状态 
localparam DOWN  = 4'b0010;//按键按下抖动
localparam HOLD  = 4'b0100;//按键按下后稳定
localparam UP    = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态

//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up  ;
wire up2idle  ;

reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿

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

reg [KEY_W - 1:0] key_out_r;//输出寄存

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(idle2down)begin
                state_n = DOWN;
            end
            else begin
                state_n = state_c;
            end
        end
        DOWN:begin
            if(down2idle)begin
                state_n = IDLE;
            end
            else if(down2hold)begin
                state_n = HOLD;
            end
            else begin
                state_n = state_c;
            end
        end
        HOLD:begin
            if(hold2up)begin
                state_n = UP;
            end
            else begin
                state_n = state_c;
            end
        end
        UP:begin
            if(up2idle)begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        default:state_n = state_c;
    endcase
end

assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up   = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle   = (state_c == UP)   && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_20ms <= 0;
    end
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms)begin
            cnt_20ms <= 0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'b1;
        end
    end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零



//同步打拍
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_r0 <= {KEY_W{1'b1}};
        key_r1 <= {KEY_W{1'b1}};
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
    end
end

assign nedge = ~key_r0 &  key_r1;//检测下降沿
assign pedge =  key_r0 & ~key_r1;//检测上升沿

//按键赋值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_out_r <= {KEY_W{1'b1}};
    end
    else if(state_c == HOLD && hold2up)begin
        key_out_r <= key_r1;
    end
    else begin
        key_out_r <= {KEY_W{1'b1}};
    end
end
assign key_out = key_out_r;

endmodule

  1. 顶层
module led_test_top (
    input                   clk     ,//全局时钟
    input                   rst_n   ,//复位
    input   wire     [3:0]   key     ,//2个按键

    output  wire     [3:0]   led         //四个led
);
wire [3:0] key_out;

key_debounce key_debounce_inst( 
        .clk    (clk),
        .rst_n	(rst_n),
        .key_in	(key),
        .key_out(key_out)	
);

led_test led_test_inst(
        .clk     (clk),//全局时钟
        .rst_n   (rst_n),//复位
        .key     (key_out),//2个按键
        .led     (led)    //四个led
);

endmodule
  1. 测试文件
`timescale 1ns/1ns
module led_tb ();
    
reg             clk     ;
reg             rst_n   ;
reg    [3:0]    key     ;
wire    [3:0]   led     ;


parameter   CYCLE = 20 ;
defparam    top_inst.key_debounce_inst.MAX_20ms = 5;




always #(CYCLE/2)   clk = ~clk ;

initial begin
    clk = 1'b0;
    rst_n = 1'b0;
    key = 4'b1111;
    #10
    rst_n = 1'b1;
    #10
    key = 4'b1110;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    key = 4'b1110;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    key = 4'b1110;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    key = 4'b1110;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    key = 4'b1101;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    key = 4'b0111;
    wait(top_inst.key_debounce_inst.end_cnt)
    #(CYCLE*10);
    key = 4'b1111;
    #(CYCLE*10)
    $stop;

end

led_test_top top_inst(
        .clk    (clk),
        .rst_n  (rst_n),
        .key    (key),
        .led    (led)
);
endmodule

三,测试波形

在这里插入图片描述

四,视频演示

目前无法拍摄视频后续补上

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/814921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

<MyBatis>MyBatis把空字符串转换成了0的问题处理方案

先看问题: Postman入参: MyBatis采用map循环插入: // Mapper接口层void addPar(Param(value "question") Map<String, Object> paramMap);<!-- 新增&#xff1a;参数 --><insert id"addPar" parameterType"map">INSERT IGNO…

高忆管理:集合竞价可以卖股票吗?

集合竞价是证券买卖所开盘前的一种买卖方式&#xff0c;其意图是为了在买卖所开盘之前构成有用的商场价格。因为商场处于初始状况&#xff0c;买卖量较小&#xff0c;因而集合竞价价格或许与买卖日中的实践买卖价格有所不同。那么&#xff0c;集合竞价是否能够卖股票呢&#xf…

Python3,1行代码,批量把图片转换成PDF文档,女神终于同意跟我吃夜宵了。

批量图片转换成PDF文档 1、引言2、代码示例2.1 安装2.2 单张转换2.3 批量转换 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 求助&#xff0c;求助。 小鱼&#xff1a;有啥事&#xff0c;这大惊小怪的。 小屌丝&#xff1a;我女神跟我说&#xff0c; 如果我把她的照片…

Spark 5:Spark Core 内核调度

DAG Spark的核心是根据RDD来实现的&#xff0c;Spark Scheduler则为Spark核心实现的重要一环&#xff0c;其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据&#xff0c;根据RDD的依赖关系构建DAG&#xff0c;基于DAG划分Stage&#xff0c;将每个…

跨境电商ERP源码选型指南,如何找到最适合你的?

在跨境电商行业&#xff0c;一个高效的ERP系统是保证业务顺利进行和管理的关键。选择适合自己的跨境电商ERP源码至关重要。本指南将帮助你了解如何找到最适合你的跨境电商ERP源码。 跨境电商ERP源码的重要性 跨境电商ERP源码在现代电商营运中起着至关重要的作用。它提供了一套…

音频开发-小程序和H5

微信录音 1、引入sdk 2、录音操作 浏览器录音 参考文献&#xff1a;前端H5实现调用麦克风&#xff0c;录音功能_h5 录音_Darker丨峰神的博客-CSDN博客 function record() { window.navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 44100, // 采样率 channelCount…

游戏APP开发:创新设计的秘诀

在游戏 APP开发中&#xff0c;创新设计是游戏开发公司的一大追求&#xff0c;为了可以为用户带来更好的游戏体验&#xff0c;这就需要对游戏 APP开发进行创新设计。那么&#xff0c;游戏 APP开发中的创新设计是什么呢&#xff1f;接下来&#xff0c;我们就一起来看看吧。 想要…

一起学算法(递推篇)

前言&#xff1a;递推最通俗的理解就是数列&#xff0c;递推和数列的关系就好比算法和数据结构的关系&#xff0c;数列有点像数据结构中的顺序表&#xff0c;而递推就是一个循环或者迭代的过程的枚举过程 1.斐波那契数列 斐波那契数形成的序列称为斐波那契数列&#xff0c;该…

【Java|golang】143. 重排链表---快慢指针

给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 …

python中有哪些异常,怎么处理

目录 python报的错误怎么处理 1. 使用 try-except 语句块 2. 使用 finally 语句块 3. 主动引发异常 python中有哪些异常 不知道是什么异常时怎么操作 总结 python报的错误怎么处理 在Python中&#xff0c;当程序执行时遇到错误&#xff0c;Python会抛出异常。要处理Pyt…

孩子近视有必要用全光谱灯吗?全光谱led灯推荐

当然&#xff0c;有必要!全光谱LED灯的光源分布更加均匀&#xff0c;使空间更加美观舒适&#xff0c;而普通灯的光源分布可能会在一定范围内分布不均匀。全光谱它的使用寿命长达20-30万小时&#xff0c;而普通灯的使用寿命仅为1000-2000小时&#xff0c;因此在长期使用上&#…

list模拟

之前模拟了string,vector&#xff0c;再到现在的list&#xff0c;list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表&#xff0c;该结构虽然看起来比较复杂&#xff0c;但是却非常有利于我们做删除节点的操作&#xff0c;结构图如下。 由于其节点结构特…

收发存和进销存有什么区别?

一、什么是收发存和进销存 1、收发存 收发存是供应链管理中的关键概念&#xff0c;用于描述企业在供应链中的物流和库存管理过程。 收发存代表了企业在采购、生产和销售过程中的物流活动和库存水平。 收&#xff08;Receiving&#xff09; 企业接收供应商送达的物料或产品…

归并排序算法

归并排序 算法说明与实现代码&#xff1a; 归并排序&#xff08;Merge Sort&#xff09;: 归并排序是一种分治算法&#xff0c;它将列表分成两个子列表&#xff0c;分别进行排序&#xff0c;然后将排序好的子列表合并成一个有序列表。 package mainimport "fmt"fu…

手机商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框…

微信小程序代码优化3个小技巧

抽取重复样式 样式复用 我们会发现很多时候在开发的过程中会存在多个页面中都用到了同样的样式&#xff0c;那么其实之前有提到过&#xff0c;公用样式可以放在app.wxss里面这样就可以直接复用。 如&#xff1a;flex布局的纵向排列&#xff0c;定义在app.wxss里面 .flex-co…

win10安装cygwin

参考 Cygwin简介及其下载安装卸载_cygwin是什么软件_徐晓康的博客的博客-CSDN博客https://blog.csdn.net/weixin_42837669/article/details/114381405这个文章写的非常好&#xff0c;不过现在如果想安装多个包的话&#xff0c;采用gui的方式可以不行了&#xff0c;我采用的方式…

JavaScript 简单实现观察者模式和发布-订阅模式

JavaScript 简单实现观察者模式和发布-订阅模式 1. 观察者模式1.1 什么是观察者模式1.2 代码实现 2. 发布-订阅模式2.1 什么是发布-订阅模式2.2 代码实现2.2.1 基础版2.2.2 取消订阅2.2.3 订阅一次 1. 观察者模式 1.1 什么是观察者模式 概念&#xff1a;观察者模式定义对象间…

【Windows11】家庭版开启组策略指南

目录 背景新建一个cmd文件运行运行结果 背景 Win11找不到gpedit.msc怎么办&#xff1f;有用户通过命令窗口想要去打开本地组策略的时候&#xff0c;系统突然弹出了一个错误提示&#xff0c;显示系统缺少了gpedit.msc导致无法开启本地组策略编辑器了。那么这个情况要怎么去进行…

【Web开发指南】如何用MyEclipse进行JavaScript开发?

由于MyEclipse中有高级语法高亮显示、智能内容辅助和准确验证等特性&#xff0c;进行JavaScript编码不再是一项繁琐的任务。 MyEclipse v2023.1.2离线版下载 JavaScript项目 在MyEclipse 2021及以后的版本中&#xff0c;大多数JavaScript支持都是开箱即用的JavaScript源代码…