单片机简易密码锁

使用按键、LCD1602、EEPROM (AT24C02)做一个简易密码锁程序。
(1)用户可以通过输入 6 位数字密码来打开保险箱。
(2)当密码正确时,保险箱柜门打开(步进电机正转 90 度) ;当密码不正确时,保险箱
柜门不打开;保险箱柜门打开后,若按下关门键,则保险箱柜门关上(步进电机反转 90 度)。
(3)用户密码可以自行修改(密码输入 2 次,第 2 次和第 1 次相同才有效)。
(4)有相应的密码输入显示窗口,输入数字用相应符号替代以免被偷窥。
我是通过实物完成的。
我的工程下有5个文件:
1.主函数.c

#include <reg52.h>
#define uchar unsigned char
#define ulong unsigned long
signed long beats = 0;
extern void InitLcd1602();
extern void LcdShowStr(uchar x, uchar y, uchar *str);
extern void E2Read(uchar *buf, uchar addr, uchar len);
extern void E2Write(uchar *buf, uchar addr, uchar len);
extern void KeyDriver();
extern void KeyScan();
extern void LcdFullClear();
extern void LcdAreaClear(uchar x,uchar y,uchar len);
void ConfigTimer1(uchar ms);
void StartMotor(signed long angle);
void LongToString(uchar *str, ulong dat);
void KeyAction(uchar keycode);
void NumberAction(uchar n);
void Reset();
void save();
void pass();
void HidePass(uchar *str, ulong password);
bit pass_the = 0;//这个值判断是否校验密码通过,通过时才能关箱门和修改密码
uchar step = 0;//0表示修改密码,1表示第二次输入,2表示完成后清零,3表示输入
uchar count = 0;//计算输入的位数
ulong password1 = 0;//六位密码
ulong password2 = 0;
uchar password[6];//password只要三位存储,便于显示
uchar str[20];
uchar T1RL;
uchar T1RH;
ulong real_pass;
void main()
{
	uchar buf[6];
	EA = 1;//使能总中断
	TMOD = 0x01;//设置T0为模式1
	TH0 = 0xf8;//为T0赋初值0xF8CD,赋值2ms
	TL0 = 0xcd;
	ET0 = 1;//使能T0中断
	TR0 = 1;//启动T0	
	InitLcd1602();
	ConfigTimer1(1);
	E2Read(buf, 0x8e, sizeof(buf));
	real_pass = buf[0] * 100000 + buf[1] * 10000 + buf[2] * 1000 + buf[3] * 100
				+ buf[4] * 10 + buf[5] * 1;//读取真实的密码
	LcdShowStr(0, 0, "Input:");//显示到液晶
	while(1)
	{
		KeyDriver();//调用按键驱动函数
	}
}
void StartMotor(signed long angle)
{
	//在计算前关闭中断,完成后再打开,以避免中断打断计算过程造成错误
	EA = 0;
	beats = (angle * 4076) / 360;
	EA = 1;
}
void HidePass(unsigned char *str, unsigned long dat)
{
	signed char i = 0;
	uchar buf[12];
	do {
		buf[i++] = dat % 10;
		dat /= 10;
	}  while (dat > 0);
	while(i-->0)
	{
		*str++ = '*';
	}
	*str = '\0';
}
void Reset()
{
	uchar i;
	for(i=0; i<6; i++)
		password[i] = 0;
	step = 0;
	password1 = 0;
	password2 = 0;
	count = 0;
	LcdFullClear();
	if (pass_the == 0)
		LcdShowStr(0, 0, "Input:");
	else if (pass_the == 1)
		LcdShowStr(0, 0, "Modify:");
}
void save()
{
	uchar buf[6];
	password[0] = password2 / 100000;
	password[1] = password2 / 10000 % 10;
	password[2] = password2 / 1000 % 10;
	password[3] = password2 / 100 % 10;
	password[4] = password2 / 10 % 10;
	password[5] = password2 % 10;
	E2Write(password, 0x8e, sizeof(password));
	LcdShowStr(0, 0, "Password saved");	
	LcdAreaClear(0, 1, 16);
	E2Read(buf, 0x8e, sizeof(buf));//重新读取密码
	real_pass = buf[0] * 100000 + buf[1] * 10000 + buf[2] * 1000 + buf[3] * 100
				+ buf[4] * 10 + buf[5] * 1;
	step = 4;//应该有一个新的状态
}
void pass()
{
	if(password1 == real_pass)
	{
		LcdShowStr(0, 0, "Up close");
		LcdShowStr(0, 1, "Down modify");
		StartMotor(90);//正转90度打开箱门
		pass_the = 1;//校验通过,这个值将在修改密码和关箱门后清零
		step = 3;
	}	
	else 
	{
		LcdShowStr(0, 0, "Fail pass");
		step = 2;
	}	
}
void KeyAction(uchar keycode)//存在一个按键锁定问题,比如在输入密码后不准按数字键
{
	if((keycode>='0') && (keycode<='9') && step != 3)//step=3时要锁住
	{
		NumberAction(keycode - '0');
	}
	else if(keycode == 0x1b)//退出键重置
	{
		if(step < 3)
		{
			if(pass_the == 1)
			{
				pass_the = 0;
				StartMotor(-90);
			}
			Reset();
		}
		else
		{
			LcdFullClear();
			step = 3;//回退一个状态
			LcdShowStr(0, 0, "Up close");
			LcdShowStr(0, 1, "Down modify");
		}		
	}
	else if(keycode == 0x26 && pass_the == 1 && step==3)//通过后关箱门
	{
		StartMotor(-90);
		pass_the = 0;
		step = 2;
		LcdFullClear();
		LcdShowStr(0, 0, "Input:");
	}
	else if(keycode == 0x28 && pass_the == 1 && step == 3)//进入修改密码环节
	{
		step = 2;//进入修改密码阶段
		LcdFullClear();
		LcdShowStr(0, 0, "Modify:");
	}
	else if (keycode == 0x0d && count < 6)//小于6个数
	{	
		LcdAreaClear(0, 0, 16);	
		LcdShowStr(0, 0, "Continue to 6:");
	}
	else if (keycode == 0x0d && count > 6)//超出留个数
	{
		LcdAreaClear(0, 0, 16);		
		LcdShowStr(0, 0, "over 6!");
		step = 2;
	}
	else if (keycode == 0x0d && step == 0 && pass_the == 0)//校验密码
	{
		LcdFullClear();
		pass();
	}
	else if (keycode == 0x0d && step == 0 && pass_the == 1)//修改密码
	{
		step = 1;
		count = 0;
		LcdFullClear();
		LcdShowStr(0, 0, "Input again:");
	}
	else if (keycode == 0x0d && step ==1 && password1 == password2 && pass_the == 1)//回车键,存储密码
	{
		save();//保存后要Reset(),回退到上一个页面,强制只有关箱门才能退出
		LcdShowStr(0, 1, "Press esc");
	}	
	else if (keycode == 0x0d && step ==1 && password1 != password2 && pass_the == 1)//回车键,两次输入密码不相等
	{
		LcdFullClear();
		LcdShowStr(0, 0, "Different");
		step = 2;
	}
}

void NumberAction(uchar n)
{
	if (step == 0)//输入密码时要隐藏密码
	{
		password1 = password1 * 10 + n;
		HidePass(str, password1);
		LcdShowStr(0, 1, str);
		count++;
	}
	else if(step == 1)
	{
		password2 = password2 * 10 + n;
		HidePass(str, password2);
		LcdShowStr(0, 1, str);
		count++;
	}
	else if(step == 2)
	{
		Reset();
		password1 = n;
		HidePass(str, password1);
		LcdShowStr(0, 1, str);
		count = 1;
	}
}
void TurnMotor()
{
	unsigned char tmp;
	static unsigned char index = 0;
	unsigned char code BeatCode[8] = {0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6};

	if (beats != 0)//节拍数不为0则产生一个驱动节拍
	{
		if (beats > 0)//节拍数大于0时正转
		{
			index++;//正转时节拍输出索引递增
			index = index & 0x07;//用&操作实现到8归零
			beats--;//正转时节拍计数递减
		}
		else
		{
			index--;
			index = index & 0x07;
			beats++;
		}
		tmp = P3;
		tmp = tmp & 0xF0;
		tmp = tmp | BeatCode[index];
		P3 = tmp;
	}
	else
	{
		P3 = P3 | 0x0F;
	}
}
//T0中断服务函数,用于驱动步进电机旋转
void InterruptTimer0() interrupt 1
{
	TH0 = 0xf8;
	TL0 = 0xcd;
	TurnMotor();
}
void ConfigTimer1(uchar ms)
{
	ulong tmp;
	tmp = 11059200 / 12;
	tmp = (tmp * ms) / 1000;
	tmp = 65536 - tmp;
	tmp = tmp + 13;
	T1RH = (uchar)(tmp >> 8);
	T1RL = (uchar)tmp;
	TMOD &= 0x0f;
	TMOD |= 0x10;
	TH1 = T1RL;
	TL1 = T1RL;
	ET1 = 1;
	TR1 = 1;
}
void InterruptTimer1() interrupt 3
{
	TH1 = T1RH;
	TL1 = T1RL;
	KeyScan();
}

