单片机课程设计报告电子密码锁HEN system office room 【HEN16H-HENS2AHENS8Q8-HENH1688】山东交通学院单片机原理与应用课程设计院(部):轨道交通学院班级:自动化121学生姓名:学号:指导教师:时间:—课程设计任务书题目电子密码锁设计系 (部) 轨道交通学院专业班级自动化121学生姓名学号06 月 01 日至 06 月 12 日共 2 周指导教师(签字)系主任(签字)年月日目录3.总体设计 (2)4密码比较模块 (6) (6) (8) (9)附录 (10)摘要设计运用了ATMEL公司的AT89S52芯片系统,将微处理器、总线、蜂鸣器、矩阵键盘、存储器和I/O口等硬件集中一块电路板上,通过读取键盘输入的数据(密码)并储存到ATMEL912 24C08存储器中,然后判断之后键盘输入的数据与已存储的数据是否相同来决定打开密码箱或锁键盘或报警。
在keil4软件中编程,系统可实现6位密码的处理,并通过控制步进电机控制密码箱门的电子锁,同时还可以修改改密码。
利用单片机系统制作的密码箱安全性能更高,更易操作且体积小。
关键词:单片机、密码锁、修改密码1.设计要求本实验将实现六位数的电子密码锁。
要求使用4X4 行列式键盘作为输入,并用LCD 实时显示。
具体要求如下:1. 开机时LCD显示“welcome to use”,初始化密码为“123456”,密码可以更改。
2. 按下“10”,开始则显示“Enter Please:”。
3. 随时可以输入数值,并在LCD上实时显示‘*’。
当键入数值时,为了保密按从左到右依次显示‘*’,可键入值为0~9。
4. 按下“13”键,则表示确定键按下,进行密码对比。
如相符则在LCD第一行显示“Open the door!”,同时指示灯亮起并且步进电机旋转一定的角度;如不符,则LCD第一行显示“Wrong password!”,并且蜂鸣器同时提示一下。
如果密码连续三次错误则蜂鸣器连续响5下,并且持续5秒不能进行任何操作 5.在开锁状态下按下“12”键,进入修改密码状态,LCD同时提示“Enter new code!”。
为删除按键,出入之后可以进行删除。
按键为关闭按键,只有在打开状态下才可以关闭,按下之后LCD显示“Close the door!”。
2.功能概述此设计分为四个功能模块。
第一模块:按键输入模块,用于密码的输入以及其他的密码操作按键。
第二模块:LCD模块,是与使用者交流的界面,用于显示各种状态下的内容。
第三模块:步进电机模块,用于控制密码锁的打开与关闭。
第四模块:24C08模块,用于储存输入的密码并读出来。
3.总体设计本次设计作品的主要构成部分包括80C51单片机、LCD1602、24C08、矩阵按键、LED 等、蜂鸣器。
如图1总体仿真图,图2实物图。
图1 总体电路图图2 密码锁实物图4.硬件设计矩阵按键设计如图3所示矩阵按键由P1口控制,了加强密码的保密性,采用一个4×4的矩阵式键盘可以任意设置用户密码(1-16位长度),从而提高了密码的保密性,同时也能减少与单片机接口时所占用的I/O口线的数目,节省了单片机的宝贵资源,在按键比较多的时候,通常采用这种方法。
每一行与每一列的交叉处不相同,而是通过一个按键来连通,利用这种行列式矩阵结构只需要N根行线与M根列线,即可组成具有N × M 个按键的矩阵键盘。
在这种行列式矩阵键盘编码的单片机系统中,键盘处理程序首先执行等待按键并确认有无按键按下的程序段。
当确认有按键按下后,下一步就是要识别哪一个按键被按下。
对键的识别方法通常有两种:一种是通用的组行扫描查询法;另一种是速度较快的线反转法。
此系统中,我们采用线反转法。
首先辨别键盘中有无按键被按下,在单片机I/O口向键盘送全扫描字,然后读入行线状态来判断。
具体方法是:向行线输出全扫描字00H,把全部列线置成低电平,然后将列线的电平状态读入累加器A中。
如果有按键被按下,总会有一根行线电瓶被拉至低电平从而使行线不全为1。
判断键盘中哪一个按键被按下通常是通过将列线逐列至低电平后,检查行输入状态来实现的。
方法是:依次给列线送低电平,然后检查所有行线状态,如果全为1,则所按下的按键不在此列;如果不全为1,则所按下的按键必在此列,而且是在与零电平行线相交的交点上的那个按键。
图3 矩阵按键电路LCD显示设计显示电路是为了给使用者以提示而设置的,显示部分由液晶显示器LCD1602(如图4所示)取代普通的数码管完成。
P0口作为数据传输口、、分别连接RS、RW、E。
开锁时,按下键盘上的开锁按键后,利用键盘上的数字键0-9输入密码,每按下一个数字键后在显示器上显示一个*,输入多少位就显示多少个*。
当密码输入完成时,如果输入的密码正确的话, LCD显示“open the dore!”。
如果密码不正确,LCD显示屏会显示“Wrong password!”,同时红灯亮起。
通过LCD显示屏,可以清楚地判断出密码锁所处的状态。
图4 LCD显示屏步进电机模块设计步进电机是一种将电脉冲转化为角位移的执行机构。
通俗一点讲:当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度步进角。
您可以通过控制脉冲个来控制角位移量,从而达到准确定位的目的;同时您可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。
步进电机28BYJ48型四相八拍电机,电压为DC5V—DC12V。
当对步进电机施加一系列连续不断的控制脉冲时,它可以连续不断地转动。
每一个脉冲信号对应步进电机的某一相或两相绕组的通电状态改变一次,也就对应转子转过一定的角度(一个步距角)。
当通电状态的改变完成一个循环时,转子转过一个齿距。
四相步进电机可以在不同的通电方式下运行,常见的通电方式有单(单相绕组通电)四拍(A-B-C-D-A...),双(双相绕组通电)四拍(AB-BC-?CD-DA-AB-...),八拍(A-AB-B-BC-C-CD-D-DA-A...)。
如图4所示。
由ULN2003来控制,ULN2003的1、2、3、4引脚分别连接、、、口。
图5 步进电机模块密码修改设计AT24C02是美国ATMEL公司的低功耗CMOS串行EEPROM,它是内含256×8位存储空间,具有工作电压宽(~)、擦写次数多(大于10000次)、写入速度快(小于10ms)等特点。
下面是它的电路图。
图5中AT24C02的1、2、3脚是三条地址线,用于确定芯片的硬件地址。
在AT89C51试验开发板上它们都接地,第8脚和第4脚分别为正、负电源。
第5脚SDA 为串行数据输入/输出,数据通过这条双向I2C 总线串行传送,在AT89C51试验开发板上和单片机的连接。
第6脚SCL 为串行时钟输入线,在AT89C51试验开发板上和单片机的连接。
SDA 和SCL 都需要和正电源间各接一个的电阻上拉。
第7脚需要接地。
通过使用24C02便可以实现对密码的储存于读取进一步实现密码锁的改密码功能。
图6 AT24C02密码比较设计该模块将输入密码字符串与设定密码字符串比较。
如果相同,执行开锁动作并将输入错误次数清零;如果不同,累计错误次数,如果是第三次输入错误,系统锁死并发出声光告警;如果小于三次,显示密码错误信息,返回密码输入环节。
逻辑框图如图6所示。
图7 密码比较流程图 5.软件设计及流程图 系统的软件设计采用汇编语言编码。
设计方法是先用文本编辑器编写源码,然后用软件Keil C51编译,如果没有错误,可连接生成.HEX 格式的文件。
如果有错误则无法连接,但可在生成的.OBJ 文件中找到代码错误的地方,便于修改。
当然也可以直接在Keil 中编码。
生成的HEX 文件是记录文本行的ASCII 文本文件,在HEX 文件中,每一行是一个HEX 记录,由十六进制数组成的机器码或者数据常量。
HEX 文件经常被用于将程序或数据传输存储到ROM 、EPROM ,大多数编程器和模拟器使用HEX 文件。
图8 单片机控制总体电路 图7为单片机控制总体电路,图8软件运行流程图。
图9 软件运行流程图 6.个人体会 通过这次课程设计,让我更加深刻了解课本知识,和以往对知识的疏忽得以补充,在设计过程中遇到一些模糊的操作和专业用语,比如说单片机定时器,以及中断的选择,通过对单片机的操作实现自己设计的功能, 在使用手册时,有的数据很难查出,但是这些问题经过这次设计,都一一得以解决,我相信单片机这本书中还有很多我为搞清楚的问题,但是这次的课程设计给我相当的基础知识,为我以后工作打下了严实的基础。
虽然这次课程是那么短暂的2周时间,我感觉到这些天我的所学胜过我这一学期所学,这次任务原则上是设计,其实就是一次大的作业,是让我对课本知识的巩固和应用,对程序的设计,修改以及调试,使我做事的耐心和仔细程度得以提高。
课程设计是培训学生运用本专业所学的理论知识和专业知识来分析解决实际问题的重要教学环节,是对三年所学知识的复习和巩固。
同样,也促使了同学们的相互探讨,相互学习。
因 此 , 我 们 必 须 认 真 、谨 慎 、踏 实、一步一步 的 完 成 设 计。
如 果 时 间 可 以 重来,我可能会认真的去学习和研究,也可能会自己独立的完成一个项目,我相信无论是谁看到自己做出的成果时心里一定会很兴奋。
模块启动验证密码判断错误次数显示错误并且锁定10秒 发出报警 系统锁死 作出相应 的动作 连续错误次数小于三次 密码正确 密码错误此次设计让我明白了一个很深刻的道理:团队精神固然很重要,但人往往还是要靠自己的努力,自己亲身去经历,这样自己的心里才会踏实,学到的东西才会更多。
参考文献[1] 马建国、孟宪元.电子设计自动化技术基础.机械工业出版社.2004.[2]姜威.实用电子系统设计基础.北京理工大学出版社.2008.[3]张靖武.单片机系统的PROTEUS设计与仿真.电子工业出版社.2007.[4] 孙福成.KEIL C项目教程.西安电子科技大学出版社.2012.[5] 张毅刚.单片机原理及接口技术.人民邮电出版社.2008.8.附录:源程序#include<>#include <>#define OP_READ 0xa1 // 器件地址以及读取操作,0xa1即为1010 0001B#define OP_WRITE 0xa0 // 器件地址以及写入操作,0xa1即为1010 0000B#define uint unsigned int#define uchar unsigned char#define KEY P1#define No_key 20#define lcddata P0sbit SDA=P3^5; //将串行数据总线SDA位定义在为引脚sbit SCL=P3^4; //将串行时钟总线SDA位定义在为引脚sbit lcden=P2^2;sbit lcdrs=P2^0;sbit lcdrw=P2^1;sbit light=P2^3;sbit light1=P2^4;sbit deng=P3^7;sbit BEEP= P3^6;uchar j,z,y,j1,j2; //h使用修改后的密码开锁标志位uchar n=0,h=0; //中间标志位用于传递信息保证密码修改过后按复位按键密码修改标志位不改变uchar aa;uchar code FFW[8]={0xf1,0xf3,0xf2,0xf6,0xf4,0xfc,0xf8,0xf9};uchar code REV[8]={0xf9,0xf8,0xfc,0xf4,0xf6,0xf2,0xf3,0xf1}; //反转编码uchar code table[] ="Welcome to use!";uchar code table1[]="Open the door! ";uchar code table2[]="Enter Please: ";uchar code table3[]="Close the door!";uchar code table4[]="Wrong password!";uchar code table5[]="Enter new code!";uchar code table6[]="New code finish";uchar code key_table[16]={1,2,3,10,4,5,6,11,7,8,9,12,0,13,14,15};uchar Password[]={1,2,3,4,5,6}; //设置的初始密码uchar save[15];uchar mima[15];uchar conflag; //确认标志uchar lockflag; //键盘锁定标志uchar startflag; //开始标志uchar open; //门打开标志位uchar begain; //开始标志void delay1(uint t);void delay(uint z);void wright_com(uchar com); //写命令函数void wright_data(uchar date); //写数据函数void init(); //初始化函数void display_open(); //显示open the doorvoid display_close(); //显示close the doorvoid display_wrong();void display_newcode(); //显示输入新密码void display_codefinish(); // 显示新密码成功void delete(); //删除输入的最后一个数uchar keyscan(); //带返回值的键盘扫描程序void enter_code(uchar t); //void enter_code1(uchar t);void enter_code2(uchar t);void confirm(); //确认密码对不对,把输入的数据与密码逐一对比void confirm1();void succeed_an(); //密码正确时的响应void fail_an(); //密码失败时的响应void alarm(); //发出警报声void reset(); //复位函数void reset_save();void display_enter(); //显示输入void motor_ffw();void motor_rev();/*****************************************************函数功能:延时1ms(3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒***************************************************/void delay1ms(){uchar i,n;for(i=0;i<10;i++)for(n=0;n<33;n++);}/*****************************************************函数功能:延时若干毫秒入口参数:n***************************************************/void delaynms(uint n){uchar i;for(i=0;i<n;i++)delay1ms();}void start() // 开始位{SDA = 1; //SDA初始化为高电平“1”SCL = 1; //开始数据传送时,要求SCL为高电平“1”_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SDA = 0; //SDA的下降沿被认为是开始信号_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SCL = 0; //SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)}void stop() // 停止位{SDA = 0; //SDA初始化为低电平“0”_nSCL = 1; //结束数据传送时,要求SCL为高电平“1”_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SDA = 1; //SDA的上升沿被认为是结束信号_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SDA=0;SCL=0;}//**********从AT24Cxx读取数据********unsigned char ReadData()// 从AT24Cxx移入数据到MCU{unsigned char i;unsigned char x; //储存从AT24Cxx中读出的数据for(i = 0; i < 8; i++){SCL = 1; //SCL置为高电平x<<=1; //将x中的各二进位向左移一位x|=(unsigned char)SDA; //将SDA上的数据通过按位“或“运算存入x中SCL = 0; //在SCL的下降沿读出数据}return(x); //将读取的数据返回}//*******函数功能:向AT24Cxx的当前地址写入数据********//在调用此数据写入函数前需首先调用开始函数start(),所以SCL=0bit WriteCurrent(unsigned char y){unsigned char i;bit ack_bit; //储存应答位for(i = 0; i < 8; i++) // 循环移入8个位{SDA = (bit)(y&0x80); //通过按位“与”运算将最高位数据送到S//因为传送时高位在前,低位在后_nop_(); //等待一个机器周期SCL = 1; //在SCL的上升沿将数据写入AT24Cxx_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SCL = 0; //将SCL重新置为低电平,以在SCL线形成传送数据所需的8个脉冲y <<= 1; //将y中的各二进位向左移一位}SDA = 1; // 发送设备(主机)应在时钟脉冲的高电平期间(SCL=1)释放SDA 线,//以让SDA线转由接收设备(AT24Cxx)控制_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期SCL = 1; //根据上述规定,SCL应为高电平_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期_nop_(); //等待一个机器周期ack_bit = SDA; //接受设备(AT24Cxx)向SDA送低电平,表示已经接收到一个字节//若送高电平,表示没有接收到,传送异常SCL = 0; //SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)return ack_bit; // 返回AT24Cxx应答位}//***************向AT24Cxx中的指定地址写入数据*****************)void WriteSet(unsigned char add, unsigned char dat)// 在指定地址addr处写入数据WriteCurrent{start(); //开始数据传递WriteCurrent(OP_WRITE); //选择要操作的AT24Cxx芯片,并告知要对其写入数据WriteCurrent(add); //写入指定地址WriteCurrent(dat); //向当前地址(上面指定的地址)写入数据stop(); //停止数据传递delaynms(4); //1个字节的写入周期为1ms, 最好延时1ms以上}unsigned char ReadCurrent() //从AT24Cxx中的当前地址读取数据{unsigned char x;start(); //开始数据传递WriteCurrent(OP_READ); //选择要操作的AT24Cxx芯片,并告知要读其数据x=ReadData(); //将读取的数据存入xstop(); //停止数据传递return x; //返回读取的数据}unsigned char ReadSet(unsigned char set_addr) //从AT24Cxx中的指定地址读取数据{start(); //开始数据传递WriteCurrent(OP_WRITE); //选择要操作的AT24Cxx芯片,并告知要对其写入数据WriteCurrent(set_addr); //写入指定地址return(ReadCurrent()); //从指定地址读出数据并返回}void gaimima() //****改密码程序****{uchar temp,i;SDA=1;SCL=1;if(z==1){while(1){temp=keyscan();enter_code(temp);if(temp==13){for(i=0;i<6;i++){WriteSet(i,save[i]);delaynms(10);}for(i=0;i<6;i++){mima[i]=ReadSet(i);delaynms(10);}display_codefinish();reset_save();break;}if(temp==14){delete();}}}}void main(void){uchar temp;y=0;open=1; //open门开关标志位 1为关闭 0为打开while(1){init();if(h==1){deng=0;}while(1){begain=0;if(lockflag){temp=keyscan(); //按键期间也要进行键盘扫描if(temp!=No_key) //重新计时三秒{aa=0; //重新在定时器中计数}}else{temp=keyscan(); //反复扫描输入,等待随时输入if(temp!=No_key) //有按键按下才能进行下一步{if(temp==10&&open==1){reset();startflag=1; //开始标志位}if(startflag){if(h==0) //更改密码前的密码确认{enter_code(temp); //每扫描一次键盘就要进行一次处理保存输入的数值if(temp==13&&open==1) //按下确认键进行密码确认{confirm(); //进行确认判断if(conflag){succeed_an(); //密码正确作出相应的反应open=0;z=1;reset_save();}else{fail_an(); //密码错误作出相应的反应}}}else //更改密码后的密码确认{enter_code(temp); //每扫描一次键盘就要进行一次处理保存输入的数值if(temp==13&&open==1) //按下确认键进行密码确认{confirm1(); //进行确认判断if(conflag){succeed_an(); //密码正确作出相应的反应open=0;z=1;}else{fail_an(); //密码错误作出相应的反应}}}if(temp==14){delete();}if(temp==12&&z==1){reset();display_newcode();gaimima();h=1; // 改密码成功标志位用于以后选择密码对比}if(temp==15&&z==1){uchar r;open=1;display_close();for(r=0;r<18;r++){motor_rev(); //电机反转}}}}}if(temp==11&&begain==0&&open==1){begain=1;break;}}}}void motor_rev() //电机反转函数{uchar i;uint j;z=0;for (j=0; j<8; j++) //转1×n圈{for (i=0; i<8; i++) //一个周期转45度{P3 = REV[i]; //取数据delay1(2); //调节转速}}}void motor_ffw() //电机转动函数{uchar i;uint j;for (j=0; j<8; j++) //转1*n圈{for (i=0; i<8; i++) //一个周期转45度{P3 = FFW[i]; //取数据delay1(2); //调节转速}}}void display_enter() //显示enter{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table2[num]);}}void display_close() //显示close{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table3[num]);}}void display_open() //显示open{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table1[num]);}}void display_wrong() //显示wrong{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table4[num]);}}void display_newcode() //显示输入新密码{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table5[num]);}}void display_codefinish() //显示新密码完成{uchar num;wright_com(0x80);for(num=0;num<15;num++){wright_data(table6[num]);}}void delete() //删除最后一个{wright_com(0x80+0x40+j-1); //确定删除对象wright_data(' '); //显示空格即为删除save[--j]=0; //删除后数据清零wright_com(0x80+0x40+j); //为下次输入数据时写好位置}void reset() //复位函数{uchar num;display_enter();wright_com(0x80+0x40); //擦除屏幕上的显示for(num=0;num<15;num++){save[num]=0; //对输入的数值进行清零wright_data(' ');}wright_com(0x80+0x40);lockflag=0;conflag=0;j=0;}void reset_save(){uchar num;wright_com(0x80+0x40); //擦除屏幕上的显示for(num=0;num<15;num++){save[num]=0; //对输入的数值进行清零wright_data(' ');}wright_com(0x80+0x40);}void succeed_an() //输入密码正确进行响应的函数{uchar r;light=0;display_open();for(r=0;r<18;r++){motor_ffw(); //电机正转}delay(1000);light=1;}void fail_an() //输入密码错误进行响应的函数{uchar j,i=0;while(1){light1=0;display_wrong();for(j=3000;j>0;j--) //蜂鸣器响大约500MS{BEEP = ~BEEP;delay(1); //延时500US 发出大约1KHZ频率的响声}BEEP=1; //蜂鸣器不响delay(500);light1=1;break;}y++;if(y==3){while(1){light1=0;display_wrong();for(j=3000;j>0;j--) //蜂鸣器响大约500MS{BEEP = ~BEEP;delay(1); //延时500US 发出大约1KHZ频率的响声}BEEP=1; //蜂鸣器不响delay(500);light1=1;i++;if(i==4){break;}}lockflag=1;}}void enter_code(uchar t) //输入密码并在屏幕上显示星号{if(t>=0&&t<10){if(j==0){wright_com(0x80+0x40);wright_data('*');}else{wright_data('*');}save[j++]=t;}}void confirm() //校对密码以确定是否正确函数{uchar k;for(k=0;k<6;k++){if(Password[k]!=save[k]){break;}}if(k==6){conflag=1;}}void confirm1() //校对密码以确定是否正确函数{uchar k;for(k=0;k<6;k++){if(save[k]!=mima[k]){break;}}if(k==6){conflag=1;}}void timer0() interrupt 1{TH0=(65536-50000)/256;TL0=(65536-50000)%256;if(lockflag){y=0;aa++;light1=0;if(aa>=200){aa=0;light1=1;lockflag=0;}}}void init() //初始化{uchar num;open=1;TMOD=1;TH0=(65536-50000)/256;TL0=(65536-50000)%256;ET0=1;EA=1;TR0=1;lcdrw=0;lcden=0;wright_com(0x38);wright_com(0x0c);wright_com(0x01);wright_com(0x80);for(num=0;num<15;num++){wright_data(table[num]);delay(1);}}void wright_com(uchar com) //1602写入指令{lcdrs=0;lcddata=com;delay(1);lcden=1;delay(1);lcden=0;}void wright_data(uchar date) //1602写入数据{lcdrs=1;lcddata=date;delay(1);lcden=1;delay(1);lcden=0;}void delay1(uint t){uint k;while(t--){for(k=0; k<125; k++){ }}}void delay(uint z){uint y;for(;z>0;z--)for(y=110;y>0;y--);}uchar keyscan() //4*4按键扫描函数{uchar temp,num=No_key;//第一行KEY=0xfe;temp=KEY;temp=temp&0xf0; //读出高四位while(temp!=0xf0){delay(10);temp=KEY;temp=temp&0xf0;while(temp!=0xf0){temp=KEY;switch(temp){case 0xee:num=1;break;case 0xde:num=2;break;case 0xbe:num=3;break;case 0x7e:num=10;break;}while(temp!=0xf0) //等待松手{temp=KEY;temp=temp&0xf0;}}}//第二行KEY=0xfd;temp=KEY;temp=temp&0xf0; //读出高四位while(temp!=0xf0){delay(10);temp=KEY;temp=temp&0xf0;while(temp!=0xf0){temp=KEY;switch(temp){case 0xed:num=4;break;case 0xdd:num=5;break;case 0xbd:num=6;break;case 0x7d:num=11;break;}while(temp!=0xf0) //等待松手{temp=KEY;temp=temp&0xf0;}}}//第三行KEY=0xfb;temp=KEY;temp=temp&0xf0; //读出高四位while(temp!=0xf0){delay(10);temp=KEY;temp=temp&0xf0;while(temp!=0xf0){temp=KEY;switch(temp){case 0xeb:num=7;break;case 0xdb:num=8;break;case 0xbb:num=9;break;case 0x7b:num=12;break;}while(temp!=0xf0) //等待松手{temp=KEY;temp=temp&0xf0;}}}//第四行KEY=0xf7;temp=KEY;temp=temp&0xf0; //读出高四位while(temp!=0xf0){delay(10);temp=KEY;temp=temp&0xf0;while(temp!=0xf0){temp=KEY;switch(temp){case 0xe7:num=0;break;case 0xd7:num=13;break;case 0xb7:num=14;break;case 0x77:num=15;break;}while(temp!=0xf0) //等待松手{temp=KEY;temp=temp&0xf0;}}}return num;}。