文章目录
- 0 前言
- 1 简介
- 2 主要器件
- 3 实现效果
- 4 设计原理
- 4.1 硬件部分
- 4.2 软件设计
- 5 部分核心代码
- 6 最后
0 前言
🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。
为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是
🚩 基于单片机的智能蓝牙密码锁设计与实现
🥇学长这里给一个题目综合评分(每项满分5分)
- 难度系数:4分
- 工作量:4分
- 创新点:3分
🧿 选题指导, 项目分享:
https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md
1 简介
基于51单片机的按键蓝牙密码锁,可通过LCD1602显示屏显示密码信息。利用矩阵按键输入密码,当输入密码次数达到上限后蜂鸣器模块进行报警
2 主要器件
- STC89C52单片机
- 4×4矩阵键盘
- LCD1602显示
3 实现效果
4 设计原理
4.1 硬件部分
硬件电路主要分为STC89C51单片机、AT24C02存储模块、按键电路、报警电路、继电器驱动模块五个部分。该控制系统的硬件设计以STC89C51单片机为主控芯片,利用单片机丰富的I/O端口将各个外围电路连接起来构成主系统,可以利用矩阵键盘实现密码输入与修改、关锁、复位等功能,并且通过单片机外接 LCD1602 液晶显示屏提示用户进行下一步操作。系统的硬件电路设计如图所示:
STC89C52单片机的最小系统
STC89C52单片机共有40只管脚,分为电源、时钟、控制和I/O引脚四类,它的优点是容易操作,原有程序可直接使用,硬件也无须改动,运行速度快且功耗低,而且成本低,抗干扰能力强,可提升产品性能,这使得在操作与成本方面都有极大的优势。单片机最小系统的工作由电源、晶振电路以及复位电路构成。
AT24C02存储模块
AT24C02存储器的数据传送率高且能与IIC总线兼容,功耗低,数据保存时间较长,还有一个专门的防误擦除写保护功能。芯片工作时有读和写两种操作,执行读操作时有当前地址读、随机读和顺序读三种方法;执行写操作时可根据数据量的大小选择字节写还是页写。在本设计中可直接将存储芯片的A0、A1、A2三个引脚连接至GND,为了方便读/写操作,将WP写保护引脚也连接到GND,最后将SDA、SCL两引脚分别接到单片机对应的两个引脚。
按键电路
该设计在操作过程中所需按键数目较多,所以采用矩阵式扫描的方法来作为键盘的输入形式且用4×4矩阵键盘可满足该设计所设定的功能。使用矩阵扫描法不仅可以减少单片机I/O端口的占用,也会降低电路连接的复杂程度。根据具体要实现的功能,密码锁的按键分布为数字键0-9、输入密码键、退格键、退出输入键、密码修改键、重置键、确认键。用户根据定义的按键功能实现输入,矩阵键盘直接连接单片机的P1口进行输入,通过输入高低电平判断键盘是否按下。
报警电路
报警电路由 LED 灯和蜂鸣报警器组成。这样可直观地观察密码锁的工作情况。本设计选用5V电磁式有源蜂鸣器,因为蜂鸣器工作时所需的电流较大,无法驱动单片机的I/O接口,电路中需要用一个三极管来驱动。当输入低电平时,三极管导通,蜂鸣器发出报警同时连接的红色LED灯亮;当输入高电平时,三极管截止,蜂鸣器停止鸣叫。
4.2 软件设计
电子密码锁控制系统的软件设计主要分为主程序、LCD1602显示程序、AT24C02存储程序、矩阵按键电路及中断服务程序的设计。为了实现密码锁的预期功能,软件设计部分以 STC89C51 单片机为核心编写程序,首先对整个系统程序进行初始化设置,开启电子密码锁的功能,采用4×4矩阵式键盘扫描方法来判断是否已按下按键,可通过LCD1602液晶显示屏清楚地看出当前已输入的密码位数,输入完成后按下确认键,密码锁会将输入的密码与事先存储在AT24C02芯片中的原密码进行比对,若密码一致则打开锁,若密码不一致则蜂鸣器报警且LED 灯亮,可选择重新输入,当密码错误三次则键盘将被锁定且报警。
软件设计流程如图所示:
5 部分核心代码
//MatrixKey.c文件
#include <REGX52.H>
#include "Delay.h"
#define FULL 4 //密码锁密码位数为4位
#define KEY_SCAN P1 //定义P1口/*** @brief 传回输入键值0-10,及输入的有效密码位数* @param *keyValue:0-11,*times:1-4* @retval 无*/void MatrixKey(unsigned char* keyValue,unsigned char* times)
{KEY_SCAN = 0X0F; //置行为0,进行列扫描if(KEY_SCAN!=0X0F) //若有键按下{Delay(10); //消抖KEY_SCAN = 0X0F; //再次进行列扫描switch(KEY_SCAN){ //若有某一列值为0,表示该列有按键按下case 0X07:*keyValue = 0;break;case 0X0B:*keyValue = 1;break;case 0X0D:*keyValue = 2;break;case 0X0E:*keyValue = 3;break;}KEY_SCAN = 0XF0; //行扫描switch(KEY_SCAN){ //若某行有值为0表示该行有键按下case 0X70:*keyValue = *keyValue;break;case 0XB0:*keyValue += 4;break;case 0XD0:*keyValue += 8;break;case 0XE0:*keyValue += 12;break;}if(*keyValue>=0 && *keyValue<=9){ //按下为有效密码*times = *times % 4 + 1;}while(KEY_SCAN!=0XF0){ //检测到按键松开时,退出循环Delay(10);}}
}
#include <REGX52.H>//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_E=P2^7;
#define LCD_DataPort P0//函数定义:
/*** @brief LCD1602延时函数,12MHz调用可延时1ms* @param 无* @retval 无*/
void LCD_Delay() //@12.000MHz 1ms 如果是更快的单片机,这里延时要长一点
{unsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i);
}/*** @brief LCD1602写命令* @param Command 要写入的命令* @retval 无*/
void LCD_WriteCommand(unsigned char Command)
{LCD_RS=0;//写指令LCD_RW=0;LCD_DataPort=Command;LCD_E=1;//这里置高又置低,高电平速度太快,反应不过来,因此进行延时LCD_Delay();LCD_E=0;LCD_Delay();
}/*** @brief LCD1602写数据* @param Data 要写入的数据* @retval 无*/
void LCD_WriteData(unsigned char Data)
{LCD_RS=1;//写数据LCD_RW=0;LCD_DataPort=Data;LCD_E=1;//这里置高又置低,高电平速度太快,反应不过来,因此进行延时LCD_Delay();LCD_E=0;LCD_Delay();}/*** @brief LCD1602初始化函数* @param 无* @retval 无*/
void LCD_Init(void)
{LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵LCD_WriteCommand(0x0C);//显示开,光标关,闪烁关LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动LCD_WriteCommand(0x01);//清屏}/*** @brief LCD1602设置光标位置* @param Line 行位置,范围:1~2* @param Column 列位置,范围:1~16* @retval 无*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{if(Line==1){LCD_WriteCommand(0x80|(Column-1));//0x80是确定光标位置的指令,A0-A6是确定地址}else{LCD_WriteCommand(0x80|(Column-1)+0x40);//换行所以要加一个基地址0x40}
}/*** @brief 在LCD1602指定位置上显示一个字符* @param Line 行位置,范围:1~2* @param Column 列位置,范围:1~16* @param Char 要显示的字符* @retval 无*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char)
{LCD_SetCursor(Line,Column);LCD_WriteData(Char); //char x=‘A’;(等效于char x=0x41;)
}/*** @brief 在LCD1602指定位置开始显示所给字符串* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param String 要显示的字符串* @retval 无*/
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char String[])
{//这里用到指针的作用,String[]指针指向该字符组的第一个地址,依次显示出来,直到'\0'的出现unsigned char i;LCD_SetCursor(Line,Column);for(i=0;String[i]!='\0';i++){LCD_WriteData(String[i]);}
}/*** @brief 返回值=X的Y次方*/
int LCD_Pow(int X,int Y)//x的y次方
{unsigned char i;int Result=1;for(i=0;i<Y;i++){Result*=X;}return Result;
}
/*i
789/100%10 7 3 10^(3-1)
789/10%10 8 2 10^(2-1)
789/1%10 9 1 10^(1-1)
*/
/*** @brief 在LCD1602指定位置开始显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~65535* @param Length 要显示数字的长度,范围:1~5* @retval 无*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);//根据长度确定所要显示的位数,通过计算将Number中从高位到低位依次显示出来,对照ASCII码表0的基地址//为0x30,显示几就在这个地址上加几就可以for(i=Length;i>0;i--){LCD_WriteData(0x30+Number/LCD_Pow(10,i-1)%10);//将数字转化为ASCII码表}
}/*** @brief 在LCD1602指定位置开始以有符号十进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:-32768~32767* @param Length 要显示数字的长度,范围:1~5* @retval 无*/
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(0x30+Number1/LCD_Pow(10,i-1)%10);//将数字转化为ASCII码表}
}/*
输入0xA0 i
160/16%16 10 2 16^(2-1)
160/1%16 0 1 16^(1-1)
*/
/*** @brief 在LCD1602指定位置开始以十六进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~0xFFFF* @param Length 要显示数字的长度,范围:1~4* @retval 无*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column, int Number,unsigned char Length)
{unsigned char i;unsigned char SingleNumber;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){SingleNumber=Number/LCD_Pow(16,i-1)%16;//将16进制转化为ASCII码表,这里输入16进制,但进行计算的是十进制if(SingleNumber<10){LCD_WriteData('0'+SingleNumber);}else{LCD_WriteData('A'+SingleNumber-10);//这里的‘A’相当于是一个基地址}}
}/*** @brief 在LCD1602指定位置开始以二进制显示所给数字* @param Line 起始行位置,范围:1~2* @param Column 起始列位置,范围:1~16* @param Number 要显示的数字,范围:0~1111 1111 1111 1111* @param Length 要显示数字的长度,范围:1~16* @retval 无*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column, int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(0x30+Number/LCD_Pow(2,i-1)%2);//将数字转化为ASCII码表}
}
//main.c文件
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "MatrixKey.h"#define DEL 10 //矩阵键盘键值为10的键表示删除键
#define SURE 11 //矩阵键盘键值为11的键表示确认键
#define TRUE 1 //真值
#define FALSE 0 //假值
#define NONE 20 //初始化键值,用非0-9数字表示无效值unsigned char value = NONE; //键值,并初始化为无效值NONE
unsigned char times; //输入有效的密码位数unsigned char password[] = {0,1,0,0,7}; //密码“1007”,第0位不用
unsigned char identify = TRUE; //用于判断密码是否输入正确
unsigned char input[5]={0,0,0,0,0}; //用于存放输入的密码void main()
{LCD_Init(); //初始化LCD屏幕LCD_ShowString(1,1,"Password:");LCD_ShowString(2,1,"XXXX");while(1){MatrixKey(&value,×); //获得输入的键值和输入密码位数if(value>=0 && value <=9){ //判断是否为有效键值(密码)LCD_ShowNum(2,times,value,1); //对应位输出密码input[times] = value; //保存输入密码if(times==FULL){ //输入密码为4位时,判断是否密码正确unsigned int i;for(i=1; i<=4; i++){ //遍历密码password进行判断if(input[i] != password[i])identify = FALSE;}}}if(value == DEL){ //若输入删除键LCD_ShowString(2,times,"X"); //在删除位输出X表示对应位删除times--; //清空删除的密码,表示当前输入密码位数value = NONE; //调用前value=10,执行后value值为无效值}if(identify==FALSE && times==FULL && value==SURE ){ //输入4位密码不正确LCD_ShowString(1,12,"Error");LCD_ShowString(2,1,"XXXX");identify = TRUE;value = NONE;}else if(identify==TRUE && times==FULL && value==SURE){ //输入4位密码正确LCD_ShowString(1,12,"Right");}}
}