C51单片机密码锁课程设计

提示:文章写完后,目录可以自动加粗样式生成,如何生成可参考右边的帮助文档


设计要求

微机原理及单片机应用技术的课程设计,C51设计一个密码锁;

要求:键盘上有0-9个数字按键,功能键:确认和取消等,可设置复合键。密码的位数及密码可以任意设定,,当输入数字和设置的密码相同的时候,锁打开,否则无法打开。
基本实现:掉电以后密码不保存,回复初始密码;
提高实现:掉电以后密码不丢失,可由键盘输入任意密码

一、系统模型设计

Proteus仿真图
在这里插入图片描述
附图为提高实现中的仿真,基础实现的区别在右下角少了一个AT24c02芯片。

二、基础实现

2.1 程序结构

程序主要有四个函数:

void delay()					//延时函数
void display()					//数码管显示函数
void key_scan()					//键盘扫描函数
void main()						//
void time_count() interrupt 1	//中断定时函数

初始化代码:

#include <reg51.h>
#define keyboard P2

unsigned char code table[]={
    
    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned char code lig[]={
    
    0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};			//P1引脚选位
unsigned int code number[4][3]={
    
    {
    
    7,8,9},{
    
    4,5,6},{
    
    1,2,3},{
    
    0,0,0}};			//键盘查表
unsigned char show[8]={
    
    0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf,0xbf};			//数码管只显示show中的内容,数码管默认显示‘横杠’
unsigned char  keywords[21] = {
    
    1,2,3,4};						//初始密码
unsigned char input[21] = {
    
    0};								//承载输入密码
unsigned char a=0,b,c,d,j,k,temp,key,M,N,clc,enter,change,count=0,number_1=60,z,pp = 3;	//寄存器的定义	**clc,enter,change 这三个我将其称为状态寄存器,当对应按键按下的时候置1;pp密码位数,初始密码更改时要同步更改
signed int num=-1,lock=1;	//lock和上述的clc一样,都是状态寄存器,lock=1时锁定,0解锁;num为密码输入时候的位数辅助计数,注意初始为-1

sbit led1=P3^3;			//红灯--锁定指示灯
sbit led2=P3^4;			//绿灯--解锁指示灯

void delay(unsigned char o)
{
    
    
	while(o--);
}

void display()					//8位数码管显示输出
{
    
    
		unsigned int i;
		
		for(i=0;i<8;i++)
		{
    
    	
			P1 = lig[i];		//多位数码管选位
			P0 = show[i];		//数码管显示内容
			delay(100);
			P0 =0xff;
		}
}

2.2 4×4键盘扫描

键盘扫描代码:

void key_scan()						//键盘按键检测
{
    
    	
	P2=0x0f;						
	delay(10);
	if(keyboard == 0x0f)			//a用作按钮松手检测
		a=0;
	if(a == 0)		
	{
    
    
		P2=0x0f;
		temp=P2;
		temp=temp&0x0f;
		if(temp!=0x0f)
		{
    
    
			delay(10);
			switch(temp)
			{
    
    
				case 0x0e:N=0;break;
				case 0x0d:N=1;break;
				case 0x0b:N=2;break;
				case 0x07:N=3;break;
			 }
			P2=0xf0;
			temp=P2;
			temp=temp&0xf0;
			if(temp!=0xf0)
			{
    
    
				delay(10);
				switch(temp)
				{
    
    
					case 0xe0:M=0;a=1;break;
					case 0xd0:M=1;a=1;break;
					case 0xb0:M=2;a=1;break;
					case 0x70:M=3;a=1;break;
				}
				if(M<3)
				{
    
    
					key=number[N][M];		//查表确定按键对应的数
					num++;					//输入位数统计
				}
				else
				{
    
    
					switch(N)				//对应最后一列按键
					{
    
    
						case 0:change=1;break;		//功能按键置位对应的状态寄存器
						case 1:clc=1;break;
						case 2:enter=1;break;
						case 3:lock=1;break;
					}
				}
			}
		}
	}				

}

2.3 处理函数(数据处理函数)

数据处理的内容都放置在这个函数中

void processing()
{
    
    
	if(num>-1)																						//输入input数组转换为显示数组
	{
    
    	
		input[num]=key;				//输入密码进入input数组中
		
		if(num>-1 && num<7)			
		{
    
    
			show[num]=table[key];	//将输入的密码实时放入show数组中并显示在数码管上;**注意放入show中的是对应显示数字的8位二进制数不是密码本身
		}
		if(num>7 && num != b)
		{
    
    
			for(c=0;c<8;c++)
			{
    
    
				show[c] = table[input[num-7+c]];	//在密码超出8位后数码管显示内容跟着滚动
			}
		}
		b = num;						//实现每按一次数码管内容刷新一次
	}
	
	if(lock == 1 && enter == 1 )			//在锁定状态下,按确认键开锁
	{
    
    
		for(z = 0;z < pp+1;z++)				
		{
    
    
			if(input[z] == keywords[z])		//比较输入的密码与设定的是否一致
				d = 1;
			if(input[z] != keywords[z])		
			{
    
    
				d = 0;
				break;
			}
		}
		if(pp != num)						//判断密码位数是否一样
			d = 0;
		
		if(d)								//设备解锁
		{
    
    
			lock = 0;
			number_1 = 30;					//启动30秒倒计时锁定
		}
		for( z = 0;z<21;z++)				//无论解锁与否都对input和show复位
			{
    
    input[z] = 0;}
		for( z = 0;z<8;z++)
			{
    
    show[z]=0xbf;}
		num = -1;
		d = 0;
		enter = 0;							//状态寄存器复位
	}
	
	if(clc)									//清除键按下																//清除键
	{
    
    
		for( z = 0;z<21;z++)
			{
    
    input[z] = 0;}
		for( z = 0;z<8;z++)
			{
    
    show[z]=0xbf;}

		num = -1;
		clc = 0;
	}
	if(lock == 0 && change == 1)			//在开锁情况下按更改密码
	{
    
    	
		for(z=0;z<21;z++)
		{
    
    
			keywords[z] = input[z];
		}
		lock = 1;							//更改密码后设备自动锁定
		for( k = 0;k<8;k++)
			{
    
    show[k]=0xbf;}
		for( k = 0;k<21;k++)
			{
    
    input[k] = 0;}
		pp = num;							//更改密码位数
		num = -1;
		change = 0;	
	}
	
	if(lock == 0 && number_1 == 0)			//倒计时结束后锁定
		lock = 1;
}

2.4 主函数和中断定时函数

void main()
{
    
    	
	EA=1;								//系统初始化寄存器
	ET0=1;
	TMOD=0x01;
	TH0=(65536-50000)/256;
	TL0=(65536-50000)%256;
	TR0=1;
	while(1)
	{
    
    
		display();
		key_scan();
		processing();
		if(lock == 1)					//指示灯
		{
    
    
			led1 = 0;
			led2 = 1;}
		else
		{
    
    
			led1 = 1;
			led2 = 0;}
	}
}

void time_count() interrupt 1		//定时器中断实现延时
{
    
    
	TH0=(65536-50000)/256;
	TL0=(65536-50000)%256;
	count++;
	if(count==20)
	{
    
    
		count=0;
		number_1--;
		if(number_1<0)
			number_1=59;
	}
}

三、提高实现

说明

提高实现在基础实现的基础上有两处的差异。
(1)主函数中系统启动从AT24C02中读取密码放入keywords中;
(2)更改密码时,多了一个密码写入AT24C02的步骤
(3)读写AT24C02的函数编写
附上上述代码

更改密码部分

if(lock == 0 && change == 1)									//在开锁情况下按更改密码
	{
    
    	
		number_1 = 20;
		for(z=0;z<8;z++)
		{
    
    
			keywords[z] = input[z];
		}
		qq = num;
		
		At24c02Write(0x50, keywords);							//密码写入24C02
		delay(1000);
		
		lock = 1;
		for( k = 0;k<8;k++)
			{
    
    show[k]=0xbf;}
		for( k = 0;k<8;k++)
			{
    
    input[k] = 0;}
		num = -1;
		change = 0;
	}

主函数部分

void main()							//主程序
{
    
    	
	EA=1;							//系统初始化寄存器
	ET0=1;
	IT0=0;
	TMOD=0x01;
	TH0=(65536-50000)/256;
	TL0=(65536-50000)%256;
	TR0=1;
	
//	At24c02Write(0x50, keys);		//keys[]为写入芯片的初始密码,应当在程序开头定义
//	Delay10us();					//用来第一次运行的时候写进24C02(由于AT24C02芯片第一次没有密码数据在里面),在第一次运行后将这两行注释,从新编译一次就可以正常运行
	At24c02Read(keywords,0x50);		//启动读取密码
	
	while(1)
	{
    
    
		display();
		key_scan();
		processing();
		if(lock == 1)				//指示灯程序
		{
    
    
			led1 = 0;
			led2 = 1;}
		else
		{
    
    
			led1 = 1;
			led2 = 0;}
	}
}

AT24C02读写代码

AT24C02的读写89C51没有直接的函数调用,只能自己按照手册通过引脚写通讯函数。
AT24C02的读写下面两篇博客写的很好,想要学习AT24C02的读者可以移步至

https://blog.csdn.net/snyanglq/article/details/50408489?
https://blog.csdn.net/zbl12345678910/article/details/106029054

下面附上读写代码

/**********************************
**	函数名称 :ACK
**	函数功能 : 应答信号
**********************************/
void ACK()			//发送应答A子函数ACK
{
    
    
	unsigned char i;
	SCL = 1;		//时钟线发出时钟脉冲
	Delay10us();
	while((SDA == 1) && (i < 200))i++ ;
	SCL = 0;		//与SCL=1组成时钟脉冲
	Delay10us();
}
/**********************************
**	函数名称 :NACK
**	函数功能 : 非应答信号
**********************************/
void NACK()			//发送应答A子函数NACK,见实例354
{
    
    

	SDA = 1;		//数据线高电平(发送数据1)
	SCL = 0;
	Delay10us();
	SCL = 1;		//时钟线发出时钟脉冲
	Delay10us();
	SCL = 0;		//与SCL=1组成时钟脉冲
	SDA = 0;		//数据线低电平复位
}
 
/*******************************************************************************
* 函数名         : Start_signal(void)
* 函数功能		 : I2C总线起始信号
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
 
void I2C_Start_signal(void)
{
    
    
	 SDA = 1 ;
	 Delay10us();
	 SCL = 1 ;
	 Delay10us();
	 SDA = 0 ;
	 Delay10us();
	 SCL = 0 ;
	 Delay10us();
}
 
/*******************************************************************************
* 函数名         : Start_signal(void)
* 函数功能		 : I2C总线终止信号
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
 
void I2C_Stop_signal(void)
{
    
    
	SDA = 0 ;
	Delay10us();
   	SCL = 1 ;
	Delay10us();
	Delay10us();
	SDA=1;
	Delay10us();
}
 
/*******************************************************************************
* 函数名         : I2C_SendByte(unsigned char dat, unsigned char ack)
* 函数功能		 : I2C总线发送数据
* 输入           : dat,一个字节的数据
* 输出         	 : 发送成功返回1,发送失败返回0
* 备    注       : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0
*******************************************************************************/
 
unsigned char I2C_SendByte(unsigned char dat, unsigned char ack)
{
    
    
 
 
 
	unsigned char a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。
Replay:
    b = 0 ;			
	for(a=0; a<8; a++)//要发送8位,从最高位开始
	{
    
    
		SDA = dat >> 7;	 //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
		dat = dat << 1;
		Delay10us();
		SCL = 1;
		Delay10us();//建立时间>4.7us
		SCL = 0;
		Delay10us();//时间大于4us		
	}
 
	SDA = 1;
	Delay10us();
	SCL = 1;
	while(SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
	{
    
    
		b++;
		if(b > 200)	 //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
		{
    
    
			SCL = 0;
			Delay10us();
		//	return 0;
			goto Replay ;   //如果超过200us没有应答则发送失败,或者为非应答,这时候系统启动重发机制
							//使用goto语句返回到上面接着发
		}
	}
 
	SCL = 0;
	Delay10us();
 	return 1;		
}
 
/*******************************************************************************
* 函数名         : I2cReadByte()
* 函数功能		 : I2C总线接收数据
* 输入           : 无
* 输出         	 : dat,数据
*******************************************************************************/
 
unsigned char I2cReadByte()
{
    
    
	unsigned char a=0,dat=0;
	SDA=1;			
	Delay10us();
	for(a=0;a<8;a++)//接收8个字节
	{
    
    
		SCL=1;
		Delay10us();
		dat<<=1;
		dat|=SDA;
		Delay10us();
		SCL=0;
		Delay10us();
	}
	return dat;	
}
 
/*******************************************************************************
* 函数名         : Delay10us()
* 函数功能		 : 延时
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
 
void Delay10us()
{
    
    
   unsigned char a,b;
	for(b=1;b>0;b--)
		for(a=2;a>0;a--);
}
 
/*******************************************************************************
* 函 数 名         : void At24c02Write(unsigned char addr,unsigned char dat)
* 函数功能		   : 往24c02的8个地址写入8个数据
* 输    入         : 地址和数据
* 输    出         : 无
*******************************************************************************/
 
void At24c02Write(unsigned char addr,unsigned char dat[])
{
    
    
	unsigned char i;
   	I2C_Start_signal();
	I2C_SendByte(0xa0, 1);//发送写器件地址
	I2C_SendByte(addr, 1);//发送要写入内存地址
	for(i = 0; i < 8; i++)
		I2C_SendByte(dat[i], 0);	//发送数据
	I2C_Stop_signal();
 
}
 
/*******************************************************************************
* 函 数 名         : unsigned char At24c02Read(unsigned char addr)
* 函数功能		   : 读取24c02的一个地址的一个数据
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/


void At24c02Read(unsigned char b[], unsigned char addr)
{
    
    
	unsigned char i;
	for(i = 0; i < 7; i++)
	{
    
    
		I2C_Start_signal();
		I2C_SendByte(0xa0, 1); 			//发送写器件地址	   1010   0000
		I2C_SendByte(addr + i, 1); 		//发送要读取的地址
		I2C_Start_signal();
		I2C_SendByte(0xa1, 1); 			//发送读器件地址
		b[i] = I2cReadByte();	 		//读取数据
		ACK();	
	}
	I2C_Start_signal();
	I2C_SendByte(0xa0, 1); 				//发送写器件地址	   1010   0000
	I2C_SendByte(addr + i, 1);			 //发送要读取的地址
	I2C_Start_signal();
	I2C_SendByte(0xa1, 1); 				//发送读器件地址
	b[i] =  I2cReadByte();
	NACK();
	I2C_Stop_signal();
	Delay10us();
}

*由于AT24C02读写函数一次只读写8位,所以提高实现中的密码默认只设8位,如果需要更多位密码可以通过多次读取拼接简单实现更多位密码设定。

总结

上述的课程设计完整代码和仿真文件可以下载,文章中没有讲清除的地方可以结合程序进一步理解,希望对各位有所帮助。

https://download.csdn.net/download/canmianlaida/85656284

猜你喜欢

转载自blog.csdn.net/canmianlaida/article/details/125312169