【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)

news2025/1/16 0:05:46

🎉欢迎来到FPGA专栏~按键消抖模块设计与验证


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

🎉 目录-按键消抖模块设计与验证

  • 一、效果演示
  • 二、模块设计
  • 三、仿真测试
    • 3.1 常规编写
    • 3.2 task编写
  • 四、仿真模型

遇见未来

一、效果演示

🥝模块设计:
模块设计


🥝按键消抖模块的完整代码,可直接使用:

//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
/
module KeyFilter(
	input Clk,
	input Rst_n,
	input key_in,
	output reg key_flag,
	output reg key_state
);

	//按键的四个状态
	localparam
		IDLE 		= 4'b0001,
		FILTER1 	= 4'b0010,
		DOWN 		= 4'b0100,
		FILTER2 	= 4'b1000;

	//状态寄存器
	reg [3:0] curr_st;
	
	//边沿检测输出上升沿或下降沿
	wire pedge;
	wire nedge;
	
	//计数寄存器
	reg [19:0]cnt;
	
	//使能计数寄存器
	reg en_cnt;
	
	//计数满标志信号
	reg cnt_full;//计数满寄存器
	
//------<边沿检测电路的实现>------
	//边沿检测电路寄存器
	reg key_tmp0;
	reg key_tmp1;
	
	//边沿检测
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			key_tmp0 <= 1'b0;
			key_tmp1 <= 1'b0;
		end
		else begin
			key_tmp0 <= key_in;
			key_tmp1 <= key_tmp0;
		end	
	end
		
	assign nedge = (!key_tmp0) & (key_tmp1);
	assign pedge = (key_tmp0)  & (!key_tmp1);

//------<状态机主程序>------	
	//状态机主程序
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			curr_st <= IDLE;
			en_cnt <= 1'b0;
			key_flag <= 1'b0;
			key_state <= 1'b1;
		end
		else begin
			case(curr_st)
				IDLE:begin
					key_flag <= 1'b0;
					if(nedge)begin
						curr_st <= FILTER1;
						en_cnt <= 1'b1;
					end
					else
						curr_st <= IDLE;
				end
				
				FILTER1:begin
					if(cnt_full)begin
						key_flag <= 1'b1;
						key_state <= 1'b0;
						curr_st <= DOWN;
						en_cnt <= 1'b0;
					end	
					else if(pedge)begin
						curr_st <= IDLE;
						en_cnt <= 1'b0;
					end
					else
						curr_st <= FILTER1;
				end
				
				DOWN:begin
					key_flag <= 1'b0;
					if(pedge)begin
						curr_st <= FILTER2;
						en_cnt <= 1'b1;
					end
					else
						curr_st <= DOWN;
				end
				
				FILTER2:begin
					if(cnt_full)begin
						key_flag <= 1'b1;
						key_state <= 1'b1;
						curr_st <= IDLE;
						en_cnt <= 1'b0;
					end	
					else if(nedge)begin
						curr_st <= DOWN;
						en_cnt <= 1'b0;
					end
					else
						curr_st <= FILTER2;
				end
				
				default:begin
					curr_st <= IDLE;
					en_cnt <= 1'b0;
					key_flag <= 1'b0;
					key_state <= 1'b1;
				end
			endcase
		end
	end
	
//------<20ms计数器>------		
	//20ms计数器
	//Clk 50_000_000Hz
	//一个时钟周期为20ns
	//需要计数20_000_000 / 20 = 1_000_000次
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt <= 20'd0;
		else if(en_cnt)
			cnt <= cnt + 1'b1;
		else
			cnt <= 20'd0;
	end
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			cnt_full <= 1'b0;
		else if(cnt == 999_999)
			cnt_full <= 1'b1;
		else
			cnt_full <= 1'b0;
	end
	
endmodule


🥝RTL视图:
RTL


🥝状态转移:
状态转移


🥝仿真结果:
仿真结果


二、模块设计

🥝模块设计:

信号作用
clk时钟信号输入
rst_n复位信号输入
key_in按键信号输入
key_flag消抖结束之后的标志位
key_state消抖结束之后按键的状态

模块设计


🥝上升沿检测电路:
上升


🥝下降沿检测电路:

下降


🥝边沿检测电路的实现:
检测到下降沿,nedge输出高电平;检测到上升沿,pedge输出高电平。

