fpga[2.1]按键消抖电路(附源码)

前言

在上一篇博文中我们了解了状态机的概念和作用并设计了一个简单的状态机,博文详情→fpga自学之路[2]状态机,本次我们将使用状态机来对实现机械按键的消抖功能。

1.知识准备

机械按键是在电子设计中被广泛使用的一种基本人机交互元件,从系统复位到控制设置都可以看到其身影。
其物理结构一般如图所示:
在这里插入图片描述
由于按键存在一个反作用弹簧,因此当按下或者松开时均会产生额外的物理抖动,物理抖动便会产生电平的不稳定,导致我们在按下按键使常常无法达到我们想要的效果。在按键从按下再到松开的过程中,其电平变化如图所示:
在这里插入图片描述
产生的抖动次数以及间隔时间均是随机的,这就需要通过滤波来消除抖动带来的影响。一般情况下抖动的总时间会持续 20ms以内。这种抖动可以通过设计硬件电路来消除,也可以通过软件的方式完成。我们接下来将使用硬件电路消除抖动,这一般适用于按键数目较少的场合。

2.功能设计

在FPGA设计中,通常使用状态机来进行按键消抖处理。
消抖模块端口列表如下:

名称类型功能
sys_clkinput系统时钟50MHz
sys_rst_ninput系统复位
key_ininput按键输入信号
key_stateoutput消抖后信号输出

明确了输入输出端口之后我们开始状态机的设计,需要用到的状态如下:

现态次态转移条件
IDLEFILTER0检测到下降沿(按键被按下)
FILTER0IDLE检测到上升沿(说明按键还在抖动)
FILTER0DOWN一段时间电平不发生改变(计数值计满),即按键按下稳定时
DOWNFILTER1检测到上升沿(按键松开)
FILTER1DOWN检测到下降沿(说明按键还在抖动)
FILTER1IDLE一段时间电平不发生改变(计数值计满),即按键松开稳定时

根据图表编写代码如下:

module key
(
	input		sys_clk		,		
	input		sys_rst_n	,
	input		key_in		,

	output	reg key_state		
);
/*	计数器最大值设置	*/
parameter	CNT_MAX	=	999_999;
/*	状态机编码为独热码	*/
parameter	IDLE	=	4'b0001;	
parameter	FILTER0 =	4'b0010;
parameter	DOWN	=	4'b0100;
parameter	FILTER1 =	4'b1000;
/*	输入时打两拍需要的信号	*/
reg	key_in_a,key_in_b;
/*  检测上升沿模块用到的变量	 */
reg key_tmpa,key_tmpb;
wire pedge,nedge;
/*	判断按键是否稳定用到的变量*/
reg [19:0]cnt;
reg en_cnt;
reg cnt_full;
/*	设置状态机的四个状态*/
reg	[3:0]state;

always@(posedge sys_clk or negedge sys_rst_n)		//输入信号打两拍,同步到fpga工作的时钟域
	if(!sys_rst_n)begin
		key_in_a <= 1'b0;
		key_in_b <= 1'b0;
	end
	else begin
		key_in_a <= key_in;
		key_in_b <= key_in_a;
	end

always@(posedge sys_clk or negedge sys_rst_n) 	//检测上升沿模块
	if(!sys_rst_n)begin
		key_tmpa <= 1'b0;
		key_tmpb <= 1'b0; 
	end
	else begin
		key_tmpa <= key_in_b;
		key_tmpb <= key_tmpa; 
	end
assign nedge = !key_tmpa & key_tmpb; 	//获得下降沿信号
assign pedge = key_tmpa & (!key_tmpb);	//获得上升沿信号

always@(posedge sys_clk or negedge sys_rst_n)	//描述状态转移
	if(!sys_rst_n)
		state <= IDLE; 
	else begin
		case(state) 
			IDLE	:	
			begin
				if(nedge)
					state <= FILTER0;
				else
					state <= IDLE;
			end
			FILTER0	:
			begin
				if(cnt_full)		
					state <= DOWN;
				else if(pedge)
					state <= IDLE;
				else 
					state <= FILTER0;
			end
			DOWN	:
			begin
				if(pedge)
					state <= FILTER1;
				else 
					state <= DOWN;
			end
			FILTER1	:
			begin
				if(cnt_full)
					state <= IDLE;
				else if(nedge)
					state <= DOWN;
				else
					state <= FILTER1;
			end
			default	:	state <= IDLE;	
		endcase
	end

