本文是关于液晶显示屏的相关介绍。相对于静态数码管、动态数码管、LED点阵等,LCD1602液晶显示器能够显示更多的字符数字信息,并且也是常用的一种显示装置。
1602液晶,也叫做1602字符型液晶,可以显示2行字符信息,每行可以显示16个字符,是一种专门用来显示字母、数字、符号的点阵型液晶模块。
LCD1602由若干个5x7或者5x10的点阵字符位组成,每个点阵字符位都可以显示一个字符,每位之间有一个点距地间隔,每行之间也有间隔,起到了字符间距和行间距的作用。
LCD1602实物如下:
从实物图中可以看到16个管脚孔,从左至右管脚编号顺序是1-16,管脚功能定义如下表:
管脚编号 | 符号 | 管脚说明 |
---|---|---|
1 | VSS | 电源地 |
2 | VDD | 电源正极 |
3 | VL | 液晶显示偏压信号 |
4 | RS | 数据/命令选择端 H/L |
5 | R/W | 读/写选择端 H/L |
6 | E | 使能信号 |
7~14 | D0~D7 | Data I/O |
15 | BLA | 背光源正极 |
16 | BLK | 背光源负极 |
管脚详细说明:
LCD1602内部含有80字节的DDRAM,是用来存储显示字符的。其地址和屏幕的对应关系如下:
显示位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | … | 40 | |
---|---|---|---|---|---|---|---|---|---|---|
DDRAM地址 | 第一行 | 00H | 01H | 02H | 03H | 04H | 05H | 06H | … | 27H |
DDRAM地址 | 第二行 | 40H | 41H | 42H | 43H | 44H | 45H | 46H | … | 67H |
从上图可以看到,并不是所有的地址都能直接用来显示字符。只有第一行中的00-0F,第二行中的40-4F才能显示,其它地址只能用于存储。
要显示字符时首先要输入显示字符地址,即明确在哪里显示字符。比如第二行第一个字符地址是40H,不能够直接写入40H,因为写入显示地址时要求最高位D7为1,所以第二行第一个字符地址应该是40H|80H=C0H。
LCD1602有一些常用指令,这些指令对于初始化是必须的。
要使用LCD1602,首先需要对其初始化,即通过写入一些特定的指令实现;然后选择要在LCD1602的哪个位置显示并将所要显示的数据发送到LCD的DDRAM。
使用LCD1602通常用于写数据进去,比较少使用读功能。
LCD1602操作步骤如下:
① 初始化
② 写命令,RS=L,设置显示坐标;
③ 写数据,RS=H;
这里不需要读数据,所以只需要两个写时序。
从上面两个时序可以看出,写指令和写数据只是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液晶显示屏。
proteus中设计原理图如下:
从上面的原理图可以看出该电路不是独立的,LCD1602的8位数据口DB0-DB7与单片机的P0.0-P0.7管脚链接;LCD1602的RS、RW、E脚与单片机的P2.6、P2.5、P2.7管脚链接;RV1是一个电位器,用来调节LCD1602显示亮度。
软件设计:
LCD发送命令和数据代码如下:
#include <reg52.h>
#include "lcd1602.h"
// 延时函数,延时ims,12MHz晶振下,12分频单片机的延时
void lcd1602_Delay1ms(uint i)
{
uchar a,b;
for(;i>0;i--)
{
for(b=199;b>0;b--)
{
for(a=1;a>0;a--);
}
}
}
#ifndef LCD1602_4PINS // 8位数据线
// 向LCD写入一个字节的命令
void lcd1602_WriteCom(uchar com)
{
LCD_EN=0; // 使能
LCD_RS=0; // 发送命令
LCD_RW=0; // 选择写命令
LCD_DATA=com; // 放入数据
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
}
// 向LCD写入一个字节的数据
void lcd1602_WriteData(uchar dat)
{
LCD_EN=0; // 使能
LCD_RS=1; // 发送数据
LCD_RW=0; // 选择写命令
LCD_DATA=dat; // 放入数据
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
}
// LCD1602初始化
void lcd1602_Init()
{
lcd1602_WriteCom(LCD_MODE_8_2_5X7); // 8位数据,显示2行,5x7点阵/每字符 38H
lcd1602_WriteCom(LCD_CURSOR_RIGHT); // 写入数据后光标右移 06H
lcd1602_WriteCom(LCD_CLEAR); // 清屏 01H
lcd1602_WriteCom(LCD_OPENSHOW); // 显示开 0CH
lcd1602_WriteCom(LCD_START_ADDR1); // 设置数据指针地址起始点是第一行第一列,80H=00H|80H
}
#else // 4位数据线
// 向LCD写入一个字节的命令
void lcd1602_WriteCom(uchar com)
{
LCD_EN=0; // 使能
LCD_RS=0; // 发送命令
LCD_RW=0; // 选择写命令
LCD_DATA=com; // 放入数据,4位数据,接线到P0的高四位,传送高四位不用改
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
LCD_DATA=com<<4;
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
}
// 向LCD写入一个字节的数据
void lcd1602_WriteData(uchar dat)
{
LCD_EN=0; // 使能
LCD_RS=1; // 发送数据
LCD_RW=0; // 选择写命令
LCD_DATA=dat; // 放入数据,4位数据,接线到P0的高四位,传送高四位不用改
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
LCD_DATA=dat<<4;
lcd1602_Delay1ms(1); // 等待数据稳定
LCD_EN=1; // 写入时序
lcd1602_Delay1ms(5); // 保持稳定
LCD_EN=0;
}
// LCD1602初始化
void lcd1602_Init()
{
lcd1602_WriteCom(0x32); // 将8位总线转为4位总线
lcd1602_WriteCom(LCD_MODE_8_2_5X7); // 8位数据,显示2行,5x7点阵/每字符28H
lcd1602_WriteCom(LCD_CURSOR_RIGHT); // 写入数据后光标右移 06H
lcd1602_WriteCom(LCD_CLEAR); // 清屏 01H
lcd1602_WriteCom(LCD_OPENSHOW); // 显示开 0cH
lcd1602_WriteCom(LCD_START_ADDR1); // 设置数据指针地址起始点是第一行第一列,80H=00H|80H
}
#endif
主函数调用:
/*
实现功能:系统运行后,LCD1602液晶显示屏显示字符
[2024-01-03] zoya
*/
#include <reg52.h>
#include "lcd1602.h"
typedef unsigned char u8;
typedef unsigned int u16;
u8 Disp[]="Pechin Science:";
void delay(u16 i)
{
while(i--);
}
void main()
{
u8 i;
lcd1602_Init(); // LCD初始化
for(i=0;i<16;i++)
{
lcd1602_WriteData(Disp[i]);
delay(5000);
}
while(1);
}
proteus仿真结果:
LCD扩展实验实现的功能:系统运行时,LCD1602显示时间,显示格式:第一行显示年-月-日 星期“xxxx-xx-xx xth”,第二行显示时:分:秒"xx:xx:xx"。使用EEPROM记录是否已经初始化(读取EEPROM中0x00地址的数据,如果数据为0xff表示未进行初始化,如果为0x01表示已经进行初始化)。用到的资源有DS1302时钟芯片、AT24C02 EEPROM芯片、LCD1602液晶显示器。
proteus原理图设计如下:
设计思路,首先读取eeprom中0x00地址的数据,根据数据判断DS1302是否进行初始化,然后初始化LCD1602,在循环中读取时间并将其显示到LCD中。主程序编写如下:
void main()
{
u8 i;
u8 dat;
dat=AT24C02Read(EN_INIT_ADDR);
if(0xff==dat)
{
DS1302Init();
AT24C02Write(EN_INIT_ADDR, 0x01);
}
lcd1602_Init();
while(1)
{
DataPros();
lcd1602_WriteCom(LCD_START_ADDR1); // 年月日星期显示在第一行
for(i=0;i<14;i++)
{
lcd1602_WriteData(Disp1Row[i]);
}
lcd1602_WriteCom(LCD_START_ADDR2); // 时分秒显示在第二行
for(i=0;i<8;i++)
{
lcd1602_WriteData(Disp2Row[i]);
}
delay(500);
}
}
数据处理函数:
uchar szTime[]="0123456789 -th:";
uchar Disp2Row[16];
uchar Disp1Row[16];
void DataPros()
{
DS1302ReadTime();
Disp1Row[0]=szTime[2]; // 2
Disp1Row[1]=szTime[0]; // 0
Disp1Row[2]=szTime[TIME[6]>>4]; // 年的十位
Disp1Row[3]=szTime[TIME[6]&0x0f]; // 年的个位
Disp1Row[4]=szTime[11]; // -
Disp1Row[5]=szTime[TIME[4]>>4&0x01]; // 月的十位
Disp1Row[6]=szTime[TIME[4]&0x0f]; // 月的个位
Disp1Row[7]=szTime[11]; // -
Disp1Row[8]=szTime[TIME[3]>>4&0x03]; // 日的十位
Disp1Row[9]=szTime[TIME[3]&0x0f]; // 日的个位
Disp1Row[10]=szTime[10]; // 空格
Disp1Row[11]=szTime[(TIME[5]&0x0f) - 1]; // 星期几
Disp1Row[12]=szTime[12]; // t
Disp1Row[13]=szTime[13]; // h
Disp2Row[0]=szTime[TIME[2]/16]; // 时的十位
Disp2Row[1]=szTime[TIME[2]&0x0f]; // 时的个位
Disp2Row[2]=szTime[14]; // : 冒号
Disp2Row[3]=szTime[TIME[1]/16]; // 分的十位
Disp2Row[4]=szTime[TIME[1]&0x0f]; // 分的个位
Disp2Row[5]=szTime[14]; // : 冒号
Disp2Row[6]=szTime[TIME[0]>>4&0x07]; // 秒的十位
Disp2Row[7]=szTime[TIME[0]&0x0f]; // 秒的个位
}
仿真结果: