FPGA基础——矩阵键盘(FSM)

题目:请实现对4x4矩阵式键盘的按键识别,假设每次都是单按键输入,需要有去抖功能(持续20ms以上被认为是有效键值),模块时钟频率为1kHz,要求用状态机实现,定义状态,画出状态转移图,并用verilog完整描述该识别模块。矩阵式键盘电路结构参见下图,其中列线1-4由识别模块控制输出,行线5-8为识别模块的输入。

确认矩阵键盘上哪个按键被按下有多同方法,其中行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法。

1. 判断键盘中有无键按下:将全部行线 KEY_R1~KEY_R4 置低电平,然后检测列线 KEY_C1~KEY_C4 的状态。只要有一列的电平为低,则表示键盘中有键被按下,而且闭合的键位于低电平线与 4 根行线相交叉的 4 个按键之中。若所有列线均为高电平,则键盘中无键按下。

2. 判断闭合键所在的位置:在确认有键按下后,即可进入确定具体闭合键的过程。其方法是:

依次将行线置为低电平,即在置某根行线为低电平时,其它线为高电平。在确定某根行线位置为低电平后,再逐行检测各列线的电平状态。若某列为低,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。

 ➢ 打拍操作
输入的 key_col 是异步信号,通常要进行打两拍操作,将异步信号 key_col 同步化,并防止亚稳态。

按键消抖

软件方法消抖,即检测出键闭合后执行一个延时程序,抖动时间的长短由按键的机械特性决定,一般为 5ms20ms, 让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认按下按键操作有效。当检测到按键释放后,也要给 5ms20ms 的延时,待后沿抖动消失后才能转入该键的处理程序。由于按键按下去的时间一般都会大于 20ms,为了达到不管按键按下多久,都视为按下一次的效果,提出以下计数器架构,如下图所示:

 

消抖计数器 shake_cnt:用于计算 20ms 的时间,加一条件为 key_col_ff1 != 4'hf && flag_key==0,表示有某个按键按下并且之前没有按键按下;数到 1,000,000 下,表示数到 20ms 就结束。

行扫描计数器 row_index:用于区分扫描的行,加一条件为 key_row_check && end_shake_cnt,表示当处于行扫描状态并且每行消抖 20ms 后,开始扫描下一行;数到 4 下,表示 4 行按键扫描完了。

按键:表示有无按键按下,没被按下时为高电平,按下后为低电平。

按键指示信号 flag_key:该信号为低电平,指示之前没有按键按下;否则,指示有按键按下并且按键已消抖。

行扫描指示信号 key_row_check:该信号为高电平,指示当前处于行扫描状态。

矩阵键盘列信号 key_col_ff14bit 位宽的矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认信号为 key_col_ff1 = 4'hf,否则表示该信号低电平对应位的列有按键按下。

矩阵键盘扫描代码如下(MDY):

module key_scan(
        input  clk ,
        input  rst_n ,
        input  [3:0] key_col,
        output reg [3:0] key_row,
        output reg [3:0] key_out,
        output reg key_vld
        );
parameter TIME_20MS = 1_000_000 ;

reg [3:0] key_col_ff0 ;
reg [3:0] key_col_ff1 ;
reg key_col_check;
reg [ 21:0] shake ;
wire add_shake ;
wire end_shake ;
reg [1:0] key_col_get ;
reg key_row_check;
reg [1:0] row_index ;
wire add_row_index;
wire end_row_index;
wire flag ;
reg flag_add ;

always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_col_ff0 <= 4'b1111;
        key_col_ff1 <= 4'b1111;
    end
    else begin
        key_col_ff0 <= key_col ;
        key_col_ff1 <= key_col_ff0;
    end
end