2.I2C.c:用于单片机和EEPROM通信。

②I2C.c
#include <reg52.h>
#include <intrins.h>
#define uchar unsigned char
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
extern void LcdShowStr(uchar x, uchar y, uchar *str);

//产生总线起始信号
void I2CStart()
{
	I2C_SDA = 1;//首先确保SDA, SCL都是高电平
	I2C_SCL = 1;
	I2CDelay();
	I2C_SDA = 0;//先拉低SDA
	//起始信号的条件是SCL为高电平期间,SDA由高电平向低电平产生一个下降沿
	I2CDelay();
	I2C_SCL = 0;//再拉低SCL
}
//产生总线停止信号
void I2CStop()
{
	I2C_SCL = 0;//首先要确保SDA, SCL都是低电平
	I2C_SDA = 0;
	I2CDelay();
	I2C_SCL = 1;//先拉高SCL
	I2CDelay();
	I2C_SDA = 1;//再拉高SDA
	//停止信号的条件是SCL为高电平期间,SDA由低电平向高电平产生一个上降沿
	I2CDelay();
}
//I2C总线写操作,dat-待写入字节,返回值-从机应答位的值
bit I2CWrite(uchar dat)
{
	bit ack;
	uchar mask;//用于探测字节内某一位值的掩码变量
	//mask=1000 0000向右进1,即为1100 0000
	for (mask = 0x80; mask!=0; mask>>=1)//从高位到低位依次进行
	{
		if ((mask&dat) == 0)//该为输出到SDA上
			I2C_SDA = 0;
		else
			I2C_SDA = 1;
		I2CDelay();
		I2C_SCL = 1;
		I2CDelay();
		I2C_SCL = 0;
	}
	I2C_SDA = 1;//8位数据发送完后,主机释放SDA,以检测从机应答
	I2CDelay();
	I2C_SCL = 1;//拉高SCL
	ack = I2C_SDA;//读取此时的SDA值,即为从机的应答值
	I2CDelay();
	I2C_SCL = 0;//再拉低SCL完成应答位,并保持住总线
	return (~ack);//返回从机应答值
}
//I2C总线读操作,并发送非应答信号,返回值-读到的字节
uchar I2CReadNAK()
{
    uchar mask;
    uchar dat;
    I2C_SDA = 1;//首先确保主机释放SDA
    for (mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行    
	{
        I2CDelay();
        I2C_SCL = 1;//拉高SCL
        if (I2C_SDA == 0)//读取SDA的值          
			dat &= ~mask;//为0时,dat对应位清零
        else
            dat |= mask;//为1时,dat对应位置1
        I2CDelay();
        I2C_SCL = 0;//再拉低SCL以使从机发出下一位
    }
    I2C_SDA = 1;//8位数据发送完以后,拉高SDA,产生非应答信号
    I2CDelay();
    I2C_SCL = 1;//拉高SCL
    I2CDelay();
    I2C_SCL = 0;//再拉低SCL完成非应答为位,并保持住总线
    return dat;
}
//I2C总线读操作,并发送应答信号,返回值-读到的字节
uchar I2CReadACK()
{
    uchar mask;
    uchar dat;
    I2C_SDA = 1;//首先确保主机释放SDA
    for (mask=0x80;mask!=0;mask>>=1)//从高位到低位依次进行    
	{
        I2CDelay();
        I2C_SCL = 1;//拉高SCL
        if (I2C_SDA == 0)//读取SDA的值          
			dat &= ~mask;//为0时,dat对应位清零
        else
            dat |= mask;//为1时,dat对应位置1
        I2CDelay();
        I2C_SCL = 0;//再拉低SCL以使从机发出下一位
    }
    I2C_SDA = 0;//8位数据发送完以后,拉高SDA,产生应答信号
    I2CDelay();
    I2C_SCL = 1;//拉高SCL
    I2CDelay();
    I2C_SCL = 0;//再拉低SCL完成非应答为位,并保持住总线
    return dat;
}

3.EEPROM.c写入数据:

#include <reg52.h>
#define uchar unsigned char
extern void I2CStart();
extern void I2CStop();
extern uchar I2CReadACK();
extern uchar I2CReadNAK();
extern bit I2CWrite(uchar dat);
//E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度
void E2Read(uchar *buf, uchar addr, uchar len)
{
	do {
		I2CStart();
		if(I2CWrite(0x50<<1))
		{
			break;
		}
		I2CStop();
	} while(1);
	I2CWrite(addr);//写入起始地址
	I2CStart();//发送重复启动信号
	I2CWrite((0x50<<1)|0x01);//寻址器件,后续为读操作
	while(len>1)//连续读取len-1个字节
	{
		*buf++ = I2CReadACK();//最后字节之前为读取操作+应答
		len--;
	}
	*buf = I2CReadNAK();//最后一个字节为读取操作+非应答
	I2CStop();
}
//写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度
void E2Write(uchar *buf, uchar addr, uchar len)
{
	while(len > 0)
	{
		//等待上次写入操作完成
		do {
			I2CStart();
			if (I2CWrite(0x50<<1))
			{
				break;
			}
			I2CStop();
		} while(1);
		//按页模式连续写入字节
		I2CWrite(addr);
		while (len > 0)
		{
			I2CWrite(*buf++);
			len--;
			addr++;
			if ((addr&0x07) == 0)
			{
				break;
			}
		}
		I2CStop();
	}
}

//将一段内存数据转换为十六进制格式字符串,str-字符串指针,
//src源数据地址,len-数据长度
void MemToStr(uchar *str, uchar *src, uchar len)
{
	uchar tmp;
	while(len--)
	{
		tmp = *src >> 4;//先取高4位
		if (tmp <= 9)
			*str++ = tmp + '0';
		else
			*str++ = tmp - 10 + 'A';
		tmp = *src & 0x0f;//再取低4位
		if (tmp <= 9)
			*str++ = tmp + '0';
		else
			*str++ = tmp - 10 + 'A';
		//*str++ = ' ';//转换玩每一个字节添加一个空格
		src++;
	}
	*str = '\0';//添加字符串结束符
}

4.Keyborad.c:键盘驱动程序:

#include <reg52.h>
#define uchar unsigned char 

sbit key_in_1 = P2^4;
sbit key_in_2 = P2^5;
sbit key_in_3 = P2^6;
sbit key_in_4 = P2^7;
sbit key_out_1 = P2^3;
sbit key_out_2 = P2^2;
sbit key_out_3 = P2^1;
sbit key_out_4 = P2^0;

uchar code KeyCodeMap[4][4] = {
	{'1', '2', '3', 0x26},//向上键
	{'4', '5', '6', 0x25},//向左键
	{'7', '8', '9', 0x28},//向下键
	{'0',0x1B, 0x0D, 0x27}//ESC键,回车键,向右键
};

uchar pdata KeySta[4][4] = {
	{1,1,1,1}, {1,1,1,1}, {1,1,1,1}, {1,1,1,1} 
};

extern void KeyAction(uchar keycode);

void KeyDriver()
{
	uchar i, j;
	static uchar pdata backup[4][4] = {
		{1,1,1,1}, {1,1,1,1}, {1,1,1,1}, {1,1,1,1}	
	};
	for (i=0; i<4; i++)
	{
		for (j=0; j<4; j++)
		{
			if(backup[i][j] != KeySta[i][j])
			{
				if (backup[i][j] != 0)//即按键按下时
				{
					KeyAction(KeyCodeMap[i][j]);//调用响应的动作函数
				}
				backup[i][j] = KeySta[i][j];//刷新备份
			}
		}
	}
}

//按键扫描函数
void KeyScan()
{
	uchar i;
	static uchar keyout = 0;//矩阵按键扫描输出索引
	static uchar keybuf[4][4] = {
		{0xff,0xff,0xff,0xff}, {0xff,0xff,0xff,0xff},
		{0xff,0xff,0xff,0xff}, {0xff,0xff,0xff,0xff}
	};
	//将一行的四个键值移入缓冲区
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | key_in_1;
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | key_in_2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | key_in_3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | key_in_4;
	//消抖后更新按键状态
	for (i=0; i<4; i++)
	{
		if((keybuf[keyout][i] & 0x0f) == 0x00)
		{
			KeySta[keyout][i] = 0;
		}
		else if((keybuf[keyout][i] & 0x0f) == 0x0f)
		{
			KeySta[keyout][i] = 1;
		}
	}
	//执行下一次的扫描输出
	keyout++;
	keyout &= 0x03;
	switch(keyout)
	{
		case 0:key_out_4 = 1;key_out_1 = 0;break;
		case 1:key_out_1 = 1;key_out_2 = 0;break;
		case 2:key_out_2 = 1;key_out_3 = 0;break;
		case 3:key_out_3 = 1;key_out_4 = 0;break;
		default: break;
	}
}

5.LCD1602.c屏幕显示:

#include <reg52.h>

#define uchar unsigned char
#define lcd1602_db P0

sbit lcd1602_rs = P1^0;
sbit lcd1602_rw = P1^1;
sbit lcd1602_e = P1^5;

void LcdWaitReady()
{
	uchar sta;
	lcd1602_db = 0xff;
	lcd1602_rs = 0;
	lcd1602_rw = 1;
	do {
		lcd1602_e = 1;
		sta = lcd1602_db;
		lcd1602_e = 0;
	} while(sta & 0x80);//只是检测是否忙没有执行任何操作,1表示在忙,0表示空闲
}

//向液晶写入一行命令,cmd-待写入命令值
void LcdWriteCmd(uchar cmd)
{
	LcdWaitReady();
	lcd1602_rs = 0;//0为命令
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = cmd;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//向液晶写入一字节数据,dat-待写入数据值
void LcdWriteDat(uchar dat)
{
	LcdWaitReady();
	lcd1602_rs = 1;//1为数据
	lcd1602_rw = 0;//0表示写入
	lcd1602_db = dat;
	lcd1602_e = 1;//产生高脉冲
	lcd1602_e = 0;
}

//设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标
void LcdSetCursor(uchar x, uchar y)
{
	uchar addr;
	if(y==0)
		addr = 0x00 + x;//这意味着第一个显示字符x=0
	else
		addr = 0x40 + x;
	LcdWriteCmd(addr | 0x80);//设置RAM地址
}

void LcdShowStr(uchar x,uchar y,uchar *str)
{
	LcdSetCursor(x, y);	
	while(*str != '\0')//通过长度来改变,而静态显示时是通过判断最后一个字符是否为/0,while(*str!='\0')
	{
		LcdWriteDat(*str++);
	}
}

//打开光标闪烁效果
void LcdOpenCursor()
{
	LcdWriteCmd(0x0f);
}

//关闭光标显示
void LcdCloseCursor()
{
	LcdWriteCmd(0x0c);
}

//区域清除,清除从(x,y)坐标起始的len个字符位
void LcdAreaClear(uchar x,uchar y,uchar len)
{
	LcdSetCursor(x, y);
	while(len--)
	{
		LcdWriteDat(' ');
	}
} 


//整屏清除
void LcdFullClear()
{
	LcdWriteCmd(0x01);
}


//初始化1602液晶
void InitLcd1602()
{
	LcdWriteCmd(0x38);//写入
	LcdWriteCmd(0x0c);//显示器开,光标关闭
	LcdWriteCmd(0x06);//文字不动,地址自动+1
	LcdWriteCmd(0x01);//清屏操作
}

密码锁部分显示设置了几个步骤,在不同的步骤有不同的操作,相同的按键响应也不同,后来我的设置是:第零步,没有通过密码检验,第零步,已经通过密码检验,第一步,第二次输入密码后,第二步,开始修改密码阶段,第三步,进入电机反转90度还是修改密码选择阶段。这样程序更加清楚,相关功能也更加好实现。
要锁住按键,在电机反转90度还是修改密码选择阶段,因此这是应该让输入锁住,禁止输入。
还有一个问题是,就是一开始如果选择修改秘密时没有关上让电机反转90度,在实际应用中相当于箱门一直打开,而如果下次输入正确密码后箱门又正转90度,这显然是错误的,因此我设置成了修改密码后回到选择让电机反转90度还是继续修改密码阶段,这样不选择关上反转90度是无法退出,再次重新输入密码的,比较符合实际的应用,当然读者也可以按照自己的想法修改。
还有一些问题没有弄好,就是初始时输入密码,假设这是一个保险柜,在卖给用户后应该要设置一个初始化密码阶段,或是告诉用户初始密码。还有在用户忘记密码时,应该有办法找回密码,这是可以改进的部分。

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值