Verilog实现按键消抖

目录

  • 1、实验平台
  • 2、实验目的
    • 2.1、实验要求
  • 3、实验流程
    • 3.1、实验原理
    • 3.2、系统架构
    • 3.3、功能模块划分
      • 3.3.1、按键消抖模块
          • 模块框图
          • 信号定义
          • 时序信号图
          • 设计文件
          • 仿真文件
          • 仿真图
      • 3.3.2、LED驱动模块
          • 模块框图
          • 信号定义
          • 信号时序图
          • 设计文件
          • 仿真文件
          • 仿真图
      • 3.3.3、顶层文件
    • 3.4、上板验证
  • 4、总结

1、实验平台

软件:PC、Quartus Prime 18.1、Modelsim 10.5b
硬件:Altera FPGA开发板(EP4CE6E22F17C8)

2、实验目的

  • 1、通过延时方式实现按键消抖
  • 2、状态机demo熟悉
  • 3、状态机实现按键消抖(多位宽)

2.1、实验要求

使用经过消除抖动后的按键信号控制LED呈现两种不同的状态
 a)	流水灯
 b)	闪烁

3、实验流程

3.1、实验原理

根据开发板的原理图,可得到以下资料

独立按键
Verilog实现按键消抖_第1张图片
Verilog实现按键消抖_第2张图片
Verilog实现按键消抖_第3张图片

这类机械按键由于弹簧片的存在,默认处于高电平,按下和释放时都会有一定的抖动时间,在抖动时,其电平状态为不定值,故需要在稳定时再读取信号,通常抖动时间在5~10ms,故设置一个20ms的计数器,用于计时低电平时长。

LED:
Verilog实现按键消抖_第4张图片

根据硬件原理图所示,8颗发光二极管,所有的阳极都接通3.3V的正电压,也即————高电平,所以如果我们想要
发光二极管导通的话,需要在阴极接通低电平,就可以让LED亮起来。

3.2、系统架构

根据系统要求,可以得到以下框架分布

Verilog实现按键消抖_第5张图片

3.3、功能模块划分

根据系统构建,可得到以下模块

3.3.1、按键消抖模块

模块框图

Verilog实现按键消抖_第6张图片

信号定义
信号名 端口类型 数据位宽 信号说明
Clk i 1 输入时钟信号,50MHz
Rst_n i 1 输入复位信号,低电平有效
key i 1 输入按键信号,低电平有效
flag o 1 输出按键消抖信号,低电平有效
时序信号图

Verilog实现按键消抖_第7张图片

设计文件
/*================================================*\
		  Filename ﹕key_filter.v
			Author ﹕Adolph
	  Description  ﹕计数器实现按键消抖
		 Called by ﹕key_top.v
Revision History   ﹕ 2022-5-9 
		  			  Revision 1.0
  			  Email﹕ [email protected]
\*================================================*/
module key_filter(
	input	clk		,//system clock ,50MHz
	input	rst_n	,//system reset ,low valid
	input	key_in	,//key input,high valid
	
	output	key_flag //filter out,high valid
);
	parameter CNT_20MS = 20'd100_0000;//20ms counter value

