寫在前面的話
在使用按鍵的時候,如果按鍵不多的話,我們可以直接讓按鍵與FPGA相連接,但是如果按鍵比較多的時候,如果還繼續使用直接讓按鍵與FPGA相連接的話,會大量增加FPGA端口的消耗,為了減少FPGA端口的消耗,我們可以把按鍵設計成矩陣的形式。接下來,夢翼師兄將和大家一起學習掃描鍵盤的電路原理以及驅動方式。
項目需求
設計4*4矩陣鍵盤按鍵掃描模塊,正確解析按鍵值。
矩陣鍵盤的原理
由上圖可以知道,矩陣鍵盤的行row(行)與col(列)的交點,都是通過一個按鍵相連接。傳統的一個按鍵一個端口的方法,若要實現16個按鍵,則需要16個端口,而現在這個矩陣鍵盤的設計,16個按鍵,僅僅需要8個端口,如果使用16個端口來做矩陣鍵盤的話,可以識別64個按鍵,端口的利用率遠遠比傳統的設計高好多,所以如果需要的按鍵少的話,可以選擇傳統的按鍵設計,如果需要的按鍵比較多的話,可以采用這種矩陣鍵盤的設計。而我們現在就以掃描法為例來介紹矩陣鍵盤的工作原理。
首先col(列)是FPGA給矩陣鍵盤輸出的掃描信號,而row(行)是矩陣鍵盤反饋給FPGA的輸入信號,用於檢測哪一個按鍵被按下,示意圖如下:
如上圖所示,FPGA給出掃描信號COL[3:0],COL = 4’b0111,等下一個時鍾周期COL = 4’b1011,再等下一個時鍾周期COL = 4’b1101,再等下一個時鍾周期COL = 4’b1110,再等下一個時鍾周期COL = 4’b0111,COL就是這樣不斷循環,給矩陣鍵盤一個低電平有效的掃描信號,當FPGA給矩陣鍵盤COL掃描信號的同時,FPGA也要在檢測矩陣鍵盤給FPGA的的反饋信號ROW,舉個例子,假若矩陣鍵盤中的9號按鍵被按下了:
當COL = 4’b0111,ROW = 4’b1111;
當COL = 4’b1011,ROW = 4’b1111;
當COL = 4’b1101,ROW = 4’b1011;
當COL = 4’b1110,ROW = 4’b1111;
有人問,為什么當COL = 4’b1101的時候,ROW = 4’b1011呢?我們現在就以矩陣鍵盤的電路來分析一下這個原因,如上圖所示:
當9號按鍵被按下的時候,9號按鍵的電路就會被導通,掃描電路COL開始掃描,當掃描到COL[1]的時候,由於9號按鍵的電路被導通了,COL[1]的電壓等於ROW[2]的電壓,所以會出現當COL = 4’b1101的時候ROW = 4’b1011(掃描信號的頻率大概1K左右)。
通常的按鍵所用開關為機械彈性開關,當機械觸點斷開、閉合時,由於機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上穩定地接通,在斷開時也不會一下子斷開。因而在閉合及斷開的瞬間均伴隨有一連串的抖動,為了不產生這種現象而作的措施就是按鍵消抖。
抖動時間的長短由按鍵的機械特性決定,一般為5ms~10ms。這是一個很重要的時間參數,在很多場合都要用到。按鍵穩定閉合時間的長短則是由操作人員的按鍵動作決定的,一般為零點幾秒至數秒(按鍵按下的時間一般都會大於20ms)。鍵抖動會引起一次按鍵被誤讀多次。為確保CPU對按鍵的一次閉合僅作一次處理,必須去除按鍵抖動。在按鍵閉合穩定時讀取按鍵的電平狀態,並且必須判別到按鍵釋放穩定后再作處理。
然后我們就可以利用這些現象,來設計一個識別按鍵的電路。
架構設計
根據原理分析,我們設計出架構圖如下:
模塊功能介紹
模塊名 |
功能描述 |
key_scan |
檢測按鍵值 |
頂層模塊端口描述
端口名 |
端口說明 |
clk |
系統時鍾輸入 |
rst_n |
系統復位 |
row |
矩陣鍵盤的行線 |
data |
按鍵值 |
flag |
按鍵值有效(尖峰脈沖) |
col |
矩陣鍵盤的列線 |
代碼解釋
Key_scan模塊代碼
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function: 檢測出鍵盤矩陣的按鍵值 *****************************************************/ 000 module key_scan ( 001 clk, //系統時鍾輸入 002 rst_n, //系統復位 003 row,//矩陣鍵盤的行線 004 flag,//輸出值有效標志(尖峰脈沖) 005 data,//按鍵值 006 col//矩陣鍵盤的列線 007 ); 008 //系統輸入 009 input clk;//系統時鍾輸入 010 input rst_n;//系統復位 011 input [3:0] row;//矩陣鍵盤的行線 012 //系統輸出 013 output reg flag;//輸出值有效標志(尖峰脈沖) 014 output reg [3:0] data;//按鍵值 015 output reg [3:0] col;//矩陣鍵盤的列線 016 017 reg clk_1K;//1K的時鍾 018 reg [20:0] count;//計數器 019 020 always @ (posedge clk or negedge rst_n) 021 begin 022 if (!rst_n) 023 begin 024 clk_1K <= 1; 025 count <= 0; 026 end 027 else 028 if (count < 24999) // 50000分頻,得出1K的時鍾 029 count <= count + 1; 030 else 031 begin 032 count <= 0; 033 clk_1K <= ~clk_1K; 034 end 035 end 036 037 reg [4:0] cnt_time;//按鍵按下的時間 038 reg [1:0] state;//狀態寄存器 039 reg [7:0] row_col;//按鍵對應的行列值 040 041 always @ (posedge clk_1K or negedge rst_n) 042 begin 043 if (!rst_n)//復位時,將中間寄存器和輸出置0 044 begin 045 flag <= 0; 046 state <= 0; 047 cnt_time <= 0; 048 row_col <= 0; 049 col <= 4'b0000; 050 end 051 else 052 begin 053 case (state) 054 0 : begin 055 if (row != 4'b1111)//當有按鍵按下時,開始計數,只有 056 begin //一直按下20ms才會被當做有效的按鍵 057 if (cnt_time < 19) 058 cnt_time <= cnt_time + 1; 059 else 060 begin 061 cnt_time <= 0; 062 state <= 1; 063 col <= 4'b1110;//掃描的初始值 064 end 065 end 066 else 067 cnt_time <= 0; 068 end 069 070 1 : begin 071 if (row!=4'b1111) 072 begin 073 row_col <= {row,col};//當檢測出來時,把行列線的值存起來 074 flag <= 1; //拉高有效標志 075 state <= 2; 076 col <= 4'b0000;//用於判斷按鍵是否抬起來 077 end 078 else 079 begin 080 col <= {col[2:0],col[3]};//沒有檢測出來時,換成下一列 081 end //掃描 082 end 083 084 2 : begin 085 if (row == 4'b1111)//當按鍵釋放20ms以后才會被當做釋放 086 begin //跳轉到0狀態進行新的按鍵值的檢測 087 if (cnt_time < 19) 088 begin 089 cnt_time <= cnt_time + 1; 090 flag <= 0; 091 end 092 else 093 begin 094 cnt_time <= 0; 095 state <= 0; 096 col <= 4'b0000; 097 end 098 end 099 else 100 begin 101 cnt_time <= 0; 102 flag <= 0; 103 end 104 end 105 106 default : state <= 0; 107 endcase 108 end 109 end 110 111 always @ (*) 112 begin 113 if(!rst_n) 114 begin 115 data =0; 116 end 117 else 118 begin 119 case(row_col) 120 8'b1110_1110: data =0; 121 8'b1110_1101: data =1; //每一個按鍵的位置被行線和列線唯一確定 122 8'b1110_1011: data =2; //根據行線和列線的值給出對應的按鍵值 123 8'b1110_0111: data =3; 124 8'b1101_1110: data =4; 125 8'b1101_1101: data =5; 126 8'b1101_1011: data =6; 127 8'b1101_0111: data =7; 128 8'b1011_1110: data =8; 129 8'b1011_1101: data =9; 130 8'b1011_1011: data =10; 131 8'b1011_0111: data =11; 132 8'b0111_1110: data =12; 133 8'b0111_1101: data =13; 134 8'b0111_1011: data =14; 135 8'b0111_0111: data =15; 136 default : data = 0; 137 endcase 138 end 139 end 140 141 endmodule |
測試代碼
/**************************************************** * Engineer : 夢翼師兄 * QQ : 761664056 * The module function:矩陣鍵盤測試代碼 *****************************************************/ 000 `timescale 1ns/1ps 001 002 module key_scan_tb; 003 //系統輸入 004 reg clk;//系統時鍾輸入 005 reg rst_n;//系統復位 006 reg [3:0] row;//矩陣鍵盤的行線 007 //系統輸出 008 wire flag;//輸出值有效標志(尖峰脈沖) 009 wire [3:0] data;//按鍵值 010 wire [3:0] col;//矩陣鍵盤的列線 011 012 initial 013 begin 014 clk=0; 015 rst_n=0; 016 # 1000.1 rst_n=1; 017 end 018 019 always #10 clk=~clk;//50M的時鍾 020 021 reg [4:0] pnumber;//按鍵值 022 023 initial 024 begin 025 pnumber=16;//無按鍵按下 026 # 6000000 pnumber=1; 027 # 3000000 pnumber=16; 028 # 6000000 pnumber=1; 029 # 3000000 pnumber=16; 030 # 6000000 pnumber=1;//模仿了一段抖動 031 # 3000000 pnumber=16; 032 # 6000000 pnumber=1; 033 # 3000000 pnumber=16; 034 # 6000000 pnumber=1; 035 # 21000000 036 pnumber=1;//按鍵“1”按下了21ms 037 # 3000000 pnumber=16; 038 # 6000000 pnumber=1; 039 # 3000000 pnumber=16; 040 # 6000000 pnumber=1;//模仿釋放時的抖動 041 # 3000000 pnumber=16; 042 # 6000000 pnumber=1; 043 # 3000000 pnumber=16; 044 # 6000000 pnumber=1; 045 # 3000000 pnumber=16; 046 # 60000000 047 pnumber=2; 048 # 3000000 pnumber=16; 049 # 6000000 pnumber=2; 050 # 3000000 pnumber=16; 051 # 6000000 pnumber=2; 052 # 3000000 pnumber=16;//按下時的抖動 053 # 6000000 pnumber=2; 054 # 21000000 055 pnumber=2;//按下21ms 056 # 3000000 pnumber=16; 057 # 6000000 pnumber=2; 058 # 3000000 pnumber=16; 059 # 6000000 pnumber=2; 060 # 3000000 pnumber=16;//釋放時的抖動 061 # 6000000 pnumber=2; 062 # 3000000 pnumber=16; 063 # 6000000 pnumber=2; 064 # 3000000 pnumber=16; 065 end 066 067 //當有按鍵按下時,行線和列線的變化 068 always @(*) 069 case (pnumber) 070 0: row = {1'b1,1'b1,1'b1,col[0]}; 071 1: row = {1'b1,1'b1,1'b1,col[1]}; 072 2: row = {1'b1,1'b1,1'b1,col[2]}; 073 3: row = {1'b1,1'b1,1'b1,col[3]}; 074 4: row = {1'b1,1'b1,col[0],1'b1}; 075 5: row = {1'b1,1'b1,col[1],1'b1}; 076 6: row = {1'b1,1'b1,col[2],1'b1}; 077 7: row = {1'b1,1'b1,col[3],1'b1}; 078 8: row = {1'b1,col[0],1'b1,1'b1}; 079 9: row = {1'b1,col[1],1'b1,1'b1}; 080 10: row = {1'b1,col[2],1'b1,1'b1}; 081 11: row = {1'b1,col[3],1'b1,1'b1}; 082 12: row = {col[0],1'b1,1'b1,1'b1}; 083 13: row = {col[1],1'b1,1'b1,1'b1}; 084 14: row = {col[2],1'b1,1'b1,1'b1}; 085 15: row = {col[3],1'b1,1'b1,1'b1}; 086 16: row = 4'b1111; 087 default: row = 4'b1111; 088 endcase 089 090 //實例化key_scan模塊 091 key_scan key_scan ( 092 .clk(clk), //系統時鍾輸入 093 .rst_n(rst_n), //系統復位 094 .row(row),//矩陣鍵盤的行線 095 .flag(flag),//輸出值有效標志(尖峰脈沖) 096 .data(data),//按鍵值 097 .col(col)//矩陣鍵盤的列線 098 ); 099 100 endmodule |
在測試模塊的中的68行至88行,描述了矩陣鍵盤的響應方式。假如:“5”被按下,“5”處在row[1]和col[1]的位置,只有當col[1]為低電平時,row[1]才能檢測到低電平,並且row=4’b1101唯一確定了按鍵的位置。
在測試中,模擬了數字“1”以及數字“2”按下以及釋放時的抖動。
仿真分析
從波形中,我們可以看出:按鍵穩定前,pnumber有一段抖動,穩定之后,data變成了按鍵值,釋放時pnumber又有一段抖動,兩段抖動data都沒有發生改變。