11.1 1602 液晶显示模块简介
1602 液晶显示模块是一种通用的工业液晶显示模块,专门用来显示字母、数字、符号等的点阵型液晶显示模块。从名字就可以看出该液晶显示模块有 2 行,每行 16 各字符共可以显示 32 个字符。每个字符是由 5x7 或者 5x11 的点阵位组成,我们这里讲解的为 5x7 模式的模块。模块显示界面结构尺寸如下图所示:
与数码管相比,不需周期性的对显示进行刷新,只需将需要显示的字符按照时序要求发送给 1602 液晶显示模块便可一直显示,有效的节省了单片机的系统资源。通过单片机控制 1602 的读写硬件( RS, RW)进行控制,根据相应的时序对数据 D0-D7 进行操作便可实现显示指定的字符。1602 模块共有 16 个引脚,各引脚定义如下图所示:
-
第 1,2 引脚:VDD、VS S 为 1602 供电的正负极,最佳供电电压为 5V。
-
第 3 引脚:VL 液晶显示偏压信号,即为显示屏的对比度调节端,当接电源时对比度最高,接低时对比对最低,在使用中往往通过接一个可变电阻来调节显示对比度。
-
第 4 引脚:RS 为数据/命令选择端,当 RS=1 时,选择操作 1602 内部的数据寄存器,当 RS=0 时,选择操作内部指令寄存器。
-
第 5 引脚:R/W 为读写选择端,当 R/W=1 时,对 1602 进行读数据操作,当 R/W=0 时,对 1602 进行写操作。
-
第 6 引脚:E 为 1602 液晶显示模块使能端,与 RS,R/W 配合使用在高电平时读取信息,负跳变时执行指令。
-
第 7-14 引脚:为 8 位的双向数据端口 D0~D7。
-
第 15,16 引脚:BLA 背光源正极,BLK 背光源负极。BLK 接地,BLA 接电源 VDD 时,1602 液晶背光点亮,接 GND 时,背光熄灭。
RY-51 单片机开发板 1602 液晶显示模块的电路连接图如下图所示:
如上图所示,1602 的 4,5,6 引脚分别接到到了单片机的 P3.4-P3.6 端口,数据端口 D0-D7 链接到单片机的 P0 端口与 RY-51 开发板的 8 位数码管共用 P1 端口,因此,在应用 1602 液晶显示模块的时候首先要关闭数码管。
11.2 1602 读、写操作时序解析
刚开始接触 1602 液晶显示模块的时候可能会觉得有一点难度,感觉挺复杂,理解起来比较费劲。其实,只要掌握了的操作规律,多次的练习之后你会发现其实很简单。我们要实现对单片机的操作无非就是 1602 的初始化、对 1602 的读操作、写操作。对 1602 液晶显示的一切都是建立在数据的读、写操作之上了,因此,我们只要研究明白了读、写的操作时序,掌握它的应用就非常简单了。通过查找 1602 的数据手册,读、写时序如下图所示:
读操作时序解析如下:
将 R/W 拉高进入读操作模式,同时 RS 要么为高电平要么为低电平,高电平时为读数据操作,低电平时为读状态操作,经过 tSP1 时间之后才可将使能信号 E 拉高,并且 E 的高电平维持时间为 tPW,在使能信号 E 拉高之后不超过 tD 时间内,1602 液晶显示器将数据放在 DB0~DB7 数据线上。这时我们将数据读取并将使能 E 拉低,整个数据或状态的读取就完成了。
那么,我们在将某个信号拉高或者拉低需要满足上面一些时间的先后顺序要求。例如我们以读 1602 状态为例,第一条语句首先将 RS 拉低,即 RS = 0,然后将 R/W 拉高,即 R/W=1,接下来将使能信号 E 高,即 E=1,上面两条语句的时间间隔要求不低于 tSP1,查询数据表格可知 tSP1 最小值为 30ns,而我们开发板执行一条语句的时间大约为 1us,因此远远能满足要求,如果我们在执行语句比较快的系统运行时,为了保证时序要求,可以在两条语句之间插入软件延时。E 的高电平维持时间 tPW 最小值为 150ns,而我们 E 维持高电平约为 1us,满足要求。tD 最大值为 100ns,因此,可以直接将数据读取,接着将 E 拉低完成状态读取,我们将代码放到函数当中,返回值为读取的状态数据,应用的时候只需调用该函数即可,代码如下:
unsigned char RD_sta()//读状态函数
{
unsigned char sta;
RS_1602 = 0;
RW_1602 = 1;//进入读1602状态模式
EN_1602 = 1;//拉高使能信号
sta = P0; //将1602状态数据读取
EN_1602 = 0;//拉低使能,完成读操作
return sta; //将状态值返
}
写操作时序与读操作的基本一致,唯一不同的是,在使能信号 E 拉高之前 tSP2 单片机必须把需要学的数据送到数据端口上。当 RS=1 时,表示将要往 1602 写数据,当 RS=0 时,表示将要往 1602 里面写指令。
LCD1602 时序参数如下图所示:
我们分别把写数据、写指令分别放到函数 WR_Cmd(unsigned char cmd)、 WR_Dat(unsigned char dat)中。
void WR_Cmd(unsigned char cmd)//写指令函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 0;
RW_1602 = 0;//进入写指令模式
P0 = cmd; //将指令数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
void WR_Dat(unsigned char dat)//写数据函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 1;
RW_1602 = 0;//进入写数据模式
P0 = dat; //将数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
在上述函数中,增加了一个 Ready()函数,这个函数的作为有先查询 1602 是否处于空闲状态,当为空闲状态时,才开始写操作。到目前为止,我们已经完成了对 1602 最底层的数据读写操作,下面我们只要按照数据手册的要求调用这三个函数便可实现各种类型的显示功能。对于 1602 而言,读状态函数 RD_sta(),的返回值数据格式如下:
如上表所示,返回状字的低六位为当前数据地址指针的数值,最高位读写操作使能,当返回值为 1 时,表示 1602 正处于忙的状态,不允许读写操作,当返回值为 0 时,表示可以对 1602 进行读写,因此,在读写前进行判断,我们将忙检测函数如下:
void Ready() //空闲检测函数
{
P0 = 0xFF;//P0端口设置为输入时,先赋值0xFF
while(RD_sta() & 0x80);//bit7等于1表示忙,一直检测到0为止
}
11.3 1602 液晶显示初始化
在使用 1602 进行显示前时,首先要通过写一系列的指令对 1602 进行初始化,各指令码及功能定义如下所示:
初始化如下:
-
- 写指令 38H:设置 16x2 显示,5x7 点阵,8 位数据接口;
-
- 写指令 0CH:开显示,关闭光标;
-
- 写指令 06H:读或写完一个字符后,地址指针、光标均加 1;
-
- 写指令 01H:数据指针清零、所示显示清零
将初始化程序放到初始化函数 Init_1602()中,代码如下图所示:
void Init_1602() //1602初始化函
{
WR_Cmd(0x38);//设置16x2显示,5x7点阵,8位数据接口
WR_Cmd(0x0C);//开显示,关闭光标
WR_Cmd(0x06);//读或写完一个字符后,地址指针、光标均加1
WR_Cmd(0x01);//数据指针清零、所示显示清零
}
11.4 1602 液晶显示字符操作
当我们开始要往 1602 里面写字符的时候,我们首先得告诉 1602 我们要从哪个地方开始写,也就是写数据开始地址,1602 共可以显示 2 行 32 个字符,那么每个字符的地址是多少呢?1602 内部控制器有 80 字节的 RAM 缓冲区,RAM 地址与 1602 显示模块的对应关系如下图所示:
如图所示,地址 00H-0FH 的 RAM 与显示模块的第一行一一对应。地址 40H~4FH 的 RAM 与显示模块的第二行一一对应。这里要特别注意的是,第一行到第二行的地址不是连续的。通过写指令可以告诉 1602 我们将要从哪里开始写字符了,写地址指令码及功能定义如下表所示:
例如,若需要往第二行的第一列开始写字符,首先应该写指令 WR_Cmd(0x80 + 0x40)。接下通过调用写数据函数 WR_Dat(dat)将对应的字符显示到 1602 了。写数据函数中的 dat 为字符对应的字符码,字符码可以通过查询 1602 的标准字符库,如下图所示,行坐标为字符码的低 4 位,纵坐标为字符码的高四位。例如,我们要将“C”显示到 1602,需要调用 WR_Dat(0x43)完成显示。 标准 1602 字符字库如下:
将显示字符的步骤写到函数中,函数实的功能为:从第 row 行,第 column 列开始显示字符串 str,函数代码如图所示:
//字符串显示功能:从第row行,第column列开始显示字符串str
void Disp_1602_str(unsigned char row,unsigned char column,char *str)
{
unsigned char addr;
addr = (row-1)*0x40 + (column-1);//组合成地址
WR_Cmd(0x80+addr);//写地址命令
while(*str)//判断str字符串是否已结束
{
WR_Dat(*str++);//将str字符串数据依次写入
}
}
11.5 1602 显示应用实例
我们结合上面讲解的各个函数,实现从 1602 液晶显示模块第 1 行第 3 列开始显示字符串“RongYi RY-51”。由于 RY-51 开发板的 8 个数码管与 1602 共用 P0 口,因此在主程序开始处将数码管关闭,接着初始化 1602,然后显示字符串,最后进入死循环。程序代码如下:
#include <reg52.h>
sbit DU = P2^7;//数码管段选、位选引脚定义
sbit WE = P2^6;
sbit RS_1602 = P3^4; //1602,RS、R/W,E引脚定义
sbit RW_1602 = P3^5;
sbit EN_1602 = P3^6;
//字符串显示函数声明
void Disp_1602_str(unsigned char row,unsigned char column,char *str);
void Init_1602();//1602初始化函数声明
void Ready();//空闲检测函数声明
void WR_Cmd(unsigned char cmd);//写指令函数声明
void WR_Dat(unsigned char dat);//写数据函数声明
unsigned char RD_sta();//读状态函数声明
void main()
{
P0 = 0;//关闭所有数码管
WE = 1;
WE = 0;
Init_1602();//1602初始化
Disp_1602_str(1,3,"RongYi RY-51");//第1行第3列开始显示"RongYi RY-51"
while(1);
}
//字符串显示功能:从第row行,第column列开始显示字符串str
void Disp_1602_str(unsigned char row,unsigned char column,char *str)
{
unsigned char addr;
addr = (row-1)*0x40 + (column-1);//组合成地址
WR_Cmd(0x80+addr);//写地址命令
while(*str)//判断str字符串是否已结束
{
WR_Dat(*str++);//将str字符串数据依次写入
}
}
void Init_1602() //1602初始化函
{
WR_Cmd(0x38);//设置16x2显示,5x7点阵,8位数据接口
WR_Cmd(0x0C);//开显示,关闭光标
WR_Cmd(0x06);//读或写完一个字符后,地址指针、光标均加1
WR_Cmd(0x01);//数据指针清零、所示显示清零
}
void Ready() //空闲检测函数
{
P0 = 0xFF;//P0端口设置为输入时,先赋值0xFF
while(RD_sta() & 0x80);//bit7等于1表示忙,一直检测到0为止
}
unsigned char RD_sta()//读状态函数
{
unsigned char sta;
RS_1602 = 0;
RW_1602 = 1;//进入读1602状态模式
EN_1602 = 1;//拉高使能信号
sta = P0; //将1602状态数据读取
EN_1602 = 0;//拉低使能,完成读操作
return sta; //将状态值返
}
void WR_Cmd(unsigned char cmd)//写指令函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 0;
RW_1602 = 0;//进入写指令模式
P0 = cmd; //将指令数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
void WR_Dat(unsigned char dat)//写数据函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 1;
RW_1602 = 0;//进入写数据模式
P0 = dat; //将数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
将上面程序下载到 RY-51 开发板后观察现象,并通过调准可变电阻 R6 观察 1602 显示器对比度的变化。
大家有没有发现,我们将和 1602 有关的函数都放在主程序所在的。c 文件中,以后我们每次要用到 1602 时,都要将这些函数复制到主函数下面,这样是不是有点不方便。其实,我们可将这些函数都放到另外一个。c 文件、将函数声明放到一个。h 格式的文件中,在主程序中我们只要包含这个文件就可以了,主程序代码如下:
#include <reg52.h>
#include "Drive_1602.h"
sbit DU = P2^7;//数码管段选、位选引脚定义
sbit WE = P2^6;
void main()
{
P0 = 0;//关闭所有数码管
WE = 1;
WE = 0;
Init_1602();//1602初始化
Disp_1602_str(1,3,"RongYi RY-51");//第1行第3列开始显示"RongYi RY-51"
while(1);
}
如上图所示,将 Drive_1602.c、Drive_1602.h 添加到工程当中,并且在主函数文件 Main1602.c 中添加语句#include "Drive_1602.h"即可。Drive_1602.h 文件内容如下所示:
#ifndef __1602_H__
#define __1602_H__
//字符串显示函数声明
extern void Disp_1602_str(unsigned char row,unsigned char column,char *str);
extern void Init_1602();//1602初始化函数声明
void Ready();//空闲检测函数声明
void WR_Cmd(unsigned char cmd);//写指令函数声明
void WR_Dat(unsigned char dat);//写数据函数声明
unsigned char RD_sta();//读状态函数声明
#endif
关键字“extern”表示该函数可以在其它文件中被调用。Drive_1602.c 部分代码如下所示:
#include <reg52.h>
#include "Drive_1602.h"
sbit RS_1602 = P3^4; //1602,RS、R/W,E引脚定义
sbit RW_1602 = P3^5;
sbit EN_1602 = P3^6;
//字符串显示功能:从第row行,第column列开始显示字符串str
void Disp_1602_str(unsigned char row,unsigned char column,char *str)
{
unsigned char addr;
addr = (row-1)*0x40 + (column-1);//组合成地址
WR_Cmd(0x80+addr);//写地址命令
while(*str)//判断str字符串是否已结束
{
WR_Dat(*str++);//将str字符串数据依次写入
}
}
void Init_1602() //1602初始化函
{
WR_Cmd(0x38);//设置16x2显示,5x7点阵,8位数据接口
WR_Cmd(0x0C);//开显示,关闭光标
WR_Cmd(0x06);//读或写完一个字符后,地址指针、光标均加1
WR_Cmd(0x01);//数据指针清零、所示显示清零
}
void Ready() //空闲检测函数
{
P0 = 0xFF;//P0端口设置为输入时,先赋值0xFF
while(RD_sta() & 0x80);//bit7等于1表示忙,一直检测到0为止
}
unsigned char RD_sta()//读状态函数
{
unsigned char sta;
RS_1602 = 0;
RW_1602 = 1;//进入读1602状态模式
EN_1602 = 1;//拉高使能信号
sta = P0; //将1602状态数据读取
EN_1602 = 0;//拉低使能,完成读操作
return sta; //将状态值返
}
void WR_Cmd(unsigned char cmd)//写指令函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 0;
RW_1602 = 0;//进入写指令模式
P0 = cmd; //将指令数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
void WR_Dat(unsigned char dat)//写数据函数
{
Ready(); //检测1602是否处于空闲状态
RS_1602 = 1;
RW_1602 = 0;//进入写数据模式
P0 = dat; //将数据输出
EN_1602 = 1;//拉高使能信号
EN_1602 = 0;//拉低使能,完成写操作
}
除了如上面定义语句外,其他函数代码均从最开始的主程序中拷贝过来即可。
11.6 本章小结
本章详细介绍了 1602 字符显示的原理,以及读写操作函数的实现,后续项目中需要显示字符时,调用上面的函数即可。