always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_col_check <= 1'b0;
    end
    else if(key_col_ff1 !=4'hf && end_shake)begin
        key_col_check <= 1'b1;
    end
    else if(key_col_ff1==4'hf)begin
        key_col_check <= 1'b0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
        shake <= 0;
    end
    else if(add_shake) begin
        if(end_shake)
            shake <= 0;
        else
            shake <= shake+1 ;
    end
end

assign add_shake = (key_col_ff1 !=4'hf && flag_add==0);
assign end_shake = add_shake && shake == TIME_20MS-1 ;

always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        flag_add <= 0;
    end
    else if(end_shake)begin
        flag_add <= 1;
    end
    else if(key_col_ff1 == 4'hf)begin
        flag_add <= 0;
    end
end


always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_col_get <= 0;
    end
    else if(key_col_check) begin
        if(key_col_ff1==4'b1110)
            key_col_get <= 0;
        else if(key_col_ff1==4'b1101)
            key_col_get <= 1;
        else if(key_col_ff1==4'b1011)
            key_col_get <= 2;
        else if(key_col_ff1==4'b0111)
            key_col_get <= 3;
    end
end

always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_row_check <= 0;
    end
    else if(key_col_check)begin
        key_row_check <= 1;
    end
    else if(flag)begin
        key_row_check <= 0;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
        row_index <= 0;
    end
    else if(add_row_index) begin
        if(end_row_index)
            row_index <= 0;
        else
            row_index <= row_index+1 ;
    end
end

assign add_row_index = key_row_check && end_shake;
assign end_row_index = add_row_index && row_index == 4-1 ;

always @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        key_row = 4'b0;
    end
    else if(key_row_check)begin
        key_row = ~(4'b0001 << row_index);
    end
    else begin
        key_row = 4'b0;
    end
end

assign flag = key_row_check && key_col_ff1[key_col_get]==1'b0 && key_col_check==0;

always @(*)begin
    if(rst_n==1'b0)begin
        key_vld = 1'b0;
    end
    else if(flag )begin
        key_vld = 1'b1;
    end
    else begin
        key_vld = 1'b0;
    end
end


always @(*)begin
    if(rst_n==1'b0)begin
        key_out = 4'd0;
    end
    else if(flag )begin
        key_out = {row_index,key_col_get};
    end
    else begin
        key_out = 4'd0;
    end
end

endmodule

 

用状态机实现:

对于列扫描法(列线置低电平,检测行线状态),其状态如下:

 

  1 module matrixKeyboard(
  2   input            clk,
  3   input            rst_n,
  4   input      [3:0] row,                 // 矩阵键盘 行
  5   output reg [3:0] col,                 // 矩阵键盘 列
  6   output reg [3:0] keyboard_val         // 键盘值     
  7 );
  8  
  9 //++++++++++++++++++++++++++++++++++++++
 10 // 分频部分 开始
 11 //++++++++++++++++++++++++++++++++++++++
 12 reg [19:0] cnt;                         // 去抖动计数器
 13  
 14 always @ (posedge clk, negedge rst_n)
 15   if (!rst_n)
 16     cnt <= 0;
 17   else
 18     cnt <= cnt + 1'b1;
 19  
 20 wire key_clk = cnt[19];                 //T =(2^20/50M = 20.97152)ms 
 21 //--------------------------------------
 22 // 分频部分 结束
 23 //--------------------------------------
 24  
 25  
 26 //++++++++++++++++++++++++++++++++++++++
 27 // 状态机部分 开始
 28 //++++++++++++++++++++++++++++++++++++++
 29 // 状态数较少,独热码编码
 30 parameter NO_KEY_PRESSED = 6'b000_001;  // 没有按键按下  
 31 parameter SCAN_COL0      = 6'b000_010;  // 扫描第0列 
 32 parameter SCAN_COL1      = 6'b000_100;  // 扫描第1列 
 33 parameter SCAN_COL2      = 6'b001_000;  // 扫描第2列 
 34 parameter SCAN_COL3      = 6'b010_000;  // 扫描第3列 
 35 parameter KEY_PRESSED    = 6'b100_000;  // 有按键按下
 36  
 37 reg [5:0] current_state, next_state;    // 现态、次态
 38  
 39 always @ (posedge key_clk, negedge rst_n)
 40   if (!rst_n)
 41     current_state <= NO_KEY_PRESSED;
 42   else
 43     current_state <= next_state;
 44  
 45 // 根据条件转移状态
 46 always @(*)
 47   case (current_state)
 48     NO_KEY_PRESSED :                    // 没有按键按下
 49         if (row != 4'hF)
 50           next_state = SCAN_COL0;
 51         else
 52           next_state = NO_KEY_PRESSED;
 53     SCAN_COL0 :                         // 扫描第0列 
 54         if (row != 4'hF)
 55           next_state = KEY_PRESSED;
 56         else
 57           next_state = SCAN_COL1;
 58     SCAN_COL1 :                         // 扫描第1列 
 59         if (row != 4'hF)
 60           next_state = KEY_PRESSED;
 61         else
 62           next_state = SCAN_COL2;    
 63     SCAN_COL2 :                         // 扫描第2列
 64         if (row != 4'hF)
 65           next_state = KEY_PRESSED;
 66         else
 67           next_state = SCAN_COL3;
 68     SCAN_COL3 :                         // 扫描第3列
 69         if (row != 4'hF)
 70           next_state = KEY_PRESSED;
 71         else
 72           next_state = NO_KEY_PRESSED;
 73     KEY_PRESSED :                       // 有按键按下
 74         if (row != 4'hF)
 75           next_state = KEY_PRESSED;
 76         else
 77           next_state = NO_KEY_PRESSED;                      
 78   endcase
 79  
 80 reg       key_pressed_flag;             // 键盘按下标志
 81 reg [3:0] col_val, row_val;             // 列值、行值
 82  
 83 // 根据次态,给相应寄存器赋值
 84 always @ (posedge key_clk, negedge rst_n)
 85   if (!rst_n)
 86   begin
 87     col              <= 4'h0;
 88     key_pressed_flag <=    0;
 89   end
 90   else
 91     case (next_state)
 92       NO_KEY_PRESSED :                  // 没有按键按下
 93       begin
 94         col              <= 4'h0;
 95         key_pressed_flag <=    0;       // 清键盘按下标志
 96       end
 97       SCAN_COL0 :                       // 扫描第0列
 98         col <= 4'b1110;
 99       SCAN_COL1 :                       // 扫描第1列
100         col <= 4'b1101;
101       SCAN_COL2 :                       // 扫描第2列
102         col <= 4'b1011;
103       SCAN_COL3 :                       // 扫描第3列
104         col <= 4'b0111;
105       KEY_PRESSED :                     // 有按键按下
106       begin
107         col_val          <= col;        // 锁存列值
108         row_val          <= row;        // 锁存行值
109         key_pressed_flag <= 1;          // 置键盘按下标志  
110       end
111     endcase
112 //--------------------------------------
113 // 状态机部分 结束
114 //--------------------------------------
115  
116  
117 //++++++++++++++++++++++++++++++++++++++
118 // 扫描行列值部分 开始
119 //++++++++++++++++++++++++++++++++++++++
120 always @ (posedge key_clk, negedge rst_n)
121   if (!rst_n)
122     keyboard_val <= 4'h0;
123   else
124     if (key_pressed_flag)
125       case ({col_val, row_val})
126         8'b1110_1110 : keyboard_val <= 4'h0;
127         8'b1110_1101 : keyboard_val <= 4'h4;
128         8'b1110_1011 : keyboard_val <= 4'h8;
129         8'b1110_0111 : keyboard_val <= 4'hC;
130          
131         8'b1101_1110 : keyboard_val <= 4'h1;
132         8'b1101_1101 : keyboard_val <= 4'h5;
133         8'b1101_1011 : keyboard_val <= 4'h9;
134         8'b1101_0111 : keyboard_val <= 4'hD;
135          
136         8'b1011_1110 : keyboard_val <= 4'h2;
137         8'b1011_1101 : keyboard_val <= 4'h6;
138         8'b1011_1011 : keyboard_val <= 4'hA;
139         8'b1011_0111 : keyboard_val <= 4'hE;
140          
141         8'b0111_1110 : keyboard_val <= 4'h3; 
142         8'b0111_1101 : keyboard_val <= 4'h7;
143         8'b0111_1011 : keyboard_val <= 4'hB;
144         8'b0111_0111 : keyboard_val <= 4'hF;        
145       endcase
146 //--------------------------------------
147 //  扫描行列值部分 结束
148 //--------------------------------------
149        
150 endmodule

 

 

参考资料:明德杨至简设计法

参考资料:驱动4x4矩阵键盘的思路

posted on 2021-08-04 10:58  一曲挽歌  阅读(1834)  评论(0编辑  收藏  举报

导航