Skip to content

XJTUSE-计组大实验 ​

引言 ​

背景 ​

随着科技的不断进步,智能家居技术逐渐成为人们生活中的重要组成部分。在传统门锁存在的问题和需求的推动下,智能门锁作为一种创新的解决方案受到了广泛关注。智能门锁不仅提供了更高级别的安全性,还赋予了用户更便捷和智能化的门禁控制体验。

项目介绍 ​

本实验报告旨在设计和实现一个智能门锁系统,该系统具备密码设定和重置功能,并在密码输入完成后给予反馈信息。我们将探索智能门锁在提供安全和便利性方面的潜力,并评估其性能和功能的可行性。

具体可用功能如下:

门锁设定密码:用户可以通过设定密码来保护门锁的安全性。通过按键输入。

密码验证:门锁可以对用户输入的密码进行验证,以确保只有授权用户可以进入。

输错密码锁定:系统可以追踪用户输入的错误密码次数,当错误次数达到一定阈值时,智能门锁会自动锁定,以防止暴力破解。

密码重置:用户可以选择重置密码,以便更换旧密码或恢复遗忘的密码。这个功能可以通过特定的操作流程或者按键组合来实现。

项目目标:

提供高级别的安全性:智能门锁的主要目标是提供比传统门锁更高级别的安全性。通过使用密码验证和输错密码锁定功能,确保只有授权用户能够进入。

提供便捷的门禁控制体验:智能门锁旨在提供便捷的门禁控制体验。用户可以通过设定和重置密码来管理门禁权限,而无需使用传统的钥匙。

保护门锁免受暴力破解:通过输错密码锁定功能,智能门锁可以防止恶意攻击者通过暴力破解方式尝试多次密码,从而提高门锁的安全性。

提供可定制的安全功能:智能门锁的设计允许用户根据自己的需求定制安全功能,如设定密码长度、锁定时间等,以满足不同环境和使用场景的要求。

## Quartus

硬件设计原理图 ​

首先是整体:

我们这里的电路首先大致上的思路是把显示器的电路和44键盘的电路所结合,它们各需要一个口进行数据的传输,因而确定了P0口用来进行显示器的相关数据的绑定,P3口用于进行44键盘的数据传输。

我们这里对P3和P1口进行处理,使得这两个I/O口成为双向口。

(如图为P3的处理,P1口的处理与其相同)

键盘帮助我们进行数据的输入,显示器用于相关信息的输出,为了实现多种功能,我们还需要其他的输出进行调控,这里我们使用了P2I,即P2口的输入。 P2的多位数值能够根据我们的需要随时修改为0或1。

这样我们还剩下一个P1口没有使用,P1可以帮助我们完成lcd显示的相关工作,还可以帮助完成蜂鸣器的发声。

我们这里P1的输出其中的声音信号以及控制lcd显示的相关信号。

关于这些P0,P1,P2,P3口的具体使用将在程序中介绍。

模式 ​

我们选取的标准是存在扬声器并且尽可能拥有多的按键

于是最终采用的是存在扬声器且键1到键8均能控制一位数据的模式5

引脚说明 ​

这里和我们功能密切相关的引脚有P1O[0..3],P2I[0..7],P0,P3

我们先说输入,

即P2I[0..6],本来可以使用8位,不过我们最后只使用了7位,这7个引脚对应的是键7到键1,每次键的按下都会改变P2I中对应的值。

然后是其他的引脚,

P0,P3以及P1O[1..3]都对应的是电路板上的系统扩展端口,绑定引脚之后我们就能在程序中控制这些数据的改变,同时实现键盘、显示器和芯片之间的数据传输,从而在程序中获取4*4键盘按下的哪个按键,以及将我们想要显示的内容显示在显示器上。

最后还有一个P1O[0]这一个是我们实现蜂鸣器的关键,绑定的是DA24口,让我们得以输出声音信号,并且程序中不断改变其值能够实现播放不同的歌曲。

软件设计 ​

流程图 ​

下面给出我们智能门锁设计流程图:

流程图描述 ​

我们智能门锁一共分为两个部分,初始化模式和工作模式。两个模式可以相互转化,通过键5进行工作模式转化到初始化模式。

下面详细介绍两个模式:

初始化模式:

该模式分为两个模块,录入密码长度和录入密码。

首先,我们要求使用者录入密码长度(1-8位),当按下确定键后转入录入密码模块。

当使用者设置好密码后,按下确定键,转化至工作模式。

工作模式:

工作模式分为三个大块:输入密码,门铃模式,开锁模式。

用户可以选择输入密码,然后按下确定开锁按钮,系统进行判断:1.当密码准确,开启门锁;2.当密码错误,但还有试错机会,回到输入密码模式;3.当试错机会使用完毕,门锁进入死锁模式,只有按下对应按钮解锁死锁模式。

门铃模式则为按下按钮,蜂鸣器发出声音,即门铃功能。

## C语言源代码

下面我们将详细讲述我们编写的C语言源代码。由于代码部分涵盖老师给的代码和一些标准引用,因此,在本环节我们只讲述实现项目的关键部分。

常量/变量部分 ​

`// 下面为实现功能所需的变量,全局变量存在ROM中,节省RAM uchar i; // 循环时的变量 uchar pivot = 0; // 循环时的变量 uchar keynum=16; //两个全局变量 uchar now_pd[8]={0}; // 存放当前输入的密码 uchar pass_pd[8] = {0}; // 存放设置好的密码 uchar chances = 3; // 代表试错机会一共有三次 uchar flag_verifypd = 1; // 在初始化密码时候,代表是否确认密码 uchar pd_len = 0; // 存储密码的长度 uchar flag_init = 2; // 一个辅助标记变量,用在初始化中

// 下面是将变量绑定各引脚 sbit SPK = P1^0; sbit pd7 = P2^0; sbit pd6 = P2^1; sbit pd5 = P2^2; sbit pd4 = P2^3; sbit pd3 = P2^4; sbit pd2 = P2^5; sbit pd1 = P2^6;

uchar code song_tone[]= // 存储音乐,下面三个同理 { 190,212,142,212,0 };

uchar code song_tone2[]= { 212,212,190,0 };

uchar code song_long[]= { 12,12,12,12,0 }; uchar code song_long2[]= { 3,12,12,0 };`

可以看到,常量/变量部分都是根据各功能所定义出来的,具体的功能已经给出,不再赘述。

工具函数 ​

`// 该方法转化uchar pd为str类型,可用于LED屏幕显示 uchar *transpd(uchar pd){ switch(pd) { case 0: return "0"; case 1: return "1"; case 2: return "2"; case 3: return "3"; case 4: return "4"; case 5: return "5"; case 6: return "6"; case 7: return "7"; case 8: return "8"; case 9: return "9"; case 10: return "A"; case 11: return "B"; case 12: return "C"; case 13: return "D"; case 14: return "E"; case 15: return "F"; default: return "0";// } }

// 该方法为将三个按钮pd1,pd2,pd3表示二进制转化为十进制。 uchar getNum(){ if(pd1==0 && pd2==0 && pd3 ==0){ return 0; } else if(pd1==1 && pd2==0 && pd3 ==0){ return 1; } else if(pd1==0 && pd2==1 && pd3 ==0){ return 2; } else if(pd1==1 && pd2==1 && pd3 ==0){ return 3; } else if(pd1==0 && pd2==0 && pd3 ==1){ return 4; } else if(pd1==1 && pd2==0 && pd3 ==1){ return 5; } else if(pd1==0 && pd2==1 && pd3 ==1){ return 6; } else if(pd1==1 && pd2==1 && pd3 ==1){ return 7; } return 0; }

// 检查now_pd即输入密码是否与pass_pd一致即设置密码 uchar check_pd(){ for (pivot=0;pivot<pd_len;pivot++){ if (now_pd[pivot]!=pass_pd[pivot]){ return 0; } } return 1; }

// 初始化密码,全部设为0 void init_pd(){ for (pivot=0;pivot<pd_len;pivot++){ now_pd[pivot]=0; pass_pd[pivot] = 0; } }

// 改变密码,将输入的密码存为设置好的密码 void change_pd(){ for (pivot=0;pivot<pd_len;pivot++){ pass_pd[pivot] = now_pd[pivot]; } }`

这些工具函数在后续的编写有用,实现功能都较为简单,而且实现方法也较为简单,不再赘述。

主函数 ​

`void main(void){

    lcd_init();//初始化

    // 初始化模式
    while(1){//循环执行
            if (flag_init==2){ // 进入初态
            lcd_init();//初始化
            while (1){
                    display(1,0,"set u pd length(1-8)");
                    if (P3!=0xf0) key_scan();//判断按键是否按下,按下时扫描键盘获得按键序号
                    if (keynum!=16){
                            if (keynum<=8 && keynum>=0){
                                    pd_len = keynum; // 设置密码长度
                                    flag_init--; // 更新状态
                                    display(2,0,transpd(keynum)); // 在特定的LED屏幕位置进行展示,后面的类似,不再赘述
                                    break;
                            }
                    }
            }
            if(flag_init==1){
                    display(3,0,"set u pd:");
                    while(1){
                                    P3=0xf0;
                                    if (P3!=0xf0) key_scan();//判断按键是否按下,按下时扫描键盘获得按键序号
                                    if (keynum!=16)
                                    {
                                            if (getNum()<pd_len && getNum()>=0){ // 密码只能设置1-8位
                                                    now_pd[getNum()] = keynum; // 设置密码
                                            }
                                    }
                                    for(pivot=0;pivot<pd_len;pivot++){
                                            display(4,pivot,transpd(now_pd[pivot]));
                                    }
                                    if (pd6 == 1){ // 确定设置密码
                                            change_pd(); //改变密码
                                            flag_init --; // 更改状态
                                            break;
                                    }
                    }
            }
            lcd_init();//初始化
    }

    // 工作模式
    display(1,0,"please entry pd:");

    P3=0xf0;
    if (P3!=0xf0) key_scan();//判断按键是否按下,按下时扫描键盘获得按键序号
    if (keynum!=16)
    {
            if (getNum()<pd_len && getNum()>=0){
                    now_pd[getNum()] = keynum; // 获取当前输入密码
            }
    }
    // 循环展示密码
    for(pivot=0;pivot<pd_len;pivot++){
            display(2,pivot,transpd(now_pd[pivot]));
    }
    // 下面为验证密码
    if (pd4==1 && chances>0 && flag_verifypd==1){
            // 成功
            if (check_pd()==1){
                    display(3,0,"success!!         ");
                    display(4,0," ");
                    chances = 3;
                    playmusic(); // 播放音乐
            }
            // 失败
            else{
                    chances --;
                    flag_verifypd = 0;
                    display(3,0,"fail!! chance(s):");
                    display(4,0,transpd(chances));
            }
    }
    // 重置辅助修改密码的flag
    if (pd4 == 0){
            flag_verifypd = 1;
    }
    // 下面回到初始化模式
    if (pd5==1){
            init_pd();
            chances = 3;
            keynum = 16;
            flag_init = 2;
    }
    // 下面为重置修改次数,仍在工作模式
    if(pd6==1){
            chances = 3;
              lcd_init();//初始化
    }
    // 按门铃功能
    if(pd7==1){
            playmusic2();
    }
    delay_ms(500);

} }`

简单来讲,在主函数存在两个模式:初始化模式和工作模式。在不同模式中,不同按钮、按键存在不同含义,同时不同模式可以切换,不同模式存在不同的具体功能。

初始化模式功能:设置密码长度,设置密码。

工作模式:输入密码,按门铃,重置密码,回到初始化模式。

代码没有完全给出,这是因为有些代码是冗余以保障功能健硕,有些代码是老师提供的。全部代码可以参考压缩包中的.c文件。

调试总结&实验过程 ​

下面给出我们实验的全过程:

首先切换到模式5,将程序导入rom中,我们就会进入我们程序的初始化界面

在这个界面下,根据提示,我们需要使用4*4键盘输入我们设定密码的位数,数值必须在1~8以内,如果输入在这之外的数字是无效的,不会进入下一步。

我们这里输入数字2

这时候就需要我们设定密码了,默认密码的每一位都是0,我们这里没有进行改动。

使用左侧键6提交我们的初始密码,我们就能够进入我们程序的主要界面,并且刷新。

那么我们先尝试一下提交密码,看看是什么结果

当然00是正确的密码,这里会提示文字信息,并且还会播放一段音乐。

我们也可以尝试其他密码,比如这里我们修改一下第0位为6

(注释:如何修改其他位的密码?

这里我们就要使用左侧的键1到键3,它们各自对应一个二进制的值,现在全是0,那么就是000,对应的是第0位。

如果我们要修改第1位的密码,那么只需要将键1点亮,使得二进制对应为001,这样就可以修改第1位的密码。

假设要修改第3位的密码,只要键3不亮,键2点亮,键1点亮,即011,对应3即可使用4*4键盘修改对应位。

当然我们这里只设置了2位,只需要调整键1就可以全部控制了。)

如果继续提交两次。

我们只有三次错误机会,如果全部错误就会锁定,无法继续提交了。

那么最后总结一下左侧按键的功能:

键1~键3:调整4*4键盘对应密码的位数,000对应第0位,111对应第7位。

键4:在主界面确定密码并提交。

键5:重新进入初始化界面。

键6:在初始化界面确认设置密码并提交,在主界面是刷新显示界面。

键7:控制蜂鸣器的发声。

实验困难及解决 ​

模式的选择难题 ​

一开始我们是想在老师给的4×4key的样例的模式2上进行智能门锁的实现。

如图所示扩展了两个输出口,利用到了P0,P1,P2

事实证明,我们完成地也不错,我们将图示的三个显示屏用来展示密码

然后,用三个按钮控制密码输入输出和确定。

但是,我们接下来就遇到了巨大的问题:

每个显示屏需要7个引脚,这是难以忍受的:我们没有多余的引脚来实现其他功能了!!!

同时,我们注意到模式2没有蜂鸣器,这对于门铃来讲是巨大的挫折。

最后,我们舍弃了之前做过的工作,选择模式5,完成了较为完善的引脚配置和其他功能的结合。

引脚的配置难题 ​

当我们从模式2转移到模式5,遇到的第一个问题就是硬件引脚的配置,尤其是4×4key键盘引脚的配置,不过好在通过耐心的查看老师样板引脚配置,最终理解了板子的引脚如何设置,我们完成了引脚的配置。

ROM内存难题 ​

一开始编写C程序,由于我们不敢修改老师的代码和想要实现非常多的功能,因此导致编译后的内存超过了4K。我们一开始没有理解为什么程序跑不起来,后来仔细对比了程序编译后的字节和ROM的内存,才发现内存超出了。对此,我们进行了如下操作:

精简了功能实现的代码,尤其是对于一些变量的使用。

删改了老师的参考代码,比如对于歌曲,我们再理解频率等相关知识后,自己修改出来了一个更为短暂的门铃。

删除了部分非必要的功能。

## 实验结论

实验结果表明,智能门锁成功实现了密码设定和验证的基本功能。密码验证过程稳定可靠,能够准确判断用户输入的密码是否正确,并根据设定的要求进行相应的锁定操作。密码重置功能也得到了有效实现,用户可以通过特定的操作流程轻松重置密码。

在实验过程中,智能门锁系统表现出较高的安全性和便利性。输错密码锁定功能有效地防止了暴力破解的尝试,提高了门锁的安全性。同时,用户可以通过简单的操作来设定密码和重置密码,无需使用传统的钥匙,提供了更便捷的门禁控制体验。

心得 ​

本次实验完成了一个智能电子门锁,过程可以说是非常曲折了,不过好在我们成功完成了一个具有实际意义的小应用。这次应用主要的创新点应该是我们想法的创新,软件的编写以及硬件模式的融合。

在硬件实现上虽然参照了老师给的样例,但我们也进行了模式融合的创新。

相较于其他队伍,我们选题独树一帜,也算是创新点。

软件即C语言编写,经过了许多版本迭代,最后在有限的内存中折中达成了设想的门锁功能。

当然,也要勇于承认自己的不足:

我们硬件调试依旧不是完美,有着部分缺陷,比如延迟过高,灵敏度不足;

软件代码的编写还是存在部分冗余,占用内存。