STC89C52单片机 启动!!!(三)

矩阵键盘介绍

当键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。

扫描原理

让我们来对比一下独立按键和矩阵按键

在独立按键中我们是按键一端接地,当按键按下时端口处变为低电平。之前我只是记住了这一点,但是现在我才了解到内部的原理。请看下面介绍。

51系列单片机I/O口各种不同的工作模式以及配置介绍

首先我们要先知道一件事情:所有IO口通电后默认是高电平

总的来说,我们使用的STC89C52中,P1,P2,P3,P4口都是弱上拉模式,只有P0端口为开漏输出模式。具体什么是弱上拉模式呢?

简单一点来讲就是像我们下面图中的结构,开关打到上面,此时端口为高电位,输出为1,但是此时驱动能力很弱,当端口外面接低电位的时候允许将它的电位拉低,(我的理解是当外面接低电位的时候vcc和低电位之间形成通路,有了电流,这样电阻就能分压,使得端口处电位降低,不知道是否正确)这就是我们一会儿讲到矩阵按键时的原理。当我们让端口为低电位,开关打到下面时,端口处为低电位,输出为0,此时驱动能力很强,能吸收许多的电流。这其实就是在51单片机中能让发光二极管(LED)发光比较亮的原理,当端口电位为0的时候驱动能力很强。所以我们通常检验端口电位是不是变化为0。

逐行扫描读取按键状态(具体原理)

结构:s1,s2,s3,s4的左边都连在了P17,s5,s6,s7,s8的左边都连在了P16,依次共四行占用了4个端口,s1,s5,s9,s13的右边都连在了P13,s2,s6,s10,s14的右边都连在了P12,依次共四列占用了4个端口。我们的想法是:

p17  p16   p15   p14

0        1        1        1 //通过其余四个端口电位状态来检查第一行是否有按键按下

1        0        1        1 //通过其余四个端口电位状态来检查第二行是否有按键按下

1        1        0        1 //通过其余四个端口电位状态来检查第三行是否有按键按下

1        1        1        0 //通过其余四个端口电位状态来检查第四行是否有按键按下    

具体原理:这部分很重要!这部分很重要!这部分很重要!!!

.首先我们要知道当p17  p16   p15   p14中的端口给0时才能检测到是否这行的按键按下了,当p17  p16   p15   p14中仅P17端口给0,其他全给1时,就算第二行的按键按下了但第一行的按键没有按下,我们的P10,P11,P12,P13也检测不到低电平来判断某一列有按键按下,因为此时只有第一行的一端是低电平,其他行按键左端都是高电平。当我们按键一端赋值高电平的时候,不论按键是否按下,另一端的电平都不会改变,还是为默认的高电平。

二.接着解释一下为什么按键一边端口赋为低电平,开关按下后,另一端也变成低电平。我们刚刚在上面讲过了,所有IO口通电后默认是高电平,当然这里的P10,P11,P12,P13也不例外,但是弱上拉模式当端口外面接低电位的时候允许将它的电位拉低,即使它处在高电位,这里的P1系列8个端口都是弱上位模式,所以比如给P17接低点位,并且s2按下了,此时P12的电位就变为了低电位,检测为0,实现了精准读取按键状态的效果。

三.最后来结合第一点,解释一下为什么一次只能给p17  p16   p15   p14其中之一端口赋0,让其处于低点位。假如我们同时让P17,P16为低电平,此时当我们按下s5的时候和按下s1的时候,或者说同时按下的时候,P13端口的电位都变为了0,我们无法分辨到底是哪个按键按下了,所以一次只能给p17  p16   p15   p14其中之一端口赋0

对I/O工作模式的补充

补充:上面仅仅介绍了I/O口的弱上拉模式,下面补充一些对于STC89C52系列单片机中的开漏输出模式的介绍。

