FPGA实现(按键消抖+矩阵扫描+数码管显示)

一、按键消抖

谈到按键,我们首先应该想到消抖,在一般设计中,认为抖动的总时间会持续20ms以内(这个数值在程序消抖时会用到),具体情况如下图所示:
在这里插入图片描述
硬件消抖
一方面我们可以通过RS触发器来完成消抖:
在这里插入图片描述
但是比较明显,这种电路只适用于单刀双掷的情况,而通常我们使用更多的是两脚或四脚按键,针对此类按键目前常用下面两种电路来实现消抖:
在这里插入图片描述
其原理是利用电阻和电容对波形进行积分,不过硬件方法一般只在按键数量较少时使用,不然会使用到大量的电阻电容;更为经济和简洁的方法是通过编程实现消抖。

软件消抖
对于单按键的消抖模块,其接口如下图所示:
在这里插入图片描述
接口声明功能描述如下:

接口名称I/O功能描述
CLKI50M时钟
nRSTI复位信号
KEY_INI按键输入
KEY_FLAGO按键状态的切换
KEY_STATEO按键状态

软件消抖主要涉及信号跳变沿检测、计数器和状态机

	//======判断按键输入信号跳变沿========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			begin
				key_a <= 1'b0;
				key_b <= 1'b0;
			end
		else
			begin
				key_a <= KEY_IN;
				key_b <= key_a;
			end
	assign flag_H2L = key_b && (!key_a);//下降沿
	assign flag_L2H = (!key_b) && key_a;//上升沿

设计状态机如下:
在这里插入图片描述

当前状态下一个状态跳转条件
Key_upKey_up(!flag_H2L) — 没检测的下降沿
Key_upFilter_Up2Down(flag_H2L) — 检测到下降沿
Filter_Up2DownKey_up(!cnt_full).(flag_L2H) — 还没达到消抖时间就遇到上升沿
Filter_Up2DownFilter_Up2Down(!flag_L2H).(!cnt_full) — 没到达消抖时间也没遇到上升沿
Filter_Up2DownKey_down(cnt_full) — 到达消抖时间
Key_downFilter_Down2Up(flag_L2H) — 遇到上升沿
Key_downKey_down(!flag_L2H) — 没遇到上升沿
Filter_Down2UpKey_down(flag_H2L).(!cnt_full) — 还没达到消抖时间就遇到下升沿
Filter_Down2UpFilter_Down2Up(!flag_H2L).(!cnt_full) — 没到达消抖时间也没遇到下升沿
Filter_Down2UpKey_up(cnt_full) — 到达消抖时间

按键消抖完整模块 KeyPress.v 如下:

