Bootstrap

独立按键消抖

前言

        独立按键消抖(Debouncing)是指在电子设备中对按键操作信号进行处理,以消除由于机械开关接触不良导致的多次触发信号。机械按键在按下和释放时可能会产生短暂的接触不稳定,导致多个信号被误读。消抖技术可以确保每次按键操作只被识别为一次有效的输入,提高系统的稳定性和可靠性。消抖技术可以防止因为按键抖动产生的重复信号,避免了由于抖动引起的误操作。例如,一个按键被按下时,系统可能会错误地认为按键被按了多次,导致不必要的功能触发。通过消抖处理,按键的响应更加准确和一致,用户在操作设备时不会因为信号错误而感到困惑或不满意,从而提升整体用户体验。常见的消抖方法有硬件消抖(使用电容、RC电路等)和软件消抖(通过编程实现延迟判断、状态监测等)。选择适当的消抖方法取决于具体的应用需求和系统设计。

正文

一、独立按键消抖

        1.项目需求

        利用有限状态机理论,设计并验证对开发板上集成的独立按键进行消抖处理,使其产生一个标志信号flag表示对按键进行一次操作,同时也产生一个使能信号key_en表示按键稳定保持在低电平。

        2技术介绍

        将对按键进行一次操作划分为四个状态,分别为:空闲状态,下抖动状态,稳定状态,上抖动状态。

        空闲到下抖动状态的条件为:检测到按键的电压值变化为0。下抖动状态到稳定状态的条件为:延时计数器cnt计数到最大值:假设我们对按键进行检测的时钟频率为1khz,那么最大值为:9;稳定状态到上抖动状态的条件为:检测到按键的电压值变化为1。上抖动状态到空闲状态的条件为:延时计数器cnt计数到最大值。

状态

执行动作

空闲

表示对按键没有操作

下抖动

表示刚开始对按键进行操作

稳定

标志信号flag只拉高一个驱动时钟周期

上抖动

表示对按键进行一次操作马上结束

        状态转移图为:

        3.顶层架构

        cnt_freq模块为分频器,由于常用开发板时钟频率为50MHz,该时钟频率较快,为确保下抖动状态时不产生flag信号,需要一相对较慢的时钟信号,这里选用1kHz时钟信号。jitter_ctrl为独立按键消抖控制模块,利用三段式有限状态机完成消抖处理。

        4.端口描述

clk开发板时钟
rst_n复位按键(低电平有效)
key目标按键
flag按键有效信号
key_en输出使能(为1时输出有效)

二、代码验证

        cnt_freq模块

module cnt_freq(

	input clk,
	input rst_n,
	
	output reg clk_1khz
);

//计数目标信号半个周期0.5ms
reg [14:0] cnt;
//开发板时钟50MHz,目标信号1KHz
//即50_000个时钟信号产生一个目标信号
//需要的目标信号也是作为下个模块时钟信号
//占空比50%,即记25_000个时钟信号使目标信号电平翻转一次
parameter MAX = 15'd25_000;

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		cnt <= 15'd0;
	else
		if(cnt < MAX - 1)//记25_000个时钟信号
			cnt <= cnt + 15'd1;
		else
			cnt <= 15'd0;
end 

always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		clk_1khz <= 1'b0;
	else
		if(cnt == MAX - 1)
			clk_1khz <= ~clk_1khz;//目标信号电平翻转
		else
			clk_1khz <= clk_1khz;	
end 

endmodule 

        jitter_ctrl模块

//三段式
module jitter_ctrl_v2(

	input clk,
	input rst_n,
	input key,
	
	output reg flag,
	output reg key_en
);

reg [3:0] n_state;
reg [3:0] c_state;

parameter s0 = 4'b0001;//空闲状态
parameter s1 = 4'b0010;//下抖动状态
parameter s2 = 4'b0100;//稳定状态
parameter s3 = 4'b1000;//上抖动状态

reg [3:0] cnt;
//FSM1
always @(posedge clk,negedge rst_n)
begin
	if(rst_n == 0)
		c_state <= s0;
	else
		c_state <= n_state;
end 

