FPGA学习笔记(九)------按键消抖

9 篇文章 4 订阅

消抖原因

按键分为两种:

  • 无需消抖:如复位按键,短时间内可以多次触发;

  • 需要消抖:如计数按键,在按下按键的一瞬间,可能已经触发了许多次。
    在这里插入图片描述

消抖方法

硬件消抖

0.1uF电容滤波:
img

  • 104的电容起高频滤波的作用。在按键不多,要求不高时,可以使用。

缺点:增加成本,电路复杂。不如软件消抖好

软件消抖

delay函数

if(key_in == 0)
{
    deley(2000);   //延时20ms
    if(key_in == 0)
    {
         //按键按下,执行相应操作 
     }   
}

  以上,是C中常用的消抖方式。FPGA中可采用计数器延时:sys_clk = 50MHz T=20ns 故计数1_000_000个clk即可,采用FSM设计。

  • FSM( finite state machine)是对具有逻辑规律和时序逻辑的事物的描述。设计如下:

  1、分析状态变量

IDLE:按键空闲状态(由于上拉电阻的作用,按键未被按下时保持高电平);

FILTER_DOWN:按下滤波状态;

DOWN:按下稳定状态;

FILTER_UP:释放滤波状态;

  2、分析状态转移条件,绘制状态转移图

在这里插入图片描述

  3、实现方案

my_key_filter

module	my_key_filter(clk,rst_n,key_in,key_flag,key_state);

	input		clk,rst_n,key_in;
	
	output	reg	key_flag;	//消抖完毕输出脉冲
	output	reg	key_state;	//按键状态输出

	//状态one-hot编码
	localparam				
		IDEL			=	4'b0001,		//空闲状态
		FILTER_DOWN	=	4'b0010,		//按下消抖状态
		DOWN			=	4'b0100,		//按下稳定状态
		FILTER_UP	=	4'b1000;		//释放消抖状态
		
	reg	[3:0]	state;		//nextstate
	reg	key_tmp0,key_tmp1; 
	
	reg	[19:0]	counter;    //需要计数次数1_000_000
	reg	en_counter;				//enble counter
	reg	counter_full;			//20ms flag
	
	wire	pedge,nedge;		//assign	左值必须是一个标量或向量线网
	
	//边沿检测电路
	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;
			key_tmp1 <= key_tmp0;
		end	
		assign	nedge = !key_tmp0 & key_tmp1;		//assign语句瞬时发生(忽略门延时)	
		assign	pedge = key_tmp0 & (!key_tmp1);

// 补充一个边沿检测电路(详见博客链接)
//    always@(posedge clk)
//        key_temp <= key_in;                          //暂存上一个clk按键状态
//    assign key_nedge = (key_temp)&&(!key_in);        //下降沿检测
//    assign key_pedge = (!key_temp)&&(key_in);        //上升沿检测
//		
		
		always @ (posedge clk or negedge rst_n)
			if	(!rst_n) begin
				state <= IDEL;  		//绌洪棽
				en_counter <= 1'b0;
				key_flag	  <= 1'b0;
				key_state  <= 1'b1;
			end
			else begin
				case(state)
					IDEL:
						begin
							key_flag	  <= 1'b0;
							key_state  <= 1'b1;
							if(nedge)begin
								state      <= FILTER_DOWN;
								en_counter <= 1'b1;		
							end
							else
								state      <= IDEL;
						end
						
					FILTER_DOWN:	
						if(counter_full)begin	
							state 	  <= DOWN;
							en_counter <= 1'b0;
							key_flag	  <= 1'b1;		
							key_state  <= 1'b0;		
						end
						else if(pedge)begin
							state      <= IDEL; 
							en_counter <= 1'b0;
						end
						else
							state <= FILTER_DOWN;		
							
					DOWN:
						begin	
								key_flag	  <= 1'b0;
							if(pedge) begin
								state <= FILTER_UP;
								en_counter <= 1'b1;
							end
							else
								state <= DOWN;
						end
			
		         FILTER_UP:
						if(counter_full)begin		
							state 	  <= IDEL;
						 //en_counter <= 1'b0;	//	IDEL清零了
							key_flag	  <= 1'b1;		
							key_state  <= 1'b1;		
						end
						else if(nedge)begin
							state      <= DOWN; 
							en_counter <= 1'b0;
						end
						else
							state <= FILTER_UP;	
					default:
						begin 
							state 	  <= IDEL;
							en_counter <= 1'b0;
							key_flag	  <= 1'b0;
							key_state  <= 1'b1;
						end
				endcase  
			end

//带使能端计数器	delay 20ms	
	always @ (posedge clk or negedge rst_n)
		if(!rst_n)
			counter <= 20'd0;
		else if(en_counter)
			counter <= counter + 1'b1;
		else
			counter <= 20'd0;
	always @ (posedge clk or negedge rst_n)
		if(!rst_n)
			counter_full <= 1'b0; 
		else if(counter == 999_999)
			counter_full <= 1'b1; 
		else
			counter_full <= 1'b0;	
			
endmodule

my_key_filter_tb

`timescale 1ns/1ns

`define	clk_period	20

module	my_key_filter_tb;

	reg	clk,rst_n,key_in;		//对应输入
	
	wire	key_flag,key_state;	//对应输出
	
	my_key_filter my_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 #(`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);key_in = 1'b0;
//一次按下
		#1000; key_in = 1'b1;		//模拟抖动 20ms内
		#2000; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1060; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2000; key_in = 1'b0;
		#20_001_000			//低电平了
		#1000_000			//保持
				 key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;	//释放
		#20_001_000			//高电平了
		#50_001_000;
//二次按下		
		#1000; key_in = 1'b1;		//模拟抖动 20ms内
		#2000; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1060; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2000; key_in = 1'b0;
		#20_001_000			//低电平了
		#1000_000			//保持
				 key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;	//释放
		#20_001_000			//高电平了
		#50_001_000;
//三次按下		
		#1000; key_in = 1'b1;		//模拟抖动 20ms内
		#2000; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1060; key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;
		#2000; key_in = 1'b0;
		#20_001_000			//低电平了
		#1000_000			//保持
				 key_in = 1'b1;
		#2400; key_in = 1'b0;
		#1700; key_in = 1'b1;
		#2570; key_in = 1'b0;
		#1040; key_in = 1'b1;
		#2040; key_in = 1'b0;
		#1000; key_in = 1'b1;	//释放
		#20_001_000			//高电平了
		#50_001_000;
		$stop;	
	end
endmodule

这个文件写的很繁琐,可以进行一下优化,利用$random函数产生随机延时值模拟抖动,用task/endtask将重复代码进行封装。见参考博客。
状态图
可以看到,与我们描述的一致。
在这里插入图片描述

FPGA— 状态机设计实例之独立按键消抖
Verilog实现的多个按键消抖(具体模块可用)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值