FPGA学习笔记:按键消抖模块设计

一、按键抖动现象

通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
按键抖动的时间一般为5~10ms,不会超过20ms软件消抖的方案通常采用延时的方法对按键进行消抖,等待按键抖动结束后,再判断按键的状态。

二、按键抖动与亚稳态问题解决方案

按键电路
以上图电路为例,按键未按下时,连接3.3V电源,当按键按下时,IO口接地,即按键按下为低电平,按键未按下为高电平。因此IO口检测到下降沿时,按键按下,检测到上升沿时,按键松开。
按键消抖
如图所示,根据D触发器的特性,输入信号和输出信号间隔一个时钟周期,可将按键Key值输入D触发器,并将其与Q值(按键前一状态值Key_pre)做逻辑运算,即可判断按键的上升沿和下降沿。
当Key=0,Key_pre=1时,为下降沿nedge;当Key=1,Key_pre=0时,为上升沿pedge。
亚稳态问题是指触发器无法在某个规定时间段内达到一个可确认的状态。当一个触发器进入亚稳态时,既无法预测该单元的输出电平,也无法预测何时输出才能稳定在某个正确的电平上。在这个稳定期间,触发器输出一些中间级电平,或者可能处于振荡状态,并且这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去。按键在抖动过程中如果出现亚稳态问题,则无法正确判断下降沿和上升沿,就无法正确判断按键是否按下。
亚稳态不能从根本上消除,但是可以采取一定的措施,降低其对电路的影响。可通过改进上述电路,将一级D触发器改成两级D触发器,以应对亚稳态问题
按键亚稳态

三、按键消抖模块设计

1.设计思路

模块端口说明:

端口名称方向说明
Clkinput系统时钟
Rst_ninput系统复位
key_ininput按键输入端
key_flagoutput按键消抖标志
key_stateoutput按键状态标志

消抖完成时,key_flag置1,若为按键按下消抖,则|key_state=0,表示按键已按下,反之表示按键已抬起。
可利用状态机实现按键消抖过程,状态定义如下:
S0:空闲状态,按键未按下
S1:按键按下,抖动滤波
S2:按键稳定按下
S3:按键抬起,抖动滤波

状态转换图:
按键状态转换图

2.按键消抖代码设计

(1)亚稳态问题应对与上升沿下降沿判断
定义3位宽的寄存器key_sync_reg,key_sync_reg[0]为按键输入key_in,key_sync_reg[1]为第一级D触发器的Q端,key_sync_reg[2]为第二级D触发器的Q端,即key_pre。定义wire型的pedge和nedge,根据上述电路计算上升沿和下降沿。

	reg [2:0]key_sync_reg;
	//二级寄存器解决亚稳态问题, 2:key_pre  1:key_reg  0:key_in
	wire pedge,nedge;//定义上升沿下降沿
		always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		key_sync_reg <= 3'd0;
	else
		key_sync_reg <= {key_sync_reg[1:0],key_in};
		
	assign pedge = key_sync_reg[2:1] == 2'b01;
	assign nedge = key_sync_reg[2:1] == 2'b10;

(2)20ms计数器
在cnt_en被使能时,开始计数;20ms计数完成,dly_done置1,状态机根据dly_done跳转到下一状态。

	reg dly_done,cnt_en;//延时完成标志,计数器使能
	reg [19:0]cnt;//计数器
	localparam cnt_max = 20'd999_999;//20ms
	
	always@(posedge Clk or negedge Rst_n)//计数器
	if(!Rst_n) begin
		cnt <= 20'd0;
		dly_done <= 1'b0;
	end
	else if(cnt_en) begin
		if(cnt == cnt_max) begin//计时20ms
			dly_done <= 1'b1;
			cnt <= 20'd0;
		end
		else
			cnt <= cnt + 1'b1;
	end
	else	begin
		cnt <= 20'd0;
		dly_done <= 1'b0;
	end

(3)按键消抖状态机
由S0跳转到S1时,使能cnt_en,在S1状态根据dly_done判断20ms延时是否结束,若结束,按键消抖标志置1,跳转到S2状态,完成按键按下消抖。按键抬起消抖过程类似。

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) begin
		state <= S0;
		key_flag <= 1'b0;
		cnt_en <= 1'b0;
		key_state <= 1'b1;
	end
	else
		case(state)
			S0: begin
				key_flag <= 1'b0;
				if(nedge) begin
					state <= S1;
					cnt_en <= 1'b1;
				end
				else
					state <= S0;
			end
				
			S1:
				if(dly_done) begin
					state <= S2;
					cnt_en <= 1'b0;
					key_flag <= 1'b1;
					key_state <= 1'b0;//按键按下为低电平
				end
				else if(pedge) begin
					state <= S0;
					cnt_en <= 1'b0;
				end
				else
					state <= S1;
			
			S2: begin
				key_flag <= 0;//抖动滤除完成,标志清零
				if(pedge) begin
					state <= S3;
					cnt_en <= 1'b1;
				end
				else
					state <= S2;
			end
			
			S3:
				if(dly_done) begin
					state <= S0;
					cnt_en <= 1'b0;
					key_flag <= 1'b1;
					key_state <= 1'b1;
				end
				else if(nedge) begin
					state <= S2;
					cnt_en <= 1'b0;
				end
				else
					state <= S3;
					
			default: begin
				state <= S0;
				key_flag <= 1'b0;
				cnt_en <= 1'b0;
				key_state <= 1'b1;
			end
		endcase