个人总结(欢迎大家指正):我认为P0端口的开漏输出配置就相当于上面这幅图中将电阻去掉,这样的话就不能让端口连通接地就使得端口的电位由1变为0,它可以实现输出低电平(0),但不能直接通过接地来改变电位1为0,这一点和弱上拉输出端口不一样。

开漏输出端口在输出高电平(1)时,实际上是处于高阻态,也就是并没有输出电平的功能,因此无法通过接地来将其改变为低电平。如果需要在开漏输出端口上输出低电平,通常需要外部上拉电阻和一个连接到负极(比如地)的开关或器件,当外部上拉电阻连接到 VCC 电源时,输出端口会被拉高到高电平(1)。当开关或器件闭合时,输出端口会连接到地,从而形成电路,使得输出端口能够输出低电平(0)。

继续拓展

代码实现

具体要求:按下按键s1-s16,依次在液晶屏上显示数字1-16。

//main.c里面代码
#include<regx52.h>
#include"LCD1602.h"
#include"matrix.h"
void main(){
	unsigned char i=0;
	LCD_Init();
  LCD_ShowString(1,1,"hello world");
	while(1){
		i=matrix_num();
		if(i){//这里注意,如果不加这个if判断条件的话,就会导致可能按下的时候闪烁一瞬间甚至不闪烁
//因为这里不写这个if的话当我们没有按下的时候会一直显示00,所以我们要加一个if判断来让它不显示00
			LCD_ShowNum(2,2,i,2);
		}
	}
	
}
//matrix.c里面代码
#include<regx52.h>
#include<delay.h>
unsigned char matrix_num(){
	unsigned char key_num=0;
	P1=0xFF;
	P1_7=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=1;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=2;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=3;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=4;}}
	P1=0xFF;
	P1_6=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=5;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=6;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=7;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=8;}}
	P1=0xFF;
	P1_5=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=9;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=10;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=11;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=12;}}
	P1=0xFF;
	P1_4=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=13;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=14;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=15;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=16;}}
	return key_num;
}

完整代码包括LCD系列内部实现代码参考文末,并且注意使用这个函数要先进行初始化先调用LCD_Init()函数,这个矩阵按键在理解了原理之后代码容易写出来

下面做出两点解释:

1.matrix_num()代码逻辑解释,我们每次检测一行按键的时候都是:像这样P1=0xFF; P1_7=0;仅仅让一个端口输出地低电平,然后检测后四个端口是否会出现低电平。这里代码是逐行检测,逐列检测和逐行检测代码逻辑几乎一样,只不过前四个端口用途和后四个端口用途颠倒一下,不多赘述。

2.代码耦合性,最好不要让这边刚读取到了按键按下,立即在这个代码中就显示读取到的数字,最好写成这边用一个函数来读取按下的按键,然后函数执行结束将按下按键的编号返回,然后再显示,这样代码更便于理解

用矩阵按键实现密码锁

功能:按下1-9号按键输入对应数字,按下10号按键输入0,按下11号按键表示确认提交,按下12号按键表示退格,按下13号按键表示清除。密码正确提交显示"YES 666",错误提交显示“ERROE”密码锁一共四位数字。

代码这里只展示了main.c里面的,因为是在上面那个显示1-16的代码基础上来的,除了main.c不同,其他都一样。

纯代码版本

#include<regx52.h>
#include"LCD1602.h"
#include"matrix.h"
void main(){
	unsigned char i=0,count=0;
	unsigned int password=0,set_password=3226;
	LCD_Init();
  LCD_ShowString(1,1,"password");
	while(1){
		i=matrix_num();
		if(0<i&&i<=10&&count<4){
			LCD_ShowString(1,10,"       ");
			password=password*10+i%10;
			LCD_ShowNum(2,2,password,4);
			count++;
			
			}
		if(i==12&&count>0){
			password=password/10;
			LCD_ShowNum(2,2,password,4);
			count--;
		}
	  if(i==13){
			password=0;
			LCD_ShowNum(2,2,password,4);
			count=0;
		}
		if(i==11){
			if(count==4){
				if(password!=set_password){
					LCD_ShowString(1,10,"ERROR");
					password=0;
					count=0;
				}
				if(password==set_password){
					LCD_ShowString(1,10,"YES 666");
					password=0;
					count=0;
				}
		  }
	  }
		
  }
}

 代码解释版本

