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

一,理论概念

  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) beginif (!rst_n) beginkey_r0 <= 4'b1111;key_r1 <= 4'b1111;endelse beginkey_r0 <= key_in;key_r1 <= key_r0;end
endassign 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) beginif (!rst_n) begincnt_20ms <= 20'd0;endelse if (nedge) begincnt_20ms <= 20'd0;endelse if (add_cnt) beginif (end_cnt) begincnt_20ms <= 20'd0;endelse begincnt_20ms <= cnt_20ms + 1'b1;endendelse begincnt_20ms <= 20'd0;end
endassign add_cnt = start;
assign end_cnt = add_cnt && (cnt_20ms == MAX20ms);//约束start
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginstart <= 1'b0;endelse if (nedge) beginstart <= 1'b1;endelse if (end_cnt) beginstart <= 1'b0;endelse beginstart <= start ;end
end//约束flag
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginflag <= 4'b1111;endelse if (nedge) beginflag <= 4'b1111;endelse if (end_cnt) beginflag <= key_r0;endelse beginflag <= 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) beginif (!rst_n) beginkey_out_r <= 4'b1111;endelse if (!flag[0]) beginkey_out_r <= 4'b1110;endelse if (!flag[1]) beginkey_out_r <= 4'b1101;endelse if (!flag[2]) beginkey_out_r <= 4'b1011;endelse if (!flag[3]) beginkey_out_r <= 4'b0111;endelse beginkey_out_r <= key_out_r;end
endassign 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)beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;end
endalways@(*)begincase(state_c)IDLE:beginif(idle2down)beginstate_n = DOWN;endelse beginstate_n = state_c;endendDOWN:beginif(down2idle)beginstate_n = IDLE;endelse if(down2hold)beginstate_n = HOLD;endelse beginstate_n = state_c;endendHOLD:beginif(hold2up)beginstate_n = UP;endelse beginstate_n = state_c;endendUP:beginif(up2idle)beginstate_n = IDLE;endelse beginstate_n = state_c;endenddefault:state_n = state_c;endcase
endassign 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)beginif(!rst_n)begincnt_20ms <= 0;endelse if(add_cnt_20ms)beginif(end_cnt_20ms)begincnt_20ms <= 0;endelse begincnt_20ms <= cnt_20ms + 1'b1;endend
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)beginif(!rst_n)beginkey_r0 <= {KEY_W{1'b1}};key_r1 <= {KEY_W{1'b1}};endelse beginkey_r0 <= key_in;key_r1 <= key_r0;end
endassign nedge = ~key_r0 &  key_r1;//检测下降沿
assign pedge =  key_r0 & ~key_r1;//检测上升沿//按键赋值
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginkey_out_r <= {KEY_W{1'b1}};endelse if(state_c == HOLD && hold2up)beginkey_out_r <= key_r1;endelse beginkey_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)beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;end
endalways@(*)begincase(state_c)IDLE:beginif(idle2down)beginstate_n = DOWN;endelse beginstate_n = state_c;endendDOWN:beginif(down2idle)beginstate_n = IDLE;endelse if(down2hold)beginstate_n = HOLD;endelse beginstate_n = state_c;endendHOLD:beginif(hold2up)beginstate_n = UP;endelse beginstate_n = state_c;endendUP:beginif(up2idle)beginstate_n = IDLE;endelse beginstate_n = state_c;endenddefault:state_n = state_c;endcase
endassign 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)beginif(!rst_n)begincnt_20ms <= 0;endelse if(add_cnt_20ms)beginif(end_cnt_20ms)begincnt_20ms <= 0;endelse begincnt_20ms <= cnt_20ms + 1'b1;endend
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)beginif(!rst_n)beginkey_r0 <= {KEY_W{1'b1}};key_r1 <= {KEY_W{1'b1}};endelse beginkey_r0 <= key_in;key_r1 <= key_r0;end
endassign nedge = ~key_r0 &  key_r1;//检测下降沿
assign pedge =  key_r0 & ~key_r1;//检测上升沿//按键赋值
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginkey_out_r <= {KEY_W{1'b1}};endelse if(state_c == HOLD && hold2up)beginkey_out_r <= key_r1;endelse beginkey_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 beginclk = 1'b0;rst_n = 1'b0;key = 4'b1111;#10rst_n = 1'b1;#10key = 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;endled_test_top top_inst(.clk    (clk),.rst_n  (rst_n),.key    (key),.led    (led)
);
endmodule

三,测试波形

在这里插入图片描述

四,视频演示

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

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

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

相关文章

数据结构: 线性表(顺序表实现)

文章目录 1. 线性表的定义2. 线性表的顺序表示:顺序表2.1 概念及结构2.2 接口实现2.2.1 顺序表初始化 (SeqListInit)2.2.2 顺序表尾插 (SeqListPushBack)2.2.3 顺序表打印 (SeqListPrint)2.2.6 顺序表销毁 (SeqListDestroy)2.2.5 顺序表尾删 (SeqListPopBack)2.2.6 顺序表头插 …

安全学习DAY08_算法加密

算法加密 漏洞分析、漏洞勘测、漏洞探针、挖漏洞时要用到的技术知识 存储密码加密-应用对象传输加密编码-发送回显数据传输格式-统一格式代码特性混淆-开发语言 传输数据 – 加密型&编码型 安全测试时&#xff0c;通常会进行数据的修改增加提交测试 数据在传输的时候进行…

【Linux】关于Bad magic number in super-block 当尝试打开/dev/sda1 时找不到有效的文件系统超级块

每个区段与 superblock 的信息都可以使用 dumpe2fs 这个指令来查询的&#xff01; 不过可惜的是&#xff0c;我们的 CentOS 7 现在是以 xfs 为默认文件系统&#xff0c; 所以目前你的系统应该无法使用 dumpe2fs 去查询任何文件系统的。 因为目前两个版本系统的根目录使用的文…

IT职场笔记

MySQL笔记之一致性视图与MVCC实现 一致性读视图是InnoDB在实现MVCC用到的虚拟结构&#xff0c;用于读提交&#xff08;RC&#xff09;和可重复度&#xff08;RR&#xff09;隔离级别的实现。 一致性视图没有物理结构&#xff0c;主要是在事务执行期间用来定义该事物可以看到什…

护网行动:ADSelfService Plus引领企业网络安全新纪元

随着信息技术的飞速发展&#xff0c;企业网络的重要性变得愈发显著。然而&#xff0c;随之而来的网络安全威胁也日益增多&#xff0c;网络黑客和恶意软件不断涌现&#xff0c;给企业的数据和机密信息带来巨大风险。在这个信息安全威胁层出不穷的时代&#xff0c;企业急需一款强…

Ubuntu的安装与部分配置

该教程使用的虚拟机是virtuabox&#xff0c;镜像源的版本是ubuntu20.04.5桌面版 可通过下面的链接在Ubuntu官网下载&#xff1a;Alternative downloads | Ubuntu 也可直接通过下面的链接进入百度网盘下载【有Ubuntu20.04.5与hadoop3.3.2以及jdk1.8.0_162&#xff0c;该篇需要使…

idea 关于高亮显示与选中字符串相同的内容

dea 关于高亮显示与选中字符串相同的内容&#xff0c;本文作为个人备忘的同时也希望可以作为大家的参考。 依次修改File-settings-Editor-Color Scheme-General菜单下的Code-Identifier under caret和Identifier under caret(write)的Backgroud色值&#xff0c;可以参考下图。…

算法leetcode|64. 最小路径和(rust重拳出击)

文章目录 64. 最小路径和&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 64. 最小路径和&#xff1a; 给定一个包含非负整数的 m x n 网…

【linux--->传输层协议】

文章目录 [TOC](文章目录) 一、端口号1.端口号划分范围2.常用知名端口号 二、网络命令1.netstat 命令2.pidof 命令 三、UDP协议1.格式2.协议的分离和合并3.特点4.缓冲区 四、TCP协议1.格式2.4位的数据偏移3.确认应答机制4.序号与确认序号5.16位窗口6.标志位7.超时重传8.三次握手…

大脑睡眠是否因智力的不同而不同?

摘要 目的&#xff1a;比较不同智力水平儿童的睡眠脑电图。 方法&#xff1a;根据韦氏儿童智力量表(WISC)评分进行分组(17名智商正常[NIQ组]&#xff0c;24名高智商[HIQ组])。采用方差分析和线性回归模型(根据年龄和性别进行校正)比较组间频谱功率及其与WISC评分的关系。 结…

SpringBoot运维

能够掌握SpringBoot程序多环境开发 能够基于Linux系统发布SpringBoot工程 能够解决线上灵活配置SpringBoot工程的需求 Windows打包运行 你的电脑不可能一直开着机联网作为服务器&#xff1a; 我们将我们项目打包放到外部的服务器上&#xff0c;这样其他用户才能正常访问&#x…

【使用维纳滤波进行信号分离】基于维纳-霍普夫方程的信号分离或去噪维纳滤波器估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

BUU [网鼎杯 2020 朱雀组]phpweb

BUU [网鼎杯 2020 朱雀组]phpweb 众生皆懒狗。打开题目&#xff0c;只有一个报错&#xff0c;不知何从下手。 翻译一下报错&#xff0c;data()函数:,还是没有头绪&#xff0c;中国有句古话说的好“遇事不决抓个包” 抓个包果然有东西&#xff0c;仔细一看这不就分别是函数和参…

MySQL 数据库 【增删查改(二)】

目录 一、表的设计 1、一对一 2、一对多 3、多对多 二、新增 三、查询 1、聚合查询 &#xff08;1&#xff09;聚合函数&#xff1a; &#xff08;2&#xff09; group by 子句 &#xff08;3&#xff09;having 2、联合查询 (1)内连接 (2)外连接 (3)自链接 (4)…

142. 环形链表 II

142. 环形链表 II 中等 2.2K 相关企业 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定…

服务器(容器)开发指南——SSH打洞开发

文章目录 SSH容器服务打包测试服务文件镜像打包 SSH打洞开发部署带SSH的容器SSH连接服务器&#xff08;容器内部&#xff09;SSH访问容器内的缺陷 IDE远程SSH开发VSCode远程SSH开发Jetbrains系列产品SSH远程开发 在进行定制化的服务开发时&#xff0c;我们有时候只能在固定的服…

没有使用IaC的DevOps系统都是耍流氓 |IDCF

作者&#xff1a;徐磊 文章首发地址&#xff1a;https://smartide.cn/zh/blog/2022-1010-iac/ 作为现代软件工程的基础实践&#xff0c;基础设施即代码&#xff08;Infrastructure as Code, IaC&#xff09;是云原生、容器、微服务以及DevOps背后的底层逻辑。应该说&#xff…

layui框架学习(34:数据表格_基本用法)

Layui中的数据表格模块table支持动态显示并操作表格数据&#xff0c;之前学习的页面元素中的表格主要是通过使用样式及属性对表格样式进行设置&#xff0c;而table模块支持动态显示、分页显示、排序显示、搜索等形式各样的动态操作&#xff0c;参考文献3中也给出了数据表格的各…

Git初始化

查看git版本 git --version 设置Git的配置变量 方法&#xff1a; 修改全局文件&#xff08;用户主目录下.gitconfig&#xff09;修改系统文件&#xff08;如/etc/gitconfig&#xff09; 用户姓名和邮件地址 修改用户名和邮件地址 git config --global user.name "用…

LLM系列 | 18 : 如何用LangChain进行网页问答

简介 一夕轻雷落万丝&#xff0c;霁光浮瓦碧参差。 紧接之前LangChain专题文章&#xff1a; 15:如何用LangChain做长文档问答&#xff1f;16:如何基于LangChain打造联网版ChatGPT&#xff1f;17:ChatGPT应用框架LangChain速成大法 今天这篇小作文是LangChain实践专题的第4…