基于FPGA的 矩阵键盘按键识别 【原理+源码】
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
引言
最近了解了矩阵键盘扫描的原理动手实现了一下在这里做一个简单的总结。
原理阐述
矩阵键盘典型电路
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
有问题可以在评论区留言交流~~