FPGA 14 按键滤波模块设计实验(有限状态机【FSM】实现)

在这里插入图片描述

FPGA 14 按键滤波模块设计实验(有限状态机【FSM】实现)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdzffPkV-1627398044404)(/img/blog_img/fpga/test.png)]
主要功能: 当按键按下时,我们在按下和松开的时候,不可避免的会出现按键抖动的情况,我们设计了一个按键消抖模块,让按键经过消抖后,输出一个稳定的电平。

实现(设计)流程: 模块设计了处理亚稳态电路,并在内部使用了20ms定时器,对输入的 key_in 信号进行消抖,最后在输出段,得到稳定的输出按键状态的电平(key_state)和按键按下的滤波信号(key_flag)。在模块中使用了常用的FSM来设计整体的按键按下滤波和按键松开滤波的状态转移图。

实验目的 : 了解异步电路的输入信号处理方式、常用的FSM(线性状态机的使用)的简单控制过程,了解状态转移图的使用过程.

按键消抖状态转移图:

在这里插入图片描述

按键消抖我们一共分成4个状态

分别是 : 【IDEL】 【 FITEL】 【 DOWN 】 【FITEL1】

一、第一个状态是 IDEL(空闲状态) —》此时 key_in 的电平是高电平。当我们检测到下降沿的时候,我们进入到第二个状态 STATE = FITEL ,即:抖动滤波状态。在这里,我们还会启动一个20ms的 计时定时器。

二、在20ms内,我们要判断两种状态,在20ms 计时定时器以内,状态机信号 STATE 有两个方向:

① 如果在20ms内出现上升沿,那么就会再次进入STATE = IDEL状态,此时也就表明 这是一个按键按下抖动信号,我们把定时器使能信号关闭,且always块中,让计数器寄存器重新清零。 key_state = 1 , key_flag= 0

②如果到达20ms后,再次检测没有出现上升沿,那么表示这次按键按下状态已经稳定,我们将 STATE = DOWN 即:按键按下稳定状态。并且定时器【使能信号关闭】,always计数器寄存器重新清零。 key_state = 0,key_flag= 1

三、在 STATE = DOWN 状态时,我们【只要】判断是否出现高电平(上升沿)即可,出现高电平,则进入【按键松开】消抖滤波状态,即 STATE = FITEL_1 ,另外,我们还要开启20ms 计时定时器进行滤波 。 key_state = 0,key_flag= 0

四、 在 STATE = FITEL_1 中,我们仍然会出现两种状态

① 20ms内,出现了下降沿,则我们重新重新返回到状态 STATE = DOWN 中,表示这个是一个按键松开的抖动信号,另外定时【使能信号】关闭,计数器寄存器清零。 key_state = 0, key_flag= 0

②到达20ms,key_in 仍然还是高电平,则表示按键已经松开,此时 STATE = IDEL初始状态,此时【使能信号】关闭,计数器寄存器清零 key_state = 1,

key_flag= 1 。 重新开始下一轮的按键检测。

注: 这里有一个输出的key_flag信号,这个时一个脉冲信号,在本次设计的电路中,按键按下成功会出现两次key_flag脉冲信号,分别在按键按下时刻和按键松开时刻出现。也可以考虑设计只有一次key_flag信号,即只要有key_flag脉冲,就代表按键按下(应该也是可以的)。

​ key_state 输出信号可以理解为key_in的输入信号经过滤波得到的,但是呢,在输出的时候时经过了20ms的延时得到的输出滤波信号。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGCJNuNq-1627398044411)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210304120235892.png)]

使用状态机来设计按键消抖实验:

边缘检测电路实际模型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mowB2aMK-1627398044412)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210304121403281.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vVOY5LYo-1627398044413)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210304140945225.png)]

key_filter.v文件

