草庐IT

51单片机矩阵键盘的控制原理-扫描及使用方式

孤心亦暖 2023-11-06 原文

矩阵键盘

矩阵键盘一般和之前学的LCD1602液晶屏一起控制,建议先看一下之前的博客学习一下液晶屏的使用。

当然矩阵键盘也可以和数码管和LED等一起操作,但是数码管需要扫描,很费CPU,LED又不能直观的看到键盘的操作。用LCD1602来学习矩阵键盘比较合适。

简介

在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排成矩阵形。

采用逐行或逐列扫描,就可以读出任意位置按键的状态。

解释一下

从原理图中我们可以看到,独立按键每个按键要使用一个I/O口,而矩阵键盘只用了8个I/O口。

假如矩阵键盘用独立按键的控制方式,四行四列需要16个I/O口。但是采用了扫描的控制方式后,我们只用了4+4=8个I/O口。

扫描的原理:

我们之前了解过数码管的扫描,叫做输出扫描。从第一位依此往下扫描到最后一位,然后快速循环,最终实现所有数码管同时显示。优点是解决了多位数码管不能在同一时间显示不同数字的情况,缺点是很费CPU。这里比较难理解,可以看看之前分析动态数码管的博客

矩阵键盘的扫描叫做输入扫描。单片机读取每一行(列)的状态,然后快速循环,最终实现所有按键同时检测的效果。

扫描也是显示器的一种显示方式,里面也有很多学问,感兴趣的朋友们可以去学习一下

原理

现在说一下矩阵键盘的使用原理。

大家想象一下,假如把矩阵键盘的第一行,全部接地(即P17赋值为0),那么是不是第一行就变成了一组独立按键呢?这个时候我们判断一下P10~P3是否位0,就能看出哪个按键按下了。(如果按键按下,说明和P17短路了,对应的管脚口肯定就是是0了)

那么扫描第一行的方式就是:P17赋值为0,P16~P14赋值为1,判断P10~P13哪个为零,对应的按键就按下了。比如这个时候我们检测到P11为零, 说明S3按键按下了。

以此类推,扫描第二行的方式就是:P16赋值为0,P17,P15,P14赋值为1,判断P10~P13哪个为零。……这种方法叫做逐行扫描

同理,我们也可以用逐列扫描,先依次给P10~ P13低电平,依次判断P14~P17的电平,来判断哪个按键被按下了。

弱上拉(强下拉)模式

再来了解一下弱上拉模式:具有较弱的拉高电平或电位的能力,虽然弱,但是能够实现电平改变。

说人话,比如扫描第一行按键,P17赋值0,我们以前说过,单片机上电后每个I/O都是高电平,我们按下S1,P13和P17短接,我们的单片机是弱上拉模式,拉高电平能力有限,所以P13口的电平才能够被拉低。P13就变成了低电平。假如这是一个强上拉模式的单片机,那么即使P17被赋低电平,按下S1,P13的电平也不能够被拉低,因为单片机上电后的电平被强上拉了。

内部原理

画个草图,单片机内部是这样的,上电后,VCC和单片机读取电平的部分连接,这样I/O口就会输出高电平,同时单片机读取到高电平。

施密特触发器是数电里的一个知识点,是一种正反馈比较器,内部有高电阻。当外部接地后,强电流会趋向于往电阻较低的方向流,相当于节点和触发器那一部分断开了,这样单片机就读取不到电平了。

如果大家的数电模电底子比较扎实,应该很容易理解这个意思。当然没有看明白的朋友也不必担心,这些都是了解性内容,大概知道一下就行

除了弱上拉模式,单片机还有推挽输出(没有上拉电阻,高电平直接接VCC,低电平直接接GND)、高阻输入(既没有上拉,也没有下拉)、开漏输出……这些模式是嵌入式和开关电源的知识点,我们的管脚口是可以自由配置。现在不用理解太深,以后会再提。

大家可以在我们STC89C52的手册上查看一下

写代码

新建工程

同时去以前我们写过的工程里,把LCD1602的驱动和Delay的模块复制并添加进来。

编译好之后,我们会发现下面有一下关于函数定义的报错,其实影响不大,如果觉得碍眼可以关掉。


添加模块MatrixKey

生成.h的模板

我们在左侧的工具栏点击Functions就可以看到我们在各个文件内定义的函数了。

同样,点击books就可以看到一些官方给的参考资料。

点击Templates能够看到官方给的模板,点击就可以直接生成这些模板。

