北邮密码锁小学期报告
北京邮电大学
电路综合实验报告
实验题目:
学生姓名:
班级:
小班学号:
同组姓名:
E-MAIL:
目录
一.设计目的、用途、功能二.硬件设计
三、软件设计
四、实验器材
五、实验过程
六、分工情况
七、实验总结和心得体会
八、参考文献
九、代码
摘要:本项目是做一个基于ATMega 16单片机的电子密码锁,通过4×4键盘和LCD液晶显示,实现了密码锁的密码验证、重新设置、输入错误后报警直到复位后启动倒计时功能,在倒计时内键盘自动锁死,倒计时结束后恢复所有功能等一些基本功能,通过LCD的巧妙使用实现友好的人机界面功能,更具人性化,除此之外还创新性的实现了LCD的开锁功能和键盘的重新定义。通过LCD的开锁功能和对键盘的重新定义以及倒计时功能可以有效地防止密码被盗,同时也降低了成本,便于携带安装。
关键字:ATMega16 单片机、密码锁、键盘、 LCD 、开锁、倒计时
一.设计目的、用途、功能
1.设计目的
随着科技的发展,安全问题越来越受到人们重视,不仅居家安全受到重视,出门在外行李物品的安全问题也受到了很多关注,在此基础上各种安全产品相继问世,如指纹防盗,红外防盗等,虽然这类产品的安全性较高,但其成本也同样较高,并且携带安装不方便,这种种缺点限制了其发展。本设计的目的就是为了在保证高安全性的前提下,实现降低成本,方便携带安装的功能,真正服务于人民。
当然对于密码锁,我们接触最多的就是取款机了,我们设计的这个密码锁,与众不同的地方就是我们可以让我们的液晶显示屏无限循环的显示操作指南,只有当你按下键盘上的任意一个键的时候才可以终止这种循环。还有一点,就是我们的密码锁加了倒计时这个功能,当输入错误次数超过三次的时候,就不能再输入了,更加安全。
2. 设计用途
由于本设计成本较低且安全性能稳定,可适用于家庭防盗、行李箱、车载行李箱等一些安全性能要求不高且需要便携的产品上,其安全性能远远优于同等成本的安全性。其中输入密码错误无限报警和自动锁死的功能能提高用户的警觉性,降低失窃率。更重要的是本设计的键盘重新定义的思想可移植到自动提款机、公共场合的提款装置等,可以防止用户输入密码时被别人偷看、窃取等,增加用户提款时的安全性。LCD具有良好的人机友好界面,在未开锁之前显示“welcome”,并且只有知道如何开锁才能使用密码锁,进一步提高了安全性能。
3.设计功能
(1)系统功能:
1、密码键盘的输入:包括0-9数字输入、A-F为功能键。
键盘默认如下:
1 2 3 4
5 6 7 8
9 0 A B
C D E F
其中A代表reset(复位),B代表*,C代表关锁,D 代表clear(删除),E代表change (修改),F代表#。
2、液晶显示:主要用于液晶显示屏将具体输入数字用*号代替,便于用户看到自己是
否输入六位。最开始显示“Welcome”字样,然后显示操作指南
“A=reset,B=*,C=close,D=del,E=change,F=#,”密码输入正确显示“right”,密码输入
错误显示“wrong”。
(2)处理技术:
1、密码正误信息输出:“确认”输入后,扫描键盘,与EEPROM中的内置密码相比较,
若相同,进入“密码重置”功能;否则,在三次提示输入错误后蜂鸣器鸣响报警。
2、密码重置:密码正确后,提示是否修改密码,修改按“g”。两次输入一致后修改内
置密码并液晶屏文字提示。否则进入“开锁功能”。
(3)扩展功能:
倒计时功能:密码三次输入错误后需间隔1分钟(或更久)后才能再次输入,需两位数码管进入倒计时。
液晶显示变幻:可实现闪烁,无限滚动等效果(利用C语言编程实现)。
基本原理:
二.硬件设计
1.系统硬件设计思路
系统以Atmega16单片机为控制核心,4*4键盘作为输入模块,1602液晶显示屏作为显示模块实现密码锁的各项功能。
(1)系统硬件方框图如下:
(2)系统工作的流程图如下
2.电路连接原理
(1)键盘电路:4*4键盘采用行列式键盘,使用单片机的8个I/O端口就可以控制16个按键。键盘的8个引脚分别接在PORTB的8个端口。对键盘进行扫描时,可以采用查询扫描的方法:先对一行进行扫描,再对列进行扫描,直至将每行每列都扫描一次,通过单片机端口电平的变化,判断哪个按键被按下。
(2)LCD液晶显示电路:1602液晶主要用于与用户交互,提示密码输入状态和显示结果。其中LCD的7~14号引脚分别连结单片机的PORTA各端口,用于数据的并行传输。4~6号引脚与PC0,PC1,PC7相连,分别控制RS(数据/指令寄存器选择)、RW(读写选择引脚)、E(读写使能引脚)。相关功能,我们参照1602的数据手册。
(3)蜂鸣器电路:蜂鸣器电路用于报警。当密码输入错误时,单片机的PD1端口便会输出高电平,蜂鸣器即发出报警声。
(4)开锁电路:在此,用指示灯模拟继电器。当密码输入正确时,单片机的PD0端口便会输出高电平,此时发光二极管发光。如果二极管亮,说明锁已开;如果不亮,则锁未打开。3.电路原理图
三、软件设计
1.程序设计思想
(1)键盘扫描:键盘扫描程序的流程图如下图所示。对键盘进行查询扫描,确定有键按下后,读取键值,进行延时去抖,再次读取键值,若两次键值相等则返回键值。
一般情况下,我们运用软件进行延时去抖,通常用delay_ms(10),在此期间CPU不能进行其它工作。
(2)LCD显示程序:根据1602的数据手册,我们可以针对其各引脚的定义和内部结构,定义显示字符函数、显示字符串函数、清屏函数和初始化函数,在后续程序中调用即可。(3)整体流程:此程序通过设置多个全局变量把各部分的程序统一成为整体。其中input_sign 用于标记是否有键按下,当其为1时代表有键按下;state用于标记功能,state=0代表输入密码,state=1代表密码输入成功,state=2代表改密码,state=3代表修改键盘,state=4代表功能选择;数组password[]用于存储密码;数组key[][]用于存储键值。运用if语句即可实现各种功能。
实验器材
4*4键盘1个
1602液晶显示1个
蜂鸣器1个
发光二极管1个
五、实验过程
1.实验历程
(1)9月22日确定设计方案
(2)9月23日-25日上午第一实验周期
9月23日,上午进行视频学习,下午完成电路的一些基本的连接。
9月24日上午,检查硬件和管脚。观看关于键盘和液晶显示的所有视频。有一个初步的想法。完成所有电路的连线和布局。
9月24日下午,确定原设计方案下管脚不够。更改设计方案:取消数码管,准备将倒计时功能模块置于液晶屏上显示。此时出现芯片无法写入的情况。
9月25日上午,和同组同学经过多方排除故障原因,最终更换芯片解决问题。编写扫描键盘和在液晶显示屏上两个大函数。
(3)9月25日中午-9月27日上午第二实验周期
9月25日中午,完成线路重新设计及布局。进入单步功能实现及测试。
9月25日下午,按照设计思想编写代码,主要是写键盘控制状态的那个功能函数,主要解决键盘控制不了显示屏的问题
9月26日上午,解决了键盘的控制问题。然后检测并实现了前边写的LED双行显示功能。
9月26日下午,编好了显示屏和键盘的所有函数,并调试成功。
(3)9月26日晚至9月27日上午
26日晚调试基本功能成功。并经过十几次的测试,解决了好多细小的问题。和同伴编写了倒计时的代码。
27日上午,在其基础上添加显示指南代码,进行整体功能调试。并请老师验收了实验。
2.系统测试过程和结果(截图)
(1)欢迎界面(2)操作指南无限循环
(3)当任意按一个键,进入键盘解锁界面(4)进入输入密码界面
(5)输入六位密码(6)判断正误(左边正确,右边错误)
(7)如果正确,进入判断是否修改密码界面(8)修改密码,输入两次确认
(9)如果输入错误达到三次,进入倒计时(10)倒计时结束,恢复到初始状态
六、分工情况
(1)分工:
李向前:在第一次实验周期中解决电路连接中管脚不够,芯片无法写入的问题,确定解决方案。负责LED显示屏代码的编写和原理研究,编程实现双行滚动显示的功能段代码,并在最后实验调试中发挥了较大作用。
闫圆圆:负责两次实验周期中的布线工作。认真研究键盘原理,实现键盘模块功能,编写了键盘的扫描函数和键盘相应状态控制函数,当然也完成了其它一些小函数的编写。
在最后实验调试中提出了很多宝贵意见。
(2)合作:倒计时部分代码由双方讨论决定。两人一起完善出了最终的代码。在遇到没有预期的功能时,两个人总是携手一起解决。
七、实验总结和心得体会
(1)实验总结
①概括这次实验
本项目是做一个基于ATMega 16单片机的电子密码锁,通过4×4键盘和LCD液晶显示,实现了密码锁的相应功能。
我们的出发点是设计出既安全又成本低的密码锁。我们的密码锁有如下功能:密码验证、修改密码、输入错误后鸣笛等基本功能,当然也有复位后启动倒计时功能、显示操作指南等两个创新功能。
我们收获了很多基础知识,比如说:对键盘进行扫描时,可以采用查询扫描的方法,即先对一行进行扫描,再对列进行扫描,直至将每行每列都扫描一次,通过单片机端口电平的变化,判断哪个按键被按下。当然,我们也掌握了液晶显示屏的各个管脚的左右,也掌握了ATmega16的各个管脚的作用。
自然,我们不仅收获了上面所说的知识,也锻炼了自己的动手能力和解决问题的能力。最重要的是也培养了我们团结协作的能力。
②实验中遇到的问题以及解决方案
有一句话说的好“Man errs so long as he strives”,意思也就是说,人只要奋斗就会犯错误。这次实验真的出现了好多错误,但是最终我们都克服了它们。
1.本身是打算用数码管显示倒计时的,发现它如预期的一样,PC2-PC5管脚不能用,
尝试改变它的熔丝位试一试,结果以失败告终。
2.可能是由于上面修改熔丝位,或者连电路的时候出了一些小问题,我们的液晶显示
屏就是不能用。之后拆了原来辛辛苦苦连的电路,只是往单片机里写一些控制
二极管发光的代码,也写不进去了。最后判断是单片机锁死了,我们换了一个
ATmega16芯片后,终于解决了问题。
3.当然,我的主要工作是键盘模块。我认真的看了视频,但是,当写入代码的时候发
现,不能控制键盘,而且按#键的时候会发生复位问题,当时觉得很奇怪。后来
经过不断地探索,发现是判断按键的那段代码的思路错了,最终可以了,但是
奇怪的是,最右边的那一排,一按就复位了。后来发现是导线错连了一位,有
一根导线,连到了reset引脚。最终解决了这个问题。
4.紧接着就是显示屏的代码问题,会遇到一些字符显示太快,或者是没有达到预期的
结果,但是都通过不断地修改断码来解决问题。距离说一下,就是我要实现的
功能是按了*键之后,显示”press #”,但是每次都显示“press *”,后来发现
是函数里有一个case少了一个break。
5.最后呢是显示倒计时,刚开始显示乱码。最后写了这样一个函数LCD_show(m,n);
利用for循环,把数组里的0,1,2等换成‘0’,‘1’,‘2’才解决了问题。
(2)心得体会
一分耕耘一分收获,这次实验,把这句话展现的淋漓尽致。我想在实验成功的那一刹那,就是对我们这历时两周的忙碌最好的报答。
这次实验还算顺利,在规定日期前就完成了任务。时间呢,可能大部分花费在解决一些不可预知的问题上了,无论是连电路,还是写代码,虽然也会出现问题,整体来说还算顺利。
很喜欢这样的小学期,它不仅可以增长我们的知识,还可以锻炼我们的动手能力,还可以增进同学之间团结互助的感情。这样的小学期,充满了乐趣,探索和收获。
有时候一整天都待在实验室,虽然说累,但是感觉整个人都充满了能力。怀揣着一份新奇,不断尝试,感觉那样的日子很充实,很快乐。
再来说说这次实验,真的是很用心的看视频,然后动手做流水灯,刚开始拿着ATmega16和面包板,真的感觉很新奇,在这之前都没有接触过呢。看着视频,当看到流水灯工作的时候,觉得那是我见过最美的灯了。接着呢,就是用2个数码管做一个秒表计时器,实现按下按键后能中断的功能。感觉中断真的是较难的一个知识了,视频都看了好多编,最后才搞清楚。中间放假三天,然后我们开始着手设计方案,找自己感兴趣的课题,当然也有想过,做一个更复杂的,更好玩的。有想过去做一个游戏机,类似快乐大本营的那个拼音游戏,把它做到一个板子上,玩着更方便。但是后来觉得无法突破判断是否正确的那个环节,所以就结束了那个想法。后来又考虑做一个智能垃圾箱,可以用手的感应去控制垃圾箱的开关,觉得模拟起来对于材料要求较高,所以最终做一个密码锁,即使用,又在我们的能力范围内。
当然对于密码锁,我们接触最多的就是取款机了,我们设计的这个密码锁,与众不同的地方就是我们可以让我们的液晶显示屏无限循环的显示操作指南,只有当你按下键盘上的任意一个键的时候才可以终止这种循环。还有一点,就是我们的密码锁加了倒计时这个功能,当输入错误次数超过三次的时候,就不能再输入了,更加安全。
总之,这次实验真的收获了很多,虽然遇到的问题很多,但是解决问题的过程真的是受益匪浅。
八、参考文献
参考资料:
1、《基于AVR 的单片嵌入式系统原理与实践应用》华东师范大学电子科学技术系马潮
2、《avr单片机原理及测控工程应用》
代码
#include
#include
#define uchar unsigned char
#define uint unsigned int
unsigned char key_stime_ok;
unsigned char key_temp=0;
unsigned char error=0, number=0;
unsigned char key_stime_counter;
unsigned char animation_state=0;
unsigned char code_number[6], temp1[6],temp2[6];
int password[6]={6,6,6,6,6,6};
uchar time_show[]={'0','1','2','3','4','5','6','7','8','9',':'};
uchar instr[]="A=reset,B=*,C=close,D=del,E=change,F=#,";
void xianshi(uchar a[]);
#define No_key 255
#define K1_1 1
#define K1_2 2
#define K1_3 3
#define K1_4 4
#define K2_1 5
#define K2_2 6
#define K2_3 7
#define K2_4 8
#define K3_1 9
#define K3_2 0
#define K3_3 'r'
#define K3_4 '*'
#define K4_1 's'
#define K4_2 'c'
#define K4_3 'g'
#define K4_4 '#'
#define Key_mask 0b00001111
void delay(uint ms)//延时函数
{
uint i,j;
for(i=0;i { for(j=0;j<1141;j++); } } void write_com(uchar com)//写指令 { PORTC &= ~0x01; PORTC &= ~0x02; PORTA = com; PORTC |= 0x80; delay(1); PORTC &= ~0x80; } void write_dat(uchar dat)//写数据 { PORTC |= 0x01; PORTC &= ~0x02; PORTA = dat; PORTC |= 0x80; delay(1); PORTC &= ~0x80; } void LCD_show(int x, int y)//显示倒计时{ write_com(0x80+3); delay(5); write_dat(time_show[x]); write_com(0x80+4); delay(5); write_dat(time_show[y]); //delay(35); delay(1); } void show(uchar j)//显示屏 { uchar i; write_com(0x0c); delay(5); write_com(0x06); delay(5); write_com(0x80+0); delay(5); switch(j) { case 0: { write_com(0x01); delay(5); write_com(0x80+9); delay(5); uchar table[] = "Welcome!";//显示welcome for (i=0;i<8;i++) { write_dat(table[i]); delay(1); } delay(10); for (i=0;i<9;i++)//移动9次 { write_com(0x18); delay(10); } xianshi(instr); break; } case 1: { uchar table1[] = "*"; for (i=0;i<1;i++) { write_dat(table1[i]); delay(1); } break; } case 2: { write_com(0x01); delay(5); uchar table2[] = "press #"; for (i=0;i<7;i++) { write_dat(table2[i]); delay(1); } break; } case 3: { write_com(0x01); delay(5); uchar table3[] = "press *"; for (i=0;i<7;i++) { write_dat(table3[i]); delay(1); } break; } case 4: { uchar table4[] = "then press #"; write_com(0x80+40);//让字符在第二行显示delay(5); for (i=0;i<12;i++) { write_dat(table4[i]); delay(1); } break; } case 5: { write_com(0x01); delay(5);uchar table5[] = "input the code"; for (i=0;i<14;i++) { write_dat(table5[i]); delay(1); } break; } case 6: { write_com(0x01); //先清屏 delay(5); uchar table6[] = "right"; for (i=0;i<5;i++) { write_dat(table6[i]); delay(1); } break; } case 7: { uchar table7[] = "open"; write_com(0x80+40);//让字符在第二行显示 for (i=0;i<4;i++) { write_dat(table7[i]); delay(1); } break; } case 8: { uchar table8[] = "press g"; write_com(0x80+40);//让字符在第二行显示 delay(5); for (i=0;i<7;i++) { write_dat(table8[i]); delay(1); } break; } case 9: { write_com(0x01); delay(5); uchar table9[] = "wrong"; for (i=0;i<5;i++) { write_dat(table9[i]); delay(1); } break; } case 10: { write_com(0x01); delay(5); uchar table10[] = "change code"; for (i=0;i<11;i++) { write_dat(table10[i]); delay(1); } break; } case 11: { write_com(0x01); delay(5); uchar table11[] = "input again"; for (i=0;i<11;i++) { write_dat(table11[i]); delay(1); } break; } case 12: { write_com(0x01); delay(5); uchar table12[] = "ok"; for (i=0;i<2;i++) { write_dat(table12[i]); delay(1); } break; } case 13: { write_com(0x01); delay(5); uchar table13[] = "modify password"; for (i=0;i<15;i++) { write_dat(table13[i]); delay(1); } break; } case 14: { write_com(0x01); delay(5); uchar table14[] = "fail"; for (i=0;i<4;i++) { write_dat(table14[i]); delay(1); } break; } default : break; } } unsigned char read_keyboard() { static unsigned char key_state = 0, key_value, key_line; unsigned char key_return = No_key,i; switch (key_state) { case 0: key_line = 0b00010000; for (i=1; i<=4; i++) // 扫描键盘 { PORTB = ~key_line; PORTB = ~key_line; key_value = Key_mask & PINB; if (key_value == Key_mask) key_line <<= 1; // 没有按键,继续扫描 else { key_state++; // 有按键,停止扫描 break; } } break; case 1: if (key_value == (Key_mask & PINB)) // 消抖处理,键盘编码,返回编码值 { switch (key_line | key_value) { case 0b00011110: key_return = K1_1; break; case 0b00101110: key_return = K1_2; break; case 0b01001110: key_return = K1_3; break; case 0b10001110: key_return = K1_4; break; case 0b00011101: key_return = K2_1; break; case 0b00101101: key_return = K2_2; break; case 0b01001101: key_return = K2_3; break; case 0b10001101: key_return = K2_4; break; case 0b00011011: key_return = K3_1; break; case 0b00101011: key_return = K3_2; break; case 0b01001011: key_return = K3_3; break; case 0b10001011: key_return = K3_4; break; case 0b00010111: key_return = K4_1; break; case 0b00100111: key_return = K4_2; break; case 0b01000111: key_return = K4_3; break; case 0b10000111: key_return = K4_4; break; } key_state++; // 转入等待按键释放状态} else key_state--; // 两次行电平不同返回状态0,(消抖处理) break; case 2: // 等待按键释放状态 PORTB = 0b00001111; // 列线全部输出低电平 PORTB = 0b00001111; // 重复送一次 if ( (Key_mask & PINB) == Key_mask) key_state=0; // 行线全部为高电平返回状态0 break; } return key_return; } void keyboard_states() { unsigned char i, j=0; if(key_temp=='s')// ----关锁 { number = 0; error=0; PORTD = 0x00; animation_state=0; } if(animation_state==0)// -----显示 //只有当用户先按"*",再按"#"键时,密码锁才能进入工作状态。 { if(key_temp=='*') { show(2); animation_state++; } if(key_temp!='*') { show(3); show(4); delay(5); } } else if(animation_state==1)// -------先按* 后按# 然后才能解锁 { if(key_temp=='#') { show(5); animation_state++; } } else if(animation_state==2)// ------输入六位密码(除了#和c以外的键){ if(key_temp=='#') animation_state++; else if(key_temp=='c') { number=0; show(5); } else { if(number==0)//输入密码先清屏,然后输入密码(小于6位) { write_com(0x01); delay(5); } write_com(0x0c); delay(5); write_com(0x06); delay(5); write_com(0x80+number); delay(5); uchar table1[] = "*"; for (i=0;i<1;i++) { write_dat(table1[i]); delay(1); } code_number[number]=key_temp; number++; } } else if(animation_state==3) { if(number==6) { for(i=0;i<6;i++) { if(code_number[i]==password[i]) //判断密码正误 j++;