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

一,理论概念

  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

三,测试波形

在这里插入图片描述

四,视频演示

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

哥大Salesforce重磅发布!最丰富的统一对话数据集,几乎支持所有对话任务

夕小瑶科技说 原创 作者 | 小戏、Python 尽管以 ChatGPT 为代表的对话式人工智能概念炒的火热&#xff0c;但是事实上作为当下智能发动机的大模型&#xff0c;其真正的动力源泉——数据集——仍然面临诸多困难。 所谓 Garbage In, Garbage Out&#xff0c;这条数据科学的朴素…

Linux复习——基础知识

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​ 1. 有关早期linux系统中 sysvin的init的7个级别描述正确的是( )[选择1项] A. init 1 关机状态 B. init 2 字符界面多用户模式 …

要单片机和RTOS有必要学习嵌入式linux吗?

学习嵌入式 Linux 是否有必要&#xff0c;取决于你的项目需求和职业发展目标。以下是一些考虑因素&#xff1a; 项目需求&#xff1a;如果你的项目需要处理复杂的网络、文件系统、多任务管理等功能&#xff0c;嵌入式 Linux 可能是更适合的选择。Linux 提供了丰富的开源软件包和…

排序算法汇总

每日一句&#xff1a;你的日积月累终会成为别人的望尘莫及 目录 常数时间的操作 选择排列 冒泡排列 【异或运算】 面试题&#xff1a; 1&#xff09;在一个整形数组中&#xff0c;已知只有一种数出现了奇数次&#xff0c;其他的所有数都出现了偶数次&#xff0c;怎么找到…

为何押注AI大模型的微软云,业绩增速反而不如谷歌云?

科技云报道原创。 上周微软、谷歌、Meta等国外科技公司相继发布最新财报。作为与人工智能、云计算和数字广告等领域相关的巨头&#xff0c;它们的一举一动都将对市场产生影响&#xff0c;同时也吸引着众多从业者的关注。 在国外三大云巨头中&#xff0c;谷歌云的市场份额长期…

渗透测试:Linux提权精讲(二)之sudo方法第二期

目录 写在开头 sudo expect sudo fail2ban sudo find sudo flock sudo ftp sudo gcc sudo gdb sudo git sudo gzip/gunzip sudo iftop sudo hping3 sudo java 总结与思考 写在开头 本文在上一篇博客的基础上继续讲解渗透测试的sudo提权方法。相关内容的介绍与背…

docker 部署 mysql8.0 无法访问

文章目录 &#x1f5fd;先来说我的是什么情况&#x1fa81;问题描述&#x1fa81;解决方法&#xff1a;✔️1 重启iptables✔️2 重启docker &#x1fa81;其他有可能连不上的原因✔️1 客户端不支持caching_sha2_password的加密方式✔️2 my.conf 配置只有本机可以访问 &#…

CTF:信息泄露.(CTFHub靶场环境)

CTF&#xff1a;信息泄露.&#xff08;CTFHub靶场环境&#xff09; “ 信息泄露 ” 是指网站无意间向用户泄露敏感信息&#xff0c;泄露了有关于其他用户的数据&#xff0c;例如&#xff1a;另一个用户名的财务信息&#xff0c;敏感的商业 或 商业数据 &#xff0c;还有一些有…

读取application-dev.properties的中文乱码【bug】

读取application-dev.properties的中文编码【bug】 2023-7-30 22:37:46 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://blog.csdn.net/qq_51625007 禁止其他平台发布时删除以上此话 bug 读取application-dev.propert…

2023年的深度学习入门指南(20) - LLaMA 2模型解析

2023年的深度学习入门指南(20) - LLaMA 2模型解析 上一节我们把LLaMA 2的生成过程以及封装的过程的代码简单介绍了下。还差LLaMA 2的模型部分没有介绍。这一节我们就来介绍下LLaMA 2的模型部分。 这一部分需要一些深度神经网络的基础知识&#xff0c;不懂的话不用着急&#xf…

