基于51单片机和LCD1602、矩阵按键、AT24C02、有源蜂鸣器的不怕你偷窥密码锁

系列文章目录


前言

用了普中A2开发板,单片机用STC89C52RC,用到板上的AT24C02、矩阵按键,还需要外接一个LCD1602和一个有源蜂鸣器模块。需要注意的是,开发板点阵屏旁边的跳线帽要接右边两个排针。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

1、如果AT24C02中没有写入过密码,会循环滚动英文进行提示,按任意按键写入默认密码。
在这里插入图片描述

2、第一行第一个数字为密码偏移量,第一行第二个数字为连续输错的次数。
在这里插入图片描述

3、按键说明。
在这里插入图片描述

4、最大输入长度为16,这一串数字中间包含有密码就行。
在这里插入图片描述

5、连续输错密码超过三次,则需要验证PIN码。
在这里插入图片描述

6、连续输错PIN码超过三次,则需要耐心等待。
在这里插入图片描述

7、修改密码时先输入旧密码进行验证。
在这里插入图片描述

8、旧密码验证成功后,第一次输入新密码。
在这里插入图片描述

9、第二次输入的新密码如果跟第一次的不一样,则需要重新输入,如果一样,则将新密码和新密码的长度写入AT24C02芯片中。
在这里插入图片描述

二、原理分析

1、检测有没有写入过密码到AT24C02存储芯片

AT24C02中的前四个字节作为校验码,可以自行设置几个数字,例如“8”、“0”、“5”、“1”。上电后先读取前四个字节,如果不是“8”、“0”、“5”、“1”,说明没写入过密码,按任意按键写入默认密码,如果是,则说明之前写入过密码,则从中读出密码。没写入过密码,并且前四个字节刚好是你自行设置的四个数字的概率是非常非常小的,可以忽略不计。

2、密码偏移量、密码输入、密码检测

假设密码为123456,密码偏移量为7,则需要输入890123,即密码的每一位加上偏移量再对10取余,就是我们需要输入的密码。每次正确输入密码后再从其余9个数字中随机选一个作为新的偏移量。

可以在所需要输入的密码的前后加多几个数字进行干扰,按确认键后或者输入长度达到16后,会进行检测,遍历所输入的数字串,只要中间含有所需要输入的密码,则判定输入正确,反之错误。例如上边的例子,输入846895 890123 7514这十六位数字也是可以的。

所输入的数字通过一个数组来存储,定义一个变量来保存输入长度InputLength,InputLength相当于一个指针,每输入一个数字InputLength加一,下一次输入就将数字保存在数组的下一个位置。

如果不知道其中的机制,就算看到了所有输入的数字,换一个偏移量后,再输入相同的数字,会发现出错开不了锁。

3、PIN码

连续输错密码超过3次,则需要验证PIN码。连续输错PIN码超过3次,则锁住30s(实际应用可以将时间设置长一些,例如5min或者更长的时间),在正确输入PIN码之前无法返回。在不知道PIN码长度的前提下,想要猜出来难于登天(就算知道长度,也很难猜出,在长度较长的前提下)。该代码的PIN码保存在单片机的Flash中,感兴趣的可以改成可修改并保存到AT24C02中。

4、有源蜂鸣器

本来想用板上的无源蜂鸣器实现按键音的音效,但是引脚冲突了,无源蜂鸣器和LCD1602都用到了P25,所以外接了一个有源蜂鸣器。有源蜂鸣器的IO接口给一个低电平,就能持续发出一定频率的声音,缺点是频率固定,不能改变。无源蜂鸣器需要不断翻转IO口来发出声音,跟有源蜂鸣器不同的是,无源蜂鸣器可以发出各种频率的声音。

5、密码修改

验证了旧密码后,需要输入两次新密码,将两次输入的新密码分别保存到两个数组中,再进行对比,如果不同,则重新输入,如果相同,就把密码和密码长度写入AT20C02中。

三、各模块代码

1、LCD1602

h文件

#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_SetCursor(unsigned char Line,unsigned char Column);
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);
void LCD_Clear(void);
void LCD_IsShowCursor(unsigned char State);
void LCD_Show16Char(unsigned char Line,unsigned char Column,char *String);

#endif

c文件

#include <REGX52.H>

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

/*函数定义*/
/**
  * 函    数:LCD1602私有延时函数,延时约50us
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Delay50us(void)	//12T@11.0592MHz
{
	unsigned char i;
	i=20;
	while(--i);
}

/**
  * 函    数:LCD1602私有延时函数,延时约2ms
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Delay2ms(void)	//12T@11.0592MHz
{
	unsigned char i,j;
	i=4;
	j=146;
	do
	{
		while(--j);
	}while(--i);
}

/**
  * 函    数:LCD1602写指令
  * 参    数:Command 要写入的指令
  * 返 回 值:无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay50us();
	LCD_EN=0;
	LCD_Delay50us();
}

/**
  * 函    数:LCD1602写数据
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay50us();
	LCD_EN=0;
	LCD_Delay50us();
}

/**
  * 函    数:LCD1602设置光标位置
  * 参    数:Line 行位置,范围:1~2
  * 参    数:Column 列位置,范围:1~16
  * 返 回 值:无
  */
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));
	}
}

/**
  * 函    数:LCD1602初始化函数
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);	//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0C);	//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);	//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);	//光标复位,清屏
	LCD_Delay2ms();	//清屏指令执行需要较长时间,需要较长的延时
}

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

/**
  * 函    数:在LCD1602指定位置开始显示所给字符串
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:String 要显示的字符串
  * 返 回 值:无
  */
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]);
	}
}

/**
  * 函    数:返回值=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;
}

/**
  * 函    数:在LCD1602指定位置开始显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~65535
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  */
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');
	}
}

/**
  * 函    数:在LCD1602指定位置开始以有符号十进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:-32768~32767
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  */
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');
	}
}

/**
  * 函    数:在LCD1602指定位置开始以十六进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~0xFFFF
  * 参    数:Length 要显示数字的长度,范围:1~4
  * 返 回 值:无
  */
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');
		}
	}
}

/**
  * 函    数:在LCD1602指定位置开始以二进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~1111 1111 1111 1111
  * 参    数:Length 要显示数字的长度,范围:1~16
  * 返 回 值:无
  */
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');
	}
}

/**
  * 函    数:LCD1602的光标复位,清屏
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Clear(void)
{
	LCD_WriteCommand(0x01);
	LCD_Delay2ms();
}

/**
  * 函    数:LCD1602设置光标是否显示
  * 参    数:State 光标状态,1:显示,0:不显示
  * 返 回 值:无
  */
void LCD_IsShowCursor(unsigned char State)
{
	if(State){LCD_WriteCommand(0x0E);}
	else{LCD_WriteCommand(0x0C);}
}

/**
  * 函    数:在LCD1602指定位置开始显示16个字符
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:String 要显示的字符串
  * 返 回 值:无
  */
void LCD_Show16Char(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;i<16;i++)
	{
		LCD_WriteData(String[i]);
	}
}

2、AT24C02

h文件

#ifndef __AT24C02_H__
#define __AT24C02_H__

void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);

#endif

c文件

#include <REGX52.H>

#define AT24C02_ADDRESS 0xA0	//AT24C02的I2C地址

sbit AT24C02_I2C_SDA=P2^0;
sbit AT24C02_I2C_SCL=P2^1;

/**
  * 函    数:I2C开始
  * 参    数:无
  * 返 回 值:无
  */
void AT24C02_I2C_Start(void)
{
	AT24C02_I2C_SDA=1;
	AT24C02_I2C_SCL=1;
	AT24C02_I2C_SDA=0;
	AT24C02_I2C_SCL=0;
}

/**
  * 函    数:I2C停止
  * 参    数:无
  * 返 回 值:无
  */
void AT24C02_I2C_Stop(void)
{
	AT24C02_I2C_SDA=0;
	AT24C02_I2C_SCL=1;
	AT24C02_I2C_SDA=1;
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的字节
  * 返 回 值:无
  */
void AT24C02_I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		AT24C02_I2C_SDA=Byte&(0x80>>i);
		AT24C02_I2C_SCL=1;
		AT24C02_I2C_SCL=0;
	}
	AT24C02_I2C_SCL=1;	//额外一个时钟,不处理应答信号
	AT24C02_I2C_SCL=0;
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:AckBit 要发送的应答位,0为应答,1为非应答
  * 返 回 值:接收到的一个字节数据
  */
unsigned char AT24C02_I2C_ReceiveByte(unsigned char AckBit)
{
	unsigned char i,Byte=0x00;
	AT24C02_I2C_SDA=1;
	for(i=0;i<8;i++)
	{
		AT24C02_I2C_SCL=1;
		if(AT24C02_I2C_SDA){Byte|=(0x80>>i);}
		AT24C02_I2C_SCL=0;
	}
	AT24C02_I2C_SDA=AckBit;	//发送应答位
	AT24C02_I2C_SCL=1;
	AT24C02_I2C_SCL=0;
	return Byte;
}

/**
  * 函    数:AT24C02写入一个字节
  * 参    数:WordAddress 要写入字节的地址
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	AT24C02_I2C_Start();
	AT24C02_I2C_SendByte(AT24C02_ADDRESS);
	AT24C02_I2C_SendByte(WordAddress);
	AT24C02_I2C_SendByte(Data);
	AT24C02_I2C_Stop();
}

/**
  * 函    数:AT24C02读取一个字节
  * 参    数:WordAddress 要读出字节的地址
  * 返 回 值:读出的数据
  */
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	AT24C02_I2C_Start();
	AT24C02_I2C_SendByte(AT24C02_ADDRESS);
	AT24C02_I2C_SendByte(WordAddress);
	AT24C02_I2C_Start();
	AT24C02_I2C_SendByte(AT24C02_ADDRESS|0x01);
	Data=AT24C02_I2C_ReceiveByte(1);
	AT24C02_I2C_Stop();
	return Data;
}

3、矩阵按键

h文件

#ifndef __MATRIXKEYSCAN_H__
#define __MATRIXKEYSCAN_H__

unsigned char MatrixKey(void);
void MatrixKey_Tick(void);

#endif

c文件

#include <REGX52.H>

#define Matrix_Port P1	//矩阵按键接口

unsigned char KeyNumber;

/**
  * 函    数:获取矩阵按键键码
  * 参    数:无
  * 返 回 值:按下按键的键码,范围:0~48,0表示无按键按下
  * 说    明:在下一次检测按键之前,第二次获取键码一定会返回0
  */
unsigned char MatrixKey(void)
{
	unsigned char KeyTemp=0;
	KeyTemp=KeyNumber;
	KeyNumber=0;
	return KeyTemp;
}

/**
  * 函    数:检测当前按下的按键,无消抖及松手检测
  * 参    数:无
  * 返 回 值:按下按键的键值,范围:0~16,无按键按下时返回值为0
  */
unsigned char Key_GetState()
{
	unsigned char KeyValue=0;
	unsigned char i=0;
	
	Matrix_Port=0x0F;	//给所有行赋值0,列全为1
	i=2;while(i--);	//适当延时,延时5us
	if(Matrix_Port!=0x0F)
	{
		Matrix_Port=0x0F;	//测试列
		i=2;while(i--);
		switch(Matrix_Port)	//所有行拉低,检测哪一列按下
		{
			case 0x07:KeyValue=1;break;
			case 0x0B:KeyValue=2;break;
			case 0x0D:KeyValue=3;break;
			case 0x0E:KeyValue=4;break;
			default:break;
		}
		Matrix_Port=0xF0;	//测试行
		i=2;while(i--);
		switch(Matrix_Port)	//所有列拉低,检测哪一行按下
		{
			case 0x70:KeyValue=KeyValue;break;
			case 0xB0:KeyValue=KeyValue+4;break;
			case 0xD0:KeyValue=KeyValue+8;break;
			case 0xE0:KeyValue=KeyValue+12;break;
			default:break;
		}
	}
	else
	{
		KeyValue=0;
	}

	return KeyValue;
}

/**
  * 函    数:矩阵按键驱动函数,在中断中调用
  * 参    数:无
  * 返 回 值:无
  */
void MatrixKey_Tick(void)
{
	static unsigned char NowState,LastState;
	static unsigned int KeyCount;

	LastState=NowState;	//更新上一次的键值
	NowState=Key_GetState();	//获取当前的键值

	//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间
	if(LastState==0)
	{
		switch(NowState)
		{
			case 1:KeyNumber=1;break;
			case 2:KeyNumber=2;break;
			case 3:KeyNumber=3;break;
			case 4:KeyNumber=4;break;
			case 5:KeyNumber=5;break;
			case 6:KeyNumber=6;break;
			case 7:KeyNumber=7;break;
			case 8:KeyNumber=8;break;
			case 9:KeyNumber=9;break;
			case 10:KeyNumber=10;break;
			case 11:KeyNumber=11;break;
			case 12:KeyNumber=12;break;
			case 13:KeyNumber=13;break;
			case 14:KeyNumber=14;break;
			case 15:KeyNumber=15;break;
			case 16:KeyNumber=16;break;
			default:break;
		}
	}
	
	//如果上个时间点按键按下,这个时间点按键还是按下,则是长按
	if(LastState && NowState)
	{
		KeyCount++;

		if(KeyCount%50==0)	//定时器中断函数中每隔20ms检测一次按键
		{	//长按后每隔1s返回一次长按的键码
			if     (LastState== 1 && NowState== 1){KeyNumber=17;}
			else if(LastState== 2 && NowState== 2){KeyNumber=18;}
			else if(LastState== 3 && NowState== 3){KeyNumber=19;}
			else if(LastState== 4 && NowState== 4){KeyNumber=20;}
			else if(LastState== 5 && NowState== 5){KeyNumber=21;}
			else if(LastState== 6 && NowState== 6){KeyNumber=22;}
			else if(LastState== 7 && NowState== 7){KeyNumber=23;}
			else if(LastState== 8 && NowState== 8){KeyNumber=24;}
			else if(LastState== 9 && NowState== 9){KeyNumber=25;}
			else if(LastState==10 && NowState==10){KeyNumber=26;}
			else if(LastState==11 && NowState==11){KeyNumber=27;}
			else if(LastState==12 && NowState==12){KeyNumber=28;}
			else if(LastState==13 && NowState==13){KeyNumber=29;}
			else if(LastState==14 && NowState==14){KeyNumber=30;}
			else if(LastState==15 && NowState==15){KeyNumber=31;}
			else if(LastState==16 && NowState==16){KeyNumber=32;}
		}
	}
	else
	{
		KeyCount=0;
	}

	//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间
	if(NowState==0)
	{
		switch(LastState)
		{
			case 1:KeyNumber=33;break;
			case 2:KeyNumber=34;break;
			case 3:KeyNumber=35;break;
			case 4:KeyNumber=36;break;
			case 5:KeyNumber=37;break;
			case 6:KeyNumber=38;break;
			case 7:KeyNumber=39;break;
			case 8:KeyNumber=40;break;
			case 9:KeyNumber=41;break;
			case 10:KeyNumber=42;break;
			case 11:KeyNumber=43;break;
			case 12:KeyNumber=44;break;
			case 13:KeyNumber=45;break;
			case 14:KeyNumber=46;break;
			case 15:KeyNumber=47;break;
			case 16:KeyNumber=48;break;
			default:break;
		}
	}

}