//定义按键函数端口
module key_filter(
	Clk ,
	Rst_n ,
	key_in ,
	
	key_flag, //检测按键成功信号
	key_state //实时的信号
);

	input Clk ;
	input Rst_n ;
	input key_in ;
	
	output reg key_flag ;
	output reg key_state ;
	

	//定义状态机
	localparam
		IDEL   	= 4'b0001 ,
		FILTER0	= 4'b0010 ,
		DOWN 	= 4'b0100 ,
		FILTER1	= 4'b1000 ;
	

	//定义使用到的寄存器
	reg [3:0]state ;
	
	reg key_tmp0 , key_tmp1 ; // key_in 的状态寄存器
	wire pedge , nedge ;
	
		
	reg en_cnt_20ms ;
	reg [19:0]cnt_20ms ;
	reg cnt_20ms_full ;
	
	//对外部输入的异步信号key_in进行同步处理
    //key_in 异步信号,指的是在输入端的输入并不会跟着时钟来发生改变的,更通俗来讲
    // 输入信号的输入在任意一个时钟边沿来保持这个变化,所以,如果在边沿的时候,输入信号key_in 发生了变化
    // 我们就不知道自己得到的是高电平或者低电平,对于这种情况,我们一般的处理方法就是在,输入端增加多级寄存器,将输入信号进行
    // 寄存,从而得到稳定的输入信号
    // 这个always块里面,主要是为了得到稳定状态的输入信号: key_in_sb
	reg key_in_sa,key_in_sb;
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		key_in_sa <= 1'b0;
		key_in_sb <= 1'b0;
	end
	else begin
		key_in_sa <= key_in;
		key_in_sb <= key_in_sa;	
	end
	
	//获取上一个周期的 按键电平 和 当前按键电平
	//目的: 获取,按键状态判断是下降沿还是上升沿到来
	always@(posedge Clk or negedge Rst_n)
	if(Rst_n == 1'b0) begin
		key_tmp0 <= 1'b0 ;
		key_tmp1 <= 1'b0 ;
	end
	else begin //每次时钟上升沿到来,两个寄存器读取上一次的数据的值
		key_tmp0 <= key_in_sb;
		key_tmp1 <= key_tmp0;	
	end	
	
	// 下降沿和上升沿到来标志位信号
	assign nedge = ((~key_tmp0 ) & ( key_tmp1)) ; //判断下降沿是否到来?  
	//  分析::  下降沿到来的清况是高电平-->低电平的变换过程,也就是说:
	//	key_tmp1 存的前1个时刻的电平,(理解这个信号端口很重要)
	// key_tmp0 存的是当前时刻的电平,
	// 要满足该条件,也就是说, key_tmp0 <= 0 且 key_tmp1 <= 1; 此时得到 nedge = 1 ;
	assign pedge = key_tmp0 & (!key_tmp1) ; //判断上升沿是否到来?	
	
	//状态机判断
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n) //复位状态下,状态机处于空闲状态 
		begin	
			state <= IDEL ;		 //复位状态下,状态机处于空闲模式下
			en_cnt_20ms <= 1'b0 ;//消抖计数器关闭
			key_flag  <= 1'b0 ;	 //按键成功标志位清零
			key_state <= 1'b1 ;	 //默认输入按键状态高电平
		end
	else 
	   begin
	 	case(state)
			IDEL :
				begin
					key_flag <= 1'b0 ;	//空闲状态下,按键成功标志位永远为0
					if(nedge == 1'b1) begin		//按键检测到下降沿到来
						state <= FILTER0 ;//进入下一个案件滤波状态
						en_cnt_20ms <= 1 ;//开启使能计数20ms
						end
					else
					   state <= IDEL ;	//未检测到下降沿到来,信号状态保持不变
				end
			FILTER0:
			if(cnt_20ms_full == 1'b1) begin //看消除抖动20ms后,且20ms内没有检测到上升沿到来  = 20ms
					key_flag <= 1'b1;		//表示按键	已经成功按下
					key_state<= 1'b0;		//此时输入按键按下输出状态为低电平
					state <= DOWN ;		//进入下一状态
					en_cnt_20ms <= 0 ;	//20ms计数器关闭				
				end
			else if(pedge == 1'b1) begin	//没有满20ms 以内,有上升沿的到来,表明此时是抖动信号,非按键按下信号
					state <= IDEL ;
					en_cnt_20ms <= 0 ;//计数器关闭
				end
			else	//上述两者情况均为出现,则状态机保持不变
					state <= FILTER0 ;
			DOWN:
				begin 
				key_flag <= 1'b0; //检测到有按键按下,发送一个脉冲信号,由上一个状态的高电平变为低电平
					if(pedge == 1'b1) begin //上一个状态时间超过20ms,此时需要一直等待上升沿的到来(做为按键松开的标志)
						state <= FILTER1 ;	//进入到下一状态
						en_cnt_20ms <= 1'b1 ;//开启下一次20ms 延时计数
					end
					else  //没有,则保持该状态
						state <= DOWN ;	
				end		
			FILTER1:
				if(cnt_20ms_full == 1'b1) begin//表示 20ms计数又一次到来,此时按键稳定
				    key_flag <= 1'b1;		  //表示上升沿的信号稳定的信号,随后到 IDEL又会设置为低电平,产生一个脉冲信号
					key_state<= 1'b1;		 //此时输入按键按下状态为低电平
					en_cnt_20ms <= 1'b0 ; //关闭20ms定时器
					state <=IDEL ;
				end
				else if(nedge)begin	// 20ms内又出现了下降沿,表示这是一次抖动
					en_cnt_20ms <= 1'b0 ;
					state <= DOWN ;
				end
				else	//20ms内状态保持不变
					state <=FILTER1 ;
			default:
				begin
					state <= IDEL ; 
					key_flag <= 1'b0;
					key_state<= 1'b1;     //默认状态时 1 高电平
					en_cnt_20ms <= 1'b0 ; //默认状态时 0 低电平
				end
		endcase
	  end

//	定时器启动 always块
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		cnt_20ms <= 20'd0;
	else if(en_cnt_20ms)
		cnt_20ms <= cnt_20ms + 1'b1;
	else // 关闭时,计时器清零
		cnt_20ms <= 20'd0;
	
	
	//定时器20ms always块 50Mhz 20ms = cnt_20ms : 1_000_000
	always@(posedge Clk or negedge Rst_n)
	if(Rst_n == 1'b0)
		cnt_20ms_full <= 0 ;
	else if(cnt_20ms == 20'd999_999)
		cnt_20ms_full <= 1'b1 ;  //计数值计满标志位
	else
		cnt_20ms_full <= 1'b0 ;	// 未挤满,输出0
		

endmodule

解决亚稳态的两级D触发器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DivpMCU3-1627398044414)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210304142157986.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n9o8WLae-1627398044414)(E:/Blog_Template/source/_posts/img/blog_img/fpga/image-20210304130702655.png)]

仿真测试文件

`timescale 1ns/1ns

`define clk_period 20

module key_filter_tb;

	reg Clk;
	reg Rst_n;
	wire key_in;
	
	wire key_flag;
	wire key_state;
	
	key_filter key_filter0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.key_in(key_in),
		.key_flag(key_flag),
		.key_state(key_state)
	);
	
    key_model key_model0(.key(key_in)); //模块内部编写了相关的信号输出代码
	
	initial Clk= 1;
	always#(`clk_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		#(`clk_period*10) Rst_n = 1'b1;
		#(`clk_period*10 + 1);		
	end

endmodule

仿真测试key_model.v 文件

`timescale 1ns/1ns

module key_model(press,key);
	
	input press;
	output reg key;
	
	reg [15:0]myrand;
	
	initial begin
		key = 1'b1;	
		
//		press_key;	
//		#10000;		
//		press_key;	
//		#10000;		
//		press_key;	
//		#10000;	
//		$stop;
	end


	always@(posedge press)
		press_key;
	
	task press_key;
		begin
			repeat(50)begin
				myrand = {$random}%65536;//0~65535; 产生一个随机数
				#myrand key = ~key;		// 延时 myrand 时间, 输出 key 信号翻转	
			end
			key = 0;
			#25000000;
			
			repeat(50)begin
				myrand = {$random}%65536;//0~65535;
				#myrand key = ~key;			
			end
			key = 1;
			#25000000;		
		end	
	endtask

endmodule


按键输入控制led四个以加减法的形式输出实验top文件:

module led_ctrl(

	Clk ,
	Rst_n ,
	key_flag0,
	key_flag1,
	key_state0,
	key_state1,
	
	led 
);

	input Clk   ;
	input Rst_n ;
	input key_flag0  , key_flag1  ;
	input key_state0 , key_state1 ;
	
	output [3:0]led ;
	
	reg [3:0]led_r  ;
	
	always@(posedge Clk or negedge Rst_n)
	if(Rst_n == 1'b0)
		led_r <= 4'b0000 ;
    else if(key_flag0 && !key_state0)
        //key_flag0 表示的是按键按下的一个标志
        //key_state0 表示的是按键现在所处的状态
        //key_0 按下,led 做加法
		led_r <= led_r + 1'b1 ;
	else if(key_flag1 && !key_state1)
		led_r <= led_r - 1'b1 ;
    	//key_0 按下,led 做减法
	else 
		led_r <= led_r ;
		
	assign led = ~led_r ; 
			
endmodule

本章要学习的内容有:

​ 1、按键的实际状态和状态机的相结合

​ 2、20ms循环定时器中的两个 信号, en_cnt_20ms 是使能计数器, cnt_20ms 寄存器,

我们可以看到,只要我们 使 en_cnt_20ms =0 ,那么在定时器的 always块语句中,cnt_20ms 就会被清零。

所以在顶层状态机设计的时候,cnt_20ms 并未在这里面出现,而是通过控制 en_cnt_20ms 信号达到我们所需要的效果。

​ 3、在信号输入之前,我们又加了两级D触发器,分别是key_in_sa和key_in_sb,我们是使用的非阻塞语句进行操作的,这个是解决亚稳态的两级寄存器。后面跟的key_temp_1 和 key_temp_0是两个时刻的信号状态,为了判断上升沿和下降沿的目的,所以二者功能是不同的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
有限状态机(Finite State Machine,简称FSM)是一种描述系统行为的数学模型,可以用来描述离散事件系统的状态演变。下面是关于FSM设计的一些建议: 1. 定义清晰的状态:在设计FSM时,需要明确各个状态的含义和转换条件,确保状态的定义清晰且不重叠。每个状态应该唯一表示一个系统或对象的具体情况,以便于理解和再现。 2. 考虑全面的事件:FSM的转换依赖于事件的发生,因此需要考虑到系统可能遇到的所有可能事件。对系统可能的输入进行分析,以确保设计FSM可以适应各种情况的变化。 3. 确定状态转换条件:每个状态之间的转换需要定义明确的条件,这些条件通常与特定的输入事件相关。在设计过程中,需要详细考虑这些条件,以确保状态之间的转换符合系统的要求。 4. 简化FSM设计:尽可能地简化FSM设计,避免过度复杂化。可以通过减少不必要的状态和转换来简化FSM,提高系统的可读性和可维护性。 5. 考虑FSM的扩展性:在设计FSM时,应该考虑到系统可能的扩展需求。确保FSM设计具有良好的扩展性和灵活性,以便未来可以方便地进行修改和扩展。 6. 进行充分的测试:设计FSM后,进行充分的测试以验证其正确性。通过提供各种输入和事件的组合,确保FSM能够按照设计预期的方式行为,并正确处理各种情况。 以上是关于FSM设计的一些建议,通过遵循这些建议,可以设计出高效、可靠的FSM,有效地描述系统的行为和状态变化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值