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 同步化,并防止亚稳态。
➢ 按键消抖
软件方法消抖,即检测出键闭合后执行一个延时程序,抖动时间的长短由按键的机械特性决定,一般为 5ms~20ms, 让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认按下按键操作有效。当检测到按键释放后,也要给 5ms~20ms 的延时,待后沿抖动消失后才能转入该键的处理程序。由于按键按下去的时间一般都会大于 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_ff1:4bit 位宽的矩阵键盘列信号,最高位表示矩阵键盘往右数第四列,默认信号为 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矩阵键盘的思路