按键消抖

为什么要学习按键消抖

我们经常在生活中接触到机械按键,而当我们按下和松开的时候,机械触点会在闭合和断开会产生一连串的抖动,即可能只按了一下而实际上被检测为按下多次,为了防止产生这种现象我们就需要使用到按键消抖。按键消抖多用于记录按键按下的次数这种情况。

硬件消抖

即通过RS触发器对按键进行消抖,成本较大,所以不常使用

软件消抖

第一种方法
  • 第一次按键为低电平了就开始计数,然后延时一段大于30ms 的时间后再检测得到的按键电平就是稳定的按键信号,代码和仿真如下
//rtl code
module key_filter_2(
	input		sys_clk			,
	input		sys_rst_n		,
	input		key_in			,
	output	reg	key_flag	
);

//parameter define
parameter CNT_30MS_MAX =  24'd1_500_000 - 1'd1;

//reg define
reg [23:0] 	cnt_30ms	;
reg [5:0 ]	cnt_jitter	;
reg			key_in_reg	;
reg			cnt_flag	;

//key_in_reg:对key_in延迟一个时钟周期
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		key_in_reg <= 1'b1;
	else
		key_in_reg <= key_in;
end

//cnt_jitter:记录抖动的次数
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		cnt_jitter <= 6'd0;
	else if(key_in_reg != key_in )
		cnt_jitter <= cnt_jitter + 1'd1;
	else if(cnt_30ms == CNT_30MS_MAX)	
		cnt_jitter <= 6'd0;
	else
		cnt_jitter <= cnt_jitter;
end

