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  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章