基於FPGA的4x4矩陣鍵盤驅動調試


       好久不見,因為博主最近兩個月有點事情,加上接着考試,考完試也有點事情要處理,最近才稍微閑了一些,這才趕緊記錄分享一篇博文。FPGA驅動4x4矩陣鍵盤。這個其實原理是十分簡單,但是由於博主做的時候遇到了一些有意思的情況,所以我個人覺得值得記錄分享一下。

       首先找了本書看了下矩陣鍵盤的驅動原理,一般來說4x4矩陣鍵盤的原理圖如下,有四根行線和四根列線,行選通和列選通可以確定鍵盤上的一個位置。從原理圖上看出,在沒有操作的情況下,行線上接了一個10K的上拉電阻接vcc,這使得鍵盤在沒有按下時,四根行線始終是高電平。

       列線是由處理器輸入給矩陣鍵盤,空閑狀態下保持為0。也就是行空閑時輸出給處理器為四個1,列空閑時由處理器輸入給四個0。

       當按下按鍵時,比如第一行第一個按鍵,對應的那一行導通輸出為0,即row_data = 0111,此時由處理器逐漸輸入列掃描信號由col_data = 0111——1110,當所按下按鍵為對應的那一行列的按鍵,矩陣鍵盤的行才會導通輸出為0,否則會回到1111。其他按鍵類似,就是利用這個原理來驅動矩陣鍵盤。

 

       最后FPGA部分模塊引腳設計如圖,我們需要對按鍵進行消抖,和普通按鍵一樣,采用20ms的延時對按鍵進行消抖,分為按下消抖和松開消抖,中間的狀態轉移,因為列信號需要輸出判斷行信號的變化,所以狀態機狀態轉移用兩個系統時鍾周期跳轉。采用狀態機進行描述,狀態轉移圖如下。

