目录
引言
原理阐述
实现方法
源码分享
板级调试演示
引言
最近了解了矩阵键盘扫描的原理,动手实现了一下,在这里做一个简单的总结。
原理阐述
矩阵键盘典型电路:
FPGA的应用电路:
其中,行信号为FPGA输入信号,列信号为FPGA输出信号。
原理解释:
- 起始状态,FPGA的列信号输出 全0 低电平;
- 没有任何按键按下时,FPGA接收到的 行信号 为 全1 高电平;
- 当有按键按下时,被按下的按键所在行变为低电平,此时便可以开启一次检测行为;
- 由于机械按键固有的振动特性,需要延迟约20毫秒后再次确认是否有按键按下;
- 如果20毫秒延迟后,依然检测到有按键按下,则认为按键按下有效,开始逐列扫描;
- 逐列扫描时,当前正在扫描的列,FPGA需输出低电平,其他列则输出高电平;
- 列扫描完毕就可以确定按键所在行列,进一步确定按键的数值;
- 列输出信号输出全0低电平,等待按键释放(条件是行输入信号全为高电平)。释放后回归空闲状态。完成一次按键检测。
实现方法
对于这种程序化的检测流程,状态机很适合做这个事情。我选择的状态机实现。代码贴在下面,可以仔细阅读~
源码分享
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- 矩阵键盘按键检测设计 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-19
// | 完成时间 : 2022-12-19
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1- 输出数据按照 16进制 0~F编码
// | -2- 时钟频率可变更
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
`timescale 1ns/1ps
module MATRIX_KEYBOARD_DETECT_MDL#(
// | ==================================== 模块可重载参数声明 ====================================
parameter P_CLK_FREQ = 32'd50_000_000 //时钟频率 单位 :Hz
)(
// | ==================================== 模块输入输出端口声明 ====================================
input I_SYS_CLK ,
input I_SYS_RSTN ,
input [3:0] I_ROW ,
output reg [3:0] O_COL ,
output reg O_KEYBOARD_VAL,
output reg [3:0] O_KEYBOARD_DATA
);
// | ==================================== 模块内部参数声明 ====================================
localparam LP_DLY_20MS_CNT_MAX = FUNC_CAL_DLY_20MS_CNT_MAX(P_CLK_FREQ);
localparam LP_20MS_CNT_WIDTH = $clog2(LP_DLY_20MS_CNT_MAX);
// | 状态编码
localparam LP_ST_IDLE = 5'b00001;//空闲
localparam LP_ST_DLY = 5'b00010;
localparam LP_ST_ROW = 5'b00100;//行
localparam LP_ST_COL = 5'b01000;//列
localparam LP_ST_RLS = 5'b10000;//按键释放检测
// | ==================================== 模块内部信号声明 ====================================
// 20ms计数
reg [LP_20MS_CNT_WIDTH-1:0] R_20MS_CNT;
// 状态信号
reg [4:0] R_CS;
// 下降边沿
reg [1:0] R_NDG_DETECT;
wire W_NDG_ROW;
// 寄存
reg [3:0] R_ROW;
reg [3:0] R_COL;
// wire W_ROW_0;
reg R_ROW_0;
reg [2:0] R_CNT_4;
// | ==================================== 模块内部逻辑设计 ====================================
// | 边沿
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_NDG_DETECT <= 2'b00;
end
else
begin
R_NDG_DETECT[0] <= &I_ROW ;
R_NDG_DETECT[1] <= R_NDG_DETECT[0];
end
end
assign W_NDG_ROW = !R_NDG_DETECT[0] & R_NDG_DETECT[1];
/*assign W_ROW_0 = (R_ROW == 4'b1110) ? I_ROW[0] :
(R_ROW == 4'b1101) ? I_ROW[1] :
(R_ROW == 4'b1011) ? I_ROW[2] :
(R_ROW == 4'b0111) ? I_ROW[3] : 1'b1;*/
always @ (*)
begin
if(~I_SYS_RSTN)
begin
R_ROW_0 = 1'b0;
end
else
begin
case(R_ROW)
4'b1110:
begin
R_ROW_0 = I_ROW[0];
end
4'b1101:
begin
R_ROW_0 = I_ROW[1];
end
4'b1011:
begin
R_ROW_0 = I_ROW[2];
end
4'b0111:
begin
R_ROW_0 = I_ROW[3];
end
default:
begin
R_ROW_0 = 1'b1;
end
endcase
end
end
// | 状态机
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
O_COL <= 4'b0000;
R_ROW <= 4'b0000;
R_COL <= 4'b0000;
R_CNT_4 <= 3'd0;
R_CS <= LP_ST_IDLE;
end
else
begin
case(R_CS)
LP_ST_IDLE:
begin
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
O_COL <= 4'b0000;
R_ROW <= 4'b0000;
R_COL <= 4'b0000;
R_CNT_4 <= 3'd0;
if(W_NDG_ROW)
begin
R_CS <= LP_ST_DLY;
end
else
begin
R_CS <= LP_ST_IDLE;
end
end
LP_ST_DLY:
begin
if(R_20MS_CNT == LP_DLY_20MS_CNT_MAX)
begin
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
R_CS <= LP_ST_ROW;
end
else
begin
R_20MS_CNT <= R_20MS_CNT + 1;
R_CS <= LP_ST_DLY;
end
end
LP_ST_ROW:
begin
if(!(&I_ROW))
begin
R_ROW <= I_ROW;
O_COL <= 4'b1110;
R_CS <= LP_ST_COL;
end
else
begin
R_ROW <= 4'b0000;
O_COL <= 4'b0000;
R_CS <= LP_ST_IDLE;
end
R_COL <= 4'b0000;
end
LP_ST_COL://暂不考虑列扫描失败 , 即列扫描 4 次结束后,无法定位列索引
begin
if(~R_ROW_0)
begin
O_COL <= 4'b0000;
R_COL <= O_COL;
R_CNT_4 <= 3'd0;
R_CS <= LP_ST_RLS;
end
else if(R_CNT_4 == 3'd4)
begin
O_COL <= 4'b0000;
R_CNT_4 <= 3'd0;
R_CS <= LP_ST_IDLE;
end
else
begin
O_COL <= {O_COL[2:0],O_COL[3]};
R_COL <= 4'b0000;
R_CNT_4 <= R_CNT_4 + 1;
R_CS <= LP_ST_COL;
end
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
R_ROW <= R_ROW;
end
LP_ST_RLS:
begin
if(&I_ROW)
begin
R_CS <= LP_ST_IDLE;
end
else
begin
R_CS <= LP_ST_RLS;
end
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
O_COL <= 4'b0000;
R_ROW <= R_ROW;
R_COL <= R_COL;
end
default:
begin
R_20MS_CNT <= {(LP_20MS_CNT_WIDTH){1'b0}};
O_COL <= 4'b0000;
R_CS <= LP_ST_IDLE;
end
endcase
end
end
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
O_KEYBOARD_VAL <= 1'b0;
O_KEYBOARD_DATA <= 4'd0;
end
else
begin
if(R_CS[4] & (&I_ROW))
begin
case(R_ROW)
4'b1110:
begin
O_KEYBOARD_VAL <= 1'b1;
if(((~R_COL) >> 1) == 4'b0100)
begin
O_KEYBOARD_DATA <= 4'd3;
end
else
begin
O_KEYBOARD_DATA <= (~R_COL) >> 1;
end
end
4'b1101:
begin
O_KEYBOARD_VAL <= 1'b1;
if(((~R_COL) >> 1) == 4'b0100)
begin
O_KEYBOARD_DATA <= 4'd3 + 4'd4;
end
else
begin
O_KEYBOARD_DATA <= ((~R_COL) >> 1) + 4'd4;
end
end
4'b1011:
begin
O_KEYBOARD_VAL <= 1'b1;
if(((~R_COL) >> 1) == 4'b0100)
begin
O_KEYBOARD_DATA <= 4'd3 + 4'd8;
end
else
begin
O_KEYBOARD_DATA <= ((~R_COL) >> 1) + 4'd8;
end
end
4'b0111:
begin
O_KEYBOARD_VAL <= 1'b1;
if(((~R_COL) >> 1) == 4'b0100)
begin
O_KEYBOARD_DATA <= 4'd3 + 4'd12;
end
else
begin
O_KEYBOARD_DATA <= ((~R_COL) >> 1) + 4'd12;
end
end
default:
begin
O_KEYBOARD_VAL <= 1'b0;
O_KEYBOARD_DATA <= 4'd0;
end
endcase
end
else
begin
O_KEYBOARD_VAL <= 1'b0;
O_KEYBOARD_DATA <= 4'd0;
end
end
end
// | ==================================== 模块内部函数设计 ====================================
// | 函数功能:根据时钟频率计算20ms延迟对应计数器计数的峰值
function integer FUNC_CAL_DLY_20MS_CNT_MAX ;
input integer I_ITG_CLK_FREQ;
integer ITG_1G ,ITG_20MS ,ITG_CLK_PERIOD;
begin
ITG_1G = 10**9;
ITG_20MS = 20*(10**6);
ITG_CLK_PERIOD = ITG_1G / I_ITG_CLK_FREQ;
FUNC_CAL_DLY_20MS_CNT_MAX = ITG_20MS / ITG_CLK_PERIOD;
end
endfunction
endmodule
一般来说,矩阵键盘通常没有上拉电阻,所以需要在FPGA的约束中对行输入引脚添加上拉约束。例:
以上的代码中,没有对按键释放的过程做震动延迟处理,稳妥起见还是在按键松起释放时,增加一个20毫秒的延迟,读者可以自行改进。
板级调试演示
点击链接进入观看:
矩阵键盘扫描+三线制数码管驱动显示https://live.csdn.net/v/264596文章来源:https://www.toymoban.com/news/detail-813325.html
有问题可以在评论区留言交流~~文章来源地址https://www.toymoban.com/news/detail-813325.html
到了这里,关于基于FPGA的 矩阵键盘按键识别 【原理+源码】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!