当然我们也可以自己配置模板,右键选择“Cofigure Templates”,来修改自己的配置。

按照如下操作,来添加一个模板吧!

#ifndef __|_H__
#define 



#endif

第一行的 “|“ 意思是生成模板后光标的位置。

MatrixKey文件

根据自己学到的内容,写如下代码

//MatrixKey.c
#include <REGX52.H>//我们用到了管脚口,所以需要导入这个头文件,它里面
//定义了各个管教口的地址
#include "Delay.H"//我们用到了延时函数,所以需要导入这个头文件

unsigned char Matrixkey()
{
	//我们用逐列扫描
	unsigned char KeyNumber = 0;
	//第一列
	P1 = 0xFF;//给P1的所有管脚口置1
	P1_3 = 0;
	if(P1_7==0) 
	{
		Delay(20);//消抖
		While(P1_7==0);//检测松手
		Delay(20);//消抖
		KeyNumber = 1;//赋值S1 
	}
	if(P1_6==0) 
	{
		Delay(20);//消抖
		While(P1_7==0);//检测松手
		Delay(20);//消抖
		KeyNumber = 5;//赋值S1 
	}
	if(P1_5==0) 
	{
		Delay(20);//消抖
		While(P1_7==0);//检测松手
		Delay(20);//消抖
		KeyNumber = 9;//赋值S1 
	}
	if(P1_4==0) 
	{
		Delay(20);//消抖
		While(P1_7==0);//检测松手
		Delay(20);//消抖
		KeyNumber = 13;//赋值S1 
	}
	//第二列
	P1 = 0xFF;//给P1的所有管脚口置1
	P1_2 = 0;
	if(P1_7==0) {Delay(20);While(P1_7==0);Delay(20);KeyNumber = 2; }
	if(P1_6==0) {Delay(20);While(P1_6==0);Delay(20);KeyNumber = 6; }
	if(P1_5==0) {Delay(20);While(P1_5==0);Delay(20);KeyNumber = 10; }
	if(P1_4==0) {Delay(20);While(P1_4==0);Delay(20);KeyNumber = 14; }
	//第三列
	P1 = 0xFF;//给P1的所有管脚口置1
	P1_1 = 0;
	if(P1_7==0) {Delay(20);While(P1_7==0);Delay(20);KeyNumber = 3; }
	if(P1_6==0) {Delay(20);While(P1_6==0);Delay(20);KeyNumber = 7; }
	if(P1_5==0) {Delay(20);While(P1_5==0);Delay(20);KeyNumber = 11; }
	if(P1_4==0) {Delay(20);While(P1_4==0);Delay(20);KeyNumber = 15; }
	//第四列
	P1 = 0xFF;//给P1的所有管脚口置1
	P1_0 = 0;
	if(P1_7==0) {Delay(20);While(P1_7==0);Delay(20);KeyNumber = 4; }
	if(P1_6==0) {Delay(20);While(P1_6==0);Delay(20);KeyNumber = 8; }
	if(P1_5==0) {Delay(20);While(P1_5==0);Delay(20);KeyNumber = 12; }
	if(P1_4==0) {Delay(20);While(P1_4==0);Delay(20);KeyNumber = 16; }
	return KeyNumber;
}

以上是我们比较容易判断的代码,当然我们也可以看一看单片机给的例程,学习一下矩阵键盘的代码,不过那种代码的逻辑比较复杂,不太好理解。

头文件如下:

//MatrixKey.h
#ifndef __MatrixKey_H__
#define __MatrixKey_H__

unsigned char Matrixkey();

#endif

主函数的内容:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;//定义变量来保存矩阵键盘函数的返回值

void main()
{
	LCD_Init();//这行代码必须写,不然后面怎么写我们的显示平也用不了
	LCD_ShowString(1,1,"HelloWorld!");
	while(1)
	{
		KeyNum = MatrixKey();
		if(KeyNum)	
		{
			LCD_ShowNum(2,1,KeyNum,2);
		}
	}
}

烧录程序就能看到,按下按键,显示屏第一行显示Hello world,第二行显示按下的键码值。

试着自己写注释

创建一个模板,名字叫做:@brief,brief是内容说明,param是参数说明,retval是返回值说明。有了这个模板,我们以后模块化编程的时候就更方便写注释方便自己和别人阅览。

/**
  * @brief	|
  * @param
  * @retval
*/

给我们的工程加上这段注释,那么MatrixKey.c文件前面就会有这么一段话

有关51单片机矩阵键盘的控制原理-扫描及使用方式的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