7.EP4CE10F17的矩阵键盘

前记


                                                                                                                                                           师夷长技以自强

1.基本原理

1.1键盘模块以及与FPGA的连接

矩阵键盘常用在IO端口少而需要的按键又多的场合,学习过单片机的读者应该都知道矩阵键盘是采用列(行)扫描的方式读取输入的按键的。矩阵键盘模块中每一行(列)按键都有一根导线连起来,在开发板上采用的是列扫描的方式,也就是列线作为FPGA的输出端,行线作为FPGA的输入端,行线接上拉电阻默认情况是高电平。电路如下所示

1.2状态机设计

为了便于描述,先定义几个变量

变量含义
[3:0]Row_Data对应矩阵键盘的行线
[3:0]Col_Data对应矩阵键盘的列线
Key_Flag当确有按键按下时,输出一个时钟的高电平
[3:0]Key_Value按键的值,对应于16个按键
Clk系统时钟输入
Rst_n复位信号
[3:0]Row_Data_R对Row_Data信号的寄存
[3:0]Col_Data_R对Col_Data信号的寄存
  

模块的输入输出脚应如下所示,

如果不考虑按键的消抖,那么可以把状态分成如下几个

状态解释
IDLE空闲状态,当Row_Data!=4'b1111时应该跳转到SC_C0状态
SC_C0扫描第0列的状态,获取第0列按键的按下情况。若此时Row_Data!=4'b1111,说明有按键按下,应更新Col_Data_R对应位为1。
SC_C1扫描第1列的状态,获取第1列按键的按下情况。若此时Row_Data!=4'b1111,说明有按键按下,应更新Col_Data_R对应位为1。
SC_C2扫描第2列的状态,获取第2列按键的按下情况。若此时Row_Data!=4'b1111,说明有按键按下,应更新Col_Data_R对应位为1。
SC_C3扫描第3列的状态,获取第3列按键的按下情况。若此时Row_Data!=4'b1111,说明有按键按下,应更新Col_Data_R对应位为1。
GET_RESULT得到按键按下结果的状态。此时应确保仅有一行一列的按键被按下。

状态转移图如下所示:

 

但是机械按键难免会有抖动,并且如果连续输入一个按键的值时,用户往往会按定某个按键一段时间。因此目前还需要键入按下消抖检测,松手检测,连按检测。因次需添加下面的几个状态:

状态解释
P_FILTER按下消抖状态。通过计数器计数20ms,如果计时到了就转到RED_ROW_P状态。
READ_ROW_P读按下时行数据状态。如果Row_Data!=4'b1111,则把此时的行数据寄存下来,否则为抖动,应跳转至IDEL。
WAIT_R等待按键释放状态。如果Row_Data == 4'b1111,则跳转到R_FILTER状态。此状态应该启动间隔计数器Cnt_EN2 = 1,用于处理用户连输输入某一个按键的情况。
R_FILTER松手消抖状态。通过计数器计数20ms,如果计时到了就转到RED_ROW_R状态。
READ_ROW_R读松手时行数据状态。如果Row_Data==4'b1111,则跳转到IDLE状态,否则为抖动,应跳转至R_FILTER。

需要增加两个计数器,一个是关于抖动的检测,计时值为20ms,而系统时钟周期为20ns,计数值为0-999_999,需要一个[19:0]Counter_F;另一个是关于连按间隔的检测,计时值为200ms,计数值应该是0-9_999_999,需要一个[23:0]Counter_W。

最终得到的状态转移图如下所示

2.编写代码

整个模块的代码可以分为四部分:

(1)用于消抖计时的计数器和用于连按计时的计数器。

(2)状态机的编写。

(3)信号的处理。

(4)译码部分。

