以前寫的矩陣鍵盤的驅動是以單片機的思想來實現的,在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