基於FPGA的矩陣鍵盤檢測

項目簡述

這是樂鑫提前批的一道筆試題,其實不算難,但是想在半小時內完成可能性非常小,所以樂鑫的筆試還是挺難的。

題目:請實現對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

上面的測試代碼寫起來還是挺有難度的大家可以好好學習一下。

仿真結果

仿真結果如下:
在這裏插入圖片描述

總結

創作不易,認爲文章有幫助的同學們可以關注、點贊、轉發支持。爲行業貢獻及其微小的一部分。或者對文章有什麼看法或者需要更近一步交流的同學,可以加入下面的羣:
在這裏插入圖片描述

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