//20ms counter
	reg [19:0] cnt_delay;
	
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			cnt_delay <= 20'd0;
		else if(!key_in)begin
			if(cnt_delay >= CNT_20MS)
				cnt_delay <= cnt_delay;//just valid once
			else
				cnt_delay <= cnt_delay + 20'd1;
		end
		else
			cnt_delay <= 20'd0;
	end
	
	assign key_flag = (cnt_delay == CNT_20MS - 20'd1);
	
endmodule 
仿真文件
/*================================================*\
		  Filename ﹕tb_key_filter.v
			Author ﹕Adolph
	  Description  ﹕按键消抖测试文件
		 Called by ﹕No file
Revision History   ﹕ 2022-5-9 
		  			  Revision 1.0
  			  Email﹕ [email protected]
\*================================================*/

`timescale 1ns/1ns 

module tb_key_filter();

	reg		tb_clk;
	reg		tb_rst_n;
	reg		tb_key;
	
	wire	tb_flag;
	
	parameter clk_period = 20;
	defparam U_key_filter.CNT_20MS = 1400; // 

	key_filter		U_key_filter(
		/*input	*/.clk		(tb_clk		),//system clock ,50MHz
		/*input	*/.rst_n	(tb_rst_n	),//system reset ,low valid
		/*input	*/.key_in	(tb_key		),//key input,high valid
		
		/*output*/.key_flag	(tb_flag	) //filter out,high valid
	);
	
	initial tb_clk = 1'b0;
	always #(clk_period / 2) tb_clk = ~tb_clk;

	integer i; //整形信号,32-bit
	
	initial begin
		tb_rst_n = 1'b0;
		tb_key = 1'b1;
		#(clk_period * 20 + 3);
		tb_rst_n = 1'b1;

		press_key; //调用任务
		
		#(clk_period * 4000);
		
		press_key;

		$stop;
	end

	task press_key;
		begin
			tb_key = 1'b1;
			repeat(48)begin //模拟前抖动
				i = {$random} % 300;
				#(i * clk_period); tb_key = ~tb_key;
			end
			tb_key = 1'b0;
			#(1500 * clk_period);
			repeat(55)begin //模拟后抖动
				i = {$random} % 300;
				#(i * clk_period); tb_key = ~tb_key;
			end
			tb_key = 1'b1;
			#(3000 * clk_period);
		end
	endtask 
	
endmodule 	
仿真图

在这里插入图片描述

3.3.2、LED驱动模块

模块框图

Verilog实现按键消抖_第8张图片

信号定义
信号名 端口类型 数据位宽 信号说明
Clk i 1 输入时钟信号,50MHz
Rst_n i 1 输入复位信号,低电平有效
key1 i 1 输入按键消抖信号,高电平有效
key2 i 1 输入按键消抖信号,高电平有效
led_o o 8 输出按键消抖信号,低电平有效
信号时序图

Verilog实现按键消抖_第9张图片

设计文件
/*================================================*\
		  Filename ﹕led_driver.v
			Author ﹕Adolph
	  Description  ﹕按键消抖——LED驱动部分
		 Called by ﹕key_top.v
Revision History   ﹕ 2022-5-10 
		  			  Revision 1.0
  			  Email﹕ [email protected]
\*================================================*/
module 	led_driver(
	input  wire			Clk		, //system clock 50MHz
	input  wire 		Rst_n	, //reset ,low valid
		   
	input  wire 		key_in1	, //消抖后的按键信号输入
	input  wire 		key_in2	, //消抖后的按键信号输入
	
	output reg  [07:00]	led_o	  //LED信号输出
);
//Parameter Declarations
	parameter	CNT_MAX	= 24'd1000_0000; //50MHz主频下200ms计数值

//Interrnal wire/reg declarations
	reg		[23:00]	cnt		; //200ms Counter 
	wire			add_cnt ; //Counter Enable
	wire			end_cnt ; //Counter Reset 
	
	reg				flag_1	; //key_in1 ctrl blink led
	reg		[07:00]	r1_led	; //key_in1 ctrl blink led
	reg				flag_2	; //key_in2 ctrl water led	
	reg		[07:00]	r2_led	; //key_in2 ctrl water led
	
//Logic Description
	
	always @(posedge Clk or negedge Rst_n)begin  
		if(!Rst_n)begin  
			cnt <= 24'd0; 
		end  
		else if(add_cnt)begin  
			if(end_cnt)begin  
				cnt <= 24'd0; 
			end  
			else begin  
				cnt <= cnt + 1'b1; 
			end  
		end  
		else begin  
			cnt <= 24'd0;  
		end  
	end 
	
	assign add_cnt = flag_1 || flag_2; 
	assign end_cnt = add_cnt && cnt >= CNT_MAX - 24'd1; 
	
	always @(posedge Clk or negedge Rst_n)begin  
		if(!Rst_n)begin  
			flag_1 <= 1'b0;
			flag_2 <= 1'b0;
		end  
		else begin  
			case({key_in1,key_in2})
				2'b10:begin flag_1 <= 1'b1; flag_2 <= 1'b0; end
				2'b01:begin flag_1 <= 1'b0; flag_2 <= 1'b1; end
				default: ;//当两个按键同时按下,则维持不变
			endcase
		end  
	end //always end
	
	//闪烁
	always @(posedge Clk or negedge Rst_n)begin  
		if(!Rst_n)begin  
			r1_led <= 8'd0;
		end  
		else if(key_in1)begin
			r1_led <= 8'd255;
		end 
		else if(end_cnt)begin  
			r1_led <= ~r1_led;
		end  
		else begin  
			r1_led <= r1_led;
		end  
	end //always end
	
	//流水
	always @(posedge Clk or negedge Rst_n)begin  
		if(!Rst_n)begin  
			r2_led <= 8'd1;
		end  
		else if(key_in2)begin  
			r2_led <= 8'd1;
		end  
		else if(end_cnt)begin
			r2_led <= {r2_led[6:0],r2_led[7]};
		end 
		else begin  
			r2_led <= r2_led;
		end  
	end //always end
	
	always @(posedge Clk or negedge Rst_n)begin  
		if(!Rst_n)begin  
			led_o <= 8'd0;
		end  
		else if(flag_1)begin  
			led_o <= r1_led;
		end  
		else if(flag_2)begin
			led_o <= r2_led;
		end 
		else begin  
			led_o <= led_o;
		end  
	end //always end
	
endmodule 

仿真文件
/*================================================*\
		  Filename ﹕tb_led_driver.v
			Author ﹕Adolph
	  Description  ﹕按键消抖——LED驱动部分 测试文件
		 Called by ﹕No file
Revision History   ﹕ 2022-5-10 
		  			  Revision 1.0
  			  Email﹕ [email protected]
\*================================================*/

`timescale 1ns/1ns 		//仿真系统时间尺度定义

`define clk_period 20  	//时钟周期参数宏定义	

module tb_led_driver(); 

//参数重定义
	defparam U_led_driver.CNT_MAX = 200;

//激励信号定义  
	reg				Clk		; 
	reg				Rst_n	; 
	reg 			key_in1	;
	reg 			key_in2 ;
	
//响应信号定义	  
	wire	[07:00]	led_o	; //	
	
//实例化
	led_driver	U_led_driver(
		/*input  wire		*/.Clk		(Clk	), //system clock 50MHz
		/*input  wire 		*/.Rst_n	(Rst_n	), //reset ,low valid
		
		/*input  wire 		*/.key_in1	(key_in1), //消抖后的按键信号输入
		/*input  wire 		*/.key_in2	(key_in2), //消抖后的按键信号输入
		
		/*output reg [07:00]*/.led_o	(led_o	)  //LED信号输出
	);

//产生时钟							       		 
	initial Clk = 1'b0;		       		 
	always #(`clk_period / 2) Clk = ~Clk;  		 

