第27章 LCD1602显示
第二十七章 LCD1602液晶显示
1. 导入
在前面章节, 我们已经学习过几种显示装置, 例如静态数码管、 动态数码管、8*8LED 点阵、 串口通信。 使用它们可以直观显示一些字符数据, 但是它们也有各种局限性, 比如显示字符数据太少, 硬件设计复杂、 代码编写难度大等。
这一章就来介绍一种非常简单且常用的显示装置--LCD1602 液晶显示器, 使用它可以显示更多的字符数字。 我们开发板上集成了一个 LCD1602 液晶显示器接口, 将LCD1602 液晶对应插入即可。 本章要实现的功能是: 系统运行时, 在 LCD1602 液晶上显示字符信息。
2. LCD1602介绍
2.1 LCD1602简介
1602 液晶也叫 1602 字符型液晶, 它能显示 2 行字符信息, 每行又能显示 16个字符。
它是一种专门用来显示字母、 数字、 符号的点阵型液晶模块。 它是由若干个 5x7 或者 5x10 的点阵字符位组成, 每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔, 每行之间也有间隔, 起到了字符间距和行间距的作用, 正因为如此, 所以它不能很好的显示图片。 其实物图如下所示:
在上图中可以看到有 16 个管脚孔, 从左至右管脚编号顺序是 1-16, 其功能定义如下所示:
有几个重要的管脚需要我们分析
-
3脚:VL, 液晶显示偏压信号, 用于调整 LCD1602 的显示对比度, 一般会外接电位器用以调整偏压信号, 注意此脚电压为 0 时可以得到最强的对比度。
-
4脚:RS, 数据/命令选择端, 当此脚为高电平时, 可以对 1602 进行数据字节的传输操作, 而为低电平时, 则是进行命令字节的传输操作。 命令字节, 即是用对 LCD1602 的一些工作方式作设置的字节; 数据字节, 即使用以在 1602 上显示的字节。 值得一提的是, LCD1602 的数据是 8 位的。
-
5脚:R/W, 读写选择端。 当此脚为高电平可对 LCD1602 进行读数据操作,反之进行写数据操作。
-
6脚:E, 使能信号, 其实是 LCD1602 的数据控制时钟信号, 利用该信号的上升沿实现对 LCD1602 的数据传输。
-
7~14脚:8 位并行数据口, 而 51 单片机一组 IO 也是 8 位, 使得对 LCD1602的数据读写大为方便。
在LCD1602内部含有80个字节的DDRAM,他是用来寄存显示字符的。其地址和屏幕的对应关系如下:
从上图可知, 不是所有的地址都可以直接用来显示字符数据, 只有第一行中的 00-0F, 第二行中的 40-4F 才能显示, 其他地址只能用于存储。
要显示字符时要先输入显示字符地址, 也就是告诉模块在哪里显示字符, 例如第二行第一个字符的地址是 40H, 那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢? 这样不行, 因为写入显示地址时要求最高位 D7 恒定为高电平 1所以实际写入的数据应该是 01000000B(40H) +10000000B(80H)=11000000B(C0H)。 在 1602 中我们就用前 16 个就行了。 第二行也一样用前 16 个地址。
2.2 LCD1602常用指令
- 清屏指令
功能:
-
清除液晶显示器, 即将 DDRAM 的内容全部填入"空白"的 ASCII 码 20H;
-
光标归位, 即将光标撤回液晶显示屏的左上方。
-
将地址计数器(AC)的值设为 0。
- 模式设置指令
功能:
-
设定每次写入 1 位数据后光标的移位方向, 并且设定每次写入的一个字符是否移动。
-
0=写入新数据后光标左移 1=写入新数据后光标右移
-
0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移1个字符
- 显示开关控制指令
功能:
控制显示器开/关、 光标显示/关闭以及光标是否闪烁。
-
D:0=显示功能关 1=显示功能开
-
C:0=无光标 1=有光标
-
B:0=光标闪烁 1=光标不闪烁
- 功能设定指令
功能:
设定数据总线位数、显示的行数及字符
-
DL:0=数据总线为4位 1=数据总线为8位
-
N:0=显示1行 1=显示2行
-
0=5 x 7 点阵/每字符 1=5 x 10 点阵/每字符
2.3 LCD1602使用
要使用 LCD1602, 首先需要对其初始化, 即通过写入一些特定的指令实现。然后选择要在 LCD1602 的哪个位置显示并将所要显示的数据发送到 LCD 的DDRAM。 使用 LCD1602 通常都是用于写数据进去, 很少使用读功能。 LCD1602 操作步骤如下所示:
-
初始化
-
写命令(RS=L),设置显示坐标
-
写数据(RS=H)
在此, 不需要读出它的数据的状态或者数据本身。 所以只需要看两个写时序:
-
当要写指令字, 设置 LCD1602 的工作方式时: 需要把 RS 置为低电平, RW置为低电平, 然后将数据送到数据口 D0~D7, 最后 E 引脚一个高脉冲将数据写入。
-
当要写入数据字, 在 1602 上实现显示时: 需要把 RS 置为高电平, RW 置为低电平, 然后将数据送到数据口 D0~D7, 最后 E 引脚一个高脉冲将数据写入。
写指令和写数据, 差别仅仅在于 RS 的电平不一样而已。 以下是 LCD1602 的时序图:
从上图可以看到, 以上给的时间参数全部是 ns 级别的, 而 51 单片机的机器周期是 1us, 指令周期是 2-4 个机器周期, 所以即便在程序里不加延时程序, 也可以很好的配合 LCD1602 的时序要求了。
当要写命令字节的时候, 时间由左往右, RS 变为低电平, R/W 变为低电平,注意看是 RS 的状态先变化完成。 然后这时, DB0~DB7 上数据进入有效阶段, 接着 E 引脚有一个整脉冲的跳变, 接着要维持时间最小值为 tpw=400ns 的 E 脉冲宽度。 然后 E 引脚负跳变, RS 电平变化, R/W 电平变化。 这样便是一个完整的 LCD1602写命令的时序。
更多关于LCD1602参考:【常用传感器】LCD1602液晶工作原理详解及例程代码_lcd1602工作原理-CSDN博客
透彻理解液晶显示模组LCD1602指令集与驱动编程(1) - 阳光&技术 - 博客园
3. 硬件设计
本实验使用到硬件资源如下:
- LCD1602 液晶
开发板上集成了一个 LCD1602 液晶接口, 下面来看下开发板上 LCD1602 液晶接口电路, 如下图所示:
从上图中可知, LCD1602 的 8 位数据口 DB0-DB7 与单片机的 P0.0-P0.7 管脚连接, LCD1602 的 RS、 RW、 E 脚与单片机的 P2.6、 P2.5、 P2.7 管脚连接。 RJ1是一个电位器, 用来调节 LCD1602 对比度即显示亮度。
4. 软件设计
本章所要实现的功能是:在LCD1602上显示字符信息
- LCD1602驱动函数
#ifndef _lcd1602_H
#define _lcd1602_H
#include "public.h"
//LCD1602数据口4位和8位定义,若为1,则为LCD1602四位数据口驱动,反之为8位
#define LCD1602_4OR8_DATA_INTERFACE 0 // 默认使用8位数据口LCD1602
//管脚定义
sbit LCD1602_RS = P2^6; // 数据命令选择
sbit LCD1602_RW = P2^5; // 读写选择
sbit LCD1602_E = P2^7; // 使能信号
#define LCD1602_DATAPORT P0 // 宏定义LCD1602数据端口
//函数声明
void lcd1602_init(void); // 初始化
void lcd1602_clear(void); // 清屏指令
void lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *str); // 字符显示函数
#endif
#include "lcd1602.h"
// 1602写入函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_write_cmd(unsigned char cmd)
{
LCD1602_RS = 0; // 选择命令
LCD1602_RW = 0; // 选择写
LCD1602_E = 0; // 使能
LCD1602_DATAPORT = cmd; // 准备命令
delay_ms(1);
LCD1602_E = 1; // 使能脚E先上升沿写入
delay_ms(1);
LCD1602_E=0; // 使能脚E后负跳变完成写入
}
#else // 4位LCD
void lcd1602_write_cmd(unsigned char cmd)
{
LCD1602_RS = 0;//选择命令
LCD1602_RW = 0;//选择写
LCD1602_E = 0;
LCD1602_DATAPORT = cmd;//准备命令
delay_ms(1);
LCD1602_E = 1;//使能脚E先上升沿写入
delay_ms(1);
LCD1602_E = 0;//使能脚E后负跳变完成写入
LCD1602_DATAPORT=cmd<<4; // 准备命令
delay_ms(1);
LCD1602_E = 1; // 使能脚E先上升沿写入
delay_ms(1);
LCD1602_E = 0;//使能脚E后负跳变完成写入
}
#endif
// 写入数据函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_write_data(unsigned char dat)
{
LCD1602_RS = 1; // 选择数据
LCD1602_RW = 0; // 选择写
LCD1602_E = 0;
LCD1602_DATAPORT = dat; // 准备数据
delay_ms(1);
LCD1602_E = 1; // 使能脚E先上升沿写入
delay_ms(1);
LCD1602_E = 0; // 使能脚E后负跳变完成写入
}
#else
void lcd1602_write_data(unsigned char dat) // 4位
{
LCD1602_RS = 1; // 选择数据
LCD1602_RW = 0; // 选择写
LCD1602_E = 0;
LCD1602_DATAPORT = dat;//准备数据
delay_ms(1);
LCD1602_E = 1; // 使能脚E先上升沿写入
delay_ms(1);
LCD1602_E = 0; // 使能脚E后负跳变完成写入
LCD1602_DATAPORT=dat<<4; // 准备数据
delay_ms(1);
LCD1602_E = 1; // 使能脚E先上升沿写入
delay_ms(1);
LCD1602_E = 0; // 使能脚E后负跳变完成写入
}
#endif
// 1602初始化函数
#if (LCD1602_4OR8_DATA_INTERFACE == 0) // 8位LCD
void lcd1602_init(void)
{
lcd1602_write_cmd(0x38); // 数据总线8位,显示2行,5*7点阵/字符
lcd1602_write_cmd(0x0c); // 显示功能开,无光标,光标闪烁
lcd1602_write_cmd(0x06); // 写入新数据后光标右移,显示屏不移动
lcd1602_write_cmd(0x01); // 清屏
}
#else
void lcd1602_init(void) // 4位
{
lcd1602_write_cmd(0x28); // 数据总线4位,显示2行,5*7点阵/字符
lcd1602_write_cmd(0x0c); // 显示功能开,无光标,光标闪烁
lcd1602_write_cmd(0x06); // 写入新数据后光标右移,显示屏不移动
lcd1602_write_cmd(0x01); // 清屏
}
#endif
// 1602清屏函数
void lcd1602_clear(void)
{
lcd1602_write_cmd(0x01);
}
// 1602显示字符串函数(xy代表坐标,str代表字符串)
void lcd1602_show_string(unsigned char x,unsigned char y,unsigned char *str)
{
unsigned char i=0;
if(y>1 || x>15)
return; // 行列参数不对则强制退出
if(y < 1) // 第1行显示
{
while(*str != '\0')//字符串是以'\0'结尾,只要前面有内容就显示
{
if(i <16 - x)//如果字符长度超过第一行显示范围,则在第二行继续显示
{
lcd1602_write_cmd(0x80+i+x); // 第一行显示地址设置
}
else
{
lcd1602_write_cmd(0x40+0x80+i+x-16); // 第二行显示地址设置
}
lcd1602_write_data(*str); // 显示内容
str++; // 指针递增
i++;
}
}
else // 第2行显示
{
while(*str != '\0')
{
if(i < 16 - x) //如果字符长度超过第二行显示范围,则在第一行继续显示
{
lcd1602_write_cmd(0x80+0x40+i+x);
}
else
{
lcd1602_write_cmd(0x80+i+x-16);
}
lcd1602_write_data(*str);
str++;
i++;
}
}
}
- 主函数
#include "public.h"
#include "lcd1602.h"
void main()
{
lcd1602_init();//LCD1602初始化
lcd1602_show_string(0,0,"Hello hazy1k"); // 第一行显示字符
lcd1602_show_string(0,1,"0123456789"); // 第二行显示数字
while(1)
{
}
}
与前面一样,难点在于配置,可以看到主函数倒是很简单,这个代码可以不用很深理解,但是还是要理解一些重要部分:
在代码中可以看到有如下格式:
#if (LCD1602_4OR8_DATA_INTERFACE==0)
...( 省略内容 1)
#else
...( 省略内容 2)
#endif
当初学习C语言基础的时候应该没有遇到过,这是 C 语言中的条件编译, 如果 LCD1602_4OR8_DATA_INTERFACE 值等于 0,则执行省略内容 1 部分, 否则执行省略内容 2 部分, 这个和 if...else 是类似的。而 LCD1602_4OR8_DATA_INTERFACE 是在 lcd1602.h 中定义好的宏, 用于选择使用的 LCD1602 是 8 位数据口还是 4 位数据口, 通过这个宏就可以兼容 8 位和 4 位 2款 LCD1602 驱动, 非常方便。 如果大家觉得代码过长, 可以将兼容的一块去掉,这样代码就简短明了。
lcd1602_write_cmd 函数是 LCD1602 写命令, lcd1602_write_data 函数是LCD1602 写数据, 它们之间的区别仅在于控制脚 RS 的值, 如果为 0 则写命令,为 1 则写数据。 对于 8 位数据和 4 位数据口的驱动, 不同之处在于 8 位数据是一次性传输到端口, 而 4 位数据口需要将一个字节数据分两次传输, 高 4 位和低 4位
lcd1602_init 函数是 LCD1602 初始化函数, 根据其命令设置相应的值, 开启LCD1602 显示。
lcd1602_clear 函数是 LCD1602 清屏函数, 实际上也是调用lcd1602_write_cmd 函数写 0X01 命令。
cd1602_show_string 函数是 LCD1602 显示字符函数, 该函数有 3 个入口参数, x 和 y 用来确定显示的坐标, x 范围是 0-15, y 范围是 0-1。 str 参数是一个指针类型变量, 用于传递显示的内容。 函数内首先判断 xy 坐标是否有效, 无效则强制退出, 然后根据 xy 值在对应的坐标位置显示内容。
5. 小结
这一章看起来赶紧难,但是我们完全可以只简单了解一下,后面要用到这个1602显示就只需要封装起来就行了,直接使用不用理解,但是主函数中的内容还是要理解哈
总得来说,就是一个配置的事,网上都有现成的copy,或者直接复制我的也行,如果学有余力可以尝试自己编写配置函数。
2024.7.24 第一次修订
2024.8.24 第二次修订,后期不再维护