項目簡述
這是樂鑫提前批的一道筆試題,其實不算難,但是想在半小時內完成可能性非常小,所以樂鑫的筆試還是挺難的。
題目:請實現對4x4矩陣式鍵盤的按鍵識別,假設每次都是單按鍵輸入,需要有去抖功能(持續20ms以上被認爲是有效鍵值),模塊時鐘頻率爲1kHz,要求用狀態機實現,定義狀態,畫出狀態轉移圖,並用verilog完整描述該識別模塊。矩陣式鍵盤電路結構參見下圖,其中列線1-4由識別模塊控制輸出,行線5-8爲識別模塊的輸入。
本次實驗所用到的軟硬件環境如下:
1、VIVADO 2019.1
2、Modelsim 10.7
求解過程
首先根據題意寫出狀態,然後畫出狀態轉移圖:
IDLE:自動跳轉到P_FILTER狀態;
P_FILTER:如果四條行線不全爲1,代表有按鍵按下則跳轉到DELAY狀態,沒有按鍵按下則停留在該狀態;
DELAY:計數狀態,計數20ms在這個過程中如果如果四條行線全爲1,則跳轉到P_FILTER狀態,計數到20ms跳轉到SCAN_C0狀態,並將第 0 列拉低,進入 SCAN_C0 狀態進行行掃描;
SCAN_C0:進行列掃描,來判斷該列是否有按鍵被按下, 判斷行輸入是否等於 15,如果不等於則說明該列有按鍵被按下,否則將第 1 列拉低,狀態跳轉到 SCAN_C1狀態進行行掃描;
SCAN_C1: 進行列掃描,來判斷該列是否有按鍵被按下,判斷行輸入是否等於 15,如果不等於則說明該列有按鍵被按下,否則將第 2 列拉低,狀態跳轉到 SCAN_C3狀態進行行掃描;
SCAN_C2: 進行列掃描,來判斷該列是否有按鍵被按下, 判斷行輸入是否等於 15,如果不等於則說明該列有按鍵被按下,否則將第 3 列拉低,狀態跳轉到 SCAN_C3狀態進行行掃描;SCAN_C3: 進行列掃描,來判斷該列是否有按鍵被按下, 判斷行輸入是否等於 15,如果不等於則說明該列有按鍵被按下,狀態跳轉到WAIT_R狀態;
WAIT_R:等待按鍵鬆開;
DELAY_P:等待按鍵鬆開過程穩定輸出;
狀態轉移圖如下:
經過上面的分析,我們便可以書寫FPGA代碼。
FPGA代碼
key模塊:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : [email protected]
// Website : https://blog.csdn.net/zhangningning1996
// Module Name : key.v
// Create Time : 2020-06-16 14:39:12
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module key(
input sclk ,
input rst_n ,
input [ 3:0] key_row ,
output reg [ 3:0] key_col ,
output reg [ 3:0] key ,
output reg key_en
);
//========================================================================================\
//**************Define Parameter and Internal Signals**********************************
//========================================================================================/
parameter DELAY_20MS = 20000-1 ;
parameter IDLE = 9'b000000001;
parameter P_FILTER = 9'b000000010;
parameter DELAY = 9'b000000100;
parameter SCAN_C0 = 9'b000001000;
parameter SCAN_C1 = 9'b000010000;
parameter SCAN_C2 = 9'b000100000;
parameter SCAN_C3 = 9'b001000000;
parameter WAIT_R = 9'b010000000;
parameter DELAY_P = 9'b100000000;
reg [ 8:0] state ;
reg [20:0] cnt_20ms ;
reg [ 3:0] key_row_c0 ;
reg [ 3:0] key_row_c1 ;
reg [ 3:0] key_row_c2 ;
reg [ 3:0] key_row_c3 ;
wire [15:0] key_row_c ;
//========================================================================================\
//************** Main Code **********************************
//========================================================================================/
assign key_row_c = {key_row_c3,key_row_c2,key_row_c1,key_row_c0};
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE : state <= P_FILTER;
P_FILTER: if(key_row != 4'hf)
state <= DELAY;
else
state <= state;
DELAY : if(key_row == 4'hf)
state <= P_FILTER;
else if(cnt_20ms == DELAY_20MS)
state <= SCAN_C0;
else
state <= state;
SCAN_C0 : state <= SCAN_C1;
SCAN_C1 : state <= SCAN_C2;
SCAN_C2 : state <= SCAN_C3;
SCAN_C3 : state <= WAIT_R;
WAIT_R : if(key_row == 4'hf)
state <= DELAY_P;
else
state <= state;
DELAY_P : if(cnt_20ms == DELAY_20MS)
state <= P_FILTER;
else if(key_row != 4'hf)
state <= WAIT_R;
else
state <= state;
default : state <= IDLE;
endcase
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
cnt_20ms <= 21'd0;
else if(state == DELAY || state == DELAY_P)begin
if(cnt_20ms == DELAY_20MS)
cnt_20ms <= DELAY_20MS;
else
cnt_20ms <= cnt_20ms + 1'b1;
end else
cnt_20ms <= 21'd0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c0 <= 4'hf;
else if(state == SCAN_C0)
key_row_c0 <= key_row;
else
key_row_c0 <= key_row_c0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c1 <= 4'hf;
else if(state == SCAN_C1)
key_row_c1 <= key_row;
else
key_row_c1 <= key_row_c1;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c2 <= 4'hf;
else if(state == SCAN_C2)
key_row_c2 <= key_row;
else
key_row_c2 <= key_row_c2;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_row_c3 <= 4'hf;
else if(state == SCAN_C3)
key_row_c3 <= key_row;
else
key_row_c3 <= key_row_c3;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key <= 4'd0;
else case(key_row_c)
16'b1111_1111_1111_1110 : key <= 4'd0;
16'b1111_1111_1111_1101 : key <= 4'd4;
16'b1111_1111_1111_1011 : key <= 4'd8;
16'b1111_1111_1111_0111 : key <= 4'd12;
16'b1111_1111_1110_1111 : key <= 4'd1;
16'b1111_1111_1101_1111 : key <= 4'd5;
16'b1111_1111_1011_1111 : key <= 4'd9;
16'b1111_1111_0111_1111 : key <= 4'd13;
16'b1111_1110_1111_1111 : key <= 4'd2;
16'b1111_1101_1111_1111 : key <= 4'd6;
16'b1111_1011_1111_1111 : key <= 4'd10;
16'b1111_0111_1111_1111 : key <= 4'd14;
16'b1110_1111_1111_1111 : key <= 4'd3;
16'b1101_1111_1111_1111 : key <= 4'd7;
16'b1011_1111_1111_1111 : key <= 4'd11;
16'b0111_1111_1111_1111 : key <= 4'd15;
default : key <= 4'd0;
endcase
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_en <= 1'b0;
else if(state == DELAY_P && cnt_20ms == DELAY_20MS)
key_en <= 1'b1;
else
key_en <= 1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
key_col <= 4'd0;
else case(state)
DELAY : if(cnt_20ms == DELAY_20MS)
key_col <= 4'b1110;
else
key_col <= key_col;
SCAN_C0 : key_col <= 4'b1101;
SCAN_C1 : key_col <= 4'b1011;
SCAN_C2 : key_col <= 4'b0111;
default : key_col <= 4'd0;
endcase
endmodule
測試模塊代碼
key_tb模塊:
`timescale 1ns / 1ps
// *********************************************************************************
// Project Name : OSXXXX
// Author : zhangningning
// Email : [email protected]
// Website : https://blog.csdn.net/zhangningning1996
// Module Name : key_tb.v
// Create Time : 2020-06-16 15:47:58
// Editor : sublime text3, tab size (4)
// CopyRight(c) : All Rights Reserved
//
// *********************************************************************************
// Modification History:
// Date By Version Change Description
// -----------------------------------------------------------------------
// XXXX zhangningning 1.0 Original
//
// *********************************************************************************
module key_tb();
reg sclk ;
reg rst_n ;
reg [ 3:0] Key_Row ;
reg [ 3:0] Key_Row_r ;
reg [15:0] myrand ;
reg [ 1:0] now_col,now_row ; //當前行與當前列
reg key_row_sel ; //行選通信號
wire [ 3:0] Key_Col ;
wire [ 3:0] key ;
wire key_en ;
initial begin
sclk = 1'b0;
key_row_sel <= 1'b0;
rst_n <= 1'b0;
Key_Row <= 4'hf;
#(1000)
rst_n <= 1'b1;
#(10000)
press_key(0,0);
press_key(0,1);
press_key(0,2);
press_key(0,3);
press_key(1,0);
press_key(1,1);
press_key(1,2);
press_key(1,3);
press_key(2,0);
press_key(2,1);
press_key(2,2);
press_key(2,3);
press_key(3,0);
press_key(3,1);
press_key(3,2);
press_key(3,3);
end
always #5 sclk = ~sclk;
key key_inst(
.sclk (sclk ),
.rst_n (rst_n ),
.key_row (Key_Row ),
.key_col (Key_Col ),
.key (key ),
.key_en (key_en )
);
task press_key;
input [1:0]row,col;
begin
key_row_sel = 0;
Key_Row_r = 4'b1111;
Key_Row_r[row] = 0;
now_row = row;
repeat(10)begin //重複 20 次,隨機產生按鍵按下時 20 個不同的行狀態
myrand = {$random} % 6556;
#myrand Key_Row_r[row] = ~Key_Row_r[row];
end
key_row_sel = 1; //將行選擇信號設置爲有效狀態
now_col = col; //獲取當前列狀態
#22000000;
key_row_sel = 0;
Key_Row_r = 4'b1111;
repeat(20)begin //重複 20 次,隨機產生按鍵鬆開時 20 個不同行狀態
myrand = {$random} % 6556;
#myrand Key_Row_r[row] = ~Key_Row_r[row];
end
Key_Row_r = 4'b1111;
#22000000;
end
endtask
always@(*)
if(key_row_sel) //行選擇信號有效
case(now_row) //根據當前行輸出,鍵盤的行信號
2'd0:Key_Row = {1'b1,1'b1,1'b1,Key_Col[now_col]};
2'd1:Key_Row = {1'b1,1'b1,Key_Col[now_col],1'b1};
2'd2:Key_Row = {1'b1,Key_Col[now_col],1'b1,1'b1};
2'd3:Key_Row = {Key_Col[now_col],1'b1,1'b1,1'b1};
endcase
else //保持上一個行狀態
Key_Row = Key_Row_r;
endmodule
上面的測試代碼寫起來還是挺有難度的大家可以好好學習一下。
仿真結果
仿真結果如下:
總結
創作不易,認爲文章有幫助的同學們可以關注、點贊、轉發支持。爲行業貢獻及其微小的一部分。或者對文章有什麼看法或者需要更近一步交流的同學,可以加入下面的羣: