1602全过程C语言编程_第1页
1602全过程C语言编程_第2页
1602全过程C语言编程_第3页
1602全过程C语言编程_第4页
免费预览已结束,剩余63页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

51单片机系列ー16021602字符液晶在实际的产品中运用的也比较多了,前几天留意了一下,发现宿舍门前的自动售水机就是采用的1602液晶进行显示的。而且对于单片机的学习而言,掌握1602的用法是每ー个学习者必然要经历的过程。在此,我将使用1602过程中遇到的问题以及感受记录下来,希望能够给初学者带来一点指导,少走ー点弯路。所谓1602是指显示的内容为16*2,即可以显示两行,每行16个字符。目前市面上字符液晶绝大多数是基于HD44780液晶芯片的,控制原理是完全相同的,因此基于HD44780写的控制程序可以很方便地应用于市面上大部分的字符型液晶。1602液晶的正面(绿色1602液晶的正面(绿色背光,黑色字体)1602液晶背面(绿色背光,黑色字体)1602液晶背面(绿色背光,黑色字体)背光白色字体字符型LCD1602通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线VCC(15脚)和地线GND(16脚),其控制原理与14脚的LCD完全一样,引脚定义如ド表所示:引脚号引脚g电平输入,输出作用1Uss电源地2Ucc电源(+5U)3Uee对比洞整电压4RS0/1输入然疑5R/W0/1输入即向LCD写入指令或数据“从LCDi矣取信息6E1,1—0输入便能信号,1吋渉取信息、・1ー。(下降沿)舲指令7DBO0/1输入/输出数据总线line。(最低位)8DB10/1输入,输出数据总线linel9DB20/1输入/治出数据总线1ine210DB30/1箱!入,播出数据总线Une311DB40/1输入ノ输出数据总线line412DB50/1输入,输出数据总线lines13DB60/1输入/输出数据总线line614DB70/1输入ノ输出数据总线】ine7(最髙位)15A♦Ucc"D背光电源正极16K接地"D背光电源负极HD44780内置了DDRAM、CGROM和CGRAM。DDRAM就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,其地址和屏幕的对应关系如卜.表:显示位置1234567 40DDRAM地址第一行0OH01H02H03H04H05H06H 27H第二行40H41H42H43H115H46H 67H也就是说想要在LCD1602屏幕的第一行第一列显示ー个"A"字,就要向DDRAM的OOH地址写入“パ字的代码就行了。但具体的写入是要按LCD模块的指令格式来进行的,后面我会说到的。那么一行可有40个地址呀?是的,在1602中我们就用前16个就行了。第二行也ー样用前16个地址。对应如下:DDRAM地址与显示位置的对应关系f )iOOH01H02H03H04H05H06H07H08H09HOAHOBHOCHODHOEHOFHj:40H41H42H43H44H45H46H47H48H49H4AH4BH4cHUDH"EH4FHiI J(事实上我们往DDRAM里的OOH地址处送ー个数据,譬如0x31(数字1的代码)并不能显示1出来。这是ー个令初学者很容易出错的地方,原因就是如果你要想在DDRAM的00H地址处显示数据,则必须将00H加上80H,即80H,若要在DDRAM的01H处显示数据,则必须将01H加上80H即81H。依次类推。大家看一下控制指令的的8条:DDRAM地址的设定,即可以明白是怎么样的一回事了)1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,如下表所示,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每ー个字符都有一个固定的代码,比如大写的英文字母“A”的代码是01000001B(41H),显示时模块把地址41H中的点阵字符图形显示出来,我们就能看到字母“A”