#include<regx52.h>
#include"LCD1602.h"
#include"matrix.h"
void main(){
	unsigned char i=0,count=0;//count用来记录已经输入了几位
	unsigned int password=0,set_password=3226;//保存当前数字和预置密码
	LCD_Init();
  LCD_ShowString(1,1,"password");
	while(1){
		i=matrix_num();//获取按下按键对应的数字
		if(0<i&&i<=10&&count<4){//输入密码部分
			LCD_ShowString(1,10,"       ");//为了假如一轮输入测试结束,
//再次输入后覆盖上一次提示信息
			password=password*10+i%10;//这里i%10主要是针对10%10=0
			LCD_ShowNum(2,2,password,4);
			count++;
			
			}
		if(i==12&&count>0){//如果按下退格键并且此时count>就退格且count--
			password=password/10;
			LCD_ShowNum(2,2,password,4);
			count--;
		}
	  if(i==13){//清除输入且count重置为0
			password=0;
			LCD_ShowNum(2,2,password,4);
			count=0;
		}
		if(i==11){//如果按下确认键就进行检查
			if(count==4){
				if(password!=set_password){
					LCD_ShowString(1,10,"ERROR");
					password=0;//复位
					count=0;
				}
				if(password==set_password){
					LCD_ShowString(1,10,"YES 666");
					password=0;
					count=0;
				}
		  }
	  }
		
  }
}

补充另一种扫描方法

首先第一种就是我们刚刚讲到的行列扫描法。

第二种是线翻转扫描方法:(十字交叉确定)

先给所有行赋0,给所有列赋1,判断在哪一行;

然后给所有行赋1,所有列赋0,判断在哪一列。

两次分别确定出按下按键所在的行与列,就可以知道具体按下按键在哪里了。这里确定行列的原理也是当按键按下时,弱上拉模式当端口外面接低电位的时候允许将它的电位拉低,假如按键按下时,就能在另一端检测到低电位。

下面是代码部分示例,但是我认为这种方法写代码没有上一种简单且易于理解,所以我还是推荐使用逐行或者逐列扫描。

完整代码 

//LCD1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

//LCD1602.c
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}
//delay.h
#ifndef__delay_H__
#define__delay_H__
void delay(unsigned int t);

#endif
//delay.c
void delay(unsigned int t){
	while(t--);
}
//matrix.h
#ifndef__matrix_H__
#define__matrix_H__
unsigned char matrix_num(); 

#endif
//matric.c
#include<regx52.h>
#include<delay.h>
unsigned char matrix_num(){
	unsigned char key_num=0;
	P1=0xFF;
	P1_7=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=1;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=2;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=3;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=4;}}
	P1=0xFF;
	P1_6=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=5;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=6;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=7;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=8;}}
	P1=0xFF;
	P1_5=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=9;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=10;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=11;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=12;}}
	P1=0xFF;
	P1_4=0;
	if(P1_3==0){delay(200);if(P1_3==0){while(P1_3==0);key_num=13;}}
	if(P1_2==0){delay(200);if(P1_2==0){while(P1_2==0);key_num=14;}}
	if(P1_1==0){delay(200);if(P1_1==0){while(P1_1==0);key_num=15;}}
	if(P1_0==0){delay(200);if(P1_0==0){while(P1_0==0);key_num=16;}}
	return key_num;
}

参考文章:【51单片机】矩阵键盘_51单片机4×4矩阵键盘-CSDN博客

欢迎大家指正! 

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值