1.初识 LCD1602
LCD ( Liquid Crystal Display 的简称)液晶显示器。
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过TFT上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。
1.1.1602 液晶的硬件接口
1602 液晶,从它的名字我们就可以看出它的显示容量,就是可以显示 2 行,每行 16 个字符的液晶。它的工作电压为 4.5V~5.5V ,对于这点我们在设计电路的时候,直接按照 5V 系统设计即可,但是要保证我们的 5V 系统不能低于 4.5V 。在 5V 工作电压下测量它的工作电流是 2mA ,大家注意,这个 2mA 仅仅是指液晶,而它的黄绿背光都是用 LED 做的,所以功耗不会太小。
1602 液晶一共有 16 个引脚,每个引脚的功能,我们都可以在它的数据手册中获取。而这些基本信息,在我们设计电路和编写程序之前,必须先看明白。
液晶的电源 1 脚 2 脚以及背光电源的 15 脚 16 脚,不用多说,正常接就可以了。
3 脚叫做液晶显示偏压信号,大家注意到小黑块没有,当我们要显示一个字符的时候,有的黑点显示,有的黑点就不能显示,这样就可以实现我们想要的字符了。我们这个 3 脚就是用来调整显示的黑点和不显示的之间的对比度,调整好了对比度,就可以让我们的显示更加清晰一些。在进行电路设计实验的时候,通常的办法是在这个引脚上接个电位器,也就是我们初中学过的滑动变阻器。通过调整电位器的分压值,来调整 3 脚的电压。而当产品批量生产的时候,我们可以把我们调整好的这个值直接用简单电路来实现。
4 脚是数据命令选择端。对于液晶,有时候我们要发送一些命令,让它实现我们想要的一些状态,有时候我们要发给它一些数据,让它显示出来,液晶就通过这个引脚来判断接收到的是命令还是数据。大家注意学会读手册,看到这个引脚描述里:数据/命令选择端,而后跟了括号(H/L),他的意思就是当这个引脚是 H(High)高电平的时候,是数据,当这个引脚是 L(Low)低电平的时候,是命令。
5 脚和 4 脚用法类似,功能是读写选择端。我们既可以写给液晶数据或者命令,也可以读取液晶内部的数据或状态,就是控制这个引脚。因为液晶本身内部有 RAM,实际上我们送给液晶的命令或者数据,液晶需要先保存在缓存里,然后再写到内部的寄存器或者 RAM中,这个就需要一定的时间。所以我们进行读写操作之前,首先要读一下液晶当前状态,是不是在“忙”,如果不忙,我们可以读写数据,如果在“忙”,我们就需要等待液晶忙完了,再进行操作。读状态是常用的,不过读液晶数据我接触的场合没怎么用过,大家了解这个功能即可。
6 脚是使能信号,很关键,液晶的读写命令和数据,都要靠它才能正常读写,我们后边详细讲这个引脚怎么用。
7 到 14 引脚就是 8 个数据引脚了,我们就是通过这 8 个引脚读写数据和命令的,统一接到了 P0 口上。
1.2.1602 液晶的读写时序
1602 的时序问题,大家要学会通过 LCD1602 的数据手册提供的时序图和时序参数表格来进行研究,而且看懂时序图是学习单片机所必须掌握的一项技能,如下图:
我们先来看一下读操作时序的 RS 引脚和 R/W 引脚,这两个引脚先进行变化,因为是读操作,所以 R/W 引脚首先要置为高电平,而不管它原来是什么。读指令还是读数据,都是读操作,而且都有可能,所以 RS 引脚既有可能是置为高电平,也有可能是置为低电平,大家注意图上的画法。而 RS 和 R/W 变化了经过 Tsp1 这么长时间后,使能引脚 E 才能从低电平到高电平发生变化。
而使能引脚 E 拉高经过了 tD 这么长时间后,LCD1602 输出 DB 的数据就是有效数据了,我们就可以来读取 DB 的数据了。读完了之后,我们要先把使能 E 拉低,经过一段时间后 RS、R/W 和 DB 才可以变化继续为下一次读写做准备了。
而写操作时序和读操作时序的差别,就是写操作时序中,DB 的改变是由单片机来完成的,因此要放到使能引脚 E 的变化之前进行操作,其它区别大家可以自行对比一下。
细心的同学会发现,这个时序图上还有很多时间标签。比如 E 的上升时间 tR,下降时间时间 tF,使能引脚 E 从一个上升沿到下一个上升沿之间的长度周期 tC,使能 E 下降沿后,R/W 和 RS 变化时间间隔 tHD1 等等很多时间要求,这些要求怎么看呢?放心,只要是正规的数据手册,都会把这些时间要求给大家标记出来的。我们来看下表 :
tC:指的是使能引脚 E 从本次上升沿到下次上升沿的最短时间是 400ns,而我们单片机因为速度较慢,一个机器周期就是 1us 多,而一条 C 语言指令肯定是一个或者几个机器周期的,所以这个条件完全满足。
tPW:指的是使能引脚 E 高电平的持续时间最短是 150ns,同样由于我们的单片机比较慢,这个条件也完全满足。
tR, tF:指的是使能引脚 E 的上升沿时间和下降沿时间,不能超过 25ns,别看这个数很小,其实这个时间限值是很宽裕的,我们实际用示波器测了一下开发板的这个引脚上升沿和下降沿时间大概是 10ns 到 15ns 之间,完全满足。
tSP1:指的是 RS 和 R/W 引脚使能后至少保持 30ns,使能引脚 E 才可以变成高电平,这个条件同样也完全满足。
tHD1:指的是使能引脚 E 变成低电平后,至少保持 10ns 之后,RS 和 R/W 才能进行变化,这个条件也完全满足。
tD:指的是使能引脚 E 变成高电平后,最多 100ns 后,1602 就把数据送出来了,那么我们就可以正常去读取状态或者数据了。
tHD2:指的是读操作过程中,使能引脚 E 变成低电平后,至少保持 20ns,DB 数据总线才可以进行变化,这个条件也完全满足。
tSP2:指的是 DB 数据总线准备好后,至少保持 40ns,使能引脚 E 才可以从低到高进行使能变化,这个条件也完全满足。
tHD2:指的是写操作过程中,要引脚 E 变成低电平后,至少保持 10ns,DB 数据总线才可以变化,这个条件也完全满足。
1602 液晶内部带了 80 个字节的显示 RAM ,用来存储我们发送的数据,它的构造如图所示:
第一行的地址是 0x00H 到 0x27,第二行的地址从 0x40 到 0x67,其中第一行 0x00 到 0x0F 是与液晶上第一行 16 个字符显示位置相对应的,第二行 0x40 到 0x4F 是与第二行 16 个字符显示位置相对应的。而每行都多出来一部分,是为了显示移动字幕设置的。1602 字符液晶是显示字符的,因此它跟 ASCII 字符表是对应的。比如我们给 0x00 这个地址写一个‘a’,也就是十进制的 97,液晶的最左上方的那个小块就会显示一个字母 a。
液晶有一个状态字字节,我们通过读取这个状态字的内容,就可以知道 1602 液晶的一些内部情况,如下表所示。
这个状态字节有 8 个位,最高位表示了当前液晶是不是“忙”,如果这个位是 1 表示液晶正“忙”,禁止我们读写数据或者命令,如果是 0,则可以进行读写。而低 7 位就表示了当前数据地址指针的位置。
1602 的基本操作时序,一共有 4 个,这些大家都不需要记住,但是都需要理解,因为我们现在不是为了应付考试,所以不需要你把手册背熟,但是你写程序的时候,打开手册要能看懂如何操作,还要再提醒一句,单片机读外部状态前,必须先保证自己是高电平哦。
我们这里要做 1602 液晶的程序,因此先把用到的总线接口做一个统一声明:
#define LCD1602_DB P0
sbit LCD1602_RS = P2^6 ;
sbit LCD1602_RW = P2^5 ;
sbit LCD1602_E = P2^7 ;
1、读状态:RS = L , R/W = H ,E = H 。这个逻辑很简单,也就是说我们接着写:
LCD1602_DB = 0xFF ;
LCD1602_RS = 0 ;
LCD1602_RW = 1 ;
LCD1602_E = 1 ;
sta = LCD1602_DB ;
这样就把当前液晶的状态字读到了 sta 这个变量中,我们可以通过判断 sta 最高位的值来了解当前液晶是否处于“忙”状态,也可以得知当前数据的指针位置。两个问题,一是如果我们当前读到的状态是“不忙”,那么我们程序可以进行读写操作,如果当前状态是“忙”,那么我们还得继续等待重新判断液晶的状态;问题二,大家可以看我之前的文章,流水灯、数码管、点阵、1602 液晶都是用到了 P0 口总线,我们读完了液晶状态继续保持 LCD1602_E 是高电平的话,1602 液晶会继续输出它的状态值,输出的这个值会占据了 P0 总线,干扰到流水灯数码管等其它外设,所以我们读完了状态,通常要把这个引脚拉低来释放总线,这里我们用了一个 do…while 循环语句来实现。
uchar sta ;
LCD1602_DB = 0xFF ;
LCD1602_RS = 0 ;
LCD1602_RW = 1 ;
do
{
LCD1602_E = 1 ;
sta = LCD1602_DB ; // 读取状态字
LCD1602_E = 0 ;
} while(sta & 0x80) ; // bit7为1表示液晶正忙,重复检测为0为止
2、读数据:RS=H,R/W=H,E=H。这个逻辑也很简单,但是读数据不常用,大家了解一下就可以了,这里就不详细解释了。
3、写指令:RS=L,R/W=L,D0~D7=指令码,E=高脉冲。
这个在逻辑上没什么难的,只是 E=高脉冲这个问题要解释一下。这个指令一共有 4 条语句,其中前三条语句顺序无所谓,但是 E=高脉冲这一句很关键。实际上流程是这样的:因为我们现在是写数据,所以我们首先要保证我们的 E 引脚是低电平状态,而前三句不管我们怎么写,1602 液晶只要没有接收到 E 引脚的使能控制,它都不会来读总线上的信号的。当通过前三句准备好数据之后,E 使能引脚从低电平到高电平变化,然后 E 使能引脚再从高电平到低电平出现一个下降沿,1602 液晶内部一旦检测到这个下降沿后,并且检测到 RS=L,R/W=L,就马上来读取 D0~D7 的数据,完成单片机写 1602 指令过程。归纳总结我们写了个 E=高脉冲,意思就是:E 使能引脚先从低拉高,再从高拉低,形成一个高脉冲。
4、写数据:RS=H,R/W=L,D0~D7=数据,E=高脉冲。
写数据和写指令是类似的,就是把 RS 改成 H,把总线改成数据即可。
此外要顺便提一句,这里用的1602液晶所使用的接口时序是摩托罗拉公司所创立的 6800时序,还有另外一种时序是 Intel 公司的 8080 时序,也有部分液晶模块采用,只是相对来说比较少见,大家知道这么回事即可。
1.3.1602 液晶的指令
与单片机寄存器的用法类似,1602 液晶在使用的时候,我们首先要进行初始的功能配置,1602 液晶有以下几个指令需要了解。
1、显示模式设置。
写指令 0x38,设置 16x2 显示,5x7 点阵,8 位数据接口。这条指令对我们这个液晶来说是固定的,必须写 0x38,大家仔细看会发现我们的液晶实际上内部点阵是 5x8 的,还有一些 1602 液晶还兼容串行通信,用 2 个 IO 口即可,但是速度慢,我们这个液晶就是固定的 0x38 模式。
2、显示开/关以及光标设置指令。
这里有 2 条指令,第一条指令,一个字节中 8 位,其中高 5 位是固定的 0b00001,低 3 位我们分别用 DCB 从高到低表示,D=1 表示开显示,D=0 表示关显示;C=1 表示显示光标,C=0 表示不显示光标;B=1 表示光标闪烁,B=0 表示光标不闪烁。
第二条指令,高 6 位是固定的 0b000001,低 2 位我们分别用 NS 从高到低表示,其中 N=1 表示读或者写一个字符后,指针自动加 1,光标自动加 1,N=0 表示读或者写一个字符后指针自动减 1,光标自动减 1;S=1 表示写一个字符后,整屏显示左移(N=1)或右移(N=0),以达到光标不移动而屏幕移动的效果,如同我们的计算器输入一样的效果,而 S=0 表示写一个字符后,整屏显示不移动。
3、清屏指令。
固定的,写入 0x01 表示显示清屏,其中包含了数据指针清零,所有的显示清零。写入 0x02 则仅仅是数据指针清零,显示不清零。
4、RAM 地址设置指令。
该指令码的最高位为 1,低 7 位为 RAM 的地址,RAM 地址与液晶上字符的关系如上表所示。通常,我们在读写数据之前都要先设置好地址,然后再进行数据的读写操作。
2.实例
2.1.显示字符
1602 液晶手册提供了一个初始化过程,由于不检测“忙”位,所以程序比较复杂,而我们总结了一个更加简易方便的过程提供给大家,手册上描述的那个,大家仅仅作为了解就可以了,下面我把程序写出来大家看下,我们的初始化只用了 4 条语句,没有像手册介绍的那么繁琐。
#include<reg52.h>
typedef unsigned char uchar ;
typedef unsigned int uint ;
typedef unsigned long ulong ;
#define LCD1602_DB P0
sbit LCD1602_RS = P2^6 ;
sbit LCD1602_RW = P2^5 ;
sbit LCD1602_E = P2^7 ;
void InitLcd1602();
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void main()
{
unsigned char str[] = "Kingst Studio";
InitLcd1602();
LcdShowStr(2, 0, str);
LcdShowStr(0, 1, "Welcome to YU");
while (1);
}
/* 液晶等待函数 */
void LcdWaitReady()
{
uchar sta ;
LCD1602_DB = 0xFF ;
LCD1602_RS = 0 ;
LCD1602_RW = 1 ;
do
{
LCD1602_E = 1 ;
sta = LCD1602_DB ; // 读取状态字
LCD1602_E = 0 ;
} while(sta & 0x80) ; // bit7为1表示液晶正忙,重复检测为0为止
}
/* 向LCD1602写入命令函数 */
void LcdWriteCmd(uchar cmd)
{
LcdWaitReady() ;
LCD1602_RS = 0 ;
LCD1602_RW = 0 ;
LCD1602_DB = cmd ;
LCD1602_E = 1 ;
LCD1602_E = 0 ;
}
/* 向LCD1602写入数据函数 */
void LcdWriteDat(uchar dat)
{
LcdWaitReady() ;
LCD1602_RS = 1 ;
LCD1602_RW = 0 ;
LCD1602_DB = dat ;
LCD1602_E = 1 ;
LCD1602_E = 0 ;
}
/* 设置RAM起始地址,亦光标位置,(x,y)- 对应屏幕上的字符坐标 */
void LcdSetCursor(uchar x , uchar y)
{
uchar addr ;
if (y == 0) // 由输入的屏幕坐标计算显示RAM的地址
addr = 0x00 + x ;
else
addr = 0x40 + x ;
LcdWriteCmd(addr | 0x80) ; // 设置RAM的地址
}
/* 在液晶上显示字符串 (x,y)- 对应屏幕上的起始坐标,stc - 字符串指针 */
void LcdShowStr(uchar x , uchar y ,uchar *str)
{
LcdSetCursor(x , y) ; // 设置起始地址
while(*str != '\0')
{
LcdWriteDat(*str ++) ; // 连续写入字符串数据,直到检测到结束符
}
}
/* 区域清除,清除从(x,y)坐标起始的len个字符位 */
void LcdAreaClear(uchar x , uchar y , uchar len)
{
LcdSetCursor(x , y) ;
while(len --)
{
LcdWriteDat(' ') ;
}
}
/* 整屏清除 */
void LcdFullClear()
{
LcdWriteCmd(0x01) ;
}
/* LCD1602初始化 */
void InitLcd1602()
{
LcdWriteCmd(0x38) ; // 16*2 显示,5*7点阵,8位数据接口
LcdWriteCmd(0x0C) ; // 显示器开,光标关闭
LcdWriteCmd(0x06) ; // 文字不动,地址自动+1
LcdWriteCmd(0x01) ; // 清屏
}
文章来源:https://www.toymoban.com/news/detail-489206.html
2.2.整屏移动
我们前边学点阵 LED 的时候,可以实现上下移动,左右移动等。而对于 1602 液晶来说,也可以进行屏幕移动,实现我们想要的一些效果,那我们来用一个例程实现字符串在 1602 液晶上的左移。文章来源地址https://www.toymoban.com/news/detail-489206.html
#include<reg52.h>
typedef unsigned char uchar ;
typedef unsigned int uint ;
typedef unsigned long ulong ;
#define LCD1602_DB P0
sbit LCD1602_RS = P2^6 ;
sbit LCD1602_RW = P2^5 ;
sbit LCD1602_E = P2^7 ;
bit flag500ms = 0; //500ms 定时标志
uchar T0RH = 0; //T0 重载值的高字节
uchar T0RL = 0; //T0 重载值的低字节
//待显示的第一行字符串
uchar code str1[] = "Kingst Studio";
//待显示的第二行字符串,需保持与第一行字符串等长,较短的行可用空格补齐
uchar code str2[] = "Let's move...";
void ConfigTimer0(uint ms);
void InitLcd1602();
void LcdShowStr(uchar x, uchar y, uchar *str, uchar len);
void main()
{
uchar i;
uchar index = 0; //移动索引
uchar pdata bufMove1[16+sizeof(str1)+16]; //移动显示缓冲区 1
uchar pdata bufMove2[16+sizeof(str2)+16]; //移动显示缓冲区 2
EA = 1; //开总中断
ConfigTimer0(10); //配置 T0 定时 10ms
InitLcd1602(); //初始化液晶
//缓冲区开头一段填充为空格
for (i=0; i<16; i++)
{
bufMove1[i] = ' ';
bufMove2[i] = ' ';
}
//待显示字符串拷贝到缓冲区中间位置
for (i=0; i<(sizeof(str1)-1); i++)
{
bufMove1[16+i] = str1[i];
bufMove2[16+i] = str2[i];
}
//缓冲区结尾一段也填充为空格
for (i=(16+sizeof(str1)-1); i<sizeof(bufMove1); i++)
{
bufMove1[i] = ' ';
bufMove2[i] = ' ';
}
while (1)
{
if (flag500ms) //每 500ms 移动一次屏幕
{
flag500ms = 0;
//从缓冲区抽出需显示的一段字符显示到液晶上
LcdShowStr(0, 0, bufMove1+index, 16);
LcdShowStr(0, 1, bufMove2+index, 16);
//移动索引递增,实现左移
index++;
if (index >= (16+sizeof(str1)-1))
{ //起始位置达到字符串尾部后即返回从头开始
index = 0;
}
}
}
}
/* 定时器函数 */
void ConfigTimer0(uint ms)
{
ulong tmp ;
tmp = 11059200 / 12 ; // 定时器频率
tmp = (tmp * ms) / 1000 ; // 计算所需的计数值
tmp = 65536 - tmp ; // 计算定时器重载值
tmp = tmp + 18 ; // 补偿中断响应延时造成的误差
T0RH = (uchar)(tmp >> 8) ;
T0RL = (uchar)tmp ;
TMOD = TMOD & 0xF0 ;
TMOD = TMOD | 0x01 ;
TH0 = T0RH ; // 加载T0重载值
TL0 = T0RL ;
ET0 = 1 ; // 使能T0中断
TR0 = 1 ; // 启动T0
}
/* 液晶等待函数 */
void LcdWaitReady()
{
uchar sta ;
LCD1602_DB = 0xFF ;
LCD1602_RS = 0 ;
LCD1602_RW = 1 ;
do
{
LCD1602_E = 1 ;
sta = LCD1602_DB ; // 读取状态字
LCD1602_E = 0 ;
} while(sta & 0x80) ; // bit7为1表示液晶正忙,重复检测为0为止
}
/* 向LCD1602写入命令函数 */
void LcdWriteCmd(uchar cmd)
{
LcdWaitReady() ;
LCD1602_RS = 0 ;
LCD1602_RW = 0 ;
LCD1602_DB = cmd ;
LCD1602_E = 1 ;
LCD1602_E = 0 ;
}
/* 向LCD1602写入数据函数 */
void LcdWriteDat(uchar dat)
{
LcdWaitReady() ;
LCD1602_RS = 1 ;
LCD1602_RW = 0 ;
LCD1602_DB = dat ;
LCD1602_E = 1 ;
LCD1602_E = 0 ;
}
/* 设置RAM起始地址,亦光标位置,(x,y)- 对应屏幕上的字符坐标 */
void LcdSetCursor(uchar x , uchar y)
{
uchar addr ;
if (y == 0) // 由输入的屏幕坐标计算显示RAM的地址
addr = 0x00 + x ;
else
addr = 0x40 + x ;
LcdWriteCmd(addr | 0x80) ; // 设置RAM的地址
}
/* 在液晶上显示字符串 (x,y)- 对应屏幕上的起始坐标,stc - 字符串指针 */
void LcdShowStr(uchar x , uchar y ,uchar *str,uchar len)
{
LcdSetCursor(x , y) ; // 设置起始地址
while(len--)
{
LcdWriteDat(*str ++) ; // 连续写入字符串数据,直到检测到结束符
}
}
/* 区域清除,清除从(x,y)坐标起始的len个字符位 */
void LcdAreaClear(uchar x , uchar y , uchar len)
{
LcdSetCursor(x , y) ;
while(len --)
{
LcdWriteDat(' ') ;
}
}
/* 整屏清除 */
void LcdFullClear()
{
LcdWriteCmd(0x01) ;
}
/* LCD1602初始化 */
void InitLcd1602()
{
LcdWriteCmd(0x38) ; // 16*2 显示,5*7点阵,8位数据接口
LcdWriteCmd(0x0C) ; // 显示器开,光标关闭
LcdWriteCmd(0x06) ; // 文字不动,地址自动+1
LcdWriteCmd(0x01) ; // 清屏
}
/* T0 中断服务函数,定时 500ms */
void InterruptTimer0() interrupt 1
{
static unsigned char tmr500ms = 0;
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
tmr500ms++;
if (tmr500ms >= 50)
{
tmr500ms = 0;
flag500ms = 1;
}
}
到了这里,关于51单片机入门——LCD1602的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!