//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;

//边沿检测
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)begin
		key_tmp0 <= 1'b0;
		key_tmp1 <= 1'b0;
	end
	else begin
		key_tmp0 <= key_in;
		key_tmp1 <= key_tmp0;
	end	
end
	
assign nedge = (!key_tmp0) & (key_tmp1);//检测到下降沿,nedge输出高电平
assign pedge = (key_tmp0)  & (!key_tmp1);//检测到上升沿,pedge输出高电平

🥝一段式状态机设计:
按键的四种状态:

//按键的四个状态
localparam
	IDLE 		= 4'b0001,
	FILTER1 	= 4'b0010,
	DOWN 		= 4'b0100,
	FILTER2 	= 4'b1000;

计数器:

//------<20ms计数器>------		
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次

//计数寄存器
reg [19:0]cnt;

//使能计数寄存器
reg en_cnt;

//计数满标志信号
reg cnt_full;//计数满寄存器
	
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		cnt <= 20'd0;
	else if(en_cnt)
		cnt <= cnt + 1'b1;
	else
		cnt <= 20'd0;
end

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		cnt_full <= 1'b0;
	else if(cnt == 999_999)
		cnt_full <= 1'b1;
	else
		cnt_full <= 1'b0;
end

状态机主程序:

//------<状态机主程序>------	
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)begin
		curr_st <= IDLE;
		en_cnt <= 1'b0;
		key_flag <= 1'b0;
		key_state <= 1'b1;
	end
	else begin
		case(curr_st)
			IDLE:begin
				key_flag <= 1'b0;
				if(nedge)begin
					curr_st <= FILTER1;
					en_cnt <= 1'b1;
				end
				else
					curr_st <= IDLE;
			end
			
			FILTER1:begin
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b0;
					curr_st <= DOWN;
					en_cnt <= 1'b0;
				end	
				else if(pedge)begin
					curr_st <= IDLE;
					en_cnt <= 1'b0;
				end
				else
					curr_st <= FILTER1;
			end
			
			DOWN:begin
				key_flag <= 1'b0;
				if(pedge)begin
					curr_st <= FILTER2;
					en_cnt <= 1'b1;
				end
				else
					curr_st <= DOWN;
			end
			
			FILTER2:begin
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b1;
					curr_st <= IDLE;
					en_cnt <= 1'b0;
				end	
				else if(nedge)begin
					curr_st <= DOWN;
					en_cnt <= 1'b0;
				end
				else
					curr_st <= FILTER2;
			end
			
			default:begin
				curr_st <= IDLE;
				en_cnt <= 1'b0;
				key_flag <= 1'b0;
				key_state <= 1'b1;
			end
		endcase
	end
end

三、仿真测试

3.1 常规编写

`timescale 1ns/1ns
`define clock_period 20

module KeyFilter_tb;

	reg Clk;
	reg Rst_n;
	reg key_in;
	wire key_flag;
	wire key_state;

	KeyFilter KeyFilter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		key_in = 1'b1;
		#(`clock_period*10);
		Rst_n = 1'b1;
		#(`clock_period*10 + 1);
		
		key_in = 0;#1000;
		key_in = 1;#2000;
		key_in = 0;#1400;
		key_in = 1;#2600;
		key_in = 0;#1300;
		key_in = 1;#200;
		key_in = 0;#20000100;
		#50000000;
		
		key_in = 1;#2600;
		key_in = 0;#1000;
		key_in = 1;#2000;
		key_in = 0;#1400;
		key_in = 1;#2600;
		key_in = 0;#1300;
		key_in = 1;#200;
		key_in = 1;#20000100;
		#50000000;

		$stop;
	
	end
	
endmodule

仿真结果:
结果1


3.2 task编写

`timescale 1ns/1ns
`define clock_period 20

module KeyFilter_tb;

	reg Clk;
	reg Rst_n;
	reg key_in;
	wire key_flag;
	wire key_state;

	KeyFilter KeyFilter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		key_in = 1'b1;
		#(`clock_period*10);
		Rst_n = 1'b1;
		#(`clock_period*10 + 1);
		#30000;
		
		PressKey; #10000;
		PressKey; #10000;
		PressKey; #10000;
		
		$stop;
	end
	
	reg [15:0]myrand;
	
	task PressKey;
		begin
			//50次随机时间按下抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key_in = ~key_in;
			end
			key_in = 0;
			#50_000_000;//按下稳定
			
			//50次随机时间释放抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key_in = ~key_in;
			end
			key_in = 1;
			#50_000_000;//释放稳定
		end
	endtask
	
