基于FPGA的 矩阵键盘按键识别 【原理+源码】

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


目录

引言

原理阐述

实现方法

源码分享

板级调试演示


引言

最近了解了矩阵键盘扫描的原理动手实现了一下在这里做一个简单的总结。



原理阐述

矩阵键盘典型电路

FPGA的应用电路

其中行信号为FPGA输入信号列信号为FPGA输出信号。 

原理解释 

  1. 起始状态FPGA的列信号输出 全0 低电平
  2. 没有任何按键按下时FPGA接收到的 行信号 为 全1 高电平
  3. 当有按键按下时被按下的按键所在行变为低电平此时便可以开启一次检测行为
  4. 由于机械按键固有的振动特性需要延迟约20毫秒后再次确认是否有按键按下
  5. 如果20毫秒延迟后依然检测到有按键按下则认为按键按下有效开始逐列扫描
  6. 逐列扫描时当前正在扫描的列FPGA需输出低电平其他列则输出高电平
  7. 列扫描完毕就可以确定按键所在行列进一步确定按键的数值
  8. 列输出信号输出全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毫秒的延迟读者可以自行改进。 

板级调试演示

点击链接进入观看

矩阵键盘扫描+三线制数码管驱动显示icon-default.png?t=M85Bhttps://live.csdn.net/v/264596



有问题可以在评论区留言交流~~

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“基于FPGA的 矩阵键盘按键识别 【原理+源码】” 的相关文章