1602液晶显示程序第一行地址需要加80H,那第二行呢?

www.lzmgtech.com
&&&&&&&& 本教材以残弈悟恩开发的51单片机为载体,记录了个人学习和工作的经验,并服务于单片机初学者、电子爱好者。限于时间和水平关系,资料中难免有过失之处,望各位大大使劲拍砖,拍累了,你们休息,我继续上路。本教材现已连载的方式免费共享于各大电子网站,供单片机新手们参考学习,可以自由下载传阅,但未经残弈悟恩允许,不得用于任何商业目的,若经发现,残弈悟恩将以愚公移山的精神追究到底。
版本:(V1.0)& &&& &&& &&& &&& &&& 制造者:残弈悟恩
我平生从来没有做出过一次偶然的发明。我的一切发明都是经过深思熟虑和严格试验的结果。  ---爱迪生(美国)
第九章& 心灵之窗&&液晶1602
9.1 系实际知应用
不系实际,无以知应用!
液晶在工程中的应用及其广泛,大到电视,小到手表,从个人到集体,再从家庭都广场,液晶的身影无处不在。毫不夸张的说,现代基本人人都至少带有一块液晶,因为基本每人都有一部手机。别看液晶表面很鲜艳,其实他背后有一个支持他的贤内助-控制器,如果没有控制器,液晶什么都不是,所以先学习好单片机,那么液晶的控制就水到渠成了。
9.2 积小流成江海
不积跬步,无以至千里;不积小流,无以成江海。
提到指针,或许大家很&过敏&,因为觉得指针难,其实指针是只&纸老虎&,说难也难,说不难也容易,只要抱着学好它的目的,还是可以把它学会、用好的。
究竟什么叫做指针?一般来将,指针是一个其数值为地址的变量(或更一般地说是一个数据对象)。正如char类型的变量用字符作为其数值,而int类型变量的数值是整数,指针变量的数值表示的是地址。
9.2.1 小试牛刀&&指针
如果你将某个指针变量命名为ptr,就可以使用如下语句:
prt = &&&&&&&&&&&& /* 把pooh的地址赋给ptr */
对于这个语句,我们称ptr&指向&pooh。ptr和&pooh的区别在与前者为一变量,而后者是一个变量。当然,ptr可以指向任何地方。如:ptr = & abc,这时ptr的值是abc的地址。
要创建一个指针变量,首先需要声明其类型。这就需要下面介绍的新运算符来帮忙了。
假如ptr指向abc,例如:ptr = & abc,这时就可以使用间接运算符&*&(也称作取值运算符)来获取abc中存放的数值(注意与二元运算符*的区别)。
val = *&&&&&&&&&&&&&&&& /* 得到ptr指向的值 */
这样就会有:val = abc。由此看出,使用地址运算符和间接运算符可以间接完成上述语句的功能,这也正是&间接运算符&名称的由来。因此就有了我们常听到的:所谓的指针就是用地址去操作变量。
9.2.2 指针的声明
那应该如何声明指针变量呢?或许大家会这样声明一个指针:pointer ptr,残弈悟恩告诉大伙,这样声明一个指针变量是不正确的。
为什么不能这样声明?因为这对于声明一个变量为指针是远远不够的,还需要说明指针所指向变量的类型。原因是不同的变量类型占用的存储空间大小不同,而有些指针操作需要知道变量类型所占用的存储空间。同时,程序也需要了解地址中存储的是哪种数据。例如,long和float两种类型的数值可能使用相同大小的存储空间,但是它们的数据存储方式完全不同。指针的声明形式如下:
int *&&&&&&&&&&&&&&&&&&&&&&&&&&&& /* abc是只需一个整数变量的指针 */
float * bcd, *&&&&&&&&&&&& /* bcd和cde是指向浮点变量的指针 */
读到这里,大伙都知道,上面的abc、bcd等都是一个定义的指针,那这个指针究竟是一个什么东西,或者说在内存中占多大的空间,那我们用sizeof测试一下(32位系统):sizeof(abc)、sizeof(bcd),它们的值都为:4。
这说明了什么?说明一个基本的数据类型(包括结构体等自定义类型)加上&*&号就构成了一个指针类型的变量,这个变量的大小是一定的,与&*&号前面的数据类型无关。&*&号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以32位系统下,不管什么样的指针类型,其大小都为4byte。当然大家可以测试一下sizeof(void*)。
9.2.3 指针与数组的藕断丝连
关于数组前面简简单单讲述了一下,不知大家发现了没有,在有些例程中,用下标法操作数组比较麻烦,若用指针来操作数组,或许能起到事半功倍的效果,至少有些时候比较方便、快捷。那么接下来我们看看这两者之间的恩恩怨怨。
这里定义一个数组:int a[5],所有人都明白定义了一个数组,其包含了5个int型的数据,可以用a[0]、a[1]等来访问数组里面的每一个元素,那么这些元素的名字就是a[0]、a[1]...吗?先看下面的示意图(如图13-1所示),再来阐述这个问题。
图13-1数组示意图
如上图所示,当定义了一个数组a时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小&元素个数)的一块内存,并把这块内存的名字命名为a。名字a一旦与这块内存匹配就不能改变。a[0]、a[1]等为a的元素,但并非元素的名字。数组的每一个元素都是没有名字的。现在看这样一个问题:为什么sizeof(a[5])的值在32位系统下位4呢?因为sizeof是关键字,不是函数。函数求值是在运行的时候,而关键字sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值,所以这里使用a[5]并不会出错。
现在回过头来继续讲解上面的数组a[5]。sizeof(&a[0])的值在32位系统下位4,这个很好理解,意思是取元素a[0]的首地址。sizeof(&a)的值在32位系统下也为4,意思当然是取数组a的首地址。
&a[0]和&a的区别
这里&a[0]和&a到底有什么区别呢?a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的地址,而后者是数组的首地址。
数组名a作为左值和右值的区别
简单而言,出现在赋值符&=&右边的就是右值,出现在赋值符&=&左边的就是左值。比如:a = b,则a为左值,b为右值。
(1)当a作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组的首地址。但注意这仅仅是一种代表。
&&& (2)a不能作为左值。当然可以将a[i]当做左值,这时就可以对其操作了。
数组与指针
&&& 需要注意,将数组和指针放到这里讲解,是为了区分它们,而不是为了将它们联系起来。请大家铭记:数组就是数组,指针就是指针。它们是完全不同的两码事!它们之间没有任何关系,只是经常穿着相似的衣服迷惑你罢了。
&&&&&&&& 1.以指针的形式访问和以下标的形式访问
在函数内部有两个定义:A. char *p = &abcdef&;&&&&&&&&& & B. char a[] = &123456&;
&& (1)以指针的形式访问和以下标的形式访问指针
例子A定义了一个指针变量p,p本身在栈上占4个byte,p里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7个byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问。比如现在需要读取字符&e&,我们有两种方式:
1)以指针的形式:*(p+4)。先取出p里存储的地址值,假设为0x0000FF00,然后加上 4个字符的偏移量,得到新的地址0x0000FF04。然后取出 0x0000FF04地址上的值。
2)以下标的形式:p[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p里存储的地址值,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了。
&& (2)以指针的形式访问和以下标的形式访问数组
例子B定义了一个数组a,a拥有7个char类型的元素,其空间大小为7。数组a本身在栈上面。对a元素的访问必须先根据数组的名字a找到数组首元素的首地址,然后根据偏移量找到相应的值。这是一种典型的&具名+匿名&访问。比如现在需要读取字符&5&,我们有两种方式:
1)以指针的形式:*(a+4)。a这时候代表的是数组首元素的首地址,假设为0x0000FF00,然后加上4个字符的偏移量,得到新的地址0x0000FF04。然后取出0x0000FF04地址上的值。
2)以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a作为数组首元素的首地址,然后加上中括号中4个元素的偏移量,计算出新的地址,然后从新的地址中取出值。
由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以&以指针形式&或&以下标形式&进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问。一定要注意的是这个&以 XXX的形式的访问&这种表达方式。
另外一个需要强调的是:上面所说的偏移量4代表的是4个元素,而不是4个byte。只不过这里刚好是char类型数据1个字符的大小就为1个byte。记住这个偏移量的单位是元素的个数而不是byte数,在计算新地址时千万别弄错喔!
/*郑重声明:以上部分内容引用于& 陈正冲编著的《C语言深度解剖》 */
好吧,关于指针和数组就先说这么,其实这两者之间的知识点远不止这些,限于篇幅,残弈悟恩就先引玉这么多了,剩下的大家就自行研究吧。
9.2.4 指针与函数
说到指针和函数,内容当然少不到哪儿去。这里以指针如何在函数间通信为例,来说说指针的在函数中的作用。具体源码如下:
#include &stdio.h&
void interchange(int * u,int * v);
int main(void)
&&&&int x = 5,y = 10;
&&&&printf(&Originally x = %d and y = %d.\n&,x ,y);
&&&&interchange(&x,&y);&&&&&&& /* 向函数传送地址 */
&&&&printf(&Now x = %d and y = %d.\n&,x,y);
&&&&return 0;
void interchange(int * u, int * v)
&&&&temp = *u;&&&&&&& /* temp得到u指向的值 */
&&&&*u = *v;
现对此例程作简要解释。由第7行可以看出,函数传递的是x和y的地址而不是它们的值。这就意味着interchange()函数原型声明和定义中的形式参数u和v将使用地址作为它们的值。因此,它们应该声明为指针。由于x和y都是整数,所以u和v是指向整数的指针,因而有了11行所示的函数声明;第14行,因为u的值是&x,所以u指向x的地址。这就意味着*u代表了x的值,而这正是我们需要的数值,一定不要写成:temp=u,因为赋值给变量temp的值是x的地址而不是x的值,所以不能实现数值的交换。
9.3 懂原理解过程
9.3.1 原理说明
1、1602液晶,工作电压为5V,内置192种字符(160个5x7点阵字符和32个5x10点阵字符),具有64个字节的RAM,通讯方式有4位、8位两种并口可选。其实物图如图9-1所示。
图9-1 1602液晶实物图
2、液晶接口定义如下表9-1所示:
表9-1 1602液晶的端口定义表
电源地(GND)
电源电压(+5V)
LCD驱动电压(可调)一般接一电位器来调节电压
指令、数据选择端(RS = 1&数据寄存器;RS = 0&指令寄存器)
读、写控制端(R/W = 1&读操作;R/W = 0&写操作)
读写控制输入端(读数据:高电平有效;写数据:下降沿有效)
数据输入/输出端口(8位方式:DB0~DB7;4位方式:DB0~DB3)
背光灯的正端+5V
背光灯的负端0V
3、RAM地址映射图,控制器内部带有80*8位(80字节)的RAM缓冲区,对应关系如图9-2所示。
可能对于初学者,一看到此图就过敏,觉得这个图很难,难道不难嘛?呵呵,对于此图只说两点。
第一点,两行的显示地址分别为:00~0F、40~4F,隐藏地址分别为10~27、50~67。意味着写在(00~0F、40~4F)地址的字符可以显示,(10~27、50~67)地址的不能显示,要显示,一般通过移屏指令来搞定。
第二点,该RAM是用什么来访问的?液晶内部有个数据地址指针,因而就能很容易的访问内部这80个字节的内容了。
图9-2 RAM地址映射图
4、操作指令
这里,残弈悟恩纠结了好几天,刚开始把什么写啊、读啊的指令表都COPY到了这里,之后又删了,再写上,再删掉,就这样重复了数次,最后还是决定把这部分写上,以便大伙边看代码,边查指令表,这样容易加深记忆。那为何纠结此事,因为怕写了,没人看,这样既浪费篇幅,又浪费感情。残弈悟恩在公司、学校发现,当一说写代码时,有些人并不去看数据手册、不去画流程图,而是百度一搜,随便修改一下管脚序号,烧录到单片机中,若显示正常,则认为OK,若不正常,就开始在各大电子论坛发帖、问人,花了好长时间才搞定了一个液晶的两行字符显示,这样的学习方法好吗?或许在公司,这样的CTRL+C、CTRL+V确实能加快开发速度,也能忽悠老板,可如果你开发的东西有问题,最后还是需要你来维护,此时,没有流程图、没有注释、没有任何提示的这种代码,维护起来那怎一个难字了得。若你还在初学单片机,那这样的方法就更不可取了,这种简单复制、粘贴的方法是永远都学不会、玩不好单片机的。建议大家一定养成先分析问题、再画流程图,之后自己先写代码,若有问题,要仔细的去阅读数据手册(最好是英文的,因为这是最准确、最权威的资料,这样的资料你不读,你还想读什么啊?),边阅读、边分析、边总结,若还是不能解决,这时当然可以问别人,发帖子,或许别人一句话就能点醒梦中人啊!
(1)基本的操作时序,如下表9-2所示。
表9-2基本操作指令表
RS=L,RW=H,E=H
D0~D7(状态字)
RS=L,RW=L,D0~D7=指令,E=高脉冲
RS=H,RW=H,E=H
D0~D7(数据)
RS=H,RW=L,D0~D7=数据,E=高脉冲
(2)状态字说明(如表9-3所示)
表9-3 状态字分布表
当前地址指针的数值
读/写操作使能
1:禁止 0:使能
&&& 对控制器每次进行读写操作之前,都必须进行读写检测,确保STA7为0。也即一般程序中见到的判断忙操作。
&&& (2)常用指令如表9-4所示。
表9-4常用指令表
清屏:1、数据指针清零
2、所有显示清零
AC = 0,光标、画面回HOME位
ID=1&AC自动增一;
ID=0&AC减一
S=1&画面平移;
S=0&画面不动
D=1&显示开;D=0&显示关
C=1&光标显示;C=0&光标不显示
B=1&光标闪烁;B=0&光标不闪烁
SC=1&画面平移一个字符;
R/L=1&右移;R/L=0&左移;
DL=0&8位数据接口;
DL=1&4位数据接口;
N=1&两行显示;N=0&一行显示
F=1&5*10点阵字符;F=0&5*7
5、数据地址指针设置(行地址设置具体见表9-5)
表9-5数据地址指针设置表
功能(设置数据地址指针)
0x80+(0x00~0x27)
将数据指针定位到:第一行(某地址)
0x80+(0x40~0x67)
将数据指针定位到:第二行(某地址)
6、写操作时序图,如图9-3所示。
图9-3写操作时序图
&&& 接着看看时序参数,具体数值如表9-6所示。这里附该表绝对不是为了占篇幅,只是觉得电子设计是一件很严谨的事,容不得有1ns甚至1fs的误差,当然我是做不到了,这个艰巨的任务就交个大家(祖国的未来、民族的希望,^_^)了。
表9-6 时序参数表
E上升沿/下降沿时间
地址建立时间
E、RS、R/W
地址保持时间
数据建立时间
数据保持时间
好吧,看着图9-3和表9-6,那就开始攀越珠穆朗玛峰吧,为何这么比喻,因为好多人说时序图太难了,看不懂,那就让残弈悟恩一点一滴地教大家翻越这座大山吧,边爬边欣赏雪山的美景。
液晶一般是用来显示的,所以这里主要讲解如何写数据和写命令到液晶的来龙去脉,关于读操作(一般用不着)就留给大家自行研究了。对于时序图,其实残弈悟恩以前理解的也不到位,自学完FPGA之后,对时序图才有了深刻的认识,在此与大家分享一下。
时序图,顾名思义,与时间有关、顺序有关(这不废话吗?哈哈)。时序图就是与时间有严格的关系,不看都精确到ns级了。与顺序肯定有啊,但是这个顺序严格说应该是与信号在时间上的有效顺序有关,而与图中信号线是上是下没关系。大伙都知道程序运行是按顺序执行的,可是这些信号是并行执行的,就是说只要这些时序有效之后,上面的信号都会运行,只是运行与有效的时间不同罢了,因此有效时间不同就导致了信号的执行顺序不同。这里有个难点就是&并行&,关于并行,就不过多的解释了,如大伙想知道,赶紧玩好单片机之后去玩FPGA吧,那里会给你们一个很完美的答案。可厂家在做时序图时,一般会把信号按照时间的有效顺序从上到下的排列,所以操作的顺序也就变成了先操作最上边的信号,接着依次操作后面的。这就是残弈悟恩对时序图的理解。结合上述讲解,来详细盘点一下9-3时序图。有没有信心?有(震耳欲聋)!!
★ 通过RS确定是写数据还是写命令。
写命令包括数据显示在什么位置、光标显示/不显示、光标闪烁/不闪烁、需/不需要移屏等。写数据是指要显示的数据是什么内容。若此时要写指令,结合表9-6和图9-3可知,就得先拉低RS(RS = 0)。若是写数据,那就是RS = 1。
★ 读/写控制端设置为写模式,那就是RW = 0。注意,按道理应该是先写一句RS = 0(1)之后延迟 tSP1(最小30ns),再写RW = 0,可单片机操作时间都在us级,所以就不用特意延迟了,那如果以后用FPGA来操作,这时就要根据情况加延迟了。
★ 将数据或命令送达到数据线上。形象的可以理解为此时数据在单片机与液晶的连线上,没有真正到达液晶内部。事实肯定并不是这样,而是数据已经到达液晶内部,只是没有被运行罢了,执行语句为P0 = Data(Commend)。
★ 给EN一个下降沿,将数据送入液晶内部的控制器,这样就完成了一次写操作。形象的理解为此时单片机将数据完完整整的送到了液晶内部。为了让其有下降沿,一般在P0 = Data(Commend)之前先写一句EN = 1,待数据稳定以后,稳定需要多长时间,这个最小的时间就是图中的tPW(150ns),流行的程序里面加了DelayMS(5),说是为了液晶能稳定运行,在调试程序时,最后也加了5MS的延迟。
关于时序图,在此特别提醒,上面为什么没有用标号1、2、3之类的,而是用了★,这是有原因的,如果用了顺序,怕大家误解认为上面时序图中的那些时序线条是按顺序执行,其实不是,每条时序线都是同时执行的,只是每条时序线有效的时间不同。在此大家只需理解:时序图中每条命令、数据时序线同时运行,只是有效(起作用的时间、也即非&打酱油&的时间,那就是&吃醋&时间了)的时间不同。一定不要理解为那个信号线在上,就是先运行那个信号,那个在下面,就是后运行。因为硬件的运行是并行的,不像软件按顺序执行。这里只是在用软件来模拟硬件的并行,所以有了这样的顺序语句:RS = 0;RW = 0;EN = 1; _nop_();P0 = CEN = 0。
关于时序图中的各个延时,不同厂家生产的液晶其延时不同,在此无法提供准确的数据,但大多数为ns级,一般51单片机运行的最小单位为微妙级,按道理,这里不加延时都可以,或者说加几个us就可以,可是在调试程序时发现,那样是不行,至少要有1~5个ms才行,或许是液晶与数据手册有别吧,还是什么原因,也没做深入的研究,智商就把人限制死了,鉴于这个情况,一般写程序也是延时1~5ms,具体时间就留给祖国的花朵慢慢去研究吧。
9.3.2 硬件分析
&&&&&&&& 所谓硬件设计,就是搭建1602液晶的硬件运行环境,如何搭建?当然是参考数据手册,因为那是最权威的资料,从而可以设计出如图9-4所示的电路,具体接口定义如下。
(1)液晶1(16)、2(15)分别接GND(0V)和VCC(5V)。
(2)液晶3端为液晶对比度调节端,MGMC-V1.0实验板用一个10KOhm电位器来调节液晶对比度。第一次使用时,在液晶上电状态下,调节至液晶上面一行显示出黑色小格为止。经测试,此时该端电压一般为0.5V左右。简单接法可以直接接一个2K的电阻到GND,这样也是可以的,有机会,大家可以自行焊接电路调试调试。
(3)液晶4端为向液晶控制器写数据、命令选择端,接单片机的P3.5口。
(4)液晶5端为读、写选择端,接单片机的P3.4口。
(5)液晶6端为使能信号端,接单片机的P3.3口。
(6)液晶7~14为8位数据端口,依次接单片机的P0口。
&&&&&&&&&&&
图9-4 MGMC-V1.0实验板1602液晶与单片机的接口图
9.4 做实验熟真理
实践是检验真理的唯一标准。
实验1 1602显示字符串
& 实验目的:学会1602的基本操作
& 实验器材:电脑、麦光开发板、Keil4编译软件、ISP烧录软件
& 实验原理:初始化1602后按字符串写入的方式依次写入一二行
& 实验步骤:
根据实验要求,编写程序,程序如下:
#include&reg52.h&
/* ***************************************************** */
// 为已知类型取别名
/* ***************************************************** */
typedef unsigned char uChar8;
typedef unsigned int uInt16;
/* ***************************************************** */
// 显示字符串数组定义
/* ***************************************************** */
uChar8 code TAB1[]="^_^ Welcome ^_^ ";
uChar8 code TAB2[]="I LOVE MGMC-V1.0";
/* ***************************************************** */
/* ***************************************************** */
sbit RS = P3^5;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //数据/命令选择端(H/L)
sbit RW = P3^4;&&&&&&&&&&&&&&&&&&&&&&&&& //数/写选择端(H/L)
sbit EN = P3^3;&&&&&&&&&&&&&&&&&&&&&&&&&&& //使能信号
/* ***************************************************** */
// 函数名称:DelayMS()
// 函数功能:毫秒延时
// 入口参数:延时毫秒数(ValMS)
// 出口参数:无
/* ***************************************************** */
void DelayMS(uInt16 ValMS)
&&&&&&&& uInt16 uiVal,ujV
&&&&&&&& for(uiVal = 0; uiVal & ValMS; uiVal++)
&&&&&&&&&&&&&&&&&& for(ujVal = 0; ujVal & 113; ujVal++);
/* ***************************************************** */
// 函数名称:DectectBusyBit()
// 函数功能:检测状态标志位(判断是忙/闲)
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void DectectBusyBit(void)
&&&&&&&& P0 = 0&&&&&&&&&&&&&&&&&&& // 读状态值时,先赋高电平
&&&&&&&& RS = 0;
&&&&&&&& RW = 1;
&&&&&&&& EN = 1;
&&&&&&&& DelayMS(1);
&&&&&&&& while(P0 & 0x80);&&& // 若LCD忙,停止到这里,否则走起
&&&&&&&& EN = 0;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 之后将EN初始化为低电平
/* ***************************************************** */
// 函数名称:WrComLCD()
// 函数功能:为LCD写指令
// 入口参数:指令(ComVal)
// 出口参数:无
/* ***************************************************** */
void WrComLCD(uChar8 ComVal)
&&&&&&&& DectectBusyBit();
&&&&&&&& RS = 0;
&&&&&&&& RW = 0;
&&&&&&&& EN = 1;
&&&&&&&& P0 = ComV
&&&&&&&& DelayMS(1);
&&&&&&&& EN = 0;&&&&
/* ***************************************************** */
// 函数名称:WrDatLCD()
// 函数功能:为LCD写数据
// 入口参数:数据(DatVal)
// 出口参数:无
/* ***************************************************** */
void WrDatLCD(uChar8 DatVal)
&&&&&&&& DectectBusyBit();
&&&&&&&& RS = 1;
&&&&&&&& RW = 0;
&&&&&&&& EN = 1;
&&&&&&&& P0 = DatV
&&&&&&&& DelayMS(1);
&&&&&&&& EN = 0;&&&&
/* ***************************************************** */
// 函数名称:LCD_Init()
// 函数功能:初始化LCD
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void LCD_Init(void)
&&&&&&&& WrComLCD(0x38);&&&&&&&&&&& // 16*2行显示、5*7点阵、8位数据接口
&&&&&&&& WrComLCD(0x38);
&&&&&&&& WrComLCD(0x38);&&& & // 重新设置一遍
&&&&&&&& WrComLCD(0x01);&&& & // 显示清屏
&&&&&&&& WrComLCD(0x06);&&& & // 光标自增、画面不动&
&&&&&&&& DelayMS(1);&&&&&&&&&&&&&&&&&&&&&&& // 稍作延时
&&&&&&&& WrComLCD(0x0C);&&& & // 开显示、关光标并不闪烁
/* ***************************************************** */
// 函数名称:main()
// 函数功能:1602初始化后显示字符串
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void main(void)
&&&&&&&& uChar8 ucV
&&&&&&&& LCD_Init();
&&&&&&&& DelayMS(5);
&&&&&&&& WrComLCD(0x80);&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 选择第一行
&&&&&&&& while(TAB1[ucVal] != '\0')&&&&&&&&&&&&&&&&& // 字符串数组的最后还有个隐形的"\0"
&&&&&&&& {
&&&&&&&&&&&&&&&&&& WrDatLCD(TAB1[ucVal]);
&&&&&&&&&&&&&&&&&& ucVal++;
&&&&&&&& }
&&&&&&&& ucVal = 0;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 语句简单,功能重要
&&&&&&&& WrComLCD(0xC0);&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 选择第二行(0x80+0x40)
&&&&&&&& while(TAB2[ucVal] != '\0')
&&&&&&&& {
&&&&&&&&&&&&&&&&&& WrDatLCD(TAB2[ucVal]);
&&&&&&&&&&&&&&&&&& ucVal++;
&&&&&&&& }
&&&&&&&& while(1);
检查程序中有无错误,有则改之无则加勉
程序中的遇忙检测、写数据、写指令等都是依照1602时序图编写的,对时间的要求比较严格,大家在研读该程序时要对照时序图分析,这样才能更好的理解程序达到学习的目的。
生成hex文件,下载到开发板,观察实验现象,如图9-5所示:
图9-5 静态显示实现现象
& 实验总结:
从这个实验中我们可以看出液晶操作主要有两步:一是液晶初始化;二是确定显示内容的地址,即写命令;三是要明确要显示的内容,即写数据。最后总结为一句话就是按顺序把要显示的内容放进对应的地址。
实验2 1602动静态显示
& 实验目的:掌握液晶的静、动态显示知识
& 实验器材:电脑、麦光开发板、Keil4编译软件、ISP烧录软件
& 实验原理:第一行以静态形式写入,而第二行以字符的形式写入,在超过16个字符就需要先将第一行清空,然后整体移屏,再写入第一行及第二行接下来的字符,直至写完为止。
& 实验步骤:
根据实验要求,编写程序,程序如下:
#include &reg52.h&
#include &intrins.h&
/* ***************************************************** */
/* ***************************************************** */
typedef unsigned char uChar8;
typedef unsigned int uInt16;
/* ***************************************************** */
/* ***************************************************** */
sbit RS = P3^5 ;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //数据/命令选择端(H/L)
sbit RW = P3^4 ;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //数/写选择端(H/L)
sbit EN = P3^3 ;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //使能信号
sbit SEG_SELECT = P1^7;&&&&&&&&&&&&&&&&&& && & //段选
sbit BIT_SELECT = P1^6;&&&&&&&&&& && & //位选
/* ***************************************************** */
// 显示数组
/* ***************************************************** */
uChar8 code *String1 = "Welcome to "; & &&&&&&&&&&&&&&&&&&&&&& // 待显示字符串
uChar8 code *String2 = "http://ieebase.taobao.com";
/* ***************************************************** */
// 函数名称:DelayMS()
// 函数功能:毫秒延时
// 入口参数:延时毫秒数(ValMS)
// 出口参数:无
/* ***************************************************** */
void DelayMS(uInt16 ValMS)
&&&&&&&& uInt16 uiVal,ujV
&&&&&&&& for(uiVal = 0; uiVal & ValMS; uiVal++)
&&&&&&&& for(ujVal = 0; ujVal & 113; ujVal++);
/* ***************************************************** */
// 函数名称:DectectBusyBit()
// 函数功能:液晶遇忙检测
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void DectectBusyBit(void)
&&&&&&&& P0 = 0&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 读状态值时,先赋高电平
&&&&&&&& RS = 0;
&&&&&&&& RW = 1;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 从液晶读数据
&&&&&&&& EN = 1;
&&&&&&&& DelayMS(1);
&&&&&&&& while(P0 & 0x80);&&&&&&&&&&&&& // 若LCD忙,停止到这里,否则走起
&&&&&&&& EN = 0;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 之后将EN初始化为低电平
/* ***************************************************** */
// 函数名称:WrComLCD()
// 函数功能:向液晶写命令
// 入口参数:命令(ComVal)
// 出口参数:无
/* ***************************************************** */
void WrComLCD(uChar8 ComVal)
&&&&&&&& DectectBusyBit();
&&&&&&&& RS = 0;
&&&&&&&& RW = 0;
&&&&&&&& EN = 1;
&&&&&&&& P0 = ComV
&&&&&&&& DelayMS(1);
&&&&&&&& EN = 0;&&&&
/* ***************************************************** */
// 函数名称:WrDatLCD()
// 函数功能:向液晶写数据
// 入口参数:数据(DatVal)
// 出口参数:无
/* ***************************************************** */
void WrDatLCD(uChar8 DatVal)
&&&&&&&& DectectBusyBit();
&&&&&&&& RS = 1;
&&&&&&&& RW = 0;
&&&&&&&& EN = 1;&&&&
&&&&&&&& P0 = DatV
&&&&&&&& DelayMS(1);
&&&&&&&& EN = 0;&&&&
/* ***************************************************** */
// 函数名称:LCD_Init()
// 函数功能:液晶初始化
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void LCD_Init(void)
&&&&&&&& WrComLCD(0x38);&&&&&&&&&&& // 16*2行显示、5*7点阵、8位数据接口
&&&&&&&& DelayMS(1);& &&&&&&&&&&&&&&&&&&&&&&&&&&& // 稍作延时
&&&&&&&& WrComLCD(0x38);&&& & // 重新设置一遍
&&&&&&&& WrComLCD(0x01);&&& & // 显示清屏
&&&&&&&& WrComLCD(0x06);&&& & // 光标自增、画面不动&
&&&&&&&& DelayMS(1);&&&&&&&&&&&&&&&&&&&&&&& // 稍作延时
&&&&&&&& WrComLCD(0x0C);&&& & // 开显示、关光标并不闪烁
/* ***************************************************** */
// 函数名称:ClearDisLCD()
// 函数功能:清屏
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void ClearDisLCD(void)
&&&&&&&& WrComLCD(0x01); &&&&&&&& &&&//发送清屏指令
&&&&&&&& DelayMS(1);
/* ***************************************************** */
// 函数名称:WrStrLCD()
// 函数功能:向液晶写字符串数据
// 入口参数:行(Row)、列(Column)、字符串(*String)
// 出口参数:无
/* ***************************************************** */
void WrStrLCD(bit Row,uChar8 Column,uChar8 *String)
&&&&&&&& if (!Row) &&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&& //第1行第1列起始地址0x80
&&&&&&&& {&&&&
&&&&&&&&&&&&&&&&&& WrComLCD(0x80 + Column);
&&&&&&&& }
&&&&&&&& else &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //第2行第1列起始地址0xC0
&&&&&&&& {&&&&
&&&&&&&&&&&&&&&&&& WrComLCD(0xC0 + Column);&&&&
&&&&&&&& }&&&&&&&
&&&&&&&& while (*String) &&&&&&& &&&&&&& //发送字符串
&&&&&&&& {&&&&
&&&&&&&& &&&&WrDatLCD( *String);
&&&&&&&&&&&&&&&&&& String ++;
&&&&&&&& }
/* ***************************************************** */
// 函数名称:WrCharLCD()
// 函数功能:向液晶写字节数据
// 入口参数:行(Row)、列(Column)、字节数据(Dat)
// 出口参数:无
/* ***************************************************** */
void WrCharLCD(bit Row,uChar8 Column,uChar8 Dat)
&&&&&&&& if (!Row) &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //第1行第1列起始地址0x80
&&&&&&&& {&&&&
&&&&&&&&&&&&&&&&&& WrComLCD(0x80 + Column);&&&&
&&&&&&&& }&&&
&&&&&&&& else &&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&& //第2行第1列起始地址0xC0
&&&&&&&& {&&&&
&&&&&&&&&&&&&&&&&& WrComLCD(0xC0 + Column);&&&&
&&&&&&&& }&&&&&&&
&&&&&&&& WrDatLCD( Dat);& &&&&&&&&&&&&&&&&&&&&&&&&&&& //发送数据
/* ***************************************************** */
// 函数名称:CloseDigTube()
// 函数功能:关数码管(免打扰模式)
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void CloseDigTube(void)
&&&&&&&& BIT_SELECT = 1;
&&&&&&&& P0 = 0
&&&&&&&& BIT_SELECT =0;
&&&&&&&& SEG_SELECT = 1;
&&&&&&&& P0 = 0x00;
&&&&&&&& SEG_SELECT =0;&&&&&&
/* ***************************************************** */
// 函数名称:main()
// 函数功能:液晶第一行静态显示,第二行动态显示
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void main(void)
&&&&&&&& uChar8&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&& //循环变量
&&&&&&&& uChar8 *P&&&& &&&&&&&&&&&&&&& //指针变量
&&&&&&&& CloseDigTube();&&&&&&&&&&&&&&&& &&&&&& //关闭数码管显示
&&&&&&&& LCD_Init();&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&& //初始化
&&&&&&&& while(1)
&&&&&&&& {
&&&&&&&&&&&&&&&&&& i = 0;
&&&&&&&&&&&&&&&&&& ClearDisLCD();& &&&& &&&&&&&&&&&&&&&&&&&&&&&&&& //清屏
&&&&&&&&&&&&&&&&&& Pointer = String2;&&&& &&&&&&&&&&&&&&&&&&&&&&&&& //指针指向字符串2首地址
&&&&&&&&&&&&&&&&&& WrStrLCD(0,3,String1);& &&&&&&&&&&&&&&&& //第1行第3列写入字符串1
&&&&&&&&&&&&&&&&&& while (*Pointer) &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //按字节方式写入字符串2
&&&&&&&&&&&&&&&&&& {&&
&&&&&&&&&&&&&&&&&&&&&&&&&&& WrCharLCD(1,i,*Pointer);&& //第2行第i列写入一个字符
&&&&&&&&&&&&&&&&&&&&&&&&&&& i ++;&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //写入的列地址加一
&&&&&&&&&&&&&&&&&&&&&&&&&&& Pointer ++;&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //指针指向字符串中下一个字符
&&&&&&&&&&&&&&&&&& &&&&&& if(i & 16)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //是否超出能显示的16个字符
&&&&&&&&&&&&&&&&&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& WrStrLCD(0,3,"&&&&&&&&&&&&&& ");&&&&&
&&&&&&&&&&&&&&&&//将String1用空字符串代替;清空第1行显示
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& WrComLCD(0x18);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //光标和显示一起向左移动
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& WrStrLCD(0,i - 13,String1);& &&&&&&&&&&&&&&&&&&& //原来位置重新写入字符串1
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& DelayMS(1000); &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //为了移动后清晰显示
&&&&&&&&&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&&&&&&&&&&&&& else
&&&&&&&&&&&&&&&&&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& DelayMS(250);&&&&&&&&& & &&&&&&&&&&&&& //控制两字之间显示速度
&&&&&&&&&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&&&& }&
&&&&&&&&&&&&&&&&&& DelayMS(2500); &&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&& //显示完全后等待
&&&&&&&& }
检查程序中有无错误,有则改之无则加勉
先看主函数(169行~200行)吧,首先当然是要初始化的啦。在这之后进入死循环,先清除液晶屏幕显示,送入第一行显示的内容,因为第一行是静态显示,所以一次性送入数据就可以了,用指针变量指向第二行字符串首地址,接着我们就可以从头到尾的显示这个字符串了,依次送入显示的字符并做延时就会做出一个字符接一个字符打印的效果,然而,接下来会出现这样的问题,那就是字符串长度超出了屏幕显示的范围,1602一行最多显示16个字符,超出部分怎么显示比较好呢?我们是这样做的,那就是当字符统计变量i大于16后我们先将第一行数据清除,紧接着向左移一位,这样还没有完呢,我们还需要在同样的位置把第一行的字符串显示出来,于是乎,我们在原位置输入第一行字符串,做一个延时,以便我们观看的比较清楚即可。直到将第二行所有字符全部显示完全之后,为了视觉上更美观一点,给了一个停顿的效果,这样我们的程序就完美了。
生成hex文件,下载到开发板,观察实验现象,如图9-6所示:
图9-6 动静态显示实验现象
从这个图中看不出动态显示的效果的,因此这个实验还必须的亲自动手完成了才可体会动态显示原理的。
实验总结:
动态显示就是用一个移屏的指令完成的,而在动静结合的时候就需要注意:移屏前先将静态显示清空,否则就会出现你不想看到的结果哦!
阅读(...) 评论()
www.lzmgtech.com

我要回帖

更多关于 1602液晶 的文章

 

随机推荐