module KeyPress(
	CLK,
	nRST,
	KEY_IN,
	KEY_FLAG,
	KEY_STATE
);
	input CLK;
	input nRST;
	input KEY_IN;
	
	output reg KEY_FLAG;
	output reg KEY_STATE;
	
	reg key_a, key_b;
	reg en_cnt, cnt_full;
	reg [3:0]state;
	reg [19:0]cnt;
	wire flag_H2L, flag_L2H;
	
	localparam
		Key_up			=	4'b0001,
		Filter_Up2Down	=	4'b0010,
		Key_down			=	4'b0100,
		Filter_Down2Up	=	4'b1000;
		
	//======判断按键输入信号跳变沿========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			begin
				key_a <= 1'b0;
				key_b <= 1'b0;
			end
		else
			begin
				key_a <= KEY_IN;
				key_b <= key_a;
			end
	assign flag_H2L = key_b && (!key_a);
	assign flag_L2H = (!key_b) && key_a;
	
	//============计数使能模块==========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			cnt <= 1'b0;
		else if(en_cnt)
			cnt <= cnt + 1'b1;
		else
			cnt <= 1'b0;
			
	//=============计数模块=============//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			cnt_full <= 1'b0;
		else if(cnt == 20'd999_999)
			cnt_full <= 1'b1;
		else
			cnt_full <= 1'b0;
	
	//=============有限状态机============//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			begin
				en_cnt <= 1'b0;
				state <= Key_up;
				KEY_FLAG <= 1'b0;
				KEY_STATE <= 1'b1;
			end
		else
			case(state)
				//保持没按
				Key_up: begin 
					KEY_FLAG <= 1'b0;
					if(flag_H2L) begin
						state <= Filter_Up2Down;
						en_cnt <= 1'b1;
					end
					else
						state <= Key_up;							
				end
				//正在向下按	
				Filter_Up2Down: begin
					if(cnt_full) begin
						en_cnt <= 1'b0;
						state <= Key_down;
						KEY_STATE <= 1'b0;
						KEY_FLAG <= 1'b1;
					end
					else if(flag_L2H) begin
						en_cnt <= 1'b0;
						state <= Key_up;
					end
					else
						state <= Filter_Up2Down;
				end
				//保持按下状态
				Key_down: begin
					KEY_FLAG <= 1'b0;
					if(flag_L2H) begin
						state <= Filter_Down2Up;
						en_cnt <= 1'b1;
					end
					else 
						state <= Key_down;
				end
				//正在释放按键
				Filter_Down2Up: begin
					if(cnt_full) begin
						en_cnt <= 1'b0;
						state <= Key_up;
						KEY_FLAG <= 1'b1;
						KEY_STATE <= 1'b1;
					end
					else if(flag_H2L) begin
						en_cnt <= 1'b0;
						state <= Key_down;
					end						
					else
						state <= Filter_Down2Up;
				end
				//其他未定义状态
				default: begin
					en_cnt <= 1'b0;
					state <= Key_up;
					KEY_FLAG <= 1'b0;
					KEY_STATE <= 1'b1;
				end
			endcase	
endmodule

二、键盘扫描

矩阵键盘的原理图如下所示:
在这里插入图片描述
我们将行端口接到FPGA的输出端口,列端口接到FPFA的输入端口。当行端口被全部赋值为低电平之后,如果有某一个键按下则对应的列端口会被拉低,按键所在列被确定下来;然后我们可以先将行端口全部赋值为高电平(则列端口检测到的电平全为高),再依次赋值为低电平,当列端口的电平不全为高时对应的行即为按键所在行。

对于矩阵扫描模块,其接口如下图所示:
在这里插入图片描述

接口声明功能描述如下:

接口名称I/O功能描述
CLKI50M时钟
nRSTI复位信号
KEY_COLI列端口信号
KEY_ROWO行端口信号
KEY_ValueO扫描到的键值
Value_enO键值有效性标识

设计行扫描的状态机如下:
在这里插入图片描述

当前状态下一个状态跳转条件
NO_KEYNO_KEY(!KeyPress:u0).(!KeyPress:u1).(!KeyPress:u2).(!KeyPress:u3)
NO_KEYROW_ONE(!KeyPress:u0).(!KeyPress:u1).(!KeyPress:u2).(KeyPress:u3) + (!KeyPress:u0).(!KeyPress:u1).(KeyPress:u2) + (!KeyPress:u0).(KeyPress:u1) + (KeyPress:u0)
ROW_ONENO_KEY(!KEY_COL[0]) + (KEY_COL[0]).(!KEY_COL[1]) + (KEY_COL[0]).(KEY_COL[1]).(!KEY_COL[2]) + (KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(!KEY_COL[3])
ROW_ONEROW_TWO(KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(KEY_COL[3])
ROW_TWONO_KEY(!KEY_COL[0]) + (KEY_COL[0]).(!KEY_COL[1]) + (KEY_COL[0]).(KEY_COL[1]).(!KEY_COL[2]) + (KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(!KEY_COL[3])
ROW_TWOROW_THREE(KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(KEY_COL[3])
ROW_THREENO_KEY(!KEY_COL[0]) + (KEY_COL[0]).(!KEY_COL[1]) + (KEY_COL[0]).(KEY_COL[1]).(!KEY_COL[2]) + (KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(!KEY_COL[3])
ROW_THREEROW_FOUR(KEY_COL[0]).(KEY_COL[1]).(KEY_COL[2]).(KEY_COL[3])
ROW_FOURNO_KEY

矩阵扫描完整模块 Key_Value.v 如下:

module KeyValue(
	CLK,
	nRST,
	KEY_ROW,
	KEY_COL,
	KEY_Value,
	Value_en
);
	input CLK;
	input nRST;
	input [3:0]KEY_COL;
	output reg Value_en;
	output reg [3:0]KEY_ROW;
	output reg [3:0]KEY_Value;
	
	wire [3:0]key_flag;
	wire [3:0]key_state;
	
	reg [4:0]state;
	reg row_flag;//标识已定位到行
	reg [1:0]rowIndex;
	reg [1:0]colIndex;
	
	localparam
		NO_KEY		=	5'b00001,
		ROW_ONE		=	5'b00010,
		ROW_TWO		=	5'b00100,
		ROW_THREE	=	5'b01000,
		ROW_FOUR	=	5'b10000;
		
	KeyPress u0(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_IN(KEY_COL[0]),
		.KEY_FLAG(key_flag[0]),
		.KEY_STATE(key_state[0])
	);
	
	KeyPress u1(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_IN(KEY_COL[1]),
		.KEY_FLAG(key_flag[1]),
		.KEY_STATE(key_state[1])
	);
	
	KeyPress u2(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_IN(KEY_COL[2]),
		.KEY_FLAG(key_flag[2]),
		.KEY_STATE(key_state[2])
	);
	
	KeyPress u3(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_IN(KEY_COL[3]),
		.KEY_FLAG(key_flag[3]),
		.KEY_STATE(key_state[3])
	);

	//==========通过状态机判断行===========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			begin
				state <= NO_KEY;
				row_flag <= 1'b0;
				KEY_ROW <= 4'b0000;
			end
		else
			case(state)
				NO_KEY: begin
					row_flag <= 1'b0;
					KEY_ROW <= 4'b0000;	
					if(key_flag != 4'b0000) begin
						state <= ROW_ONE;
						KEY_ROW <= 4'b1110;
					end
					else
						state <= NO_KEY;
				end
				
				ROW_ONE: begin
					//这里做判断只能用KEY_COL而不能用key_state
					//因为由于消抖模块使得key_state很稳定
					//不会因为KEY_ROW的短期变化而变化
					//而KEY_COL则会伴随KEY_ROW实时变化
					if(KEY_COL != 4'b1111) begin
						state <= NO_KEY;
						rowIndex <= 4'd0;
						row_flag <= 1'b1;
					end
					else begin
						state <= ROW_TWO;
						KEY_ROW <= 4'b1101;
					end						
				end
				
				ROW_TWO: begin
					if(KEY_COL != 4'b1111) begin
						state <= NO_KEY;
						rowIndex <= 4'd1;
						row_flag <= 1'b1;
					end
					else begin
						state <= ROW_THREE;
						KEY_ROW <= 4'b1011;
					end						
				end
				
				ROW_THREE: begin
					if(KEY_COL != 4'b1111) begin
						state <= NO_KEY;
						rowIndex <= 4'd2;
						row_flag <= 1'b1;
					end
					else begin
						state <= ROW_FOUR;
						KEY_ROW <= 4'b0111;
					end						
				end
				
				ROW_FOUR: begin
					if(KEY_COL != 4'b1111) begin
						rowIndex <= 4'd3;
						row_flag <= 1'b1;
					end
					state <= NO_KEY;
				end
			endcase
	
	//===========判断按键所在列=============//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			colIndex <= 2'd0;
		else if(key_state != 4'b1111)
			case(key_state)
				4'b1110: colIndex <= 2'd0;
				4'b1101: colIndex <= 2'd1;
				4'b1011: colIndex <= 2'd2;
				4'b0111: colIndex <= 2'd3;
			endcase
	
	//===========通过行列计算键值==========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			Value_en <= 1'b0;
		else if(row_flag)
			begin
				Value_en <= 1'b1;
				KEY_Value <= 4*rowIndex + colIndex;
			end
		else
			Value_en <= 1'b0;
			
endmodule

三、数码管显示控制

数码管的连接图如下:
在这里插入图片描述
从图上我们可以看出8个8段数码管通过共阳的方式连接,其位选端段选端分为用8个I/O进行控制。

对于显示控制模块,其接口如下图所示:
在这里插入图片描述
接口声明功能描述如下:

接口名称I/O功能描述
CLKI50M时钟
nRSTI复位信号
KEY_ValueI键盘键值
Value_enI键值有效性标识
SELO位选信号
SEGO段选信号

数码管显示控制完整模块 ShowControl.v 如下:

module ShowControl(
	CLK,
	nRST,
	KEY_Value,
	Value_en,
	SEL,
	SEG
);

	input CLK;
	input nRST;
	input Value_en;
	input [3:0]KEY_Value;
	output reg [7:0]SEL;
	output reg [7:0]SEG;
	
	reg clock_1k;
	reg [14:0]cnt;
	reg [3:0]data_tmp;
	reg [31:0]disp_data;
	
	//=========产生数码管驱动脉冲=======//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			begin
				cnt <= 15'b0;
				clock_1k <= 1'b1;
			end
		else if(cnt == 15'd24_999)
			begin
				cnt <= 15'b0;
				clock_1k <= ~clock_1k;
			end
		else
			cnt <= cnt + 1'b1;
			
	//==========更新要显示的数据=========//
	always @(posedge CLK or negedge nRST)
		if(!nRST)
			disp_data <= 32'd0;
		else if(Value_en)
			disp_data <= {disp_data[27:0],KEY_Value};
		else
			disp_data <= disp_data;
			
	//=============位选控制============//
	always @(posedge clock_1k or negedge nRST)
		if(!nRST)
			SEL <= 8'b1111_1111;
		else if(SEL == 8'b1111_1111)
			SEL <= 8'b1111_1110;
		else
			SEL <= {SEL[6:0],SEL[7]};
		
	//=============段选控制============//
	always @(*)
		case(SEL)
				8'b1111_1110: data_tmp <= disp_data[3:0];
				8'b1111_1101: data_tmp <= disp_data[7:4];
				8'b1111_1011: data_tmp <= disp_data[11:8];
				8'b1111_0111: data_tmp <= disp_data[15:12];
				8'b1110_1111: data_tmp <= disp_data[19:16];
				8'b1101_1111: data_tmp <= disp_data[23:20];
				8'b1011_1111: data_tmp <= disp_data[27:24];
				8'b0111_1111: data_tmp <= disp_data[31:28];
		endcase
	
	//=============段选解析============//
	always @(*)
		case(data_tmp)
			4'h0: SEG <= 8'b11000000;
			4'h1: SEG <= 8'b11111001;
			4'h2: SEG <= 8'b10100100;
			4'h3: SEG <= 8'b10110000;
			4'h4: SEG <= 8'b10011001;
			4'h5: SEG <= 8'b10010010;
			4'h6: SEG <= 8'b10000010;
			4'h7: SEG <= 8'b11111000;
			4'h8: SEG <= 8'b10000000;
			4'h9: SEG <= 8'b10010000;
			4'ha: SEG <= 8'b10001000;
			4'hb: SEG <= 8'b10000011;
			4'hc: SEG <= 8'b11000110;
			4'hd: SEG <= 8'b10100001;
			4'he: SEG <= 8'b10000110;
			4'hf: SEG <= 8'b10001110;
		endcase
endmodule

四、顶层模块

对于顶层模块,其接口如下图所示:
在这里插入图片描述
其接口功能如前文所述-------------------------------------------------------------------

本设计顶层模块完整代码 DigitalTube.v 如下:

module DigitalTube(
	CLK,
	nRST,
	KEY_ROW,
	KEY_COL,
	SEL,
	SEG
);
	input CLK;
	input nRST;
	input [3:0]KEY_COL;
	output [3:0]KEY_ROW;
	output [7:0]SEL; //位选
	output [7:0]SEG; //段选
	
	wire value_en;
	wire [3:0]key_value;
	
	KeyValue keyValue1(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_ROW(KEY_ROW),
		.KEY_COL(KEY_COL),
		.KEY_Value(key_value),
		.Value_en(value_en)
	);
	
	ShowControl showControl1(
		.CLK(CLK),
		.nRST(nRST),
		.KEY_Value(key_value),
		.Value_en(value_en),
		.SEL(SEL),
		.SEG(SEG)
	);
endmodule

参考文献

  • 明德杨《第二代开发板原理图v2》
  • 小梅哥《FPGA自学笔记——设计与验证》
  • 31
    点赞
  • 244
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
FPGA按键消抖实现流水灯的方法是通过在按键输入信号上应用消抖电路来解决按键抖动问题。消抖电路可以使用计数器和状态机的组合来实现。当按键按下时,计数器开始计数,如果在一定的时间内没有检测到按键抬起的信号,那么就认为按键有效,触发流水灯的状态变化。这样可以避免因按键抖动而导致流水灯频繁切换的问题。 在给出的代码中,按键消抖实现是通过计数器cnt来实现的。当按键按下时,计数器开始计数,当计数器达到最大值时,即认为按键有效,触发流水灯的状态变化。同时,在计数器达到最大值之前,如果检测到按键抬起的信号,计数器会被清零,重新开始计数。 具体实现的代码如下: ```verilog module key_ctrl ( input clk, input rst_n, input wire key, output wire \[3:0\] led_on ); parameter MAX = 25'd25_000_000; reg \[25:0\] cnt; //计数寄存器 reg\[3:0\] led_r; //记录0.5s计数器设计 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 26'd0; end else if (cnt == MAX - 1'd1) begin cnt <= 26'd0; end else begin cnt <= cnt + 1'd1; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin led_r <= 4'b0001; end else if(!key) begin if (cnt == MAX - 1'd1) begin led_r <= {led_r\[2:0\], led_r\[3\]}; end else begin led_r <= led_r; end end else begin led_r <= led_r; end end assign led_on = led_r; endmodule ``` 这段代码中,按键消抖实现是通过两个always块来完成的。第一个always块用于计数器的计数和清零,第二个always块用于观察按键状态并触发流水灯的状态变化。通过这种方式,可以实现按键消抖实现流水灯效果。 #### 引用[.reference_title] - *1* [FPGA按键消抖实现流水灯控制](https://blog.csdn.net/Headogerz/article/details/81529807)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGA按键流水灯](https://blog.csdn.net/jynyyhd/article/details/131698029)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【FPGA第二天】按键消抖+流水灯](https://blog.csdn.net/qq_53085291/article/details/130352656)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值