代碼如下:(點擊閱讀原文查看博客) 

  1 `timescale      1ns/1ps
  2 // *********************************************************************************
  3 // Project Name :       
  4 // Author       : NingHeChuan
  5 // Email        : ninghechuan@foxmail.com
  6 // Blogs        : http://www.cnblogs.com/ninghechuan/
  7 // File Name    : Matrix_Key_Scan.v
  8 // Module Name  :
  9 // Called By    : 
 10 
 11 // Abstract     :
 12 //
 13 // CopyRight(c) 2018, NingHeChuan Studio.. 
 14 // All Rights Reserved
 15 //
 16 // *********************************************************************************
 17 // Modification History:
 18 // Date         By              Version                 Change Description
 19 // -----------------------------------------------------------------------
 20 // 2018/7/28    NingHeChuan       1.0                     Original
 21 //  
 22 // *********************************************************************************
 23 
 24 module Matrix_Key_Scan(
 25     input                   clk,    //50Mhz
 26     input                   rst_n,
 27     input           [3:0]   row_data,
 28     output                  key_flag,
 29     output      reg [3:0]   key_value,
 30     output      reg [3:0]   col_data
 31 );
 32 
 33 //FSM state
 34 parameter       SCAN_IDLE       =   8'b0000_0001;
 35 parameter       SCAN_JITTER1    =   8'b0000_0010;
 36 parameter       SCAN_COL1       =   8'b0000_0100;
 37 parameter       SCAN_COL2       =   8'b0000_1000;
 38 parameter       SCAN_COL3       =   8'b0001_0000;
 39 parameter       SCAN_COL4       =   8'b0010_0000;
 40 parameter       SCAN_READ       =   8'b0100_0000;
 41 parameter       SCAN_JITTER2    =   8'b1000_0000;
 42 //
 43 parameter       DELAY_TRAN      =   2;
 44 parameter       DELAY_20MS      =   1000_000;
 45 //parameter       DELAY_20MS      =   100;//just test
 46 reg     [20:0]  delay_cnt;
 47 wire            delay_done;
 48 //
 49 reg     [7:0]   pre_state;
 50 reg     [7:0]   next_state;
 51 reg     [20:0]   tran_cnt;
 52 wire            tran_flag;
 53 //
 54 reg     [3:0]   row_data_r;
 55 reg     [3:0]   col_data_r;
 56 //
 57 
 58 //-------------------------------------------------------
 59 //delay 20ms
 60 always  @(posedge clk or negedge rst_n)begin
 61     if(rst_n == 1'b0)begin
 62         delay_cnt   <= 'd0;
 63     end
 64     else if(delay_cnt == DELAY_20MS)
 65         delay_cnt <= 'd0;
 66     else if(next_state == SCAN_JITTER1 | next_state == SCAN_JITTER2) begin
 67         delay_cnt <= delay_cnt + 1'b1;
 68     end
 69     else 
 70         delay_cnt <= 'd0;
 71 end
 72 
 73 assign  delay_done = (delay_cnt == DELAY_20MS - 1'b1)? 1'b1: 1'b0;
 74 
 75 
 76 //-------------------------------------------------------
 77 //delay 2clk
 78 always  @(posedge clk or negedge rst_n)begin
 79     if(rst_n == 1'b0)begin
 80         tran_cnt <= 'd0;
 81     end
 82     else if(tran_cnt == DELAY_TRAN)begin
 83         tran_cnt <= 'd0;
 84     end
 85     else 
 86         tran_cnt <= tran_cnt + 1'b1;
 87 end
 88 
 89 assign    tran_flag = (tran_cnt == DELAY_TRAN)? 1'b1: 1'b0;
 90 
 91 
 92 //-------------------------------------------------------
 93 //FSM step1
 94 always  @(posedge clk or negedge rst_n)begin
 95     if(rst_n == 1'b0)begin
 96         pre_state <= SCAN_IDLE;
 97     end
 98     else if(tran_flag)begin
 99         pre_state <= next_state;
100     end
101     else pre_state <= pre_state;
102 end
103 
104 //FSM step2
105 always  @(*)begin
106     next_state = SCAN_IDLE;
107     case(pre_state)
108     SCAN_IDLE:
109         if(row_data != 4'b1111)
110             next_state = SCAN_JITTER1;
111         else 
112             next_state = SCAN_IDLE;
113     SCAN_JITTER1:
114         if(row_data != 4'b1111 && delay_done == 1'b1)
115             next_state = SCAN_COL1;
116         else 
117             next_state = SCAN_JITTER1;
118     SCAN_COL1:
119         if(row_data != 4'b1111)//如果row_data是全1,說明不是列掃描沒有對應到該行
120             next_state = SCAN_READ;
121         else 
122             next_state = SCAN_COL2;
123     SCAN_COL2:
124         if(row_data != 4'b1111)
125             next_state = SCAN_READ;
126         else 
127             next_state = SCAN_COL3;
128     SCAN_COL3:
129         if(row_data != 4'b1111)
130             next_state = SCAN_READ;
131         else 
132             next_state = SCAN_COL4;
133     SCAN_COL4:
134         if(row_data != 4'b1111)
135             next_state = SCAN_READ;
136         else 
137             next_state = SCAN_IDLE;
138     SCAN_READ:
139         if(row_data != 4'b1111)
140             next_state = SCAN_JITTER2;
141         else 
142             next_state = SCAN_IDLE;
143     SCAN_JITTER2:
144         if(row_data == 4'b1111 && delay_done == 1'b1)
145             next_state = SCAN_IDLE;
146         else
147             next_state = SCAN_JITTER2;
148     default:next_state = SCAN_IDLE;
149     endcase
150 end
151 
152 //FSM step3
153 always  @(posedge clk or negedge rst_n)begin
154     if(rst_n == 1'b0)begin
155         col_data <= 4'b0000;
156         row_data_r <= 4'b0000;
157         col_data_r <= 4'b0000;
158     end
159     else if(tran_flag) begin
160         case(next_state)
161         SCAN_COL1:col_data <= 4'b0111;
162         SCAN_COL2:col_data <= 4'b1011;
163         SCAN_COL3:col_data <= 4'b1101;
164         SCAN_COL4:col_data <= 4'b1110;
165         SCAN_READ:begin
166             col_data <= col_data;
167             row_data_r <= row_data;
168             col_data_r <= col_data;
169         end
170         default: col_data <= 4'b0000;
171         endcase
172     end
173     else begin
174         col_data <= col_data;
175         row_data_r <= row_data_r;
176         col_data_r <= col_data_r;
177     end
178 end
179 
180 //這個狀態表明是掃開消完抖動的那一瞬間
181 assign key_flag = (next_state == SCAN_IDLE && pre_state == SCAN_JITTER2 && tran_flag)? 1'b1: 1'b0;
182 
183 //-------------------------------------------------------
184 //decode key_value
185 always  @(posedge clk or negedge rst_n)begin
186     if(rst_n == 1'b0)begin
187         key_value <= 'd0; 
188     end
189     else if(key_flag == 1'b1)begin
190         case({row_data_r, col_data_r})
191         8'b0111_0111: key_value <= 4'h1;  
192         8'b0111_1011: key_value <= 4'h2;
193         8'b0111_1101: key_value <= 4'h3;
194         8'b0111_1110: key_value <= 4'ha;
195         8'b1011_0111: key_value <= 4'h4;
196         8'b1011_1011: key_value <= 4'h5;
197         8'b1011_1101: key_value <= 4'h6;
198         8'b1011_1110: key_value <= 4'hb;
199         8'b1101_0111: key_value <= 4'h7;
200         8'b1101_1011: key_value <= 4'h8;
201         8'b1101_1101: key_value <= 4'h9;
202         8'b1101_1110: key_value <= 4'hc;
203         8'b1110_0111: key_value <= 4'hf;
204         8'b1110_1011: key_value <= 4'h0;
205         8'b1110_1101: key_value <= 4'he;
206         8'b1110_1110: key_value <= 4'hd;
207         default     : key_value <= key_value;
208         endcase
209     end
210     else 
211         key_value <= key_value;
212 end
213 
214 
215 endmodule
Matrix_Key_Scan

       代碼部分其實沒啥好說的,有意思的是博主連接硬件做調試的時候,博主的矩陣鍵盤模塊如圖,薄膜鍵盤。某寶客服連原理圖都沒有,有一家給的我原理圖還是錯的。問題在於代碼第一次下載到板子上的時候,沒有出結果,不知道是代碼問題還是硬件電路連接問題,我也是試了好久才猜出來正確的連接順序。

 

       這是,某寶客服給的錯的原理圖,先拿這個看一下,和這個矩陣鍵盤的構造差不多,從圖中可以看到這個圖和文章開頭的原理圖中少了點什么,上拉電阻。這里比較迷惑的是,如果沒有上拉電阻,在空閑狀態下,row_data怎么能保持輸出高電平呢。問了幾個客服,有說不懂的有說不用加上拉電阻的。

  我直接上板調試,在鍵盤的基礎上加了個數碼管顯示按下的數值。按下時發現是可以顯示正確的數字的,但是奇怪的是過一會兒數碼管顯示會清零。最開始以為是代碼的問題,檢查后從仿真和邏輯看,按鍵后譯碼的數值其實是一直保持不變的,沒有操作是不會發生變化的,但實際情況不太符合。

  這樣奇怪的情況發生,這個時候我們要相信科學。這種仿真發現不了問題,但實際運行卻又bug,這個沒法猜出來。在線邏輯分析儀就可以看到你的代碼在開發板上運行的情況,這里引出Xilinx的Chipscope,用在線邏輯分析儀幾乎可以抓到你的代碼內部的所有信號,這個時候抓到的是你的電路實際運行的情況,配置流程如下。

 

新建New source界面,選擇如圖Chipscope,next。

 

然后會自動生成一個后綴為.cdc的文件,雙擊打開。

 

這一步點擊next

同樣next

這里選擇,觸發信號的數量和位寬,我這里選擇了三個觸發信號,兩個位寬為4,對應矩陣鍵盤的行和列,一個位寬為1,為復位信號。最后邊的滾輪下拉可以看到全部信號。

這里設置抓取的信號深度,選擇上升沿采樣信號。完成后點擊next

這里選擇時鍾信號clk

選擇后點擊make connection,OK。

同樣的選擇其他觸發信號,加入行和列和復位信號。

添加完成OK

點擊完成退出。

 

保存

這里點擊這里就會啟動Chipscope了,這個時候板子就可以上電了。

點擊鏈接板子

照圖點擊下載板子。

配置相關文件

然后會彈出這個窗口,這里可以設置觸發類型和觸發方式,添加的信號都會顯示出來

設置觸發方式為M2,即復位信號。

點擊上面的按鈕開始運行,復位鍵釋放,就可以抓取到一部分信號了。

按下一個按鍵會看到對應的行列變換。

       這是Chipscope的調用流程,通過在線邏輯分析儀,博主發現了問題,在空閑無操作時,觸發復位抓取信號,抓到的row_data有時候是1111。有時候是0000或其他,但是理論上矩陣鍵盤在無操作下應該一直row_data輸出1111。就是這個奇怪的問題導致的錯誤。我們要相信科學。應該是硬件電路的問題,檢查了與開發板連接的杜邦線沒問題后,應該就是矩陣鍵盤自己的問題,上拉電阻這塊的原理,我所使用的矩陣鍵盤沒有上拉電阻,但是實際上這樣的驅動,如果row_data線上沒有上拉電阻,它很難保持為高電平,而這個地方加不加其實和驅動開發板的構造有關,據我了解,有些單片機的I/O引腳會內置上拉電阻,默認情況下是高電平,所以用這些單片機驅動是不需要加上拉電阻的。

       我這里使用FPGA驅動,FPGA的引腳特性來說,還是需要加的,使矩陣鍵盤的信號輸出穩定,對於Xilinx FPGA來說有意思的是,通過綜合工具添加引腳約束可以啟動同樣的效果,比如在ucf文件的引腳電平約束中加上pullup就可以了。

       由於我使用的Spartan-3E系列的開發板,從它手冊上可以得到。在引腳約束在電平為3.3v時加上pull up,可以等下出相當於10.8k歐姆的電阻這和矩陣鍵盤的驅動原理是完全相符。

       這篇博文主要分享的是硬件的一個調試過程,Chipscope還是很好用的。對於硬件來說,你沒辦法確定他的狀態,所以使用工具抓取他的實際信號,幫助我們更好調試。

 

轉載請注明出處:NingHeChuan(寧河川)

個人微信訂閱號:開源FPGA

如果你想及時收到個人撰寫的博文推送,可以掃描左邊二維碼(或者長按識別二維碼)關注個人微信訂閱號

知乎ID:NingHeChuan

微博ID:NingHeChuan

原文地址:https://www.cnblogs.com/ninghechuan/p/9401744.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM