文章目录
- 一、密码锁
- 1.题目要求
- 2.思路
- 3.仿真图
- 3.1 未仿真时
- 3.2 初始界面
- 3.3 输入密码界面
- 3.4 开锁成功界面
- 3.5 修改密码界面
- 3.6 输入密码错误界面
- 4.仿真程序
- 4.1 矩阵按键
- 4.2 液晶显示1602
- 4.3 存储模块2402
- 二、总结
一、密码锁
1.题目要求
以51单片机为核心,设计并制作密码锁
基本功能如下:
- 设置6位初始密码,密码输入正确时,液晶显示屏上会显示密码正确;
- 当输入密码错误时能够删除和清零,如果密码输入错误次数达到3次,将会进行蜂鸣器报警;
- 密码能够掉电保存,可通过功能按键"“修改密码”键来重新设置密码。
- 要求用户输入密码时,不可直接显示输入的值,要求用显示“*”代替。
2.思路
首先先画个51单片机:
这里的P0为什么需要画上拉排阻呢?
主要原因是因为P0口是开漏输出的,即集电极开路输出(OC门)。这种输出结构没有内置的上拉电阻,因此不能直接输出高电平。当P0口需要输出高电平时,必须通过外部电路提供驱动电流,这时就需要加上拉电阻来实现。上拉电阻的作用是将电平拉高,通常连接到电源正极(VCC),以便在需要时提供电流给负载,从而确保P0口能够正确地输出高电平信号。
要使用到多个按键进行设置密码,又不想浪费IO资源,第一时间想到的是用矩阵按键,不懂的小伙伴可以看看之前的介绍基于51单片机的矩阵按键扫描的proteus仿真(附源码)
显示的话,液晶显示屏想到的是LCD1602,但是在Proteus仿真里面叫做LM016L,不懂的小伙伴可以看看之前的介绍基于51单片机的LCD1602显示的proteus仿真(附源码)
要求断电能够保存密码,那就是要用到EEPROM芯片,这里用个简单的24C02来进行存储,,不懂的小伙伴可以看看之前的介绍基于51单片机的AT24C02存储的proteus仿真(附源码)
报警的话,用个蜂鸣器进行报警即可。
既然是密码锁,那肯定能模拟开锁,这里选用继电器来驱动电机转动达到一个模拟的效果。
3.仿真图
3.1 未仿真时
3.2 初始界面
3.3 输入密码界面
3.4 开锁成功界面
3.5 修改密码界面
3.6 输入密码错误界面
4.仿真程序
4.1 矩阵按键
/*******************************************************************************
* 函 数 名 : Key_Check()
* 函数功能 : 检测有矩阵按键按下并读取键值
* 输 入 : 无
* 输 出 : KeyValue:按键值,无按键按下返回20
*******************************************************************************/
uchar Key_Check(void)
{
static bit flag_key=0;
uchar KeyValue=20; //无按键按下返回20
uchar a=0;
GPIO_KEY=0x0f;
if(GPIO_KEY!=0x0f) //读取按键是否按下
{
if(!flag_key){ //判断之前的按键状态
flag_key=1; //按键状态1,按下状态
Delay_Us(1000); //延时10ms进行消抖
if(GPIO_KEY!=0x0f) //再次检测键盘是否按下
{
GPIO_KEY=0x0f; //测试列
switch(GPIO_KEY)
{
case(0X07): KeyValue=0;break;
case(0X0b): KeyValue=1;break;
case(0X0d): KeyValue=2;break;
case(0X0e): KeyValue=3;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 flag_key=0;
return KeyValue; //返回按键值
}
4.2 液晶显示1602
/*******************************************************************************
* 函 数 名 : Read_Busy()
* 函数功能 : 忙检测函数,判断bit7是0,允许执行;1禁止
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Read_Busy()
{
unsigned char sta;
LCD1602_DB = 0xff;
LCD1602_RS = 0;
LCD1602_RW = 1;
do
{
LCD1602_EN = 1;
sta = LCD1602_DB;
LCD1602_EN = 0; //使能,用完就拉低,释放总线
}while(sta & 0x80);
}
/*******************************************************************************
* 函 数 名 : Lcd1602_Write_Cmd()
* 函数功能 : LCD写命令
* 输 入 : cmd:命令
* 输 出 : 无
*******************************************************************************/
void Lcd1602_Write_Cmd(unsigned char cmd)
{
Read_Busy();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_EN = 1;
LCD1602_EN = 0;
}
/*******************************************************************************
* 函 数 名 : Lcd1602_Write_Data()
* 函数功能 : LCD写数据
* 输 入 : dat: 数据
* 输 出 : 无
*******************************************************************************/
void Lcd1602_Write_Data(unsigned char dat)
{
Read_Busy();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_EN = 1;
LCD1602_EN = 0;
}
/*******************************************************************************
* 函 数 名 : LcdSetCursor()
* 函数功能 : 设置坐标
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void LcdSetCursor(unsigned char x,unsigned char y)
{
unsigned char addr;
if(y == 0)
addr = 0x00 + x;
else
addr = 0x40 + x;
Lcd1602_Write_Cmd(addr|0x80);
}
/*******************************************************************************
* 函 数 名 : DisplayOneChar()
* 函数功能 : 按指定位置显示一个字符
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData)
{
Y &= 0x1;
X &= 0xF; //限制X不能大于15,Y不能大于1
if (Y) X |= 0x40; //当要显示第二行时地址码+0x40;
X |= 0x80; //算出指令码
Lcd1602_Write_Cmd(X); //发命令字
Lcd1602_Write_Data(DData); //发数据
}
/*******************************************************************************
* 函 数 名 : LcdShowStr()
* 函数功能 : 显示字符串
* 输 入 : x,y:当前字符的坐标;*str:字符串
* 输 出 : 无
*******************************************************************************/
void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str)
{
LcdSetCursor(x,y); //当前字符的坐标
while(*str != '\0')
{
Lcd1602_Write_Data(*str++);
}
}
/*******************************************************************************
* 函 数 名 : Lcd1602_Init()
* 函数功能 : LCD1602初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Lcd1602_Init() //
{
Lcd1602_Write_Cmd(0x38); //打开,5*8,8位数据
Lcd1602_Write_Cmd(0x0c);
Lcd1602_Write_Cmd(0x06);
Lcd1602_Write_Cmd(0x01); //清屏
}
4.3 存储模块2402
//IIC启动
void Start(void)
{
SDA1=1;
Delay_Us(2);
SCL1=1;
Delay_Us(2);//建立时间是SDA保持时间>4.7us
SDA1=0;
Delay_Us(2);//保持时间是>4us
SCL1=0;
Delay_Us(2);
}
//IIC停止
void Stop(void)
{
SDA1=0;
Delay_Us(2);
SCL1=1;
Delay_Us(2);//建立时间大于4.7us
SDA1=1;
Delay_Us(4);
}
//IIC发送字节
//dat:要发送的字节
void Send_Byte(unsigned char dat)
{
unsigned char a=0,b=0;
for(a=0;a<8;a++)//要发送8位,从最高位开始
{
SDA1=dat>>7; //起始信号之后SCL=0,所以可以直接改变SDA信号
dat=dat<<1;
Delay_Us(2);
SCL1=1;
Delay_Us(2);//建立时间>4.7us
SCL1=0;
Delay_Us(2);//时间大于4us
}
}
//IIC检查应答
bit Check_Ack(void)
{
//unsigned char t;
SCL1=0;
SDA1=1;
Delay_Us(2);
SCL1=1;
Delay_Us(2);
CY=SDA1;
SCL1=0;
Delay_Us(2);
return(CY);
}
//IIC应答
void Ack(void)
{
SDA1=0; //EEPROM通过在收到每个地址或数据之后
SCL1=1; //置SDA低电平的方式确认表示收到读SDA口状态
Delay_Us(1);
SCL1=0;
Delay_Us(1);
SDA1=1;
}
//IIC无应答
void NoAck(void)
{
SDA1=1;
SCL1=1;
Delay_Us(1);
SCL1=0;
}
//IIC接收字节
unsigned char Receive_Byte(void)
{
unsigned char a=0,dat=0;
SDA1=1; //起始和发送一个字节之后SCL都是0
Delay_Us(2);
for(a=0;a<8;a++)//接收8个字节
{
SCL1=1;
Delay_Us(2);
dat<<=1;
dat|=SDA1;
Delay_Us(2);
SCL1=0;
Delay_Us(2);
}
return dat;
}
//At24cxx读写数据
//addr:要读/写数据的地址
//*dat:要读/写的数据
//length;数据的长度
//RW:1:写,0:读
void At24c02_RW(unsigned char addr,unsigned char *dat,unsigned char length,bit RW)
{
Start();
Send_Byte(0xa0);//发送写器件地址
Check_Ack();
Send_Byte((unsigned char)addr);//发送要写入内存地址
Check_Ack();
if(RW) //写
{ while(length--)
{
Send_Byte(*dat++);
Check_Ack();
}
}
else
{
Start();
Send_Byte(0xa1);
Check_Ack();
while(--length)
{
*dat++=Receive_Byte();
Ack();
}
*dat=Receive_Byte();//读取最后一个字节
NoAck();
}
Stop();
if(RW)
Delay_Us(1000);
}
二、总结
今天主要讲了基于51单片机的密码锁Proteus仿真
感谢你的观看!