草庐IT

FPGA按键消抖—两种按键消抖形式的对比

南邮学渣 2023-04-21 原文

文章目录


前言

按键消抖是FPGA学习中的一个必备的基础知识模块,在我的学习过程中,共碰到过两种按键消抖模块,分别是在**《小梅哥FPGA自学笔记》《FPGA Verilog开发实战指南》**之中,两种方式的实现有着略大的不同,下面分别列举两种方式。

如果赶时间,可以跳过第一种方式,之间看第二种。


一、为什么要按键消抖

按键是最为常见的电子元器件之一,在电子设计中应用广泛,可能大家一听到按键消抖会疑问,按键不就是一个简单的按下置0,松开置1的元器件吗,只需要一句简单的赋值语句应该就可以实现的,为什么要多此一举的写按键消抖模块呢。这是因为我们使用的按键开关为机械弹性快关,当机械触点断开、闭合时,由于机械触点的弹性作用,**一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。**为了不使抖动影响我们芯片的控制产生误判,就必须要进行按键消抖。

二、如何按键消抖

按键消抖具有硬件消抖和软件消抖两种方式,硬件消抖是通过RS触发器进行消抖,可用于按键较少时的消抖,这里我们只对软件消抖进行阐述和分析。

1.按键消抖原理


在按键按下和释放的时候都会产生抖动,即极短时间内状态在高电平和低电平之间震荡,当状态在低电平持续至少20ms时我们认为此次变化非抖动,并且按键是按下状态,在释放时和按下时状态相反,超过20ms的高电平状态即已经释放按键。

2.第一种方式(状态机实现)

①代码如下:


状态机如上

module key_filter(clk,rst_n,key_in,key_flag,key_state);

	input clk;
	input rst_n;
	input key_in;
	
	output reg key_flag;
	output reg key_state;
	
	reg [19:0]cnt;
	reg en_cnt;
	reg cnt_full;
	//50_000_000时钟,周期20ns
	//延时20ms,20_000_000ns/20ns=1000_000
	
	//对外部输入信号进行同步处理,消除亚稳态
	reg key_in_s0,key_in_s1;
	
	always @(posedge clk or negedge rst_n)
	if (!rst_n)begin
		key_in_s0<=1'b0;
		key_in_s1<=1'b0;
	end
	else begin
		key_in_s0<=key_in;
		key_in_s1<=key_in_s0;
	end
	
	localparam 
		IDEL = 4'b0001,
		FILTER0 = 4'b0010,
		DOWN = 4'b0100,
		FILTER1 = 4'b1000;
		
	reg [3:0]state;
	
	reg key_tmp0,key_tmp1;
	wire pedge,nedge;
	
	//使用d触发器存储两个相邻时钟上升沿时外部输入信号的电平状态
	always @(posedge clk or negedge rst_n)
	if (!rst_n)begin
		key_tmp0<=1'b0;
		key_tmp1<=1'b0;
	end
	else begin
		key_tmp0<=key_in_s1;
		key_tmp1<=key_tmp0;
	end
	
	//产生跳变沿信号
	assign nedge=(!key_tmp0) & key_tmp1;
	assign pedge=key_tmp0 & (!key_tmp1);
	
	always@(posedge clk or negedge rst_n)
	if(!rst_n)begin
		state<=IDEL;
		en_cnt<=1'b0;
		key_flag<=1'b0;
		key_state<=1'b1;
	end
		else begin
		case(state)
			IDEL:
				begin
					key_flag<=1'b0;
					if(nedge)begin
						state<=FILTER0;
						en_cnt<=1'b1;
					end
					else
						state<=IDEL;
				end
				
			FILTER0:
				if(cnt_full)begin
					key_flag<=1'b1;
					key_state<=1'b0;
					state<=DOWN;
					en_cnt<=1'b0;
				end	
				else if(pedge)begin
					state<=IDEL;
					en_cnt<=1'b0;
				end
				else
					state<=FILTER0;
					
			DOWN:
				begin
					key_flag<=1'b0;
					if(pedge)begin
						state<=FILTER1;
						en_cnt<=1'b1;
					end
					else
						state<=DOWN;
				end
				
			FILTER1:
				if(cnt_full)begin
					key_state<=1'b1;
					key_flag<=1'b1;
					state<=IDEL;
					en_cnt<=1'b0;
				end
				else if(nedge)begin
					state<=DOWN;
					en_cnt<=1'b0;
				end
				else
					state<=FILTER1;
		
			default:
				begin
					state<=IDEL;
					en_cnt<=1'b0;
					key_flag<=1'b0;
					key_state<=1'b1;
				end
		endcase
	end
	
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		cnt<=20'd0;
	else if(en_cnt)
		cnt<=cnt+1'b1;
	else
		cnt<=20'd0;
		
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		cnt_full<=1'b0;
	else if(cnt==999_999)
		cnt_full<=1'b1;
	else
		cnt_full<=1'b0;


endmodule 

②仿真测试模块代码

`timescale 1ns/1ns

`define clock_period 20

module key_filter_tb;

	reg clk;
	reg rst_n;
	reg key_in;
	
	wire key_flag;
	wire key_state;
	
	reg [15:0]myrand;

	key_filter key_filter0(
		.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);
		press_key;
		#10000;
		press_key;
		#10000;
		press_key;
		$stop;

	end
	
	task press_key;
		begin
			repeat(50)begin
				myrand={$random}%65536;//加括号是正,产生0-65535
				#myrand key_in=~key_in;
			end
			key_in=1'b0;
			#50_000_000;
		//按下抖动
	
			repeat(50)begin
				myrand={$random}%65536;
				#myrand key_in=~key_in;
			end
			key_in=1'b1;
			#50_000_000;
		//释放抖动
		end
	endtask
endmodule 

③仿真波形图

仿真波形:

按下:

释放:
使用状态机实现的按键消抖模块在调用时需要注意标志信号的使用:
例:我们令key_in为需要消抖的按键,key_flag为按键消抖模块中的按下标志信号,key_state为按键消抖模块中的状态信号,在按键按下时key_flag产生一个脉冲信号,key_state由高电平变为低电平。因此,调用时的语句为:
assign key_in=(key_flag) && (~key_state)
这样,只有在模块确定按键按下的一瞬间才会判断按键按下,为低电平,其余时刻key_in均为高电平。


3、第二种方式

我们认为,按键处于低电平的时刻大于20ms时,按键为按下状态,而按键按下的干扰是短时、多次的从高电平到低电平的跳变。因此,我们可以用一个计数模块,当输入信号为低电平时,开始计时,计时途中,如果跳变为高电平,即计数器清0,等待下一次低电平到来后再次开始计时,如此反复,如果记满20ms,那么,认为按键为一次按下状态,使按键标志产生一个脉冲,代表按键按下。
计时模块:通常FPGA开发板板上晶振产生的系统时钟频率是50MHz,周期为20ns,要记满20ms,即需要计数(20ms/20ns=1000_000-1=999_999)次,我们需要的按键标志只是一个脉冲信号,如果在计数999_999时,将其置1,而按键低电平状态不一定正好等于20ms,一般都为大于20ms,这样的话,会产生一个持续很长时间的标志信号,不是我们想要看到的,因此,在计数器计数到999_998时即产生标志信号,这样就满足了我们的设计需要。

①代码如下:

module key_filter
#(parameter CNT_MAX=999_999)
(

	input wire clk,
	input wire rst_n,
	input wire key_in,
	
	output reg key_flag
	
);
	
	reg [19:0]cnt_20;
	
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		cnt_20<=1'b0;
	else if(key_in==1'b1)
		cnt_20<=1'b0;
	else if(cnt_20==CNT_MAX &&  key_in==1'b0)
		cnt_20<=cnt_20;
	else
		cnt_20<=cnt_20+1'b1;
		
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		key_flag<=1'b0;
	else if(cnt_20==CNT_MAX-1'b1)
		key_flag<=1'b1;
	else 
		key_flag<=1'b0;
		
endmodule 

②仿真测试代码

`timescale 1ns/1ns
`define clk_period 20

module key_filter_tb;
	
	reg clk;
	reg rst_n;
	reg [3:0]key_in;
	
	wire key_flag;
	
	reg [15:0]myrand;
	
	key_filter 
	#(
		.CNT_MAX(20'd999_999)
	)
	key_filter(

		.clk(clk),
		.rst_n(rst_n),
		.key_in(key_in),
		
		.key_flag(key_flag)
		
	);
	
	initial clk=1'b1;
	always #(`clk_period/2) clk=~clk;
	
	initial begin
		rst_n=1'b0;
		key_in=1'b1;
		#(`clk_period*10) rst_n=1'b1;
		#(`clk_period*10+1);
		press_key;
		#10000;
		press_key;
		#10000;
		press_key;
		$stop;
	end
	task press_key;
		begin
			repeat(50)begin
				myrand={$random}%65536;//加括号是正,产生0-65535
				#myrand key_in=~key_in;
			end
			key_in=1'b0;
			#50_000_000;

			repeat(50)begin
				myrand={$random}%65536;
				#myrand key_in=~key_in;
			end
			key_in=1'b1;
			#50_000_000;
		end
	endtask
endmodule 

③仿真波形图

图一:

图二:

在图二可以看到,标志信号key_flag在计数器计数到到999_998产生一个脉冲信号(寄存器起到延迟一拍的作用,因此看起来是在999_999时上升。)
在调用此按键消抖模块时,只需要看key_flag这个标志信号的变化即可。

总结

至此,两种按键消抖的方式都已经讲解完毕,可以看出,方式二相比于方式一,具有代码更简洁、理解更方便、调用更简便的优点,因此推荐使用第二种方式。

有关FPGA按键消抖—两种按键消抖形式的对比的更多相关文章

  1. ruby-on-rails - 使用回形针的嵌套形式 - 2

    我有一个名为posts的模型,它有很多附件。附件模型使用回形针。我制作了一个用于创建附件的独立模型,效果很好,这是此处说明的View(https://github.com/thoughtbot/paperclip):@attachment,:html=>{:multipart=>true}do|form|%>posts中的嵌套表单如下所示:prohibitedthispostfrombeingsaved:@attachment,:html=>{:multipart=>true}do|at_form|%>附件记录已创建,但它是空的。文件未上传。同时,帖子已成功创建...有什么想法吗?

  2. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  3. ruby-on-rails - 嵌套形式的 HABTM 复选框 - 2

    我正在尝试以嵌套形式实现HABTM复选框。目前,我有3个模型。主题、类(class)和小组。协会如下:每个科目都有很多课。每节课都属于许多小组。现在,我正在尝试在单个创建和编辑表单上实现它们。这样一节课嵌套在主题中,每节课都有一个组复选框列表来实现HABTM关系。我在实现HABTM关系时遇到了麻烦,因为每个科目都有很多类(class),而且我不确定如何区分不同的类(class)。为了进一步详细说明,我能够使嵌套表单正常工作,但我无法让HABTM复选框保存到正确的类(class)中。以下代码示例是我的HABTM复选框实现。目前,我已经使用“subject[lessons_attribut

  4. ruby-on-rails - 如何使用 globalize 和 rails 4 以一种形式显示所有翻译字段 - 2

    在使用rails4和https://github.com/globalize/globalize的情况下,我应该如何为我的模型编写表单?用于翻译。我想以一种形式显示所有翻译,如下例所示。我在这里找到了解决方案https://github.com/rilla/batch_translations但我不知道如何实现它。这个“批量翻译”是一个gem还是什么?以及如何安装它。EditingpostEnglish(defaultlocale)SpanishtranslationFrenchtranslation 最佳答案 批处理翻译gem很旧

  5. ruby - 强制 Ruby 不以标准形式/科学记数法/指数记数法输出 float - 2

    我遇到了同样的问题here对于python,但对于ruby​​。我需要输出这样一个小数字:0.00001,而不是1e-5。有关我的特定问题的更多信息,我正在使用f.write("Mynumber:"+small_number.to_s+"\n")输出到一个文件对于我的问题,准确性不是什么大问题,所以只做一个if语句来检查是否small_number那么更通用的方法是什么? 最佳答案 f.printf"Mynumber:%.5f\n",small_number您可以将.5(小数点右侧5位数字)替换为您喜欢的任何特定格式大小,例如,%8

  6. ruby - 按键数组中的顺序对 Ruby 哈希进行排序 - 2

    我有一个散列:sample={bar:200,foo:100,baz:100}如何使用sort_order中的键顺序对sample进行排序:sort_order=[:foo,:bar,:baz,:qux,:quux]预期结果:sample#=>{foo:100,bar:200,baz:100}我能想到的就是new_hash={}sort_order.each{|k|new_hash[k]=sample[k]unlesssample[k].nil?}sample=new_hash必须有更好的方法。提示?不应该出现没有值的键,即键的数量保持不变,SortHashKeysbasedonord

  7. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  8. ruby - 是否有可能在 Ruby 中以哈希的形式访问关键字参数? - 2

    我知道我能做到:classParentdefinitialize(args)args.eachdo|k,v|instance_variable_set("@#{k}",v)endendendclassA但我想使用关键字参数来更清楚地说明可以接受哪个散列键方法(并进行验证表明不支持此键)。所以我可以写:classAdefinitialize(param1:3,param2:4)@param1=param1@param2=param2endend但是有没有可能写一些更短的东西而不是@x=x;@y=y;...从传递的关键字参数初始化实例变量?是否可以访问作为哈希传递的关键字参数?

  9. ruby-on-rails - 如何以一种形式编辑多个模型? - 2

    我从教练那里接到了任务。我想以一种形式编辑两个模型。例如,我们有两个实体学生和地址。在新学生部分,我想添加学生详细信息和地址。我如何通过ruby​​onrails中的脚手架实现这一目标? 最佳答案 您可以使用accepts_nested_attributes_for和fields_for建立一个表格来同时创建两个模型,所以你也可以编辑它们。这种形式称为嵌套形式。这里有一个关于Nestedform的引用给你,. 关于ruby-on-rails-如何以一种形式编辑多个模型?,我们在Stack

  10. FPGA 之 时钟,时钟域, 以及复位系统的设计 - 2

    FPGA时钟和时钟域时钟树所谓时钟树为FPGA内部资源,分:全局时钟树,区域时钟树,IO时钟树原则上优先使用全局时钟树,在GT接口上使用IO时钟树,一般工具也会对GT时钟加以限制;时钟树使用方式正确的物理连接FPGA会由物理管脚专门用于全局时钟设置,通过查询数据手册可以在PCB设计阶段进行确认,当外部时钟接入此管脚时,工具会自动占有全局时钟树资源,当接入普通信号时不会分配时钟树资源;恰当的代码描述原语的使用,即BUFG的使用,可以将PLL的输出等内部时钟进行全局时钟资源的分配;IO时钟资源需要参考相应接口手册,以ultrascale的GTH为例,其JESD204的时钟方案针对不同的子类会由不同

随机推荐