FPGA 按键控制LED灯亮灭切换实验

一、实验内容

有四个按键,分别控制四个LED灯的亮灭切换。

二、实验说明

1、代码中,使用位宽为4的key_h作为输入,分别连接着四个按键,也即每一位的高低对应着按键的松按。使用位宽为4的led作为输出,分别连接着四个LED,也即每一位的高低对应控制LED的亮灭切换。

2、采集按键状态的时候,采用了这篇文章:FPGA中按键消抖的三种方案 中的方案二来对按键进行消抖。

3、在检测下降沿的时候,使用(前一拍数据)&(后一拍数据取反)的方式进行检测。若结果为1,则说明检测到了下降沿。

三、代码解析

1、RTL代码

//4个独立按键S3/S4/S5/S6的按下与否,对应控制LED D24/D25/D26/D27的亮灭切换。

module at7(
			input sys_clk_i,	//外部输入50MHz时钟信号
			input ext_rst_n,	//外部输入复位信号,低电平有效
			input [3:0] key_h,	//4个独立按键输入,未按下为高电平,按下后为低电平
			output reg[3:0] led		//4个LED指示灯接口	
    		);

	/***********************************************************
			 第一部分:检测按键的上升沿或者下降沿
	************************************************************/

	wire key;	//所有按键值相与的结果,用于按键触发判断
	reg[3:0] keyr;  //按键值key的缓存寄存器

	//四个键中,任何一个被按下,key都会变为0;除非全都未按下,key才为1。
	assign key = key_h[0] & key_h[1] & key_h[2] & key_h[3];

	always @(posedge sys_clk_i or negedge ext_rst_n)
		if (!ext_rst_n) keyr <= 4'b1111;
		else keyr <= {keyr[2:0],key};
	/*
		因为要连续4个时钟才可以将keyr的4位都赋值为出现过的key值,
		并且是使用的最后两个key值,也即keyr的3、4两位,
		所以必须要按键按下至少四个周期才能检测到上升沿和下降沿,
		起到了初步防止误触的效果。
	*/
	wire key_neg = ~keyr[2] & keyr[3];	//有按键被按下	
	wire key_pos = keyr[2] & ~keyr[3];	//有按键被释放

	/***********************************************************
			第二部分:定时计数20ms时间,用于对按键的消抖
	************************************************************/
	
	reg[19:0]  cnt;	

	//按键消抖定时计数器
	always @ (posedge sys_clk_i or negedge ext_rst_n)
		if (!ext_rst_n) cnt <= 20'd0;	
	//不管上升沿还是下降沿都要重新计数,只有当cnt计数到指定值才开始采集此时的键值
	//这么做就是为了实现消抖,只有按键下降沿或者上升沿发生后高低电平稳定20ms,
	//才认为此时的键值是有效的,也即此时才开始采集键值。
		else if(key_pos || key_neg) cnt <= 20'd0;
		else if(cnt < 20'd999_999) cnt <= cnt + 1'b1;
		else cnt <= 20'd0;
	
	reg[3:0] key_halue[1:0];

	//定时采集按键值
	always @(posedge sys_clk_i or negedge ext_rst_n)
		if (!ext_rst_n) begin
			key_halue[0] <= 4'b1111;
			key_halue[1] <= 4'b1111;
		end
		else begin 
			key_halue[1] <= key_halue[0];
			//因为只有当计时到20ms才可以采集键值,
			//所以下一次再次采集键值至少还的是20ms后		
			if(cnt == 20'd999_999) key_halue[0] <= key_h;//定时键值采集
			else ;	
		end
	//对于检测下降沿的信号,(前一clk的数据)&(后一clk的数据取反),结果若为1,
	//则表示检测到了下降沿,否则就没有。此时key_press的四个位对应的四个按键,
	//当为1时表示按键被按下,为0就表示此时按键没有被按下。

	//消抖后按键值变化标志位
	wire[3:0] key_press = key_halue[1] & ~key_halue[0];		

	/***********************************************************
			第三部分:LED亮灭切换控制
	************************************************************/

	always @ (posedge sys_clk_i or negedge ext_rst_n)
		if (!ext_rst_n) led <= 8'hff;
		else if(key_press[0]) led[0] <= ~led[0];
		else if(key_press[1]) led[1] <= ~led[1];
		else if(key_press[2]) led[2] <= ~led[2];
		else if(key_press[3]) led[3] <= ~led[3];
		else ;
  
endmodule

2、仿真程序

`timescale 1ns/1ps

module sim_at7();
	
reg sys_clk_i;	//50MHz时钟信号
reg ext_rst_n;	//复位信号,低电平有效
reg[3:0] key_h;	//4个独立按键输入,未按下为高电平,按下后为低电平
wire[3:0] led;	//4个LED指示灯接口		
	
at7		uut_at7(
			.sys_clk_i(sys_clk_i),	//外部输入50MHz时钟信号
			.ext_rst_n(ext_rst_n),	//外部输入复位信号,低电平有效
			.key_h(key_h),
			.led(led)		//8个LED指示灯接口	
		);			
	
initial begin
	sys_clk_i = 0;
	ext_rst_n = 0;	//复位中
	key_h = 4'b1111;
	#1000;
	@(posedge sys_clk_i); #2;
	ext_rst_n = 1;	//复位结束,正常工作
	@(posedge sys_clk_i); #2;

	//模拟按键抖动(实际未被按下)
	key_h[0] = 1'b0;
	#1_000_000;	//1ms
	key_h[0] = 1'b1;
	#5_000_000;	//5ms	
	key_h[0] = 1'b0;
	#3_000_000;	//3ms	
	key_h[0] = 1'b1;	
	
	//模拟按键动作
	key_h[0] = 1'b0;
	#1_000_000;	//1ms
	key_h[0] = 1'b1;
	#5_000_000;	//5ms	
	key_h[0] = 1'b0;
	#3_000_000;	//3ms	
	key_h[0] = 1'b1;		
	#3_000_000;	//3ms	
	key_h[0] = 1'b0;
	#500_000_000;	//500ms,按下
	key_h[0] = 1'b1;	
	#3_000_000;	//3ms	
	key_h[0] = 1'b0;
	#3_000_000;	//3ms	
	key_h[0] = 1'b1;	
	
	#50_000_000;
	$finish;
end	
	
always #10 sys_clk_i = ~sys_clk_i;	//50MHz时钟产生
	
endmodule

3、仿真结果

仿真结果不好展示,自己去仿真看一下就行。大致现象就是当按键按下的时间超过20ms,也即key_h[0]=0的时间超过20ms的时候,led[0]才会翻转,否则是不会变化的。

posted @ 2020-10-22 23:01  耐心的小黑  阅读(415)  评论(0编辑  收藏  举报