//FSM2
always @(*)//描述状态转移
begin
	if(rst_n == 0)
		n_state <= s0;
	else
		case(c_state)
			s0	:	begin
						if(key == 0)//默认按键低电平有效,即=0时按键按下
							n_state <= s1;
						else
							n_state <= s0;
					end 
			s1	:	begin
						if(cnt == 9)//延时计数器判断
							n_state <= s2;
						else
							n_state <= s1;
					end 
			s2	:	begin
						if(key == 1)//按键弹起,状态改变
							n_state <= s3;
						else
							n_state <= s2;
					end 					
			s3	:	begin
						if(cnt == 9)//延时计数器判断
							n_state <= s0;
						else
							n_state <= s3;
					end 		
			default	:	n_state <= s0;
		endcase
end 

//FSM3
always @(posedge clk,negedge rst_n)//状态输出产生flag信号
begin
	if(rst_n == 0)
		begin
			cnt <= 4'd0;
			flag <= 1'b0;
			key_en <= 1'b0;
		end
	else
		case(c_state)
			s0	:	begin 
						cnt <= 4'd0;
						flag <= 1'b0;
						key_en <= 1'b0; 
					end 
			s1	:	begin
						if(cnt < 9)//驱动延时计数器
							cnt <= cnt + 4'd1;
						else
							cnt <= 4'd0;
							
						if(cnt == 9)//延时计数器记满,产生flag
							begin
								flag <= 1'b1;//flag只产生一个时钟周期
								key_en <= 1'b1; //输出使能
							end 
						else
							begin
								key_en <= 1'b0; 
								flag <= 1'b0;
							end 
					end 
			s2	:	begin
						flag <= 1'b0;
						key_en <= 1'b1;
						cnt <= 4'd0;
					end 
			s3	:	begin
						flag <= 1'b0;//状态初始化,准备判断下次按键按下
						key_en <= 1'b0;
						if(cnt < 9)
							cnt <= cnt + 4'd1;
						else
							cnt <= 4'd0;
					end 
			default	:	begin
								cnt <= 4'd0;
								flag <= 1'b0;
								key_en <= 1'b0;
							end 
		endcase 	
end 

endmodule 

        顶层代码

module key_jitter(

	input clk,
	input rst_n,
	input key,
	
	output flag,
	output key_en
);

wire clk_1khz;//定义连线

cnt_freq #(.MAX(5)) cnt_freq_inst(//给分频计数器最大值改5,方便仿真

			.clk(clk),
			.rst_n(rst_n),
			
			.clk_1khz(clk_1khz)
);

jitter_ctrl_v2 jitter_ctrl_inst(

			.clk(clk_1khz),
			.rst_n(rst_n),
			.key(key),
			
			.flag(flag),
			.key_en(key_en)
);

endmodule 

        仿真代码

`timescale 1ns/1ps
module key_jitter_tb;

	reg clk;
	reg rst_n;
	reg key;
	
	wire flag;
	wire key_en;

key_jitter key_jitter_inst(

			.clk(clk),
			.rst_n(rst_n),
			.key(key),
			
			.flag(flag),
			.key_en(key_en)
);

initial clk = 1;
always #10 clk = ~clk;
//模拟时钟周期为20ns,目标时钟由于分频计数器最值改5后,目标时钟变为200ns
initial begin
	rst_n = 0;
	key = 1;
	#200
	rst_n = 1;
	#200
	key = 0;//模拟按键按下
	#240//
	key = 1;//模拟下抖动状态
	#230//延时计数器启动
	key = 0;
	#213
	key = 1;
	#123
	key = 0;//模拟稳定状态
	#3000//230+213+123+1234 = 1800 = 200 * 9
    //即在3000的第1234ns左右时,产生一个flag
	key = 1;//模拟上抖动状态
	#213
	key = 0;
	#123
	key = 1;
	#234
	key = 0;
	#231
	key = 1;
	#3000
	$stop;
end 

endmodule 

三、仿真验证

        运行仿真

        可以看到,有一个flag信号产生,调出中间信号分析:

        flag产生一个时钟周期,满足设计要求

        延时计数器记满9后,进入按键按下稳定状态,并且输出flag。

        即在3000ns的第1234ns左右时,输出一个flag,满足仿真预设。

参考资料

按键消抖

简易按键消抖

;