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

news2025/1/20 18:28:42


目录

引言

原理阐述

实现方法

源码分享

板级调试演示


引言

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



原理阐述

矩阵键盘典型电路:

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



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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105948.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业从哪里开始构建弹性 IT 基础架构

混合工作模式扩大了工作范围&#xff0c;增加了 IT 团队的负担&#xff0c;因为他们需要在面对增加的攻击面时保持弹性。入侵企业的 IT 基础架构只需要一个受损的身份。 什么是企业标识&#xff1f; 这些是用户名、密码、网络、端点、应用程序等&#xff0c;充当业务敏感信息…

CheatEngine教程-官方9关

文章目录第一步&#xff1a;环境准备&#xff0c;下载并安装CE第二关&#xff1a;精确扫描数值第三关&#xff1a;未知数值扫描第四关&#xff1a;浮点数的扫描第五关&#xff1a;代码替换功能第六关&#xff1a;关于指针第七关&#xff1a;简单代码注入第八关&#xff1a;查找…

力扣(LeetCode)173. 二叉搜索树迭代器(C++)

设计 根据二叉树的中序遍历的迭代解法&#xff0c;稍改代码&#xff0c;就是本题的解法。 初始化 : 传入了根结点&#xff0c;根据迭代思路&#xff0c;将结点的左链依次入栈。 nextnextnext : 栈顶结点就是所求。根据迭代思路&#xff0c;当前结点要变成栈顶结点的右儿子。由…

Openlayers:自定义坐标系

Openlayers天然支持EPSG:4326(WGS1984地理坐标系)、EPSG:3857(Web墨卡托投影坐标系,即:将WGS84坐标系投影到正方形,南北投影范围为[-85.051129,+85.051129])。同时,Openlayers也支持开发者自定义坐标系。那么具体如何操作呢? 相关的API ol.proj.projection ol.proj.…

Android设计模式详解之工厂方法模式

前言 工厂方式模式是创建型设计模式&#xff1b; 定义&#xff1a;定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪个类。 使用场景&#xff1a;在任何需要生成复杂对象的地方&#xff0c;都可以使用工厂方法模式。复杂对象适合使用工厂模式&#xff0c;用new就可…

【Java|golang】1753. 移除石子的最大得分

你正在玩一个单人游戏&#xff0c;面前放置着大小分别为 a​​​​​​、b 和 c​​​​​​ 的 三堆 石子。 每回合你都要从两个 不同的非空堆 中取出一颗石子&#xff0c;并在得分上加 1 分。当存在 两个或更多 的空堆时&#xff0c;游戏停止。 给你三个整数 a 、b 和 c &a…

攻防世界新手练习区——unseping

目录 知识点 解读题目源码&#xff1a; 命令绕过 知识点 PHP代码审计PHP序列化和反序列化PHP中魔术方法命令执行绕过方式 解读题目源码&#xff1a; 这道题首先一上来就是一段PHP代码&#xff0c;其中看到unserialize()就知道考的是反序列化&#xff0c;但是我们再往上看代码…

rust编程-struct结构体(chapter 5.1 结构体定义和实例化)

目录 1. 结构体定义和实例化 1.1 struct介绍 1.2 使用字段简写进行实例化 1.3 从其它对象实例化新结构体对象 1.4 使用无命名字段的struct类型 1.5 没有任何字段的structs结构体 1.6 结构体字段的值所有权 结构(struct)是一种自定义数据类型&#xff0c;可以将多个相关类…

存量时代下 用低代码开发平台提升你的CEM

随着人口及流量红利的逐步见顶&#xff0c;我国经济从增量市场迈入存量市场。在充分竞争的存量市场环境下&#xff0c;传统的初级竞争模式无法支撑产业的发展&#xff0c;相反还会让企业陷入持续烧钱的恶性循环中&#xff0c;获客难度的提升无疑加速了体验经济时代的到来&#…

modbus介绍、环境搭建测试与qt下串口/Tcp的demo工程测试

一、modbus的介绍 1.简介 Modbus是一种串行通信协议&#xff0c;于1979年为使用可编程逻辑控制器&#xff08;PLC&#xff09;通信而发表。Modbus已经成为工业领域通信协议的业界标准&#xff08;De facto&#xff09;&#xff0c;并且现在是工业电子设备之间常用的连接方式&a…

