基于51单片机的电子密码锁设计
0 功能介绍
1、实现开锁、上锁、修改密码的功能。
2、初始密码666666,点击开锁后,输入密码,点击确认键,然后灯亮,示意开锁。
3、点击修改密码,需要先输入原密码点击确认后,再输入新密码点击确认,修改成功后显示成功标识。
4、点击上锁按钮上锁。
1 软件平台及代码开源
仿真软件:Proteus 8.9
代码编写:Keil5
百度网盘链接:
链接:https://pan.baidu.com/s/1DhzBwq-PywJVlyiuO_-AOA
提取码:f6kg
–来自百度网盘超级会员V4的分享
Gitee链接:
51单片机项目学习: 这个仓库拿来保存一些51单片机项目学习 (gitee.com)
2 仿真硬件选型
LM016L显示提示信息,LED灯作为模拟锁,主控是89C52,使用矩阵按键。
3 代码编写
3.1 整体设计思路
在5ms定时器中进行按键扫描,获得键值,然后在主循环中实现相应键值按下后的相关功能,主要设置3个功能,开锁、上锁、修改密码,相应的按键按下后,就将对应的功能标志位置1,对应的标志位置1后,就会执行相关的功能函数。
3.2 软件代码设计
3.2.1 LCD显示
LCD初始化程序使用的是裁剪过的普中的初始化代码,显示功能函数使用的是宋学松老师手把手教你学51单片机中的代码。
/**
* @name Lcd1602_Delay1ms
* @brief 延时函数,延时1ms
* @param : [输入/出]
* @retval 返回值
*/
void Lcd1602_Delay1ms(unsigned int c) //误差 0us
{
unsigned char a,b;
for (; c>0; c--)
{
for (b=199;b>0;b--)
{
for(a=1;a>0;a--);
}
}
}
/**
* @name LcdWriteCom
* @brief 向LCD写入一个字节的命令
* @param com
* @retval 返回值
*/
void LcdWriteCom(unsigned char com) //写入命令
{
LCD1602_E = 0; //使能
LCD1602_RS = 0; //选择发送命令
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = com; //放入命令
Lcd1602_Delay1ms(1); //等待数据稳定
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
/**
* @name LcdWriteData
* @brief 向LCD写入一个字节的数据
* @param dat
* @retval 返回值
*/
void LcdWriteData(unsigned char dat) //写入数据
{
LCD1602_E = 0; //使能清零
LCD1602_RS = 1; //选择输入数据
LCD1602_RW = 0; //选择写入
LCD1602_DATAPINS = dat; //写入数据
Lcd1602_Delay1ms(1);
LCD1602_E = 1; //写入时序
Lcd1602_Delay1ms(5); //保持时间
LCD1602_E = 0;
}
/**
* @name LcdInit
* @brief 初始化LCD屏
* @param 无
* @retval 返回值
*/
void LcdInit() //LCD初始化子程序
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
}
/**
* @name LcdSetCursor
* @brief 设置显示 RAM 起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标
* @param x y
* @retval 返回值
*/
void LcdSetCursor(unsigned char x, unsigned char y)
{
unsigned char addr;
if (y == 0) //由输入的屏幕坐标计算显示 RAM 的地址
addr = 0x00 + x; //第一行字符地址从 0x00 起始
else
addr = 0x40 + x; //第二行字符地址从 0x40 起始
LcdWriteCom(addr | 0x80); //设置 RAM 地址
}
/**
* @name LcdShowStr
* @brief 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针
* @param x y str
* @retval 返回值
*/
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{
LcdSetCursor(x, y); //设置起始地址
while (*str != '\0') //连续写入字符串数据,直到检测到结束符
{
LcdWriteData(*str++); //先取 str 指向的数据,然后 str 自加 1
}
}
3.2.2 矩阵按键扫描函数
按键扫描函数也是参考的普中的代码,但是他是用的delay延时,但是我是将按键扫描函数放置在5ms的定时器中断服务函数中,所以使用的是计数延时的方法,现在这个函数,按键按下后,会一直保持那个键值,按键抬起后,键值才会清零,如果想实现单次触发,需要在使用的时候,手动将键值清零,便可以实现单次按键了,不清零就实现的长按。
/**
* @name KeyScan
* @brief 按键检测函数
* @param : [输入/出]
* @retval 返回值
*/
void KeyScan(void)
{
static u8 count = 0;
GPIO_KEY=0x0f;
if(GPIO_KEY!=0x0f && KeyState == 0)
{
count ++;
if(GPIO_KEY!=0x0f && count > 2 && KeyState == 0)
{
count = 0;
KeyState = 1;
//列
GPIO_KEY=0X0F;
switch(GPIO_KEY)
{
case(0X07): KeyValue=1;break;
case(0X0b): KeyValue=2;break;
case(0X0d): KeyValue=3;break;
case(0X0e): KeyValue=4;break;
}
//行
GPIO_KEY=0XF0;
switch(GPIO_KEY)
{
case(0X70): KeyValue=KeyValue;break;
case(0Xb0): KeyValue=KeyValue+4;break;
case(0Xd0): KeyValue=KeyValue+8;break;
case(0Xe0): KeyValue=KeyValue+12;break;
}
}
}else if(GPIO_KEY==0x0F && KeyState == 1)
{
KeyState = 0;
KeyValue = 0;
}
}
实现单次检测的方案,这样写就能够只使用一次按键按下的键值
void KeyDownFunction()
{
u8 KeyValueTemp = 0;
if(KeyState == 1)
{
KeyValueTemp = KeyValue;//用变量保存按下的键值
if(KeyValue != 0)//实现单次检测按键
KeyValue = 0;
//功能代码
}
}
3.2.3 按键按下的功能函数
按键按下后,如果是开锁键,那就打开开锁的功能代码块,关锁和修改密码同理。
void KeyDownFunction()
{
u8 KeyValueTemp = 0;
if(KeyState == 1)
{
KeyValueTemp = KeyValue;
if(KeyValue != 0)//实现单次检测按键
KeyValue = 0;
if(KeyValueTemp == KEY_Open && OpenFlag == 0)//使能开锁功能函数
{
OpenFlag = 1;
}else if(KeyValueTemp == KEY_Close && CloseFlag == 0)//使能关锁功能函数
{
CloseFlag = 1;
}else if(KeyValueTemp == KEY_Change && ChangeFlag == 0)//使能修改密码功能函数
{
ChangeFlag = 1;
}
if(OpenFlag == 1)//开锁功能使能
{
OpenLock(KeyValueTemp);
}
if(CloseFlag == 1)//关锁功能使能
{
sprintf((char*)DisBuff,"---- lock ----");
LcdShowStr(0,1,DisBuff);
CloseFlag = 0;
LED = 1;
}
if(ChangeFlag == 1)//修改密码功能使能
{
ChangePassword(KeyValueTemp);
}
}
}
3.2.4 开锁功能
由于想要实现不影响其他代码段的功能,没有使用机器周期延时,或者while等循环输入,这部分就是实现的开锁,详细可以看代码注释。大致思路是,会有一个OpenCount计数器,点击开锁按钮后,计数器值是0,就只是进入输密码的界面,同时将计数值加1,那下一次执行到该函数就会进入修改密码的代码段。
在修改密码的代码段,正常的话就是修改密码,OpenCount加1,就是下一位密码修改,但是如果点击了回退按钮,就会将OpenCount减1,回退到上一个位置,同时将该位密码清空。
如果想要在输入密码的时候显示 * 号,那么只需让DisBuff[OpenCount+5]的值一直是 * 就可以了。
void OpenLock(u8 KeyValueTemp)
{
static u8 OpenCount = 0;
static u8 PasswordTemp[7] = {0};
if(OpenCount == 0)//一个计数器,为0时,就是刚进入开锁状态的时候,显示pwd :字符串
{
sprintf((char*)DisBuff,"pwd : ");
LcdShowStr(0,1,DisBuff);
OpenCount++;//显示完后把计数器加一
}else//下次点击就进入else
{
if(KeyValueTemp == KEY_Back && OpenCount > 1)//当点击回退按钮
{
OpenCount --;//回到上一个密码数的位置
DisBuff[OpenCount+5] = ' ';//将显示位清空
PasswordTemp[OpenCount-1] = 0;//将该位密码置0,因为密码使用的字符串,一般ASCLL不会为0
LcdShowStr(0,1,DisBuff);
}else if(OpenCount < 7)//密码设定为6位,所以6位输完后就不进入输密码的功能块了
{
/*
这里有个KeyValueTable是因为按键按下的值不是实际我排版的值,所以用数组做了一次转换
*/
DisBuff[OpenCount+5] = '0' + KeyValueTable[KeyValueTemp-1];//将显示位显示输入的数值
PasswordTemp[OpenCount-1] = '0' + KeyValueTable[KeyValueTemp-1];//将输入的数值保存
LcdShowStr(0,1,DisBuff);
OpenCount++;//该位处理完后进到下一位
}
}
if(OpenCount > 6 && KeyValueTemp == KEY_Confirm)//6位密码输完,又点击了确认按钮的话,就开始比较密码
{
OpenFlag = 0;
OpenCount = 0;
if(strcmp(Password,PasswordTemp)==0)
{
sprintf((char*)DisBuff,"---- open ----");
LcdShowStr(0,1,DisBuff);
LED = 0;
}else
{
sprintf((char*)DisBuff,"---- lock ----");
LcdShowStr(0,1,DisBuff);
LED = 1;
}
}
}
3.2.5 修改密码功能
这是修改密码,原理大概和开锁功能差不多。就是多了个ChangeMode,该变量为0是输入旧密码,为1是输入新密码。大致的思路差不多。
void ChangePassword(u8 KeyValueTemp)
{
static u8 ChangeCount = 0;
static u8 ChangeMode = 0;//0是输入旧密码,1是输入新密码
static u8 PasswordTemp[7] = {0};
static u8 NewPasswordTemp[7] = {0};
if(ChangeMode == 0)
{
if(ChangeCount == 0)//一个计数器,为0时,就是刚进入修改密码状态的时候,显示pwd :字符串
{
sprintf((char*)DisBuff,"pwd : ");
LcdShowStr(0,1,DisBuff);
ChangeCount++;//显示完后把计数器加一
}else//下次点击就进入else
{
if(KeyValueTemp == KEY_Back && ChangeCount > 1)//当点击回退按钮
{
ChangeCount --;//回到上一个密码数的位置
DisBuff[ChangeCount+5] = ' ';//将显示位清空
PasswordTemp[ChangeCount-1] = 0;//将该位密码置0,因为密码使用的字符串,一般ASCLL不会为0
LcdShowStr(0,1,DisBuff);
}else if(ChangeCount < 7)//密码设定为6位,所以6位输完后就不进入输密码的功能块了
{
/*
这里有个KeyValueTable是因为按键按下的值不是实际我排版的值,所以用数组做了一次转换
*/
DisBuff[ChangeCount+5] = '0' + KeyValueTable[KeyValueTemp-1];//将显示位显示输入的数值
PasswordTemp[ChangeCount-1] = '0' + KeyValueTable[KeyValueTemp-1];//将输入的数值保存
LcdShowStr(0,1,DisBuff);
ChangeCount++;//该位处理完后进到下一位
}
}
if(ChangeCount > 6 && KeyValueTemp == KEY_Confirm)//6位密码输完,又点击了确认按钮的话,就开始比较密码
{
ChangeCount = 0;
if(strcmp(Password,PasswordTemp)==0)//密码正确就可以输入新密码了
{
sprintf((char*)DisBuff,"new : ");
LcdShowStr(0,1,DisBuff);
ChangeCount ++;
ChangeMode = 1;
}else
{
ChangeFlag = 0;
sprintf((char*)DisBuff,"---- lock ----");
LcdShowStr(0,1,DisBuff);
LED = 1;
}
}
}else if(ChangeMode == 1)
{
if(KeyValueTemp == KEY_Back && ChangeCount > 1)//当点击回退按钮
{
ChangeCount --;//回到上一个密码数的位置
DisBuff[ChangeCount+5] = ' ';//将显示位清空
NewPasswordTemp[ChangeCount-1] = 0;//将该位密码置0,因为密码使用的字符串,一般ASCLL不会为0
LcdShowStr(0,1,DisBuff);
}else if(ChangeCount < 7)//密码设定为6位,所以6位输完后就不进入输密码的功能块了
{
/*
这里有个KeyValueTable是因为按键按下的值不是实际我排版的值,所以用数组做了一次转换
*/
DisBuff[ChangeCount+5] = '0' + KeyValueTable[KeyValueTemp-1];//将显示位显示输入的数值
NewPasswordTemp[ChangeCount-1] = '0' + KeyValueTable[KeyValueTemp-1];//将输入的数值保存
LcdShowStr(0,1,DisBuff);
ChangeCount++;//该位处理完后进到下一位
}
if(ChangeCount > 6 && KeyValueTemp == KEY_Confirm)//6位密码输完,又点击了确认按钮的话,就开始比较密码
{
ChangeFlag = 0;
ChangeCount = 0;
sprintf((char*)DisBuff,"---- success----");
LcdShowStr(0,1,DisBuff);
ChangeMode = 0;
strcpy(Password,NewPasswordTemp);
}
}
}
4 总结
这个设计功能比较简单,也有些地方功能不全,后面更新,欢迎交流。