//产生激励	 
	initial  begin	 
		Rst_n = 1'b0;	 
		key_in1 = 1'b0;
		key_in2 = 1'b0;
		#(`clk_period * 10 + 3);	 
		Rst_n = 1'b1;	 
		#(`clk_period * 10); 
		
		key_in1 = 1'b1; //闪烁灯
		#`clk_period;
		key_in1 = 1'b0;
		#(10 * `clk_period * U_led_driver.CNT_MAX);
		
		key_in2 = 1'b1; //流水灯
		#`clk_period;
		key_in2 = 1'b0;
		#(10 * `clk_period * U_led_driver.CNT_MAX);
		
		key_in1 = 1'b1; //闪烁灯
		#`clk_period;
		key_in1 = 1'b0;
		#(10 * `clk_period * U_led_driver.CNT_MAX);
		
		key_in2 = 1'b1; //流水灯
		#`clk_period;
		key_in2 = 1'b0;
		#(10 * `clk_period * U_led_driver.CNT_MAX);
		
		$stop(2); 
	end	 

endmodule 
仿真图

在这里插入图片描述
在这里插入图片描述

3.3.3、顶层文件

顶层文件在此不作讲解,根据下列RTL视图,相信读者可以很轻易的完成相应代码设计

RTL视图
Verilog实现按键消抖_第10张图片

3.4、上板验证

基于前面的步骤的结束,我们开始上板验证
在quartus的Pin planner 中进行引脚绑定

后面补上

然后进行全编译,待到全编译通过后,连接好开发板,电源线和下载都要连接好,然后打开电源

下载编程文件

Verilog实现按键消抖_第11张图片

如果是第一次使用开发板的童鞋,参看这里更新驱动,切记,前提条件是开发板正确和PC连接,并且已经通电!!!
驱动更新成功后,点击“Start”进行编程,右上角的Progress为下载进度,成功后会有“100% Successful”提示字样,然后在开发板上可以看到相应的效果——流水灯。
Verilog实现按键消抖_第12张图片

4、总结

到这里基本上就结束了,给大家提几点在学习过程中可能会出现的错误
	1、我们的文件名和Module后面的模块名要保持一致,不然在仿真的时候会找不到文件的
	2、reg和wire信号的使用规则一定要分清楚
	3、任何信号在使用之前一定要先声明
	...
然后大家可以在提供的基础代码上进行创新,比如:
	1、使用状态机实现按键消抖
	2、实现一个文件同时进行多位按键消抖
	3、修改LED驱动部分显示效果
	...

你可能感兴趣的:(FPGA学习,fpga开发,verilog设计,Modelsim仿真,testbench编写)