SSM框架项目实战-CRM(客户关系管理1)

目录​​​​​​​ 1 项目介绍 1.1 crm简介 1.2 业务流程 1.3 crm的技术架构 2 物理模型设计 2.1 crm表的结构 2.2 主键字段 2.2 外键字段 2.3 关于日期和时间的字段 3 搭建项目环境 3.1 添加maven依赖 3.2 添加配置文件 3.3 添加页面和静态资源 ​编辑 4 首页…

超标量处理器设计——第九章_执行

参考《超标量处理器》姚永斌著 文章目录超标量处理器设计——第九章_执行9.1 概述9.2 FU类型9.2.1 ALU9.2.2 AGU9.2.3 BRU条件码分支正确性检查9.3 旁路网络9.3.1 简单设计的旁路网络9.3.2 复杂设计的旁路网络9.4 操作数的选择9.5 Cluster9.5.1 Cluster IQ9.5.2 Cluster Bypass…

Python 自动化测试(四):数据驱动

在实际的测试工作中&#xff0c;通常需要对多组不同的输入数据&#xff0c;进行同样的测试操作步骤&#xff0c;以验证我们的软件质量。这种测试&#xff0c;在功能测试中非常耗费人力物力&#xff0c;但是在自动化中&#xff0c;却比较好实现&#xff0c;只要实现了测试操作步…

前端基础_fillStyle和strokeStyle属性

fillStyle和strokeStyle属性 在前面的章节&#xff0c;在绘制图形时只用到默认的线条和填充样式。而在本节中将会探讨canvas全部的可选项&#xff0c;来绘制出更加吸引人的内容。如果想要给图形上色&#xff0c;有两个重要的属性可以做到&#xff1a;fillStyle和strokeStyle。…

社科院与杜兰大学金融管理硕士12门课程简介,其中有你心心念念的课程吗

当我们考量一个项目是否符合自身时&#xff0c;首先对课程设置是有要求的&#xff0c;课程设置是一个项目的灵魂所在&#xff0c;优质的课程与强大的师资更能体现项目的与众不同&#xff0c;下面一起去了解社科院与杜兰大学金融管理硕士项目12门必修课程概要&#xff0c;看看其…

【Java】java | maven | nexus私服 | maven私服 | docker安装nexus私服

一、说明 1、centos 7 2、docker 3、idea的maven项目 二、安装 1、拉取镜像 docker pull sonatype/nexus3:latest 2、创建映射目录并授权 mkdir /home/nexus/nexus-data && chown -R 200 /home/nexus/nexus-data 3、启动镜像 docker run -d -p 8081:8081 -p 8082:8082…

antd+vue——实现按钮始终固定在顶部,且根据权限的不同控制按钮组件的显示与隐藏——技能提升

最近在写后台管理系统&#xff0c;遇到一个小功能&#xff0c;就是一个按钮组件集合。 之前写过一篇文章&#xff0c;是关于按钮集合固定到页面顶部的文章。vue——实现页面滚动时&#xff0c;dom固定在顶部——基础积累 原理就是&#xff1a;监听页面的滚动&#xff0c;如果…

QT Qmake OpenGL osg笔记

文章目录概述QT修改样式qmake概述库引用和库路径指定QT创建动态库和使用小例子写动态库用动态库参考资料附录概述 需要先安装osg&#xff0c;然后再编译安装osgQOpenGL的插件。 其中&#xff0c;osgQOpenGL是OSG嵌入到qt中的一种实现方式&#xff0c;换言之&#xff0c;能够支…

AMBA、AHB、APB、AXI总线介绍及对比

link 一、AMBA概述AMBA (Advanced Microcontroller Bus Architecture) 高级微处理器总线架构定义了高性能嵌入式微控制器的通信标准&#xff0c;可以将RISC处理器&#xff08;精简指令集处理器&#xff09;集成在其他IP芯核和外设中&#xff0c;它是有效连接IP核的“数字胶”&a…

或许是市面上最强的 Mock 工具

背景 在开发过程中&#xff0c;由于后端与前端并行开发&#xff0c;或者前端需要等待后台开发&#xff0c;难以保证对接效率&#xff0c;同时即使用开发好的 API 对接&#xff0c;也有可能一个 API 不通就阻塞了整个软件的对接工作。同时对软件的敏感度也很高&#xff0c;一不…