module MAT_KEY(
    Clk,
    Rst_n,
    Row_Data,
    Col_Data,
    Key_Flag,     //按键值是否有效
    Key_Value    //按键值
);
    input Clk;
    input Rst_n;
    input [3:0]Row_Data;
    output reg [3:0]Col_Data;
    output reg Key_Flag;
    output reg [3:0]Key_Value;
    
    reg [3:0]Row_Data_R; //数据缓存
    reg [3:0]Col_Data_R;
    reg [19:0]Counter_F; //定时器1
    reg Cnt_F_En;
    reg Cnt_F_Done;
    reg [24:0]Counter_W; //定时器2,连按
    reg Cnt_W_En;
    reg Cnt_W_Done;
    
    /*1.两个定时器的实现*/
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        Counter_F <= 20'd0;
    else if(Cnt_F_En)
        if(Counter_F == 20'd999_999)
            Counter_F <= 20'd0;
        else
            Counter_F <= Counter_F + 20'd1;
    else
        Counter_F <= 20'd0;
    
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        Cnt_F_Done <= 0;
    else if(Counter_F == 20'd999_999)
        Cnt_F_Done <= 1;
    else
        Cnt_F_Done <= 0;

    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        Counter_W <= 25'd0;
    else if(Cnt_W_En)
        if(Counter_W == 25'd24_999_999)
            Counter_W <= 25'd0; 
        else
            Counter_W <= Counter_W + 25'd1;
    else
        Counter_W <= 25'd0;
    
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        Cnt_W_Done <= 0;
    else if(Counter_W == 25'd24_999_999)
        Cnt_W_Done <= 1;
    else
        Cnt_W_Done <= 0;

    /*2.状态机*/
    localparam IDLE       = 11'b00000000001,
                  P_FILTER   = 11'b00000000010,
                  READ_ROW_P = 11'b00000000100,
                  SC_C0      = 11'b00000001000,
                  SC_C1      = 11'b00000010000,
                  SC_C2      = 11'b00000100000,
                  SC_C3      = 11'b00001000000,
                  GET_RESULT = 11'b00010000000,
                  WAIT_R     = 11'b00100000000,
                  R_FILTER   = 11'b01000000000,
                  READ_ROW_R = 11'b10000000000;
    reg [10:0]State;
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)
        State <= IDLE;
    else 
        case(State)
            IDLE:
                if(Row_Data != 4'b1111)
                    State <= P_FILTER;
                else
                    State <= State;
            P_FILTER:
                if(Cnt_F_Done == 1)
                    State <= READ_ROW_P;
                else
                    State <= State;
            READ_ROW_P:
                if(Row_Data ==    4'b1111)
                    State <= IDLE;
                else
                    State <= SC_C0;
            SC_C0:
                State <= SC_C1;
            SC_C1:
                State <= SC_C2;
            SC_C2:
                State <= SC_C3;
            SC_C3:
                State <= GET_RESULT;
            GET_RESULT:
                State <= WAIT_R;
            WAIT_R:
                if(Row_Data == 4'b1111)
                    State <= R_FILTER;
                else
                    State <= State;
            R_FILTER:
                if(Cnt_F_Done)
                    State <= READ_ROW_R;
                else    
                    State <= State;
            READ_ROW_R:
                if(Row_Data != 4'b1111)
                    State <= WAIT_R;
                else
                    State <= IDLE;
            default:
                State <= IDLE;
        endcase
        
    /*3.信号的处理*/
    reg [7:0]Key_Value_R;
    reg Key_Flag_R;
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)begin
        Cnt_F_En <= 0;
        Cnt_W_En <= 0;
        Col_Data <= 4'b0000;
        Key_Flag_R <= 0;
    end
    else 
        case(State)
            IDLE:
                begin
                    Cnt_F_En <= 0;     //关闭消抖定时器
                    Cnt_W_En <= 0;         //关闭连按定时器
                    Col_Data <= 4'b0000;//列输出全低,检测所有列的按键按下情况
                    Key_Flag_R <= 0;
                end
            P_FILTER:
                begin
                    Cnt_F_En <= 1;     //开启消抖定时器
                end
            READ_ROW_P:
                begin
                    Cnt_F_En <= 0;     //关闭消抖定时器
                    Row_Data_R <= Row_Data;//寄存行输入数据
                    if(Row_Data !=    4'b1111)begin
                        Col_Data <= 4'b1110;
                        Col_Data_R <= 4'b0000;
                    end
                end
            SC_C0:
                begin
                    Col_Data <= 4'b1101;
                    if(Row_Data != 4'b1111)
                        Col_Data_R <= Col_Data_R | 4'b0001;
                end
            SC_C1:
                begin
                    Col_Data <= 4'b1011;
                    if(Row_Data != 4'b1111)
                        Col_Data_R <= Col_Data_R | 4'b0010;
                end
            SC_C2:
                begin
                    Col_Data <= 4'b0111;
                    if(Row_Data != 4'b1111)
                        Col_Data_R <= Col_Data_R | 4'b0100;
                end
            SC_C3:
                begin
                    if(Row_Data != 4'b1111)
                        Col_Data_R <= Col_Data_R | 4'b1000;
                end
            GET_RESULT:
                begin
                    if((Row_Data_R[0]+Row_Data_R[1]+Row_Data_R[2]+Row_Data_R[3])==4'd3   //只有一行按下
                        &&(Col_Data_R[0]+Col_Data_R[1]+Col_Data_R[2]+Col_Data_R[3])==4'd1)//只有一列按下
                        begin
                            Key_Value_R <= {Row_Data_R,Col_Data_R};
                            Key_Flag_R <= 1;
                        end
                end
            WAIT_R:
                begin
                    Key_Flag_R <= 0;
                    if(Row_Data != 4'b1111)
                        Cnt_W_En <= 1;            //开启连按计时器
                    else
                        Cnt_W_En <= 0;    
                end
            R_FILTER:
                begin
                    Cnt_F_En <= 1;     //开启消抖定时器
                end
            READ_ROW_R:;
            default:;
        endcase
    
    
    /*4.译码电路*/
    always@(posedge Clk,negedge Rst_n)
    if(!Rst_n)begin
        Key_Flag <= 1'd0;
        Key_Value <= 4'd0;
    end
    else begin
        Key_Flag <= Key_Flag_R | Cnt_W_Done;
        case(Key_Value_R)
            8'b1110_0001: Key_Value <= 4'd0; //(0,0)
            8'b1110_0010: Key_Value <= 4'd1; //(0,1)
            8'b1110_0100: Key_Value <= 4'd2;    //(0,2)
            8'b1110_1000: Key_Value <= 4'd3;    //(0,3)
            8'b1101_0001: Key_Value <= 4'd4; //(1,0)
            8'b1101_0010: Key_Value <= 4'd5; //(1,1)
            8'b1101_0100: Key_Value <= 4'd6;    //(1,2)
            8'b1101_1000: Key_Value <= 4'd7;    //(1,3)
            8'b1011_0001: Key_Value <= 4'd8; //(2,0)
            8'b1011_0010: Key_Value <= 4'd9; //(2,1)
            8'b1011_0100: Key_Value <= 4'd10;//(2,2)
            8'b1011_1000: Key_Value <= 4'd11;//(2,3)
            8'b0111_0001: Key_Value <= 4'd12;//(3,0)
            8'b0111_0010: Key_Value <= 4'd13;//(3,1)
            8'b0111_0100: Key_Value <= 4'd14;//(3,2)
            8'b0111_1000: Key_Value <= 4'd15;//(3,3)
            default:Key_Value <= Key_Value;
        endcase
    end
endmodule
 

为了在modelsim下仿真时序,需要设计一个仿真矩阵键盘按下的模型。这里仿真的过程为按行一次按下16个按键。使用$random模拟矩阵键盘按下时的抖动,使用task编写一次按下所发出的时序。矩阵键盘的状态大体上可以分为真正有键按下和空闲的状态,如果有按下则按下的行输出的数据为按下列的数据,可高可低。当矩阵键盘处于空闲状态(包括抖动状态)时,输出的数据应该为4’b1111或者所模拟的键盘抖动的数据。具体的代码如下,

`timescale 1ns/1ns
module MAT_KEY_MODEL(
    Col_Data,
    Row_Data
);
    input [3:0]Col_Data;
    output reg [3:0]Row_Data;
    
    reg [15:0]myrand;
    reg [3:0]Row_Data_r; //行寄存器
    reg Row_Sel;             //真正被按下
    reg [1:0]Now_Row;             //当前按下的行
    reg [1:0]Now_Col;             //当前按下的列
    
    initial begin
        Now_Row = 0;
        Now_Col = 0;
        Row_Sel = 0;
        myrand = 0;
    end
    
    initial begin
        Row_Data_r <= 4'b1111;
        #50000000;
        press(0,0);
        press(0,1);
        press(0,2);
        press(0,3);
        
        press(1,0);
        press(1,1);
        press(1,2);
        press(1,3);
        
        press(2,0);
        press(2,1);
        press(2,2);
        press(2,3);
        
        press(3,0);
        press(3,1);
        press(3,2);
        press(3,3);
        
        $stop;
    end
    
    task press;
        input [1:0]row,col;
        begin
            Row_Sel = 0;
            Row_Data_r = 4'b1111;
            Row_Data_r[row] = 0;
            Now_Row = row;
            repeat(20)begin
                myrand = {$random}%65536;
                #myrand;
                Row_Data_r[row] = ~Row_Data_r[row];
            end
            Row_Sel = 1;
            Now_Col = col;
            #22000000;
            Row_Sel = 0;
            Row_Data_r = 4'b1111;
            repeat(20)begin
                myrand = {$random}%65536;
                #myrand;
                Row_Data_r[row] = ~Row_Data_r[row];
            end
            Row_Data_r = 4'b1111;
            #22000000;
        end
    endtask
    
    always@(*)
        if(Row_Sel)
            case(Now_Row)
                2'd0:Row_Data ={1'b1,1'b1,1'b1,Col_Data[Now_Col]};
                2'd1:Row_Data ={1'b1,1'b1,Col_Data[Now_Col],1'b1};
                2'd2:Row_Data ={1'b1,Col_Data[Now_Col],1'b1,1'b1};
                2'd3:Row_Data ={Col_Data[Now_Col],1'b1,1'b1,1'b1};
            endcase
        else
            Row_Data = Row_Data_r;        
endmodule
 

最后的测试顶层文件是

`timescale 1ns/1ns

module MAT_KEY_tb;
    
    reg Clk;
    reg Rst_n;
    
    wire [3:0]Row_Data;
    wire [3:0]Col_Data;
    
    wire Key_Flag;
    wire [3:0]Key_Value;
    
    MAT_KEY MAT_KEY(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Row_Data(Row_Data),
        .Col_Data(Col_Data),
        .Key_Flag(Key_Flag),
        .Key_Value(Key_Value)
    );
    
    MAT_KEY_MODEL MAT_KEY_MODEL(
        .Col_Data(Col_Data),
        .Row_Data(Row_Data)
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Rst_n = 0;
        #200;
        Rst_n = 1;
    end
    
endmodule
 

最终的测试结果如下

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值