endmodule

注意$random随机函数的用法:

$random这一系统函数可以产生一个有符号的32bit随机整数。一般的用法是 $random%b,其中 b>0。这样就会生成一个范围在(-b+1):(b-1)中的随机数。如果只得到正数的随机数,可采用{$random}%b 来产生。

myrand = {$random}%65536;//0~65535

上述语句的作用即是产生了0~65535之间的随机数。

通过repeat语句循环50次,就产生了50次不同的延时效果:

repeat(50)begin
	myrand = {$random}%65536;//0~65535
	#myrand key_in = ~key_in;
end

仿真结果:
结果2


四、仿真模型

编写key_model并添加到测试激励文件中:

`timescale 1ns/1ns

module key_model(key);
	
	output reg key;
	
	reg [15:0]myrand;
	
	initial begin
		key = 1'b1;
		PressKey; #10000;
		PressKey; #10000;
		PressKey; #10000;
		
		$stop;
	end
	
	task PressKey;
		begin
			//50次随机时间按下抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key = ~key;
			end
			key = 0;
			#50_000_000;//按下稳定
			
			//50次随机时间释放抖动
			repeat(50)begin
				myrand = {$random}%65536;//0~65535
				#myrand key = ~key;
			end
			key = 1;
			#50_000_000;//释放稳定
		end
	endtask	

endmodule

修改KeyFilter_tb:

`timescale 1ns/1ns
`define clock_period 20

module KeyFilter_tb;

	reg Clk;
	reg Rst_n;
	wire key_in;
	wire key_flag;
	wire key_state;

	KeyFilter KeyFilter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
	key_model key_model0(.key(key_in));
	
	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		#(`clock_period*10);
		Rst_n = 1'b1;
		#(`clock_period*10 + 1);
	end
	
endmodule

整个激励文件的内部结构:
仿真模型
仿真结果:
结果3

csdn

🧸结尾


  • ❤️ 感谢您的支持和鼓励! 😊🙏
  • 📜您可能感兴趣的内容:
  • 【FPGA】串口通信讲解-状态机判断数据值
  • 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
  • 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
  • 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
    遇见未来

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

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

相关文章

Android应用开发(37)LTPO帧率测试基于Surfaceview(暂存)

Android应用开发学习笔记——目录索引 参考android官网&#xff1a; Frame rate | Android media | Android Developers多重刷新率 | Android 开源项目 | Android Open Source ProjectWindowManager.LayoutParams | Android Developers 目前市面上旗舰手机基本都是…

wangEditor5实现@评论功能

需求描述&#xff1a;在输入框输入后显示用户列表&#xff0c;实现人功能 当前环境&#xff1a;vue3viteelementPluswangEditor5 需要插件&#xff1a;wangeditor/plugin-mention 安装插件&#xff1a;npm i wangeditor/plugin-mention 输入框组件分两部分&#xff1a;1. wa…

【electron】electron安装过慢和打包报错:Unable to load file:

文章目录 一、安装过慢问题:二、打包报错&#xff1a;Unable to load file: 一、安装过慢问题: 一直处于安装过程 【解决】 #修改npm的配置文件 npm config edit#添加配置 electron_mirrorhttps://cdn.npm.taobao.org/dist/electron/二、打包报错&#xff1a;Unable to load…

CAS服务端入门使用实践

CAS服务端入门使用实践 一、前言 1.简介 CAS 是一个企业多语言单点登录解决方案&#xff0c;支持大量附加身份验证协议和功能&#xff0c;满足身份验证和授权需求的综合平台。 2.环境 Windows 10JDK 1.8git version 2.41.0.windows.3Tomcat 9.0.78Maven 3.5.3cas-overlay-…

类与对象(加深)

目录 1.类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3.析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 6.const成员 7.取地址及const取地址操作符重载 1.类的6个默认成员函数 如果…

51.C++继承

今天进行了新的学习关于c继承的知识。 目录 1.继承 基类and派生类 访问控制和继承 单继承 多继承 2.同名隐藏 1.继承 在C中&#xff0c;继承是一种面向对象编程的重要特性&#xff0c;用于构建类之间的层次关系。通过继承&#xff0c;一个类可以从另一个类继承其…

