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

文章目录

  • 前言
  • 一、为什么要按键消抖
  • 二、如何按键消抖
    • 1.按键消抖原理
    • 2.第一种方式(状态机实现)
      • ①代码如下:
      • ②仿真测试模块代码
      • ③仿真波形图
    • 3、第二种方式
      • ①代码如下:
      • ②仿真测试代码
      • ③仿真波形图
  • 总结


前言

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

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


一、为什么要按键消抖

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

二、如何按键消抖

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

1.按键消抖原理

FPGA按键消抖—两种按键消抖形式的对比_第1张图片
在按键按下和释放的时候都会产生抖动,即极短时间内状态在高电平和低电平之间震荡,当状态在低电平持续至少20ms时我们认为此次变化非抖动,并且按键是按下状态,在释放时和按下时状态相反,超过20ms的高电平状态即已经释放按键。

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

①代码如下:

FPGA按键消抖—两种按键消抖形式的对比_第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 

③仿真波形图

仿真波形:
FPGA按键消抖—两种按键消抖形式的对比_第3张图片
按下:
FPGA按键消抖—两种按键消抖形式的对比_第4张图片
释放:FPGA按键消抖—两种按键消抖形式的对比_第5张图片
使用状态机实现的按键消抖模块在调用时需要注意标志信号的使用:
例:我们令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 

③仿真波形图

图一:
FPGA按键消抖—两种按键消抖形式的对比_第6张图片
图二:
FPGA按键消抖—两种按键消抖形式的对比_第7张图片
在图二可以看到,标志信号key_flag在计数器计数到到999_998产生一个脉冲信号(寄存器起到延迟一拍的作用,因此看起来是在999_999时上升。)
在调用此按键消抖模块时,只需要看key_flag这个标志信号的变化即可。

总结

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

你可能感兴趣的:(我的FPGA自学笔记,fpga开发)