■■CGRO!中字符码与字字符字模关系对奥表■ 〇(2000010010001101000101011001111000100110101011H00110111101111oxkOOOCCGRAN(1)00P、戸ーyW瞑PoxxOOOl⑵i!1旧@ヨ回oIアチ心后q»x*x0010(3)"IZIBIRIblrl「四加"e.xx<00:1(4)1#|3|C|S|c|s|Z5M&mxxxOIOC0*|4|D|T|d|tゝセ叵GoxkOIOI<>>15i|5|ElU|e|u|♦币ナロ后・・MXxxOl10&|6|F|U|f|u|引力に目ゆ,)000(0111*331§酮向ア芹冈ラ目«x*<W:(1)1(|8H|X|h|x|イり津リGXMJUU1001(2)|>l9IIIVIi|y|司ケリ吁HoomlO^O(3)J*|5|J|Z|J|Z|エロ同レ5xxxxlOHH)1+ISIKIC|k|(|オけ旧口声万MXXXllOC(S)/ぐL用1111向ンGワ年円mxmIIOI(6)JEHHIIlnlHュ国へ麻*■(7)|-l>INrinH回セIホド肝JOUU1111(8)Jノl?IOl_lo*l«y|U|?|B[o1上表中的字符代码与我们PC中的字符代码是基本一致的。因此我们在向DDRAM写C51字符代码程序时甚至可以直接用P1='A'这样的方法。PC在编译时就把“A”先转为41H代码了。字符代码OxOO-OxOF为用户自定义的字符图形RAM(对于5X8点阵的字符,可以存放8组,5X10点阵的字符,存放4组),就是CGRAM了。后面我会详细说的。0x2〇〜0x7F为标准的ASCII码,OxA〇〜OxFF为日文字符和希腊文字符,其余字符码(0x1〇〜0x1F及0x8〇〜0x9F)没有定义。那么如何对DDRAM的内容和地址进行具体操作呢,ド面先说说HD44780的指令集及其设置说明,请浏览该指令集,并找出对DDRAM的内容和地址进行操作的指令。共11条指令:

1.清屏指令指令功能指令编码齬/HSRSR/VDB7DB6DBSDBUDB3DB2DB1DBO清屏0•电00000011.611功能:<1>清除液晶显示器,即将DDRAM的内容全部填入"空白"的ASCII码20H;<2>光标归位,即将光标撤冋液晶显示屏的左上方;<3>将地址计数器(AC)的值设为〇。2.光标归位指令1指令功能指令编码执行时间ノRSRSR/WD87DB6DBSDB"DB3DB2OB1DBD光标归位000000001X1.611功能:把光标撤冋到显示器的左上方;<2>把地址计数器(AC)的值设置为〇;<3>保持DDRAM的内容不变.进入模式设置指令指令功能指令编码牖/USRSR/WDB7DB6DBSDBHDB3DB2DB1DBO逬入模式设置00000001I/DSiiO功能:设定每次定入1位数据后光标的移位方向,并且设定每次写入的ー个字符是否移动。参数设定的情况如ド所示:位名设置I/D〇=写入新数据后光标左移1=写入新数据后光标右移S0=写入新数据后显示屏不移动1=写入新数据后显示屏整体右移1个字

.显示开关控制指令指令功能指令编码駆/USRSR/WDB7DB6DBSDB"DB3DB2DB1DBO显示开关控制0000001DCB功能:控制显示器开/关、光标显示/关闭以及光标是否闪烁。参数设定的情况如下:位名设置D0=显示功能关1=显示功能开C0=无光标1=有光标B0=光标闪烁1=光标不闪烁.设定显示屏或光标移动方向指令指令功能指令编码牆/USRSR/WDB7DB6DBSDB"DB3DB2DB1DBO聯齬關000001S/CR/LXX功能:使光标移位或使整个显示屏幕移位。参数设定的情况如ド:S/CR/L设定情况00光标左移1格,且AC值减101光标右移1格,且AC值加110显示器上字符全部左移一格,但光标不动11显示器上字符全部右移一格,但光标不动.功能设定指令指令功能指令编码办行时向/USRSR/VDB7DB6DBSDB4DB3DB2DB1DBO功能设定0■001DLNFXx40功能:设定数据总线位数、显示的行数及字型。参数设定的情况如下:

位名设置DL0=数据总线为4位1=数据总线为8位N0=显示1行1=显示2行F0=5x7点阵/每字符1=5x10点阵/每字符.设定CGRAM地址指令指令功能指令编码嬌/USRSR/WDB7086DB5DB4DB3DB2DB1DB0设定CGRAM地址0001CGRAM的地址(6位)40功能:设定下ー个要存入数据的CGRAM的地址。.设定DDRAM地址指令I指令功能指令编码豊行时间/USRSR/WDB7DB6DBSDB4DB3DB2DB1DB0设定DDRAM地址001CGRAM的地址(7位)40功能:设定ドー个要存入数据的CGRAM的地址。(注意这里我们送地址的时候应该是0x80+Address,这也是前面说到写地址命令的时候要加上0x80的原因).读取忙信号或AC地址指令指令功能指令编码时恂/USRSR/WDB7DB6DB5DB4DB3DB2DB1DB0族取忙对或AC先灌号01FBAC内容(7位)40功能:<1>读取忙碌信号BF的内容,BF=1表示液晶显示器忙,暂时无法接收单片机送来的数据或指令;当BF=O时,液晶显示器可以接收单片机送来的数据或指令;<2>读取地址计数器(AC)的内容。!〇.数据写入DDRAM或CGRAM指令一览指令功能指令编码时拘/USRSR/WDB70B6DBSDB4DB3DB2。“DBO数据写入到DDRAM或CGRAM10要写人的数据07-00H0功能:<1>将字符码写入DDRAM,以使液晶显示屏显示出相对应的字符;<2>将使用者自己设计的图形存入CGRAM。11.从CGRAM或DDRAM读出数据的指令一览指令功能指令编码嬌/usRSR/WDBZDB6DBSDB8DB3DB2DH1DBO从CGRAM或DDRAMi笑出数据,11要读岀的数据07-0040功能:读取DDRAM或CGRAM中的内容。基本操作时序:读状态 输入:RS=L,RW=H,E=H输出:DB〇〜DB7=状态字写指令 输入:RS=L,RW=L,E=下降沿脉冲,DB〇〜DB7=指令码输出:无读数据 输入:RS=H,RW=H,E=H输出:DB〇〜DB7=数据写数据 输入:RS=H,RW=L,E=下降沿脉冲,DB〇〜DB7=数据输出:无呵呵,看到这么多的控制指令希望你没有头晕。其实这么多的指令刚开始的时候没有必要全部掌握,随着学习的深入可以再尝试去用更复杂的控制指令。卜.面让我们ー起驱动1602的液晶吧。卜,面是我的液晶的连接图,用的是那种蓝底白字的液晶,其实蓝底白字和那种绿底黑字的液晶唯一的区别就是颜色的问题,至于用哪种液晶,就看各位自己的喜好咯。这就是我做测试用的最小系统,单片机是STC89c516,晶振为12M。液晶为蓝底白字的那种1602。当我们硬件连接错误,或者程序错误时就会出现ド图这种情况,就是上排显示16的白色的块(蓝底黑字的液晶则显示的是16个黑块)。ド面我们来驱动1602吧在1602的上排显示“LCD1602checkok”下排显示,'studyup”程序中没有用到忙检测,而是用的是延时函数来替代忙检测include<reg52.h> 〃包含头文件,这个嘛,就不用多说了〜〜defineuintunsignedint〃预定义一下defineucharunsignedcharsbitrs=P3A5; //1602的数据/指令选择控制线sbitrw=P3A6; 〃1602的读写控制线sbiten=P3A7; //1602的使能控制线/*P2口接1602的D0~D7,注意不要接错了顺序,我以前可在这上面吃过亏〜・/ucharcodetable[]="LCD1602checkok";〃要显示的内容1放入数组tabletucharcodetablel[]="studyup"; 〃要显示的内容2放入数组tablelvoiddelay(uintn) 〃延时函数uintx,y;for(x=n;x>0;x--)for(y=110;y>0;y--);}voidlcd_wcom(ucharcom)//1602写命令函数{rs=O; 〃选择指令寄存器rw=0; 〃选择写P2=com; 〃把命令字送入P2delay(5); 〃延时ー小会儿,让1602准备接收数据en=1J 〃使能线电平变化,命令送入1602的8位数据口en=0;)voidlcd_wdat(uchardat)//1602写数据函数{rs=1; 〃选择数据寄存器rw=0;〃选择写P2=dat; 〃把要显示的数据送入P2delay(5); 〃延时ー小会儿,让1602准备接收数据en=1J 〃使能线电平变化,数据送入1602的8位数据口en=0;)voidlcd_init() //1602初始化函数(Icd_wcom(0x38); 〃8位数据,双列,5*7字形lcd_wcom(0x0c); 〃开启显示屏,关光标,光标不闪烁Icd_wcom(0x06); 〃显示地址递增,即写ー个数据后,显示位置右移一位Icd_wcom(0x01); 〃清屏)voidmain() 〃主函数(ucharn,m=0;lcd_init(); 〃液晶初始化Icd_wcom(0x80);〃显示地址设为80H(即00H,)上排第一位for(m=0;m<16;m++) 〃将table口中的数据依次写入1602显示(lcd_wdat(table[m]);delay(200);)Icd_wcom(0x80+0x44);〃重新设定显示地址为0xc4,即卜,排第5位for(n=0;n<8;n++)〃将tablel口中的数据依次写入1602显示(Icd_wdat(table1[n]);delay(200);)while(1); 〃动态停机程序写好后烧写进单片机,现在让我们看看效果吧这就是显示的效果,你做成功了吗?ド面让我们来看看如何显示ー个自定义的字符吧我们从CGROM表上可以看到,在表的最左边是一列可以允许用户自定义的CGRAM,从上往下看着是16个,实际只有8个字节可用。它的字符码是〇〇〇〇()〇〇〇ー00000111这8个地址,表的ド面还有8个字节,但因为这个CGRAM的字符码规定〇一2位为地址,3位无效,4-7全为零。因此CGRAM的字符码只有最后三位能用也就是8个字节了。等效为0000X111,X为无效位,最后三位为000—111共8个。如果我们要想显示这8个用户自定义的字符,操作方法和显示CGROM的ー样,先设置DDRAM位置,再向DDRAM写入字符码,例如“A”就是41H。现在我们要显示CGRAM的第一个自定义字符,就向DDRAM写入000000OOB(OOH),如果要显示第8个就写入00000111(08H),简单吧!好!现在我们来看怎么向这八个自定义字符写入字模。有个设置CGRAM地址的指令大家还记得吗?赶快再找出来看看。指令功能指令编码離/USRSR/WDB7DB6DBSDB4DB3DB2DB1DHI1设定CCRAM地址0001CGRAN的地址(地)k0从这个指令可以看出指令数据的高2位已固定是01,只有后面的6位是地址数据,而这6位中的高3位就表示这八个自定义字符,最后的3位就是字模数据的八个地址了。例如第一个自定义字符的字模地址为01000000—01000111八个地址。我们向这8个字节写入字模数据,让它能显示出“。C”地址:01000000数据:00010000图小:OOOBOOOO0100000100000110OOOOOHBO0100001000001001ooooioob0100001100001000oooobooo0100010000001000oooobooo0100010100001001OOOOBOOB0100011000000110OOOOOBBO0100011100000000oooooooo下面我们写一段程序让这8个自定义字符显示出ー个心的图案:(由于上面那个显示程序已经有很详细的注释了,因此这个程序只对与上个程序不同的地方写注释)include<reg52.h>defineuintunsignedint#defineucharunsignedcharsbitrs=P3A5;sbitrw=P3A6;sbiten=P3A7;ucharcodetable[]={0x03,0x07,OxOf,Ox1f,0x1f,0x1f,0x1f,0x1f,0x18,0x1E,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x07,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x10,0x18,0x1c,0x1E,0x1E,0x1E,0x1E,0x1E,OxOf,0x07,0x03,0x01,0x00,0x00,0x00,0x00,0x1f,0x1f,0x1f,0x1f,0x1f,0x0f,0x07,0x01,0x1f,0x1f,0x1f,0x1f,0x1f,0x1c,0x18,0x00,0x1c,0x18,0x10,0x00,0x00,0x00,0x00,0x00};〃心图案/*ucharcodetable1[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00}"/字符℃*/voiddelay(uintn)(uintx,y;for(x=n;x>0;x--)for(y=110;y>0;y--);}voidlcd_wcom(ucharcom)(rs=O;rw=0;R2=com;delay(5);en=1;en=0;)voidlcd_wdat(uchardat){rs=1;rw=0;P2=dat;delay(5);en=1;en=0;)voidlcd_init()|Icd_wcom(0x38);lcd_wcom(0x0c);Icd_wcom(0x06);Icd_wcom(0x01);)voidmain()(charm=0;lcd_init();Icd_wcom(0x40); 〃设定CGRAM地址for(m=0;m<64;m+) 〃将心型代码写入CGRAM中Icd_wdat(table[m]);Icd_wcom(0x85);〃设定上排的显示位置Icd_wcom(0x85);〃设定上排的显示位置for(m=for(m=0;m<4;m++)〃显示心型图案的上半部分lcd_wdat(m);lcd_wdat(m);Icd_wcom(0xc5);〃将显示坐标转移到Icd_wcom(0xc5);〃将显示坐标转移到卜.排和上排相对应的地方for(m=4;m<8;m++)//显示心型图案的for(m=4;m<8;m++)//显示心型图案的ド半部分lcd_wdat(m);)lcd_wdat(m);)while(1);让我们ー起来看看显示的效果吧〜〜在绿底黑字液晶模块和蓝底白字液晶模块上分别显示的效果。在绿底黑字液晶模块和蓝底白字液晶模块上分别显示的效果。ド面再为大家展示几种可能出现的问题1;通电之后,程序也烧写进去了,但是1602就是不显示,只显示ー排黑块(一般都是在上排8个小黑块,记得刚开始用1602液晶的时候,被这个整怕了〜〜),怎么样,你郁闷了吧,其实出现这种问题的原因无非以下几种:硬件连线上的错误,这种错误一般用万用表仔细检查后很容易找出来。第二种情况就是硬件连接上是正确的,那么此时出问题最大的就是程序上了,如果你用的是忙检测,看ート,忙检测函数写对了没,如果用的是延时函数,那么看看延时的时间是否够长。再就是看看时序图,这点很重要的哦。如果硬件和软件都没有错,那么就要考虑1602是否坏了,但是出现这种情况的几率很小,如果遇到这种情况,你可以考虑去买彩票了〜〜ド面这种情况你遇到过吗?我遇到过了的,搞得我很是郁闷〜〜我做的实验是要液晶显示ABC这三个字母,并且开光标,光标闪烁。大家可以在第ー排的最后几位看到ABC和光标都已经显示出来了。但是为什么其它位会显示这么多8呢?嘿嘿〜〜郁闷吧。出现这种情况的原因就是在初始化液晶的时候,要把清屏指令放在最后面,否则就会出现上图这种情况。怎么样,第一次听说吧〜不过,我不知道其它的液晶是否也有这个问题出现,至少我用的这块就有这种情况,但是我的另ー个液晶则没有这种情况出现,不管是在ー开始就清屏还是最后清屏。大家注意ド就可以了,万一出现了这种情况,就会处理了—上面这张图是用1602作为显示的温度电子钟〜〜上面的年月日三个字就是用自定义字符的方法显示的。呵呵,怎么样〜〜到此1602的驱动基本上结束了,剩下的就靠大家自己去发挥了。*LCD1602.pdf(2010-02-0516:03:05,Size:579KB,Downloads:23)tiankai(2010-2-0516:31:51)晨辉教你轻松学51 按键篇对于・个由单片机为核心构成的系统而言。输入通道是相当重要的。可以看到儿乎每•样基于单片机的产品都有人机交互的部分。如各种仪器设备上的各种按钮和开关,以及我们手机上的键盘,MP3上的按键等等。最常见的输入部分,莫非就是按键了。对于大多数初学者而言,编写・个好的按键程序是•件颇为头疼的事情。于是乎在网上乱搜ー气,程序倒是找到了不少,但是看了半天依然是不明白。或者在某某论坛上面发帖“跪求xx按健程序,大虾帮忙…..”如果你偶然间进了这个论坛,乂偶然看到了这个帖ア,而且恰好你对按键程序的写法也不是很淸楚,那么我希望你能够静静的看完这个帖子。如果你觉得对你很有帮助,那么我希望你能够在以后的口子中能够坚持到这个论坛来,•起交流学习,分享自己学习过程中的喜悦或拧ー起探讨棘手的问题,这是我写这个帖子的最大的初衷了。〇K.不能再说了,再说就变成水帖了。那么我们开始吧。按键的种类很多。不过原理基本相似。下面我们以ー种轻触开关为例讲解按键程序的写法。这种轻触开关大家不陌生吧"_A一般情况下,按键与单片机的连接如下面这幅图所示。(图中电阻值•般去4.7k~10k之间,对于内部端口有上拉电阻的単片机则可省略此电阻)单片机对于按键的按下与否则是通过检测相应引脚上的电平来实现的。对于上图而言,当P17引脚上面的电平为低时,则友示按键已经按下。反之,则发明按键没有按下。我们在程序中只要检测到了P17引脚上面的电平为低了,就可以判断按键按下。呵呵,简单吧。等会,您先别乐呵,话还没说完呢。下面我们来看看,当按键按下时,P17引脚上面的波形是怎么变化的。上图是ー个理想波形图,当按键按下时,P17U的电平马上被拉低到0V了。当然理想的东西都是不现实的。所以我们还是看看现实的波形图吧。看出什么区别来了没。呵呵,只要你不是傻子我相信都能看出其中的区别。由于按键的机械特性。当按键闭合时,并不能马上保存良好的接触,而是来回弹跳。这个时间很短,我们的手根本感觉不出来。但是对于一秒钟执行百万条指令的单片机而言,这个时间是相当的长了。那么在这段抖动的时间内,单片机可能读到多次高低电平的变化。如果不加任何处理的话,就会认为已经按下,或者松开很多次了。而事实上,我们的手一直按在按键上,并没有重复按动很多次。要想能够正确的判断按键是否按下就要避开这段抖动的时间。根据・般按键的机械特点,以及按键的新旧程度等而言,这段抖动的时间•般在5Ms~20Ms之间。看到这里你明白了该如何做了吧。看看下面的这个流程图,你应该不陌生吧。这个流程是好多教科书上的做法。可惜,误导了好多人。为什么呢。因为它根本就没有考虑实际情况。我们根据这幅流程图来写它的代码看看。unsignedcharv_ReadKey_f(void)(unsignedcharKeyPress;if(P17==0)(Delay(20); 〃延时20Mslf(P17==0)While(!P17);〃等待释放elseKeyPress=0;)}这样・个程序,相信对很多初学者而言都不陌生。因为好多书上基本都是这样的・个流程和写法。可是当有一天,我们想做ー个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。为什么呢。原因就在这个键盘扫描函数。平常没有按键按下还好。•旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20Ms函数)然后,又霸占CPU(就是哪个死死等在那里的while(P17):语句)直到按键释放。对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。那么到底按键扫描函数改如何写呢……所谓众里寻她千百度,蓦然回首,那人却在灯火阑珊处。如果我们把CPU延时的那20Ms拿出来去做其它事情,那么不就充分利用CPU的时间了吗。而一般情况下我们只要前沿去抖动就可以了。也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。当然这主要和应用的场合有关。•个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。下面以四个独立按键的处理程序为例来讲解(支持单击和连发)#include"regx52.hMsbitKeyOne=P1A0;sbitKeyTwo=P1A1;sbitKeyThree=P1A2;sbitKeyFour=P1A3;defineuintl6unsignedintdefineuint8unsignedchardefineNOKEYOxffdefineKEY_WOBBLE__TIME500 〃去抖动时间(待定)#defineKEY_OVER_TIME15000 〃等待进入连击时间(待定),该常数要比正常〃按键时间要长,防止非目的性进入连击模式#defineKEY_QUICK_TIME1000〃等待按键抬起的连击时间(待定)voidv_Keylnit_f(void)(KeyOne=1; 〃按键初始化(相应端口写1)KeyTwo=1;KeyThree=1;KeyFour=1;uint8u8_ReadKey_f(void)staticuint8LastKey=NOKEY; 〃保存上一次的键值staticuint16KeyCount=0; 〃按键延时计数器staticuint16KeyOverTime=KEY_OVER_TIME;〃按键抬起时间uint8KeyTemp=NOKEY; 〃临时保存读到的键值KeyTemp=P1&OxOfJ 〃读键值if(KeyTemp==OxOf)(KeyCount=0;KeyOverTime=KEY_OVER_TIME;returnNOKEY; 〃无键按下返回NOKEY)else(if(KeyTemp==LastKey)〃是否第•次按下(if(++KeyCount==KEY_WOBBLE_TIME) 〃不是第・次按下,则判断〃抖动是否结束(returnKeyTemp; 〃去抖动结束,返回键值)else(if(KeyCount>KeyOverTime)(KeyCount=0;KeyOverTime=KEY_QUICK_TIME;)returnNOKEY;})else 〃是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较(LastKey=KeyTemp; 〃保存第一次读到的键值KeyCount=0; 〃延时计数器清零KeyOverTime=KEY_OVER_TIME;returnNOKEY;}})下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)voidmain(void)uint8KeyValue;int16Count;v_Lcdlnit_f();v_Keylnit_f();CLSLOCATE(3,1)PRINT(MKeyTest")LOCATE(6,2)SHOW_ICONwhile(1)KeyValue=u8_ReadKey_f();if(KeyValue!=NOKEY){LOCATE(1,2)if(KeyValue==OxOe)Count++;if(KeyValue==OxOd)Count--;if(KeyValue==OxOb)Count=0;if(KeyValue==0x07)Count=0;HIDE_ICONPRINTD(Count,5)LOCATE(6,2))else(//SHOW_ICON)))每次执行读键盘函数时,只是对ー些标忐进行判断,然后退出。因此能够充分的利用CPU的资源。同时可以处理连发按键。此按键扫描按键函数可以直接放在主函数中。如果感觉按键太过灵敏或者迟钝则改ー下相关消抖动的宏定义即可。此函数也可以通过中断标志位进行定时的扫描。此时,需要添加个定时标志位,并将相关消抖动的和连击时间的宏定义改小即可。然后在主程序类似下面这样写即可if(KeyTime) 〃定时扫描时间到(KeyValue=u8_ReadKey_f();)具体的工作就交给您去完成啦。看看效果:按键单击连发时候的截图至此,关于单个按键的学习就告一段落了,您是否ビ经明白了。如果您还不明白,那么把这个程序好好的看看,并画下流程图,分析分析。估计您就会恍然大悟。关键是思路要转换过来。下而我们来看看多个按键的情况吧•般情况下,如果多个按键每个都直接接在单片机的I/O上的话会占用很多的I/O资源。比较合理的・种做法是,按照行列接成矩阵的形式。按键接在每ー个的行列的相交处。这样対于m行n列的矩阵,可以接的按键总数是m*n。这里我们以常见的4*4矩阵键盘来讲解矩阵键盘的编程。上图就是矩阵键盘的・般接法。这里我们要介绍ー种快速的键盘扫描法:线反转法(或者称为行列翻转法)。具体流程如ド。首先,让单片机的行全部输出0,列全部输出1,读取列的值(假设行接P3口的高四位,列接低四位)。即P3=OxOf!此时读列的值,如果冇键按ド,则相应的列读冋来的值应该为低。譬如此时读冋来的值为OxOe;即按键列的位置已经确定。这时反过来,把行作为输入,列作为输出,即P0=OxfO;此时再读行的值,如果按键仍然被按ド,则相应的行的值应该为低,如果此时读冋来的值为OxeO,则确定了行的位置。说到这里,您应该笑了,知道了一个按键被按下的行和列的位置,那么就可以肯定确定它的位置了。我们把读回来的行值和列值进行或运算。即OxeO|Ox0e即Oxee。那么Oxee就是我们按ド的按键的键值了。怎么样。只需几步就可以判断所有的键值,简单吧。下面再结合一个例子具体看看。/******************************************此模块所需相关支持库 ・#include"regx52.h"defineuint8unsignedchardefineuintl6unsignedint与硬件连接相关的定义及宏定义和操作宏・*****************************************/defineKEYBOARDP3 〃键盘连接到单片机上的端口位置defineREAD_ROW_ENLABLEKEYBOARD=OxOf; 〃读端口之前先把相应U置位(由基本51单片机特性决定的)defineREAD_COL_ENLABLEKEYBOARD=OxfO; 〃根据实际硬件连接情况修改,模块内相关的宏定义及常数宏 ,******************************************/defineNOKEYOxff 〃定义无键按下时的返回值defineDELAY_COUNT2 〃消抖时间常数此模块所需的全局或者外部变量 ・*****************************************/bitbdataStartScan=0;〃此变量需放在定时中断中置位.按键扫描函数,按下去后经去抖,确定按下・则返回键值。~15:无键按下则返回Oxff;・此函数需要定时器的支持(去抖….) *uint8u8_KeyBoardScan_f()(staticuint8DelayCount=0;uint8KeyValueRow=0;uint8KeyValueCol=0;uint8KeyValue=0;if(StartScan) 〃开始扫描,StartScan在定时中断中置位(StartScan=0; 〃清除开始扫描标忐位,避免多次重复执行扫描程序〃读入按键状态前先向相应端口写1(由基本51单片机硬件结构决定)READROWENLABLEif((KEYBOARD&OxOf)!=OxOf)〃判断是否有键按下DelayCount++;if(DelayCount<=DELAY_COUNT) 〃有键按下则判断延时去抖的时间是否达到(returnNOKEY;)else 〃消除了抖动(if((KEYBOARD&OxOf)!=OxOf) 〃再次判断是否按键真的按下{DelayCount=0; 〃确定按下后,延时去抖计时器清〇KeyValueRow=KEYBOARD&OxOf;〃取得彳了码〃准备读列,先向相应端口写1(由基本51单片机硬件结构决定)READ_COL_ENLABLEif((KEYBOARD&OxfO)!=OxfO)〃反转,读列码(KeyValueCol=KEYBOARD&OxfO;〃取得列码〃合并取得的行码和列码,即是相应按键的键值switch(KeyValueCol|KeyValueRow)(case0x77:KeyValue=0;break;case0xb7:KeyValue=1;break;case0xd7:KeyValue=2;break;case0xe7:KeyValue=3;break;case0x7b:KeyValue=4;break;caseOxbb:KeyValue=5;break;caseOxdb:KeyValue=6;break;caseOxeb:KeyValue='7;break;case0x7d:KeyValue=8;break;caseOxbd:KeyValue=9;break;caseOxdd:KeyValue=10;break;caseOxed:KeyValue=11;break;case0x7e:KeyValue=12;break;caseOxbe:KeyValue=13;break;caseOxde:KeyValue=14;break;caseOxee:KeyValue=15;break;default:returnNOKEY;returnKeyValue;}else{DelayCount=0;returnNOKEY;))else{DelayCount=0;returnNOKEY;})}else(DelayCount=0;returnNOKEY;)))voidv_T0_lsr_f(void)interruptINTERRUPT_TIMER2_OVERFLOWStartScan・模块调试〃主函数仅作演示用,主函数除按键扫描外的函数并没在这里给出voidv_lnit_T2_f(void)T2CON=0x04;T2MOD=0x00;TOC\o"1-5"\h\zTH2 =0xd8 ;RCAP2H=0xd8;TL2 =OxfO ;RCAP2L=OxfO;ET2 =1;TR2 =1;voidmain(void)(uint8readkey=0;v_lnit_T2,f();v_Lcdlnit_f();LOCATE(1,1)PRINT(H4*4KeyBoardTest*1)EA=1;LOCATE(3,2)while(1)(SHOW_ICONreadkey=u8_KeyBoardScan_f()if)readkey!=NOKEY)(PRINTN)readkey,2)LOCATE)3,2)continue;}elsecontinue;})}呵呵,按键扫描程序己经注释的很详细了。我就不多费嘴后了。如果有不清楚的地方,欢迎跟帖讨论。下面是按键测试的截图我的门己搭建的实验板OK,Enioyit!自此按键检测告・段落。下次如果再讲按键。将会讨论另外种按键的写法:基于状态机的按键程序设计。欢迎讨论。tiankai(2010-2-0516:37:53)晨辉教你轻松学51 按键篇对于ー个由单片机为核心构成的系统而言。输入通道是相当重:要的。可以看到几乎每ー样基于单片机的产品都有人机交互的部分。如各种仪器设备上的各种按钮和开关,以及我们手机上的键盘,MP3上的按键等等。最常见的输入部分,莫非就是按键了。对于大多数初学者而言,编写ー个好的按键程序是一件颇为头疼的事情。于是乎在网上乱搜・气,程序倒是找到了不少,但是看了半天依然是不明白。或者在某某论坛上面发帖“跪求XX按键程序,大虾帮忙….:’如果你偶然间进了这个论坛,又偶然看到了这个帖子,而且恰好你对按键程序的写法也不是很清楚,那么我希望你能够静静的看完这个帖子。如果你觉得对你很有帮助,那么我希望你能够在以后的日子中能够坚持到这个论坛来,ー起交流学习,分享自己学习过程中的喜悦或者・起探讨棘手的问题,这是我写这个帖子的最大的初衷了。〇K,不能再说了,再说就变成水帖了。那么我们开始吧。按键的种类很多。不过原理基本相似。下面我们以种轻触开关为例讲解按键程序的写法。

