FPGA 34 矩阵键盘 模块设计
? 最早的 MCU(即单片机)其 IO 口相对较少,而且用到按键过多的话, 就会占用过多的 IO。 人们为了解决这个问题就引入了“矩阵键盘”。 在矩阵键盘中,每条行线和列线在交叉处都不是直接连同, 而是通过一个按键直接相连,这样以来一个 4*4 的矩阵键盘只需要 8 根控制线就可以完成16 个按键的控制。
矩阵键盘工作原理 :
? 检测矩阵键盘中某一按键是否被按下, 采用的方法是列扫描法。 图中一共有 8条控制线, 4 条行控制线(ROW),4 条列控制线(COL) 。在原理图图中,我们可以看到,ROW的信号【对于FPGA】是一个输入IO,COL0、1、2、3 是一个输出IO的配置,所以FPGA 只需要读取ROW0、1、2、3 端口的信号来进行判断。
? 假如我们让 COL0=0,然后去读取 ROW 的值,如果 4 个 ROW 信号的电平全部为高,则表明当前列并无按键按下,则切换到扫描下一列,再去读取 4 个 ROW 信号的值,根据读到的 ROW 值判断当前是哪一个按键被按下。例如当扫描第三列的时候,即 COL=4’b1011 时,检测到 ROW=4’b1011,则说明“A”被按下。
矩阵键盘扫描驱动模块设计 :
端口描述:
矩阵键盘检测状态转移图 :(P208)
key_board.v 文件
module key_board
(
Clk,
Rst_n,
Key_Board_Row_i,
Key_flag,
Key_Value,
Key_Board_Col_o
);
input Clk ;
input Rst_n;
input [3:0]Key_Board_Row_i;
output reg Key_flag;
output reg [3:0]Key_Value;
output reg [3:0]Key_Board_Col_o;
// 输入列寄存器信号,输入以后做一次寄存
reg [3:0]Key_Board_Row_r;
reg En_Cnt ; //滤波定时器
reg [19:0] counter1; //滤波定时,时钟周期计数器
reg Cnt_Done; //滤波时间完成标志信号
reg [3:0]Col_Tmp ; // 列按键按下状态
reg Key_Flag_r; // 按键成功标志位
reg [7:0]Key_Value_tmp;
reg [10:0]state;
//按键按下标志位
always@(posedge Clk)
Key_flag <= Key_Flag_r;
// 状态机状态参数
localparam
IDEL = 11'b00000000001,
P_FILTER = 11'b00000000010,
READ_ROW_P = 11'b00000000100,
SCAN_C0 = 11'b00000001000,
SCAN_C1 = 11'b00000010000,
SCAN_C2 = 11'b00000100000,
SCAN_C3 = 11'b00001000000,
PRESS_RESULT = 11'b00010000000,
WAIT_R = 11'b00100000000,
R_FILTER = 11'b01000000000,
READ_ROW_R = 11'b10000000000;
// 滤波定时计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
counter1 <= 20'd0;
else if(En_Cnt)begin
if(counter1 == 20'd999999)
counter1 <= 20'd0;
else
counter1 <= counter1 + 1'b1;
end
else
counter1 <= 20'd0;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Cnt_Done <= 1'b0;
else if(counter1 == 20'd999999)
Cnt_Done <= 1'b1;
else
Cnt_Done <= 1'b0;
//状态机
always @(posedge Clk or negedge Rst_n)
if(!Rst_n)
begin
state <= IDEL ;
Key_Board_Col_o <= 4'b0000 ; //输出端口默认设置为低电平,只有按键按下时,默认的上拉电平就会变成低电平
En_Cnt <= 1'b0;
Col_Tmp <= 4'd0;
Key_Flag_r <= 1'b0;
Key_Value_tmp <= 8'd0;
Key_Board_Row_r <= 4'b1111;
end
else
begin
case (state)
IDEL:
if(~&Key_Board_Row_i)begin // Key_Board_Row_i 不等于 4‘d1111,说明有按键按下
En_Cnt <= 1'b1 ; // 开启滤波定时器
state <= P_FILTER; // 跳转进入前级滤波状态
end
else begin // 按键未按下
En_Cnt <=1'b0 ; // 消抖滤波定时器关闭
state <=IDEL ; // 等待状态
end
P_FILTER :
if(Cnt_Done) begin //消抖滤波定时时间结束
En_Cnt <= 1'b0; //关闭滤波定时器
state <= READ_ROW_P; //跳转进入滤波检测状态
end
else begin //消抖定时未结束
En_Cnt <= 1'b1; //继续保持
state <= P_FILTER;// 状态继续保持当前值
end
READ_ROW_P:
if (~&Key_Board_Row_i) begin //滤波后,第二次确定按键确实按下
Key_Board_Row_r <= Key_Board_Row_i; // 存取当前状态的值
Key_Board_Col_o <= 4'b1110; // 设置 Key_Board_Col_o 的输出信号为 4‘b1110,其它列设置为高电平
state <= SCAN_C0; // 跳转进入第C0列的判断
end
else begin //这次是按键抖动
state <=IDEL ; // 进入空闲等待状态
Key_Board_Col_o <= 4'b0000; // 设置 Key_Board_Col_o 的输出信号为 4‘b0000
end
SCAN_C0 :
begin
Key_Board_Col_o <= 4'b1101; // 设置 Key_Board_Col_o 的输出信号为 4‘b1101
state <= SCAN_C1; // 跳转进入第C1列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0001 ; // 表示此次按下的按键是再第一列
else
Col_Tmp <= 4'b0000 ; // 表示此次按下的按键不在这列
end
SCAN_C1 :
begin
Key_Board_Col_o <= 4'b1011; // 设置 Key_Board_Col_o 的输出信号为 4‘b1011
state <= SCAN_C2; // 跳转进入第C2列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0010 | Col_Tmp ; // 表示此次按下的按键是再第2列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
SCAN_C2 :
begin
Key_Board_Col_o <= 4'b0111; // 设置 Key_Board_Col_o 的输出信号为 4'b0111
state <= SCAN_C3; // 跳转进入第C3列的判断
if(~&Key_Board_Row_i) //其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b0100 | Col_Tmp ; // 表示此次按下的按键是再第2列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
SCAN_C3 :
begin
state <= PRESS_RESULT; // 进入此状态后,表示按键检测已经结束,需要进入下一个按键结果分析状态
if(~&Key_Board_Row_i) // 其它3列设置为高电平后,Key_Board_Row_i 仍然为按键按下状态
Col_Tmp <= 4'b1000 | Col_Tmp ; // 表示此次按下的按键是再第3列
else
Col_Tmp <= Col_Tmp ; // 表示此次按下的按键不在这列
end
PRESS_RESULT:
begin
state <= WAIT_R; // 本次结果分析完成后,进入按键松开滤波状态
Key_Board_Col_o <= 4'b0000; //按键检测查找完成,所以重新设置,输出Key_Board_Col_o的信号为 4‘b0000
if(((Key_Board_Row_r[0] + Key_Board_Row_r[1] + Key_Board_Row_r[2] + Key_Board_Row_r[3]) == 4'd3) &&
((Col_Tmp[0] + Col_Tmp[1] + Col_Tmp[2] + Col_Tmp[3]) == 4'd1)) // 检验是否只有一个按键按下
begin
Key_Flag_r <= 1'b1; //产生按键成功标志信号
Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp}; //位拼接,等下来查找
end
else
begin
Key_Flag_r <= 1'b0; //非单个按键按下,不产生按键完成信号
Key_Value_tmp <= Key_Value_tmp; //按键值保持上一次按键的值
end
end
WAIT_R :
begin
Key_Flag_r <= 0 ; // 按键成功标志信号清零
if(&Key_Board_Row_i) // Key_Board_Row_i = 4’b1111 表示(按键未按下)即,按键松开,此时我们进入后消抖状态
begin
En_Cnt =1'b1; // 开启消抖滤波的计时器
state <= R_FILTER ; //进入后消抖判断转态
end
else // 按键仍然按下,我们在这次保持等待状态
begin
state <= WAIT_R ;
En_Cnt =1'b0; // 消抖滤波的计时器暂时先不开启
end
end
R_FILTER :
if(Cnt_Done)begin //后消抖滤波定时完成
En_Cnt <= 1'b0; //关闭滤波定时器使能信号
state <= READ_ROW_R; //进入按键完全释放状态判断
end
else
begin
En_Cnt <= 1'b1; // 滤波消抖定时器正在进行
state <= R_FILTER; // 继续保持滤波状态
end
READ_ROW_R :
if(&Key_Board_Row_i) // (Key_Board_Row_i =4'd1111)即,确定按键已经松开,本次按键检测已经完成
state <= IDEL;
else // 按键还没松开,再次后消抖滤波
begin
state <= R_FILTER; // 再次进入后消抖滤波状态
En_Cnt <= 1'b1; // 开启滤波消抖定时器
end
default: state <= IDEL; // 默认,或者运行出错,进入空闲状态
endcase
end
//按键输出状态查找表 根据 : Key_Value_tmp <= {Key_Board_Row_r,Col_Tmp}; 实现数据的查找
always @(posedge Clk or negedge Rst_n)
if(!Rst_n)
Key_Value <= 4'd0 ;
else if(Key_Flag_r) // 先判断,按键是否按下成功
begin
case (Key_Value_tmp)
8'b1110_0001 : Key_Value <= 4'd0;
8'b1110_0010 : Key_Value <= 4'd1;
8'b1110_0100 : Key_Value <= 4'd2;
8'b1110_1000 : Key_Value <= 4'd3;
8'b1101_0001 : Key_Value <= 4'd4;
8'b1101_0010 : Key_Value <= 4'd5;
8'b1101_0100 : Key_Value <= 4'd6;
8'b1101_1000 : Key_Value <= 4'd7;
8'b1011_0001 : Key_Value <= 4'd8;
8'b1011_0010 : Key_Value <= 4'd9;
8'b1011_0100 : Key_Value <= 4'd10;
8'b1011_1000 : Key_Value <= 4'd11;
8'b0111_0001 : Key_Value <= 4'd12;
8'b0111_0010 : Key_Value <= 4'd13;
8'b0111_0100 : Key_Value <= 4'd14;
8'b0111_1000 : Key_Value <= 4'd15;
default: Key_Value <= Key_Value ;
endcase
end
endmodule
矩阵键盘模型:
`timescale 1ns/1ns
module Key_Board_model(
Key_Col,
Key_Row
);
// 矩阵键盘模型
// 注: 写的是一个键盘的模型,相当于模拟矩阵的输入和输出
// 主要是模拟按键按下,信号输出给fpga 的过程,其过程包括按键抖动信号
input [3:0]Key_Col; //对于矩阵键盘来说,Key_Col 是一个 输入信号,对fpga是一个输出信号
output reg [3:0]Key_Row; //Key_Row 是一个输出信号,对pga 是有一个输入信号
reg [5:0]myrand ;
reg [3:0]Key_Row_r; //行寄存器
reg key_row_sel ;
reg [1:0]now_col,now_row ; //当前行与当前列
initial begin
now_col =0;
now_row =0;
key_row_sel =0 ;
myrand =0 ;
end
initial begin
Key_Row_r = 4'b1111 ; // 初始状态,即模型按键未按下时刻,矩阵键盘的行信号默认都是上拉电平,即全是高电平
#50000000 ;
// 顺序按键按下,
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);
$stop;
end
// 编写press_key任务(函数) 调用方式: press_key(row,col); //参数输入按键的位置row,col
task press_key;
input [1:0]row,col;
// 按键按下进行执行下列操作
begin
key_row_sel = 0;//将行选择信号设置为有效状态
Key_Row_r = 4'b1111; // 初始状态按键未按下
Key_Row_r[row] = 0 ; // row,按键按下,让Key_Row_r 的这个row bit 未设置为0 ,表示按键按下
now_row = row;
repeat(20)begin //重复 20 次,随机产生按键按下时 20 个不同的行状态
myrand = {$random} % 65536; //随机延时
#myrand Key_Row_r[row] = ~Key_Row_r[row]; // 模拟按键抖动(电平时高时低)状态
end
key_row_sel = 1; //将行选择信号设置为有效状态
now_col = col; //消抖以后,获取当前列输入信号 col ???
#22000000;
key_row_sel =0 ;
Key_Row_r = 4'b1111; // 进入按键后滤波状态
repeat(20)begin //重复 20 次,随机产生按键松开时 20 个不同行状态
myrand = {$random} % 65536;
#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) //根据输入的列信号,获取按键Key_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
验证测试脚本文件:
`timescale 1ns/1ns
//矩阵键盘的仿真模型
module Key_Board_tb;
reg Clk;
reg Rst_n;
wire [3:0]Key_Row;
wire [3:0]Key_Col;
wire Key_flag;
wire [3:0]Key_Value;
key_board key_board_0(
.Clk(Clk),
.Rst_n(Rst_n),
.Key_Board_Row_i(Key_Row),
.Key_flag(Key_flag),
.Key_Value(Key_Value),
.Key_Board_Col_o(Key_Col)
);
Key_Board_model Key_Board_model_inst(Key_Col,Key_Row);
initial Clk =1 ;
always #10 Clk = ~Clk ;
initial begin
Rst_n =0;
#200
Rst_n =1;
end
endmodule
FPGA 上拉设置问题 (注:不设置矩阵键盘测试无法正常工作,开始被坑了很多遍)
step1:
step2:
step3:
step4:
step5:
最终配置效果:
|