五、用矩阵键盘实现密码锁

矩阵键盘

独立键盘与单片机进行连接时,每一个按键都需要单片机的一个I/O口,若某单片机系统较多按键,如果用独立按键便会占用较多的I/O口资源。为了尽可能节省I/O口线,引入矩阵键盘。

矩阵按键原理

  • 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式
  • 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态

image-20221103204438277

以巫妖王单片机上的4×4矩阵键盘为例,讲解矩阵键盘的原理和检测方法,其原理图如上图所示。将16个按键排成4行4列,第一行将每个按键的一端连接在一起构成行线,第一列将每个按键的另一端连接在一起构成列线,这样一共有4行4列共八根线,我们将这八根线连接到单片机的8个I/O口上,通过程序扫描键盘就可检测16个键。用这种办法我们也可以实现3行3列9个键,5行5列25个键等。

无论是独立键盘还是矩阵键盘,单片机检测其是否被按下的依据都是一样的,也就是检测与该键对应的I/O口是否为低电平。独立键盘有一端固定为低电平,单片机写程序检测时比较方便,但是矩阵键盘两端都与单片机I/O口相连,因此在检测时需要人为通过单片机的I/O口送出低电平。

下面着重介绍一下扫描的概念:

  • 数码管扫描(输出扫描)

    原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果

  • 矩阵键盘扫描(输入扫描)

    原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果

  • 以按行扫描为例:

    当P1.4-P1.7口赋值为1101时,此时先看P17所在的行,无论S1、S2、S3和S4谁按下,两端都是高电平,同理P15和P14同样如此,而P16所在的行因为P1.6口赋值为0,只要在此基础检测P1.0-P1.3口电平情况就可以判断究竟是S5、S6、S7还是S8被按下。具体的说,当P1.4-P1.7口赋值为1101时,如果P1.0-P1.3口赋值为:1110(P1 .3为0),则S5被按下,其他同理,并以此类推即可。

在写代码前,再介绍C51子函数带返回值的写法

//格式
类型 函数名(形参)
{
	函数体;
    return 数据;
}
例如:
 int getSum(int num1,int num2)
{
      int sum = num1 + num2;
      return sum;
}
//参数是 函数接收外面传进来的
//返回值 是函数从里面扔出去的

模块化代码

各模块具体代码如下

  1. 主函数main()

    #include <REGX52.H>
    #include "Delay.h"
    #include "LCD1602.h"
    #include "MaxtrixKey.h"
    
    unsigned char KeyNum; //定义变量 接一下返回值
    void main()
    {
    	LCD_Init();
        LCD_ShowString(1,1,"Hello World!");
    	while(1)
    	{
    		KeyNum=MaxtrixKey();
    		
    		if(KeyNum)
    		{
    			LCD_ShowNum(2,1,KeyNum,2);
    		}
    	}
    }
    /*
    为什么需要加上if(KeyNum)进行判断,这是因为如果不加的话有一下一种情况:
    当你没有按下键时,开始执行KeyNum=MaxtrixKey()语句,即KeyNum通过调用MaxtrixKey函数获取键值,此时KeyNum获取的值为0(不按下键,MaxtrixKey函数内的值就是初始化的值0)
    然后开始执行LCD_ShowNum函数,这样1602上就会显示00
    当你按下某个键,不松手,会卡在执行KeyNum=MaxtrixKey()语,显示0
    显示的键值和显示0 间隔的时间太短 以至于人眼无法识别
    */
    
  2. MaxtrixKey.h

    #ifndef__MAXTRIXKRY_H__
    #define__MAXTRIXKRY_H__
    
    unsigned char MaxtrixKey();
    
    #endif
    
  3. MaxtrixKey.c

    #include <REGX52.H>
    #include "Delay.h"
    
    /**
      * @brief  矩阵键盘读取按键键码
      * @param  无
      * @retval KeyNumer 按下按键的键码值
      如果按下不放,程序会停留在此函数,松手一瞬间,返回按键码,没有按键时,返回0
      */
    
    unsigned char MaxtrixKey()
    {
    	unsigned char KeyNumber=0;
    	
    	P1=0xff;
    	P1_3=0;
    	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
    	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
    	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
    	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
    	
    	P1=0xff;
    	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=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_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;
    	
    }
    
    
  4. 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
    
  5. 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  在,范围:1~2
      * @param  Column 起始列位LCD1602指定位置开始显示所给数字
      * @param  Line 起始行位置置,范围: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');
    	}
    }
    
    
  6. Delay.h

    #ifndef __DELAY_H__   //
    #define __DELAY_H__
    
    void Delay(unsigned int xms);
    
    #endif
    
  7. Delay.c

    void Delay(unsigned int xms)		//@12.000MHz
    {
    	while(xms--)
    	{
    		unsigned char i, j;
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    	}	
    }
    

密码锁

在上面的基础上模块化的代码上,只需要改动主函数mian()即可实现,因此,下面就只给出主函数密码

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

unsigned char KeyNum; //定义变量 接一下返回值
unsigned int PassWord,Count; //定义四位密码和计数器

void main()
{
	LCD_Init();
    LCD_ShowString(1,1,"PassWord:");
	while(1)
	{
		KeyNum=MaxtrixKey();
		
		if(KeyNum)
		{
			if(KeyNum<=10)  //密码区,如果S1~S10按键按下,输入密码
			{
				if(Count<4)
				{
					PassWord*=10;//相当于PassWord=PassWord*10 目的:密码左移一位
				    PassWord+=KeyNum%10 ;//取余,1-9取余还是1-9 而10取余则为0 ,目的:获取一位密码
					Count++; //计次加1
				}
				LCD_ShowNum(2,1,PassWord,4);	
			}
			if(KeyNum==11)//如果S11按下,即为确认密码
			{
				if(PassWord==9981) //如果密码等于正确密码显示Pass,否则显示EROR
				{
					LCD_ShowString(1,14,"ok ");//注意OK后有空格 用于对齐ERR
					PassWord=0; //密码清0
					Count=0; //计次清0
					LCD_ShowNum(2,1,PassWord,4);//更新显示
				}
				else
				{
					LCD_ShowString(1,14,"Err");
					PassWord=0; //密码清0
					Count=0; //计次清0
					LCD_ShowNum(2,1,PassWord,4);//更新显示
				}	
			}
			
			if(KeyNum==12)//如果S12按下,取消
			{
				PassWord=0; //密码清0
				Count=0; //计次清0
				LCD_ShowNum(2,1,PassWord,4);//更新显示
			}
		
		}
	}
}

需要注意的是,当第一个输入的是0的时候会存在bug,这是因为0*10h还是等于0起不到进位的效果,此外也没加退格的功能,等后续有时间我再更新添加退格功能的代码。

  • 7
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值