QT网络编程之TCP

QT网络编程之TCP TCP 编程需要用到俩个类: QTcpServer 和 QTcpSocket。 #------------------------------------------------- # # Project created by QtCreator 2023-08-

2023-08-12 LeetCode每日一题(合并 K 个升序链表)

2023-08-12每日一题 一、题目编号 23. 合并 K 个升序链表二、题目链接 点击跳转到题目位置 三、题目描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 示例 2&…

String(字符串)

1、String概述 java.lang.String类代表字符串&#xff0c;Java程序中的所有字符串文字&#xff08;例如“abc”&#xff09;都为此类的对象。 1.1、String的注意点 字符串的内容是不会发生改变的&#xff0c;它的对象在创建后不能被更改。 1.2、总结 String是Java定义好的一个类…

LeetCode 34题:在排序数组中查找元素的第一个和最后一个位置

目录 题目 思路 代码 C语言 Python 题目 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(…

取证--理论

资料&#xff1a; 各比赛 Writeup &#xff1a; https://meiyacup.cn/Mo_index_gci_36.html 哔站比赛复盘视频&#xff1a; https://space.bilibili.com/453117423?spm_id_from333.337.search-card.all.click 自动分析取证四部曲 新建案例添加设备自动取证制作报告 取证大…

腾讯云CVM服务器竞价实例是什么?和按量计费有什么区别?

腾讯云服务器CVM计费模式分为包年包月、按量计费和竞价实例&#xff0c;什么是竞价实例&#xff1f;竞价实例和按量付费相类似&#xff0c;优势是价格更划算&#xff0c;缺点是云服务器实例有被自动释放风险&#xff0c;腾讯云服务器网来详细说下什么是竞价实例&#xff1f;以及…

全志T113-S3 Tina-linux --1. 开发环境搭建

1. 硬件环境 1.1 开发板 型号&#xff1a;100ASK_T113-PRO Base V1.1&#xff08;韦东山&#xff09;配置&#xff1a;CPU&#xff1a;T113-S3&#xff0c;RAM&#xff1a;128MB&#xff0c;ROM&#xff1a;128MB T113-S3配置 1.2 上手使用 1.2.1 串口shell 串口shell配置…

MySQL8安装教程 保姆级(Windows))

下载 官网: mysql官网点击Downloads->MySQL Community(GPL) Downloads->MySQL Community Server(或者点击MySQL installer for Windows) Windows下有两种安装方式 在线安装 一般带有 web字样 这个需要联网离线安装 一般没有web字样 安装 下载好之后,版本号可以不一样&…

【积水成渊】9 个CSS 伪元素

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人_python人工智能视觉&#xff08;opencv&#xff09;从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了&#xff1a; https://blog.csdn.net/lbcy…

预测知识 | 预测技术流程及模型评价

预测知识 | 预测技术流程及模型评价 目录 预测知识 | 预测技术流程及模型评价技术流程模型评价参考资料 技术流程 1&#xff09;模型训练阶段&#xff1a;预测因素和结局&#xff0c;再加上预测模型进行模型拟合&#xff1b; 2&#xff09;预测阶段&#xff1a;将预测因素代入拟…

如何自学(黑客)网络安全

前言&#xff1a; 想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“…

前端一键升级 package.json里面的依赖包管理

升级需谨慎 前端一键升级 package.json里面的依赖包管理 安装&#xff1a;npm-check-updates npm i npm-check-updates -g缩写 ncu 在项目根目录里面执行 ncu 如图&#xff1a;

日撸java_day61-62

决策树 package machineLearning.decisiontree;import weka.core.Instance; import weka.core.Instances;import java.io.FileReader; import java.util.Arrays;/*** ClassName: ID3* Package: machineLearning.decisiontree* Description: The ID3 decision tree inductive …

企业服务器数据库遭到malox勒索病毒攻击后如何解决,勒索病毒解密

网络技术的发展不仅为企业带来了更高的效率&#xff0c;还为企业带来信息安全威胁&#xff0c;其中较为常见的就是勒索病毒攻击。近期&#xff0c;我们公司收到很多企业的求助&#xff0c;企业的服务器数据库遭到了malox勒索病毒攻击&#xff0c;导致系统内部的许多重要数据被加…