建木使用进阶-创建密钥管理

阿丹&#xff1a; 第一次我们进入建木&#xff0c;第一件事情就是配置我们相关的密钥。 解读&#xff1a; 在建木中我们可以进行创建密钥来对我们服务器等密码进行方便的管理。 注意&#xff1a; 登录的时候账号为&#xff1a;admin 密码为&#xff1a;123456 这是初始…

Windows环境下git客户端中的git-bash和MinGW64

我们在 Windows10 操作系统下&#xff0c;安装了 git 客户端之后&#xff0c;可以通过 git-bash.exe 打开一个 shell&#xff1a; 执行一些 linux 系统里的命令&#xff1a; 注意到上图紫色的 MINGW64. Mingw-w64 是原始 mingw.org 项目的改进版&#xff0c;旨在支持 Window…

【playbook】Ansible的脚本----playbook剧本

Ansible的脚本----playbook剧本 1.playbook剧本组成2.playbook剧本实战演练2.1 实战演练一&#xff1a;给被管理主机安装Apache服务2.2 实战演练二&#xff1a;使用sudo命令将远程主机的普通用户提权为root用户2.3 实战演练三&#xff1a;when条件判断指定的IP地址2.4 实战演练…

SpringBoot中ErrorPage(错误页面)的使用--【ErrorPage组件】

SpringBoot系列文章目录 SpringBoot知识范围-学习步骤–【思维导图知识范围】 文章目录 SpringBoot系列文章目录本系列校训 SpringBoot技术很多很多环境及工具&#xff1a;必要的知识深层一些的知识 上效果图在Spring Boot里使用ErrorPage还要注意的是 配套资源作业&#xff…

使用Windbg分析从系统应用程序日志中找到的系统自动生成的dump文件去排查问题

目录 1、尝试将Windbg附加到目标进程上进行动态调试&#xff0c;但Windbg并没有捕获到 2、在系统应用程序日志中找到了系统在程序发生异常时自动生成的dump文件 2.1、查看应用程序日志的入口 2.2、在应用程序日志中找到系统自动生成的dump文件 3、使用Windbg静态分析dump文…

Mysql的锁

加锁的目的 对数据加锁是为了解决事务的隔离性问题&#xff0c;让事务之前相互不影响&#xff0c;每个事务进行操作的时候都必须先加上一把锁&#xff0c;防止其他事务同时操作数据。 事务的属性 &#xff08;ACID&#xff09; 原子性 一致性 隔离性 持久性 事务的隔离级别 锁…

大数据课程D4——hadoop的YARN

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解YARN的概念和结构&#xff1b; ⚪ 掌握YARN的资源调度流程&#xff1b; ⚪ 了解Hadoop支持的资源调度器&#xff1a;FIFO、Capacity、Fair&#xff1b; ⚪ 掌握YA…

jenkins自定义邮件发送人姓名

jenkins发送邮件的时候发送人姓名默认的&#xff0c;如果要自定义发件人姓名&#xff0c;只需要修改如下信息即可&#xff1a; 系统管理-system-Jenkins Location下的系统管理员邮件地址 格式为&#xff1a;自定义姓名<邮件地址>

三分钟白话RocketMQ系列—— 核心概念

目录 关键字摘要 Q1&#xff1a;RocketMQ是什么&#xff1f; Q2: 作为消息中间件&#xff0c;RocketMQ和kafka有什么区别&#xff1f; Q3: RocketMQ的基本架构是怎样的&#xff1f; Q4&#xff1a;RocketMQ有哪些核心概念&#xff1f; 总结 RocketMQ是一个开源的分布式消…

测试|测试分类

测试|测试分类 文章目录 测试|测试分类1.按照测试对象分类&#xff08;部分掌握&#xff09;2.是否查看代码&#xff1a;黑盒、白盒灰盒测试3.按开发阶段分&#xff1a;单元、集成、系统及验收测试4.按实施组织分&#xff1a;α、β、第三方测试5.按是否运行代码&#xff1a;静…