这种轻触开关大家不陌生吧"_A一一般情况下,按键与单片机的连接如下面这幅图所示。U1U1(图中电阻值一般去4.7k~10k之间,对于内部端口有上拉电阻的单片机则可省略此电阻)单片机对于按键的按ド与否则是通过检测相应引脚上的电平来实现的。对于上图而言,当P17引脚上面的电平为低时,则表示按键已经按下。反之,则表明按键没有按下。我们在程序中只要检测到了P17引脚I:面的电平为低了,就可以判断按键按ド。呵呵,简单吧。等会,您先别乐呵,话还没说完呢。ド面我们来看看,当按键按下时,P17引脚I:面的波形是怎么变化的。

上图是ー个理想波形图,当按键按下时,P17U的电平马上被拉低到0V了。当然理想的东西都是不现实的。所以我们还是看看现实的波形图吧。看出什么区别来了没。呵呵,只耍你不是傻子我相信都能看出其中的区别。由丁•按键的机械特性。当按键闭合时,并不能马上保存良好的接触,而是来冋弹跳。这个时间很短,我们的手根本感觉不出来。但是对于一・秒钟执行百万条指令的单片机而言,这个时间是相当的长了。那么在这段抖动的时间内,单片机可能读到多次高低电平的变化。如果不

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论