always@(posedge sys_clk or negedge sys_rst_n) 	//描述输出
	if(!sys_rst_n)begin
		en_cnt <= 1'b0;  
		key_state <= 1'b1;
	end
	else begin
		case(state)
			IDLE	:
				begin
					if(nedge)
						en_cnt <= 1'b1; 
				end	
			FILTER0	:
				begin
					if(cnt_full)begin
						key_state <= 1'b0; 
						en_cnt <= 1'b0; 
					end
					else if(pedge)begin 
						en_cnt <= 1'b0;
					end 
				end
			DOWN	:
				begin
					if(pedge)
						en_cnt <= 1'b1; 
				end
			FILTER1	:
				begin		
					if(cnt_full)begin
						key_state <= 1'b1; 
						en_cnt <= 1'b0;
						end
					else if(nedge)
						en_cnt <= 1'b0;
				end			
			default	:
				begin
					en_cnt <= 1'b0;  
					key_state <= 1'b1;
				end
		endcase
	end


always@(posedge sys_clk or negedge sys_rst_n)		//对按键稳定时间进行计数,判断是否稳定
	if(!sys_rst_n)
		cnt <= 20'd0;
	else if(en_cnt)
		cnt <= cnt + 1'b1;
	else 
		cnt <= cnt;

always@(posedge sys_clk or negedge sys_rst_n) 		//计数器计满信号,表示可以进入下一个状态
	if (!sys_rst_n) 
		cnt_full <= 1'b0;
	else if (cnt == CNT_MAX) 
		cnt_full <= 1'b1;
	else 
		cnt_full <= 1'b0;

endmodule

RTL视图如下,
请添加图片描述
为了同步输入信号,我们打了两拍,可以看到在输入端口处生成了两个D触发器:
在这里插入图片描述
根据状态转换图可知状态数和转换方向没有问题,并且状态转移的条件也正确:
在这里插入图片描述

3.仿真验证

为了进一步验证状态输出以及状态转换条件是否正确,我们需要编写testbench文件
编写代码如下:

`timescale 1ns/1ns
module tb_key();

reg	sys_clk;
reg sys_rst_n;
reg key_in;

reg [15:0]myrand;

task press_key; 
	begin
		repeat(50) begin //五十次随机时间按下抖动
			myrand = {$random}%65536;
			#myrand key_in = ~key_in; 
		end
		key_in = 0; 
		#50_000_000; //按下稳定

		repeat(50) begin //50次随机时间释放抖动 myrand = {$random}%65536;
			myrand = {$random}%65536;
			#myrand key_in = ~key_in; 
		end
		key_in = 1;
		#50_000_000; //释放稳定 
	end
endtask

initial	begin
	sys_clk = 1'b0;	sys_rst_n = 1'b0; key_in = 1'b1;
	#50
	sys_rst_n = 1'b1;
	#30000
	press_key;
	#10000
	press_key;
	#20000
	press_key;
	#30000
	press_key;
	#10000
	$stop;
end

always#10 sys_clk = ~sys_clk;

key key_inst
(
	.sys_clk		(sys_clk),		
	.sys_rst_n		(sys_rst_n),
	.key_in			(key_in),

	.key_state		(key_state)
);

endmodule

在观察波形之前我们需要先将添加key_inst的波形,产生波形如下:
在这里插入图片描述
我们需要对局部进行放大,可以看到仿真开始时出现了我们设置的随机抖动,此时cnt在达到最大计数值20ms前被多次清零(按键不稳定造成),按键稳定后(计数值达到20ms时)电路将此时的电平状态输出。虽然总体上输出波形相比输入波形延迟了20ms,但在我们的设计中是可以接受的,从这里看出来我们的设计是正确的。
在这里插入图片描述

4.总结

本次我们设计了一个按键消抖电路,通过它可以通过按键交互的方式来验证我们的设计。在设计状态机时,我们需要明确状态数、状态转移的条件以及各个状态的输出情况,这也是状态机设计的难点。对于状态机而言,不同的规模电路通常使用不同的描述方法(一段,二段,三段式),而且需要考虑电路能否综合,所以更好的掌握状态机是一个循序渐进的过程,由于还在初学阶段,我们先偏重于理解状态机的思想,后续再对状态机的书写进行探究。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值