4、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

#include <REGX52.H>

/**
  * 函    数:定时器0初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Init(void)
{	
//	AUXR&=0x7F;	//定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)
	TMOD&=0xF0;	//设置定时器模式(高四位不变,低四位清零)
	TMOD|=0x01;	//设置定时器模式(通过低四位设为16位不自动重装)
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
	TF0=0;	//清除TF0标志
	TR0=1;	//定时器0开始计时
	ET0=1;	//打开定时器0中断允许
	EA=1;	//打开总中断
	PT0=0;	//当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned int T0Count;	//定义静态变量
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

四、主函数

main.c

/*by甘腾胜@20250410
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】LCD1602、矩阵按键、AT24C02、有源蜂鸣器
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【说明】板上的无源蜂鸣器和LCD1602都用到P25引脚,冲突了,所以外接一个有源蜂鸣器
【注意】点阵屏旁边的跳线帽要接右边两个排针
【操作】

	S1		S2		S3		S4
	S5		S6		S7		S8
	S9		S10		S11		S12
	S13		S14		S15		S16

	1		2		3		4
	5		6		7		8
	9		0		清空	确认
					重置	返回

(1)S1至S9代表数字1~9,S10代表数字0
(2)输入错误可按S11(松手瞬间)清空输入
(3)按S12(松手瞬间)表示确认完成输入,程序进行密码检测
(4)长按S15超过1s进入重置密码界面
(5)重置密码界面长按S16超过1s返回密码输入界面

*/

#include <REGX52.H>
#include "LCD1602.h"
#include "AT24C02.h"
#include "MatrixKeyScan.h"
#include "Timer0.h"
#include <STDLIB.H>	//随机函数

sbit Buzzer=P2^2;	//蜂鸣器端口

unsigned char KeyNum;	//存储获得的键码

/*模式
0:正常输入密码模式
1:设置密码模式(输入旧密码)
2:设置密码模式(输入新密码)
3:输入PIN
4:锁住30s
*/
unsigned char Mode;

unsigned char RollFlag;	//屏幕滚动显示的标志,1:滚动显示(显示下一帧),0:不滚动显示(暂不显示下一帧)
unsigned char RollOffset1;	//屏幕第一行滚动显示的偏移量
unsigned char RollOffset2;	//屏幕第二行滚动显示的偏移量

//要输入的所设置密码的偏移量,范围:0~9
//例如:密码为202504,偏移量为7的话,则要输入979271才可以
//即原来密码加上7对10取余,就是你要输入的密码
unsigned char PasswordOffset;

unsigned char PasswordLength=6;	//密码长度,范围:6~11,默认密码长度为6
unsigned char InputLength;	//输入长度,范围:1~16
unsigned char LastInputLength;	//上一次的输入长度,范围:1~16,用来判断是否有按下数字键0~9
unsigned char OnceFlag=1;	//某些前提下只执行一次的标志(各模式中用来判断是否要初始化),1:执行,0:不执行
unsigned char ConfirmFlag;	//确认按键按下的标志,1:确认按键按下,0:确认按键未按下
unsigned char Result;	//密码校验结果,0:无意义,1:正确,2:错误
unsigned char SetNewPasswordFlag=1;	//设置新密码的标志,0:无意义,1:第一次输入新密码,2:第二次输入新密码
unsigned char WrongTime;	//密码输入错误的次数,连续输入错误的次数大于三次则需要输入PIN码
unsigned char PINWrongTime;	//PIN输入错误的次数,PIN连续输入错误的次数大于三次则锁住30s
unsigned char T0Count1,T0Count2,T0Count3,T0Count4;	//定时器0计数变量
//unsigned char T1Count;	//定时器1计数变量
unsigned char LockedFlag;	//锁住的标志
unsigned char LockedTime;	//锁住的时间
unsigned char idata Checkcode[4];	//存储从AT24C02中读取的前四个字节(校验码),判断是否写入过密码
unsigned char idata EnteredNumbers[16];	//存储用户所输入的数字
unsigned char idata Password[11];	//从存储芯片中读出的密码或者用户修改之后的密码,密码长度:6~11
unsigned char idata NewPassword1[11];	//存储用户第一次输入的新密码,密码长度:6~11
unsigned char idata NewPassword2[11];	//存储用户第二次输入的新密码,密码长度:6~11
unsigned char NewLength1;	//第一输入的新密码的长度
unsigned char NewLength2;	//第二输入的新密码的长度
unsigned char PressFlag;	//按键按下的标志
unsigned char code PIN[]={0,1,2,3,4,5,6,7,};	//连续三次输错密码,则需要输入PIN(感兴趣的可以改成可以修改的,保存在AT24C02中)

//默认密码,如果AT24C02存储芯片中还没写入过密码,则写入此密码,如果有写入过,则不写入
unsigned char code DefaultPassword[]={2,0,2,5,0,4,};

//如果存储芯片中没有写入过密码,则滚动显示英文进行提示
unsigned char code Table1[]="No password on the storage chip.                No password on t";
unsigned char code Table2[]="Press any key to write the default password into the chip.                Press any key to";

/**
  * 函    数:延时函数,延时约xms毫秒
  * 参    数:xms 延时的时间,范围:0~65535
  * 返 回 值:无
  */
void Delay(unsigned int xms)	//@11.0592MHz
{
	unsigned char i,j;
	while(xms--)
	{
		i=2;
		j=199;
		do
		{
			while(--j);
		}while(--i);
	}
}

/**
  * 函    数:主函数(有且仅有一个)
  * 参    数:无
  * 返 回 值:无
  * 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌
  */
void main()
{
	unsigned char i,j;
	Timer0_Init();  //定时器0初始化
	LCD_Init();	//LCD1602初始化

	for(i=0;i<4;i++)	//读出存储芯片中的前4个字节
	{
		Checkcode[i]=AT24C02_ReadByte(i);
		Delay(5);	//需要延时一小会让存储芯片缓一缓,不然会读写出错
	}

	if(Checkcode[0]==8 && Checkcode[1]==0 && Checkcode[2]==5 && Checkcode[3]==1)	//如果校验码正确
	{	//即如果前四个数字分别是“8”、“0”、“5”、“1”
		PasswordOffset=AT24C02_ReadByte(4);	//从存储芯片中的第5个字节读出要输入的密码的偏移量
		PasswordLength=AT24C02_ReadByte(5);	//从存储芯片中的第6个字节读出密码长度
		for(i=0;i<PasswordLength;i++)	//读出存储芯片中的第7个字节开始的PasswordLength个数据,即读出密码
		{
			Password[i]=AT24C02_ReadByte(6+i);
			Delay(5);
		}
	}
	else	//如果校验码不正确
	{
		while( !(KeyNum>=33 && KeyNum<=48) )	//按任意按键(松手时)退出循环
		{
			KeyNum=MatrixKey();
			if(RollFlag)
			{
				RollFlag=0;
				LCD_Show16Char(1,1,Table1+RollOffset1);	//滚动显示
				LCD_Show16Char(2,1,Table2+RollOffset2);				
				RollOffset1++;
				RollOffset1%=48;
				RollOffset2++;
				RollOffset2%=74;
			}
		}

		PressFlag=1;	//按键按下的标志置1
		Buzzer=0;	//有源蜂鸣器的数据口拉低,蜂鸣器发出一定频率的声音

		srand(TL0);	//以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据
		PasswordOffset=rand()%10;
		
		//如果前四个数字不是“8”、“0”、“5”、“1”
		//则将“8”、“0”、“5”、“1”这四个数字写入存储芯片AT24C02的前四个位置
		AT24C02_WriteByte(0,8);
		Delay(5);
		AT24C02_WriteByte(1,0);
		Delay(5);
		AT24C02_WriteByte(2,5);
		Delay(5);
		AT24C02_WriteByte(3,1);
		Delay(5);
		AT24C02_WriteByte(4,PasswordOffset);	//将密码偏移量写入存储芯片AT24C02
		Delay(5);
		AT24C02_WriteByte(5,6);	//将密码长度写入第五个字节,默认的密码长度为6
		Delay(5);

		//将默认的六位密码写入存储芯片
		for(i=0;i<6;i++)
		{
			AT24C02_WriteByte(6+i,DefaultPassword[i]);
			Delay(5);
		}

		//将默认的六位密码赋值给Password数组
		for(i=0;i<6;i++)
		{
			Password[i]=DefaultPassword[i];
		}
	}

	while(1)
	{
		KeyNum=MatrixKey();	//获取键码
		
		if(KeyNum)	//如果有按键按下
		{
			if( (KeyNum>=33 && KeyNum<=46) || KeyNum==31 || KeyNum==32 )
			{
				PressFlag=1;	//按键按下的标志置1
				Buzzer=0;
			}
			
			if(Mode==0)	//如果是正常输入密码模式
			{
				if(KeyNum>=33 && KeyNum<=42)	//如果按下“0~9”(松手瞬间),即按下S1~S10,S10为0
				{
					EnteredNumbers[InputLength]=(KeyNum-32)%10;	//保存输入的数字
					InputLength++;
				}
				if(KeyNum==43)	//如果按下S11(松手瞬间),则清空,重新输入
				{
					OnceFlag=1;
				}
				if(KeyNum==44)	//如果按下S12(松手瞬间),表示确认
				{
					if(InputLength){ConfirmFlag=1;}	//按了数字键,才能确认
				}
				if(KeyNum==31)	//如果长按S15超过1s,则进入设置密码模式
				{
					Mode=1;
					OnceFlag=1;
				}
			}

			if(Mode==1)	//如果是设置密码模式(输入旧密码)
			{
				if(KeyNum>=33 && KeyNum<=42)
				{
					EnteredNumbers[InputLength]=(KeyNum-32)%10;
					InputLength++;
				}
				if(KeyNum==43)
				{
					OnceFlag=1;
				}
				if(KeyNum==44)
				{
					if(InputLength){ConfirmFlag=1;}
				}
				if(KeyNum==32)	//如果长按S16超过1s,则返回正常输入密码模式
				{
					Mode=0;
					OnceFlag=1;
				}
			}

			if(Mode==2)	//如果是设置密码模式(输入新密码)
			{
				if(KeyNum>=33 && KeyNum<=42)
				{
					if(InputLength<=10)	//控制密码的最大长度,最大长度为11
					{
						EnteredNumbers[InputLength]=(KeyNum-32)%10;
						InputLength++;
					}
				}
				if(KeyNum==43)
				{
					OnceFlag=1;
				}
				if(KeyNum==44)
				{
					if(InputLength>=6){ConfirmFlag=1;}		//控制密码的最小长度,最小长度为6
				}
				if(KeyNum==32)	//如果长按S16超过1s,则返回正常输入密码模式
				{
					Mode=0;
					OnceFlag=1;
				}
			}

			if(Mode==3)	//如果是输入PIN模式
			{
				if(KeyNum>=33 && KeyNum<=42)
				{
					EnteredNumbers[InputLength]=(KeyNum-32)%10;
					InputLength++;
				}
				if(KeyNum==43)
				{
					OnceFlag=1;
				}
				if(KeyNum==44)
				{
					if(InputLength){ConfirmFlag=1;}
				}
			}
		}

		if(Mode==0)	//正常输入密码模式
		{
			if(OnceFlag)
			{
				OnceFlag=0;	//标志置0
				
				/*初始化*/
				LCD_ShowString(1,1,"Password:");	//显示密码的英文
				LCD_ShowNum(1,10,PasswordOffset,1);	//显示密码偏移量
				LCD_ShowString(1,11,"   ");	//清空显示
				LCD_ShowNum(1,12,WrongTime,1);	//显示错误次数
				LCD_ShowString(1,14,"   ");	//清空显示
				LCD_ShowString(2,1,"                ");	//清空第二行的显示
				LCD_SetCursor(2,1);	//光标设置在第二行第一列
				LCD_IsShowCursor(1);	//显示光标
				LastInputLength=0;	//数据清零
				InputLength=0;	//数据清零
				Result=0;	//密码验证的结果清零
				ConfirmFlag=0;	//确认标志清零
			}

			if(LastInputLength != InputLength)	//如果按了S1~S10(0~9),S10为0
			{
				LastInputLength=InputLength;	//更新上一次的输入长度
				LCD_WriteData('*');	//显示星号(光标自动加一)
			}

			if( (InputLength==16 || ConfirmFlag) && InputLength )
			{	//如果有按下0~9,且输入长度为16或按了确认按键
				ConfirmFlag=0;	//确认标志清零

				if(InputLength<PasswordLength)	//如果输入长度比密码长度短
				{
					Result=2;	//错误
				}
				else
				{
					for(j=0;j<=InputLength-PasswordLength;j++)
					{	//遍历输入的所有数字,看其中有没有一段长度为PasswordLength的跟密码(偏移后)一致的数字串
						for(i=j;i<PasswordLength;i++)
						{
							if( (Password[i]+PasswordOffset)%10 == EnteredNumbers[i+j] ){Result=1;}
							else {Result=2;break;}	//有跟偏移后的密码不一致的,则跳过此次循环
						}
						if(Result==1){break;}	//如果有跟偏移后的密码一致的数字串,则跳出外层循环
						//例如,如果密码为123456,偏移量为3,则需要输入的偏移后的密码为3456789
					}
				}
			}

			if(Result)	//判断密码正不正确
			{
				if(Result==1)	//如果密码正确
				{
					LCD_ShowString(1,14," OK");	//则显示“OK”
					while(1)	//密码正确后产生一个跟上一次不一样的偏移量
					{
						srand(TL0);	//以定时器0的低八位数据作为随机数的种子,用来产生真随机的数据
						i=rand()%10;
						if(i!=PasswordOffset){break;}
					}
					PasswordOffset=i;
					AT24C02_WriteByte(4,PasswordOffset);	//将密码偏移量写入存储芯片AT24C02
					WrongTime=0;	//错误次数清零
				}
				else if(Result==2)	//如果密码错误
				{
					LCD_ShowString(1,14,"ERR");	//则显示“ERR”(error)
					WrongTime++;
				}
				if(WrongTime>3)	//如果连续三次密码错误
				{
					Mode=3;	//则跳到输入PIN的模式
					OnceFlag=1;
					PINWrongTime=0;
				}
				OnceFlag=1;	//初始化的标志置1
				Delay(1000);	//延时1s
			}
			
		}

		else if(Mode==1)	//设置密码模式(验证旧密码)
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				LCD_ShowString(1,1,"Old Password:   ");
				LCD_ShowString(2,1,"                ");
				LCD_ShowNum(1,14,WrongTime,1);
				LCD_SetCursor(2,1);
				LCD_IsShowCursor(1);
				LastInputLength=0;
				InputLength=0;
				Result=0;
				ConfirmFlag=0;
			}

			if(LastInputLength != InputLength)
			{
				LastInputLength=InputLength;
				LCD_WriteData('*');
			}

			if( (InputLength==16 || ConfirmFlag) && InputLength )
			{
				ConfirmFlag=0;
				if(InputLength!=PasswordLength)	//如果输入长度与密码长度不一致
				{
					Result=2;
				}
				else
				{
					for(i=0;i<PasswordLength;i++)
					{
						if(Password[i] == EnteredNumbers[i]){Result=1;}	//逐个数字比较
						else {Result=2;break;}	//如果出现不一样的,则说明密码错误,跳出循环
					}
				}
			}

			if(Result)
			{
				LCD_IsShowCursor(0);
				LCD_ShowString(2,1,"                ");
				if(Result==1)
				{
					LCD_ShowString(2,1,"Correct");
					Mode=2;	//如果旧密码正确,则开始输入新密码
					SetNewPasswordFlag=1;	//新密码的标志置1
				}
				else if(Result==2)
				{
					LCD_ShowString(2,1,"Wrong");
					WrongTime++;	//如果密码错误,则错误的次数加一
				}
				if(WrongTime>3)	//密码错误的次数超过3次
				{
					Mode=3;	//则跳到输入PIN的模式
					OnceFlag=1;
					PINWrongTime=0;
				}
				OnceFlag=1;
				Delay(1000);
			}
		}

		else if(Mode==2)	//设置密码模式(输入新密码)
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				if(SetNewPasswordFlag==1)	//第一次输入新密码
				{
					LCD_ShowString(1,1,"New Password:1  ");
				}
				else if(SetNewPasswordFlag==2)	//第二次输入新密码
				{
					LCD_ShowString(1,1,"New Password:2  ");
				}
				LCD_ShowString(2,1,"                ");
				LCD_SetCursor(2,1);
				LCD_IsShowCursor(1);
				LastInputLength=0;
				InputLength=0;
				Result=0;
				ConfirmFlag=0;
			}

			if(LastInputLength != InputLength)
			{
				LastInputLength=InputLength;
				LCD_WriteData('*');
			}

			if(InputLength>=6 && InputLength<=11 && ConfirmFlag)
			{	//新密码长度要求为6~11
				ConfirmFlag=0;
				if(SetNewPasswordFlag==2)	//如果是第二次输入新密码
				{
					NewLength2=InputLength;	//将密码长度保存到另一个变量中,防止重置的时候被清空导致数据丢失
					for(i=0;i<InputLength;i++)
					{	//将输入的数字转移到另一个数组,防止重置的时候被清空导致数据丢失
						NewPassword2[i]=EnteredNumbers[i];
					}

					if(NewLength1!=NewLength2)	//如果两次输入密码的长度不一样
					{
						Result=2;	//说明出现错误
					}
					else	//如果两次输入密码的长度一样
					{
						Result=1;
						for(i=0;i<NewLength1;i++)
						{	//则逐个数字比较,看两次输入的是否一样
							if(NewPassword1[i] != NewPassword2[i]){Result=2;break;}	//如果出现不一样的,说明出现错误,跳出循环
						}
					}
				}
				if(SetNewPasswordFlag==1)	//如果是第一次输入新密码
				{
					NewLength1=InputLength;	//将密码长度保存到另一个变量中,防止重置的时候被清空导致数据丢失
					for(i=0;i<InputLength;i++)
					{	//将输入的数字转移到另一个数组,防止重置的时候被清空导致数据丢失
						NewPassword1[i]=EnteredNumbers[i];
					}
					SetNewPasswordFlag=2;
					OnceFlag=1;
				}
			}

			if(Result)	//判断两次输入的新密码是否一致
			{
				LCD_IsShowCursor(0);	//不显示光标
				LCD_ShowString(2,1,"                ");	//清空第二行显示
				if(Result==1)	//如果两次密码一致
				{
					PasswordLength=InputLength;
					AT24C02_WriteByte(5,PasswordLength);	//将密码长度写入存储芯片
					Delay(5);	//适当延时
					for(i=0;i<PasswordLength;i++)
					{
						Password[i]=NewPassword2[i];	//将新密码更新到Password数组中
						AT24C02_WriteByte(6+i,Password[i]);	//将密码写入存储芯片
						Delay(5);
					}
					LCD_ShowString(2,1,"Write OK");
					Mode=0;	//返回正常输入密码的模式
				}
				else if(Result==2)	//如果两次密码不一致
				{
					LCD_ShowString(2,1,"Different");
					SetNewPasswordFlag=1;	//设置新密码的标志置1,重新从第一次开始输入
				}
				OnceFlag=1;	//重置的标志置1
				Delay(1000);
			}
		}

		else if(Mode==3)	//验证PIN码
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				LCD_ShowString(1,1,"PIN:            ");
				LCD_ShowString(2,1,"                ");
				LCD_ShowNum(1,5,PINWrongTime,1);
				LCD_SetCursor(2,1);
				LCD_IsShowCursor(1);
				LastInputLength=0;
				InputLength=0;
				Result=0;
				ConfirmFlag=0;
			}

			if(LastInputLength != InputLength)
			{
				LastInputLength=InputLength;
				LCD_WriteData('*');
			}

			if( (InputLength==16 || ConfirmFlag) && InputLength )
			{
				ConfirmFlag=0;
				if(InputLength!=8)	//如果PIN的长度不等于8
				{
					Result=2;
				}
				else	//如果PIN的长度等于8
				{
					for(i=0;i<8;i++)
					{	//将输入的8个数字跟PIN数组的8个数字进行对比
						if(PIN[i] == EnteredNumbers[i]){Result=1;}
						else {Result=2;break;}
					}
				}
			}

			if(Result)
			{
				LCD_IsShowCursor(0);	//不显示光标
				LCD_ShowString(2,1,"                ");
				if(Result==1)	//如果PIN验证正确
				{
					LCD_ShowString(1,10,"Correct");
					Mode=0;	//返回正常输入密码的模式
					WrongTime=0;	//密码输入错误的次数清零
				}
				else if(Result==2)
				{
					LCD_ShowString(1,10,"  Wrong");
					PINWrongTime++;	//PIN输入错误的次数加一
				}
				if(PINWrongTime>3)	//如果PIN连续错误输入的次数大于3
				{
					Mode=4;	//则进入模式4(锁住30s)
					LockedFlag=1;	//锁住的标志置1
				}
				OnceFlag=1;
				Delay(1000);
			}
		}

		else if(Mode==4)	//PIN码连续错误输入3次被锁住,需要等待30s
		{
			if(OnceFlag)
			{
				OnceFlag=0;

				LCD_ShowString(1,1,"Locked          ");
				LCD_ShowString(2,1,"                ");
				LCD_IsShowCursor(0);
				T0Count3=0;
				LockedTime=30;
			}
			
			if(LockedFlag)	//如果被锁住的标志不为零
			{
				LCD_ShowNum(2,1,LockedTime,2);	//则显示倒计时
			}
			else		//如果被锁住的标志零,即倒计时结束了
			{
				Mode=3;	//返回输入PIN的模式
				OnceFlag=1;
				PINWrongTime=0;	//PIN连续错误输入的次数清零
			}
		}

	}
}