//cnt_flag:当第一次抖动时拉高
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		cnt_flag <= 1'b0;
	else if(cnt_jitter == 1'd1)
		cnt_flag <= 1'b1;
	else if(cnt_jitter == 1'd0)
		cnt_flag <= 1'b0;
	else
		cnt_flag <= cnt_flag ;
end

//cnt_30ms:从0计数到30ms的计数器
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		cnt_30ms <= 24'd0;
	else if(cnt_flag) begin
		if(cnt_30ms == CNT_30MS_MAX)
			cnt_30ms <= cnt_30ms;
		else
			cnt_30ms <= cnt_30ms + 1'd1;
	end
	else
		cnt_30ms <= 24'd0;
end

//key_flag:从第一次抖动后30ms输出按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		key_flag <= 1'b1;
	else if((cnt_30ms == CNT_30MS_MAX - 1'd1) && (key_in == 1'b0))
		key_flag <= 1'b0;
	else
		key_flag <= 1'b1;
end

endmodule
//仿真代码
`timescale 1ns/1ns
module tb_key_filter_2();

//parameter define
parameter CNT_30MS_MAX =  24'd70- 1'd1;
parameter 	CNT_1MS  = 20'd19	,//前抖动
			CNT_11MS = 20'd69	,//前抖动
			CNT_41MS = 20'd149	,//后抖动
			CNT_51MS = 20'd199	,//后抖动		
			CNT_60MS = 20'd249	;

//reg define
reg				sys_clk		;
reg				sys_rst_n	;	
reg				key_in		;	
reg		[19:0]	tb_cnt		;	//模拟计数器
//wire define  
wire			key_flag	;


initial begin
	sys_clk		=	1'b0;
	sys_rst_n	<=	1'b0;
	#20
	sys_rst_n	<=	1'b1;
end

always #10 sys_clk	=	~sys_clk	;

//tb_cnt:通过计数器来确定按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin 
	if(!sys_rst_n)
		tb_cnt <= 20'd0;
	else if(tb_cnt == CNT_60MS)
		tb_cnt <= 20'd0;
	else
		tb_cnt <= tb_cnt + 1'd1;
end

//key_in:模拟输入的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin 
	if(!sys_rst_n)
		key_in <= 1'b1;
	else if ((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)	//前抖动
	      ||(tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS)) //后抖动
		key_in <= {$random} % 2;
	else if(tb_cnt > CNT_11MS && tb_cnt < CNT_41MS)		//稳定时间
		key_in <= 1'b0;
	else
		key_in <= 1'b1;
end

key_filter_2 #(
.CNT_30MS_MAX(CNT_30MS_MAX)
)
u_key_filter_2(
	.sys_clk	(sys_clk)	,
	.sys_rst_n	(sys_rst_n)	,
	.key_in		(key_in)	,
	.key_flag	(key_flag)
);

endmodule
  • 仿真波形如下图 (可放大查看)

在这里插入图片描述

  • 第一种方法思路非常简单,就是考虑最差情况,按照最大抖动时间10ms再加上按键稳定时间20ms,所以并不是最优的解决方案。
第二种方法

在这里插入图片描述
如图所示,第二种方法是对输入按键进行检测,当按键稳定达到20ms时且按键为按下时输出一个高电平,这样和方法一比起来效率要高。代码和仿真如下

//rtl code
module key_filter(
	input 		     sys_clk		,
	input			 sys_rst_n		,
	input			 key_in			,
	
	output  reg  	key_flag

);

parameter CNT_MAX = 20'd999_999;

//reg define
reg [19:0]	cnt_20ms;

//cnt_20ms:当按键稳定开始计数999_999次
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		cnt_20ms <= 20'd0;
	else if(key_in == 1'b0) begin			//判断按键是否按下,若是抖动状态就会进入下一个else对计数器进行清零
			if(cnt_20ms == CNT_MAX)
				cnt_20ms <= cnt_20ms;
			else
				cnt_20ms <= cnt_20ms + 1'd1;
	end
	else
		cnt_20ms <= 20'd0;
end

//key_flag:输出过滤后的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
	if(!sys_rst_n)
		key_flag <= 1'b0;
	else if(cnt_20ms == CNT_MAX - 1'd1)
		key_flag <= 1'b1;
	else
		key_flag <= 1'b0;
end

endmodule

//仿真代码
`timescale 1ns/1ns
module key_filter_tb();

//parameter define
parameter 	CNT_1MS  = 20'd19	,//前抖动
			CNT_11MS = 20'd69	,//前抖动
			CNT_41MS = 20'd149	,//后抖动
			CNT_51MS = 20'd199	,//后抖动		
			CNT_60MS = 20'd249	;

//wire  define
wire 		key_flag		;

//reg   define
reg 		sys_clk			;
reg 		sys_rst_n		;
reg 		key_in			;

reg	[19:0]	tb_cnt			;	//模拟计数器
//		main code  

//初始化输入时钟和复位信号
initial begin
	sys_clk 	 = 1'b0;
	sys_rst_n 	<= 1'b0;
	#20
	sys_rst_n 	<= 1'b1;
end

//sys_clk:50mhz的时钟
always #10 sys_clk = ~sys_clk; 

//tb_cnt:通过计数器来确定按键的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin 
	if(!sys_rst_n)
		tb_cnt <= 20'd0;
	else if(tb_cnt == CNT_60MS)
		tb_cnt <= 20'd0;
	else
		tb_cnt <= tb_cnt + 1'd1;
end

//key_in:模拟输入的按键信号
always @ (posedge sys_clk or negedge sys_rst_n) begin 
	if(!sys_rst_n)
		key_in <= 1'b1;
	else if ((tb_cnt >= CNT_1MS && tb_cnt <= CNT_11MS)	//前抖动
	      ||(tb_cnt >= CNT_41MS && tb_cnt <= CNT_51MS)) //后抖动
		key_in <= {$random} % 2;
	else if(tb_cnt > CNT_11MS && tb_cnt < CNT_41MS)		//稳定时间
		key_in <= 1'b0;
	else
		key_in <= 1'b1;
end

//instantiation
key_filter 
#(.CNT_MAX(20'd24)		//修改的值一定要小于(CNT_41MS - CNT_11MS)
)
 u_key_filter(
	. sys_clk	(sys_clk)	,	//input	sys_clk		
	. sys_rst_n	(sys_rst_n)	,	//input	sys_rst_n	
	. key_in	(key_in)	,	//input	key_in	
	
	.key_flag	(key_flag)		//output	key_flag

);

endmodule

仿真波形如下
在这里插入图片描述

补充

这个代码是基于正点原子的开拓者开发板编写的,按键未按下时为高电平,按下为低电平。其他开发板上的按键情况可能与之相反,只需修改一小部分的代码就可以使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值