按键消抖模块源代码

//本模块用于实现按键消抖与检测,按下为低电平
module key_filter(
	input Clk,				//系统时钟
	input Rst_n,			//系统复位
	input key_in,			//按键输入
	output reg key_flag,	//按键消抖标志
	output reg key_state	//按键状态标志
);

	reg [1:0]state;
	localparam
		S0 = 2'd0,	//空闲状态,按键未按下
		S1 = 2'd1,	//按键按下,抖动滤波
		S2 = 2'd2,	//按键稳定按下
		S3 = 2'd3;	//按键抬起,抖动滤波
	
	reg [2:0]key_sync_reg;
	//二级寄存器解决亚稳态问题, 2:key_pre  1:key_reg  0:key_in
	wire pedge,nedge;//定义上升沿下降沿
	
	reg dly_done,cnt_en;//延时完成标志,计数器使能
	reg [19:0]cnt;//计数器
	localparam cnt_max = 20'd999_999;//20ms

	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		key_sync_reg <= 3'd0;
	else
		key_sync_reg <= {key_sync_reg[1:0],key_in};
		
	assign pedge = key_sync_reg[2:1] == 2'b01;
	assign nedge = key_sync_reg[2:1] == 2'b10;

	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) begin
		state <= S0;
		key_flag <= 1'b0;
		cnt_en <= 1'b0;
		key_state <= 1'b1;
	end
	else
		case(state)
			S0: begin
				key_flag <= 1'b0;
				if(nedge) begin
					state <= S1;
					cnt_en <= 1'b1;
				end
				else
					state <= S0;
			end
				
			S1:
				if(dly_done) begin
					state <= S2;
					cnt_en <= 1'b0;
					key_flag <= 1'b1;
					key_state <= 1'b0;//按键按下为低电平
				end
				else if(pedge) begin
					state <= S0;
					cnt_en <= 1'b0;
				end
				else
					state <= S1;
			
			S2: begin
				key_flag <= 0;//抖动滤除完成,标志清零
				if(pedge) begin
					state <= S3;
					cnt_en <= 1'b1;
				end
				else
					state <= S2;
			end
			
			S3:
				if(dly_done) begin
					state <= S0;
					cnt_en <= 1'b0;
					key_flag <= 1'b1;
					key_state <= 1'b1;
				end
				else if(nedge) begin
					state <= S2;
					cnt_en <= 1'b0;
				end
				else
					state <= S3;
					
			default: begin
				state <= S0;
				key_flag <= 1'b0;
				cnt_en <= 1'b0;
				key_state <= 1'b1;
			end
		endcase
		
		
	always@(posedge Clk or negedge Rst_n)//计数器
	if(!Rst_n) begin
		cnt <= 20'd0;
		dly_done <= 1'b0;
	end
	else if(cnt_en) begin
		if(cnt == cnt_max) begin//计时20ms
			dly_done <= 1'b1;
			cnt <= 20'd0;
		end
		else
			cnt <= cnt + 1'b1;
	end
	else	begin
		cnt <= 20'd0;
		dly_done <= 1'b0;
	end
endmodule

四、仿真

1.按键抖动模拟

将模拟按键抖动过程封装成task任务,在仿真时,只需要调用任务,可提高代码的简洁性和可阅读性。
按键抖动过程利用repeat和random随机函数模拟,random随机函数是为了模拟按键抖动过程中高低电平的随机时间,抖动模拟代码如下:

task key_press();//模拟按键按下
		reg [23:0]rand;
		begin
			repeat(5) begin
				key_in = 1'b0;
				rand = {$random} % 1001;
				#(rand * 2000);
				key_in = 1'b1;
				#(rand * 2000);
			end
			
			key_in = 1'b0;
			#40_000_000;
			
			repeat(5) begin
				key_in = 1'b1;
				rand = {$random} % 1001;
				#(rand * 2000);
				key_in = 1'b0;
				#(rand * 2000);
			end
			key_in = 1'b1;
			#30_000_000;
		end
	endtask

2.仿真文件

在仿真时,直接调用封装好的按键抖动任务,模拟按键按下过程。

`timescale 1ns/1ns
module key_filter_tb();
	
	reg Clk,Rst_n,key_in;
	wire key_flag,key_state;
	
	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'b1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b1;
		key_in = 1'b1;
		#201;
		key_press();
		$stop;	
	end

3.仿真结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值