矩阵键盘又叫行列式键盘。用带IO口的线组成行列结构,按键设置在行列的交点上。例如用4×4的行列式结构可以构成16个键的键盘。这样,当按键数量平方增长时,I/O口只是线性增长,这样就可以节省I/O口。矩阵键盘的原理图如图1.1所示:
图1.1 矩阵键盘的原理图
按键设置在行列线交叉点,行列线分别连接到按键开关的两端。列线通过上拉电阻接3.3V电压,即列线的输出被钳位到高电平状态。
判断键盘中有无按键按下式通过行线送入扫描线好然后从列线读取状态得到的。其方法是依次给行线送低电平,检查列线的输入。如果列线全是高电平,则代表低电平信号所在的行中无按键按下;如果列线有输入为低电平,则代表低电平信号所在的行和出现低电平的列的交点处有按键按下。
1.由于使用的外部时钟频率为50MHz,这个频率对扫描来说太高,所以这里需要一个分频器来分得适合键盘扫描使用的频率。可以看出,我们经过分频得到50HZ的时钟,是为下一模块键盘扫描提供的时钟,之所以是50HZ,而不是其他的数值,是因为,人在按键的时候,其停留时间大概是20ms左右,键盘扫描时,会发送固定的数值码,本例中是循环的发送4种数值,下面会提及到,50HZ转化为时间是0.02S=20ms。
module scan_clk ( clkout ,clk ,rst ); input rst ; input clk ; output clkout ; reg clkout_r ; parameter period= 200000; reg [31:0] cnt; always @( posedge clk or negedge rst) //分频50Hz begin if ( !rst ) begin cnt <= 32'b0; clkout_r <= 0 ; end else begin cnt<= cnt+1; if (cnt == (period >> 1) - 1) //设定周期时间的一半 clkout_r <=1'b1; else if (cnt == period - 1) //设定的周期时间 begin clkout_r <= 1'b0; cnt <= 1'b0; end end end assign clkout = clkout_r ; endmodule
2.键盘扫描键盘扫描电路是用于产生keydrv3~ keydrv0 信号,其变化顺序是1110→1101→1011→0111→1110…周而复始地扫描。其停留时间大慨在20ms。更短的时间没有必要,因为人为按键的时间大慨为20ms,不可能产生有更快的动作;另外,更短的停留时间还容易采集到抖动信号,会干扰判断。而太长的时间容易丢失某些较快的按键动作
module key_scan ( clk ,keydrv ,rst ); input clk ; input rst ; output [3:0] keydrv ; parameter s1 = 4'b1110; parameter s2 = 4'b1101; parameter s3 = 4'b1011; parameter s4 = 4'b0111; reg [3:0]current_state; reg [3:0]next_state; always @ ( posedge clk or negedge rst ) begin if ( !rst ) current_state <= s1 ; else current_state <= next_state ; end always @ ( current_state ) begin case ( current_state ) s1: next_state <=s2; s2: next_state <=s3; s3: next_state <=s4; s4: next_state <=s1; default: next_state <=s1; endcase end assign keydrv = current_state ; endmodule
3.键译码转换。键盘译码电路是从keydrv3~keydrv0和keyin3~keyin0信号中译码出按键值的电路。clk是全局时钟,由外部晶振提供。clk在系统的频率是最高的,其他的时钟由分频产生。Keydrv表示键盘扫描信号,keyin为键盘输入信号,keyvalue为键值
module top ( KEYO ,KEYI ,clk ,Y , rst); input [3:0] KEYO ; //与原理图一致,是键盘输出端口给FPGA input clk ; input rst ; output [3:0] KEYI ; //与原理图一致,是FPGA输出给 键盘 output [7:0] Y ; reg [7:0] temp_r ; reg [7:0] Y_r; reg [4:0] keyvalue ; reg [3:0] scankey_o; reg [3:0] scankey_i;
reg dis_pre; assign dis = &KEYO ; scan_clk key_clk( .clk ( clk), .clkout ( scanclk) , .rst ( rst ) ); key_scan key_scan( .clk ( scanclk ) , .keydrv (keydrv) , .rst ( rst ) ); always @ ( posedge clk or negedge rst ) begin if ( rst==1'b0 ) begin scankey_o <= 4'b0 ; scankey_i <= 4'b0 ; dis_pre <= dis; end else if ( clk ==1'b1 ) begin dis_pre <= dis; if ( (dis == 1'b0)&&(dis_pre==1'b1) ) begin scankey_o <= keydrv ; scankey_i <= KEYO ; end end end assign KEYI = keydrv; assign temp = {scankey_o,scankey_i}; always @ ( posedge clk or negedge rst ) begin if ( rst==1'b0 ) begin temp_r <= 8'b0; end else if ( clk == 1'b1 ) temp_r <= temp ; end always @( temp_r or rst ) begin if ( rst==1'b0 ) begin //译码输出 keyvalue <= 5'b0; end else case ( temp_r ) 8'b0111_0111 : keyvalue <= 5'hb; //无用,仅作为复位 8'b1110_1110 : keyvalue <= 5'h7; 8'b1110_1101 : keyvalue <= 5'h8; 8'b1110_1011 : keyvalue <= 5'h9; 8'b1101_1110 : keyvalue <= 5'h4; 8'b1101_1101 : keyvalue <= 5'h5; 8'b1101_1011 : keyvalue <= 5'h6; 8'b1011_1110 : keyvalue <= 5'h1; 8'b1011_1101 : keyvalue <= 5'h2; 8'b1011_1011 : keyvalue <= 5'h3; 8'b0111_1101 : keyvalue <= 5'h0; 8'b0111_1011 : keyvalue <= 5'b1_0001; //小数点 default: keyvalue <= 5'h0; endcase end always @(keyvalue or rst ) begin if ( rst==1'b0 ) //译码输出 Y_r <= 8'b0000_0000; else begin Y_r =8'b0000_0000; case (keyvalue ) 5'h0: Y_r = 8'b0011_1111; // 0 5'h1: Y_r = 8'b0000_0110; // 1 5'h2: Y_r = 8'b0101_1011; // 2 5'h3: Y_r = 8'b0100_1111; // 3 5'h4: Y_r = 8'b0110_0110; // 4 5'h5: Y_r = 8'b0110_1101; // 5 5'h6: Y_r = 8'b0111_1101; // 6 5'h7: Y_r = 8'b0000_0111; // 7 5'h8: Y_r = 8'b0111_1111; // 8 5'h9: Y_r = 8'b0110_1111; // 9 5'b1_0001: Y_r = 8'b1000_0000; //. default: Y_r = 8'b0000_0000; endcase end end assign Y =~Y_r; endmodule