FPGA的矩阵键盘驱动( 修正版)

以前写的矩阵键盘的驱动是以单片机的思想来实现的,在FPGA上完全失败了,考虑的太简单了emmmmmm。所以在查了一些资料后,对过去写过的矩阵键盘驱动做个修正

旧版本代码

module keyboard_4_4(
	input 					clk,
	input 					rst,
	output reg[3:0] 		c_pin,
	input[3:0] 				r_pin,
	output reg[3:0]		key_out
);
	reg[15:0] 				div_cnt;
	reg[2:0]				state;
	reg						cnt_full;
	localparam				CHECK_R1=3'b000;
	localparam				CHECK_R2=3'b001;
	localparam				CHECK_R3=3'b011;
	localparam				CHECK_R4=3'b010;
	//
	always@(posedge clk or negedge rst)begin//1ms
		if(!rst)begin
			div_cnt <= 16'd0;
			cnt_full <= 1'b0;
		end
		else
			if(div_cnt==16'd49999)begin
				div_cnt <= 16'd0;
				cnt_full <= 1'b1;
			end
			else begin
				div_cnt <= div_cnt + 1'b1;
				cnt_full <= 1'b0;
			end
	end

	always@(posedge cnt_full or negedge rst)begin
		if(!rst)
			state <= CHECK_R1;
		else
			case(state)
				CHECK_R1:
					if(cnt_full)
						state <= CHECK_R2;
					else
						state <= CHECK_R1;
				CHECK_R2:
					if(cnt_full)
						state <= CHECK_R3;
					else
						state <= CHECK_R2;
				CHECK_R3:
					if(cnt_full)
						state <= CHECK_R4;
					else
						state <= CHECK_R3;
				CHECK_R4:
					if(cnt_full)
						state <= CHECK_R1;
					else
						state <= CHECK_R4;
				default:
					state <= state;
			endcase
	end

	always@(posedge clk or negedge rst)begin
		if(!rst)
			c_pin <= 4'b0000;
		else
			case(state)
				CHECK_R1:begin
					c_pin <= 4'b1000;
					case(r_pin)
						4'b1000:key_out <= 4'd0;
						4'b0100:key_out <= 4'd1;
						4'b0010:key_out <= 4'd2;
						4'b0001:key_out <= 4'd3;
					endcase
				end
				CHECK_R2:begin
					c_pin <= 4'b0100;
					case(r_pin)
						4'b1000:key_out <= 4'd4;
						4'b0100:key_out <= 4'd5;
						4'b0010:key_out <= 4'd6;
						4'b0001:key_out <= 4'd7;
					endcase
				end
				CHECK_R3:begin
					c_pin <= 4'b0010;
					case(r_pin)
						4'b1000:key_out <= 4'd8;
						4'b0100:key_out <= 4'd9;
						4'b0010:key_out <= 4'd10;
						4'b0001:key_out <= 4'd11;
					endcase
				end
				CHECK_R4:begin
					c_pin <= 4'b0001;
					case(r_pin)
						4'b1000:key_out <= 4'd12;
						4'b0100:key_out <= 4'd13;
						4'b0010:key_out <= 4'd14;
						4'b0001:key_out <= 4'd15;
					endcase
				end	
				default:begin
					c_pin <= 4'b0000;
					key_out <= 4'd0;
				end
			endcase
	end
endmodule

旧版思想是典型的单片机应用思想:循环给每行置高电平,同时循环检测各列的电平状态。
这种思想在仿真时不会有任何问题,但在实际使用时有很大问题。在FPGA上应用失败的原因在于FPGA的引脚未上拉,在测试时电平有特别大的波动。
改进的方法:要在矩阵键盘上加上拉电阻,阻值10k即可。

我在网上看了小梅哥的一篇,测试也成功了,先贴出来,之后我优化一下代码
原地址:http://bbs.eeworld.com.cn/forum.php?mod=viewthread&tid=510834

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

代码

module Key4x4_Board(
	input 						Clk,
	input 						Rst_n,
	input [3:0] 				Key_Board_Row_i,
	output reg [3:0]			Key_Board_Col_o,
	output reg 					Key_Flag,
	output reg[3:0] 			Key_Value
);
 
	reg [19:0] 					counter1; //延时计数器
	reg  						En_Cnt1; //延时计数器使能控制信号,当进入延时消抖阶段时为高
	reg 						Cnt_Done1; //延时计数器延时完成(计满 20ms)信号
	
	reg [25:0]  				counter2; //连按间隔计数器
	reg 						En_Cnt2; //连按间隔计数器使能控制信号,当进入连按间隔阶段时为高
	reg 						Cnt_Done2; //连按间隔计数器延时完成(计满 200ms)信号
	
	reg [10:0] 					state; //状态寄存器,存储系统的状态
	reg [3:0]					Key_Board_Row_r;//矩阵键盘行输入寄存器,存储行状态
	reg [3:0]					Col_Tmp; //
	reg [7:0]					Key_Value_tmp;//矩阵键盘扫描结果
	reg 						Key_Flag_r; //按键检查成功标志信号
 
	localparam
		IDLE = 11'b00000000001, //空闲态,无按键按下
		P_FILTER = 11'b00000000010, //按下消抖状态
		READ_ROW_P = 11'b00000000100, //读取按下时矩阵键盘行状态
		SCAN_C0 = 11'b00000001000, //扫描矩阵键盘第 0 列(Col0)
		SCAN_C1 = 11'b00000010000, //扫描矩阵键盘第 1 列(Col1)
		SCAN_C2 = 11'b00000100000, //扫描矩阵键盘第 2 列(Col2)
		SCAN_C3 = 11'b00001000000, //扫描矩阵键盘第 3 列(Col3)
		PRESS_RESULT = 11'b00010000000, //获得扫描结果
		WAIT_R = 11'b00100000000, //等待释放信号到来
		R_FILTER = 11'b01000000000, //释放消抖
		READ_ROW_R = 11'b10000000000; //读取释放时矩阵键盘行状态
// 延时计数器,计数以延时 20ms

 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
	counter1 <= 20'd0;
 else if(En_Cnt1)begin
	if(counter1 == 20'd999999)
		counter1 <= 20'd0;
	else
		counter1 <= counter1 + 1'b1;
 end
 else
	counter1 <= 20'd0;
 
//产生延时完成标志信号,(当延时 20ms 时产生一个计数完成信号 Cnt_Done1) 
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
	Cnt_Done1 <= 1'b0;
 else if(counter1 == 20'd999999)
	Cnt_Done1 <= 1'b1;
 else
	Cnt_Done1 <= 1'b0;
 
// 连按间隔计数器,计数以延时 20ms
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
	counter2 <= 26'd49_999_999; //初始时设定间隔为 1s
 else if(En_Cnt2)begin
	if(counter2 == 26'd0)
		counter2 <= 26'd999999; //启动间隔后每 20ms 发出一次间隔标志
	else
		counter2 <= counter2 - 1'b1;
 end
 else
	counter2 <= 26'd49_999_999;
 
//产生连按间隔完成标志信号,(当延时 20ms 时产生一个计数完成信号 Cnt_Done2) 
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)
	Cnt_Done2 <= 1'b0;
 else if(counter2 == 26'd0)
	Cnt_Done2 <= 1'b1;
 else
	Cnt_Done2 <= 1'b0;

//矩阵键盘扫描状态机 
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)begin
	En_Cnt1 <= 1'b0;
	state <= IDLE;
 
//默认需要让列输出为 0,这样,当有按键按下时,行列接通,行输入才能读到有低电平
	Key_Board_Col_o <= 4'b0000;
	Col_Tmp <= 4'd0;
	Key_Flag_r <= 1'b0;
	Key_Value_tmp <= 8'd0;
	Key_Board_Row_r <= 4'b1111;
 end
 else begin
	case(state)
//空闲态,持续判断是否有按键被按下(按键按下时 Key_Board_Row_i 将不为全 1)
 IDLE: 
 if(Key_Board_Row_i != 4'b1111)begin //有按键按下
 En_Cnt1 <= 1'b1; //启动延时计数器
 state <= P_FILTER; //跳转到按下消抖状态
 end
 else begin
 En_Cnt1 <= 1'b0;
 Key_Board_Col_o <= 4'b0000;
 state <= IDLE; 
 end
 
 P_FILTER: //按下消抖状态
 if(Cnt_Done1) begin //20ms 延时完成
 En_Cnt1 <= 1'b0; //停止延时计数器
 state <= READ_ROW_P; //跳转到读取矩阵键盘行输入状态
 end
 else begin //20ms 延时还没有完成
 En_Cnt1 <= 1'b1; //保持延时计数器继续计数
 state <= P_FILTER; 
 end
 
 READ_ROW_P: //读取矩阵键盘行输入状态
 if(Key_Board_Row_i != 4'b1111)begin //行输入不全为 1,表明确实有按键按下
 Key_Board_Row_r <= Key_Board_Row_i; //将当期行输入状态存入行状态寄存器
 state <= SCAN_C0; //跳转到扫描矩阵键盘第 0 列(Col0)状态
 Key_Board_Col_o <= 4'b1110; //将第 0 列列输出置 0
 end

 else begin //若行输入恢复为了全 1,则表明此次为抖动,不再执行扫描,跳回空闲态
 state <= IDLE;
 Key_Board_Col_o <= 4'b0000;
 end
 
 SCAN_C0: //扫描矩阵键盘第 0 列(Col0)状态
 begin
 state <= SCAN_C1; //下一个状态为扫描矩阵键盘第 1 列(Col1)状态
 Key_Board_Col_o <= 4'b1101; //扫描第 1 列对应的行输出为 1101
 if(Key_Board_Row_i != 4'b1111) //行输入不全为 1,表明为当前列(第 0列)有按键按下
 Col_Tmp <= 4'b0001;//将列值存入列寄存器中
 else
 Col_Tmp <= 4'b0000;
 end
 
 SCAN_C1: //扫描矩阵键盘第 1 列(Col1)状态
 begin
 state <= SCAN_C2; //下一个状态为扫描矩阵键盘第 2 列(Col2)状态
 Key_Board_Col_o <= 4'b1011; //扫描第 1 列对应的行输出为 1011
 if(Key_Board_Row_i != 4'b1111) //行输入不全为 1,表明为当前列(第 1列)有按键按下
 Col_Tmp <= Col_Tmp | 4'b0010;//将列值存入列寄存器中
 else
 Col_Tmp <= Col_Tmp; 
 end
 
 SCAN_C2: //扫描矩阵键盘第 2 列(Col2)状态
 begin
 state <= SCAN_C3; //下一个状态为扫描矩阵键盘第 3 列(Col3)状态
 Key_Board_Col_o <= 4'b0111;//扫描第 1 列对应的行输出为 0111
 if(Key_Board_Row_i != 4'b1111)//行输入不全为 1,表明为当前列(第 2列)有按键按下
 Col_Tmp <= Col_Tmp | 4'b0100;//将列值存入列寄存器中
 else
 Col_Tmp <= Col_Tmp; 
 end
 
 SCAN_C3: //扫描矩阵键盘第 3 列(Col3)状态
 begin
 state <= PRESS_RESULT; //下一个状态为得到扫描结果状态
 if(Key_Board_Row_i != 4'b1111)//行输入不全为 1,表明为当前列(第 3列)有按键按下

 Col_Tmp <= Col_Tmp | 4'b1000;//将列值存入列寄存器中
 else
 Col_Tmp <= Col_Tmp; 
 end
 
 PRESS_RESULT: //得到扫描结果状态
 begin
 state <= WAIT_R; //下一个状态为等待按键释放状态
 
 //让列输出全 0,这样,当有按键按下时,行列接通,行输入才能读到有低电平
 Key_Board_Col_o <= 4'b0000;
 
 /*4 位行输入值相加为 3,表明有且只有一个行输入为 0,既保证只有一行中有按键被
按下
 *4 位列扫描结果相加为 1,表明有且只有列中有按键被按下,通过这两个条件保证了
一次
 *按键中只有一个按键被按下时才检测有效*/
 if(((Key_Board_Row_r[0] + Key_Board_Row_r[1] +
 Key_Board_Row_r[2] + Key_Board_Row_r[3]) == 4'd3) &&
 ((Col_Tmp[0] + Col_Tmp[1] + Col_Tmp[2] + Col_Tmp[3]) ==4'd1))begin
 Key_Flag_r <= 1'b1;//产生检测成功标志信号
 Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp};//将行列结果组合得到按键扫描结果
 end
 else begin //若不满足只有一个按键被按下,则不产生检测成功标志信号
 Key_Flag_r <= 1'b0;
 Key_Value_tmp <= Key_Value_tmp;
 end
 end
 
 WAIT_R://等待按键释放状态
 begin
 Key_Flag_r <= 1'b0;//清零检测成功标志信号
 if(Key_Board_Row_i == 4'b1111)begin//无按键按下,表明按键被释放
 En_Cnt1 <= 1'b1;//启动延时计数器
 state <= R_FILTER;//进入释放消抖状态
 En_Cnt2 <= 1'b0; //停止连按间隔计数器
 end
 else begin
 state <= WAIT_R;
 En_Cnt1 <= 1'b0;
 En_Cnt2 <= 1'b1;//启动连按间隔计数器
 end
 end
 
 R_FILTER: //释放消抖状态 
 if(Cnt_Done1) begin //延时时间到
 En_Cnt1 <= 1'b0;
 state <= READ_ROW_R;//跳转入读取按键行输入状态(按键释放阶段)
 end
 else begin
 En_Cnt1 <= 1'b1;
 state <= R_FILTER; 
 end
 
 READ_ROW_R://读取按键行输入状态(按键释放阶段)
 if(Key_Board_Row_i == 4'b1111) //无按键按下,表明确实已经稳定的释放了
 state <= IDLE; //跳转回空闲状态
 else begin //否则表明此次释放为抖动,回到释放消抖状态继续等待
 En_Cnt1 <= 1'b1;
 state <= R_FILTER; 
 end
 default:;
 endcase
 end
//将扫描结果译码输出 
 always@(posedge Clk or negedge Rst_n)
 if(!Rst_n)begin
 Key_Flag <= 1'd0;
 Key_Value <= 4'd0;
 end
 else begin
 
 //按键检查成功输出标志为扫描成功与连按间隔的逻辑或
 Key_Flag <= Key_Flag_r | Cnt_Done2;
 case(Key_Value_tmp)
 8'b1110_0001 : Key_Value <= 4'h1; //第 0 行,第 0 列
 8'b1110_0010 : Key_Value <= 4'h2; //第 0 行,第 1 列
 8'b1110_0100 : Key_Value <= 4'h3; //第 0 行,第 2 列
 8'b1110_1000 : Key_Value <= 4'ha; //第 0 行,第 3 列
 8'b1101_0001 : Key_Value <= 4'h4; //第 1 行,第 0 列
 8'b1101_0010 : Key_Value <= 4'h5; //第 1 行,第 1 列
 8'b1101_0100 : Key_Value <= 4'h6; //第 1 行,第 2 列
 8'b1101_1000 : Key_Value <= 4'hb; //第 1 行,第 3 列

 8'b1011_0001 : Key_Value <= 4'h7; //第 2 行,第 0 列
 8'b1011_0010 : Key_Value <= 4'h8; //第 2 行,第 1 列
 8'b1011_0100 : Key_Value <= 4'h9; //第 2 行,第 2 列
 8'b1011_1000 : Key_Value <= 4'hc; //第 2 行,第 3 列
 
 8'b0111_0001 : Key_Value <= 4'h0; //第 3 行,第 0 列
 8'b0111_0010 : Key_Value <= 4'h0; //第 3 行,第 1 列
 8'b0111_0100 : Key_Value <= 4'h0; //第 3 行,第 2 列
 8'b0111_1000 : Key_Value <= 4'hd; //第 3 行,第 3 列
 default:begin Key_Value <= Key_Value;Key_Flag <= Key_Flag; end
 endcase 
 end
  
endmodule  
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值