/**
  * 函    数:定时器0中断函数
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Routine() interrupt 1
{
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592MHz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592MHz
 	T0Count1++;
 	T0Count2++;
 	T0Count3++;
 	if(T0Count1>=2)	//每隔20ms检测一次按键
 	{
 		T0Count1=0;
		MatrixKey_Tick();
 	}
 	if(T0Count2>=50)	//每隔500ms滚动一次
 	{
 		T0Count2=0;
		RollFlag=1;
 	}
 	if(T0Count3>=100)	//每隔1s计时变量LockedTime减一
 	{
 		T0Count3=0;
		LockedTime--;
		if(LockedTime==0){LockedFlag=0;}
 	}
	if(PressFlag)	//如果按键按下的标志不为零
	{
		T0Count4++;
		if(T0Count4>=10)	//蜂鸣器响100ms
		{
			PressFlag=0;	//按键按下的标志清零
			T0Count4=0;	//计数清零
			Buzzer=1;	//防止蜂鸣器一直响(蜂鸣器是低电平导通)
		}
	}
}

总结

偶尔出现按下按键没反应的情况,不知道是我按的力度不够还是代码需要改进。估计是硬件的问题或者我按的力度不够。

WebSocket是一种在客户端服务器之间建立持久连接的协议,它允许双方进行双向通信,极大地提高了实时性,被广泛应用于即时通讯、在线游戏、股票交易等领域。本篇将详细讲解如何使用C#来实现WebSocket,以及Fleck库在其中的作用。 WebSocket协议基于HTTP的握手过程,但一旦连接建立,它就不再依赖HTTP,而是通过TCP直接通信。在C#中,我们可以利用.NET Framework或.NET Core提供的System.Net.WebSockets命名空间来创建WebSocket服务器客户端。然而,对于更高级的功能更好的灵活性,开发者通常会选择第三方库,如Fleck。 Fleck是C#中一个轻量级且高效的WebSocket服务器实现。它提供了丰富的事件驱动API,使得开发者可以轻松地处理WebSocket连接的建立、消息传递断开等操作。下面我们将探讨Fleck的使用步骤: 1. **安装Fleck**:你需要在你的项目中添加Fleck库。如果你使用的是NuGet包管理器,可以通过搜索"Fleck"并安装来获取。 2. **初始化服务器**:在C#代码中,首先创建一个Fleck服务器实例,指定监听的IP地址端口号。例如: ```csharp var server = new Fleck.WebSocketServer("ws://localhost:8080"); ``` 3. **配置服务器**:你可以设置各种配置选项,如最大接收/发送缓冲区大小、心跳间隔等。然后注册事件处理器,如OnOpen、OnClose、OnMessage等,以处理客户端连接、断开消息接收。 4. **连接事件**:`OnOpen`事件在客户端连接时触发,你可以在这里对新连接进行初始化操作,如存储连接信息、验证用户等。 5. **消息处理**:`OnMessage`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值