1、LCD1602简介
1.1、简要介绍
LCD1602是一种工业字符型液晶显示屏幕,能够同时显示16×2即32字符(16列2行)。每个字符是由 5X8 的点阵组成。如下图所示:
😀VSS:电源接地引脚
😀VDD:电源正极引脚,接+5V
😀VO:对比度调节电压
😀RS:数据/指令选择引脚。0表示指令,1表示数据
😀RW:读/写选择引脚。0表示写,1表示读
😀E:模块使能引脚
😀D0~D7:数据传输引脚
1.2、显示原理
LCD1602的每个显示字符位置是由 5X8 的点阵组成,如下图所示:
如图:字符"A"字模由数据0x0E,0x11,0x11,0x11,0x1F,0x11,0x11,0x00
这8个数据组成。
而在LCD1602将这些字模数据进行固化保存在 CGROM 和CGRAM 中。如下图所示:
如上图所示:字符“A”的字模数据保存在CGROM中地址为的0x41的位置,我们需要LCD1602屏幕上显示字符"A",即向模块写入0x41即可。模块接收到数据0x41后,就去找到0x41位置数据将其显示在屏幕上。
【注意】CGROM中的字模与ASCII码表是一一对应的。
LCD1602由2行16列个小屏幕组成,每个小屏幕都有相应对应的地址。在要屏幕显示数据的时候,需要发送指令,指定位置小屏幕显示。
而这些地址数据是存储在 DDRAM中, DDRAM是一个40x2的存储数据的存储区,它前面16列存储对应的屏幕地址数据,后面的位置用于存储,发送来的指令/显示的数据。
1.3、指令集
LCD1602在使用前需要发送指令对其进行配置。其指令集如下所示:
2、时序
- ①读操作
读取数据:再E = 0时,将RS = 0表示读指令(读取忙状态D7位BF)/将RS = 1表示读数据。然后将RW = 1(表示读操作)
然后延时tsp1时间,然后将E = 1,开始读取D0~D7线上的数据,延时tpw时间后将E = 0即可。
/**
* LCD1602忙检测
*/
void LCD_BusyCheck(void)
{
unsigned char temp = 0;
unsigned char Count = 0;
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 1; //RW = 1代表读操作
do{
LCD_EN = 1; // 拉高
temp = LCD_Data; // 将 LCD 状态保存在temp中,用于判忙
LCD_EN = 0; // 负跳变使能
if(Count++ >= 40)
{
break; //若死循环则通过break跳出循环
}
}while(temp & 0x80); // D7位BF结果为1,LCD 忙,继续循环;结果为0,LCD 不忙,可以进行后面的操作
}
- ②写操作
写入数据/指令:再E = 0时,将R/W = 0(表示写操作),将RS = 0表示写指令/将RS = 1表示写数据。
然后将需要写入的指令/数据写入到D0~D7数据线上,延时tsp2时间,然后将E = 1,延时tpw时间后将E = 0即可
/**
* 向LCD1602写指令
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 0; //RW = 0代表写操作
LCD_Data = Command; //写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms();
LCD_EN = 0; //使能关闭
}
/**
* 向LCD1602写入数据
*/
void LCD_WriteData(unsigned char Data)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 1; //RS = 1代表数据
LCD_RW = 0; //RW = 0代表写操作
LCD_Data = Data; //写入数据
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms(); //等待tR +tPW
LCD_EN = 0; //使能关闭
3、代码演示
使用要求:构造函数让LCD1602能够显示字符,字符串,无符号整数,有符号整数,有符号小数,16进制数,二进制数
①LCD1602.c文件的代码如下:
#include "LCD1602.h"
#include "intrins.h"
void LCD_Delay1ms(void) //@11.0592MHz,1ms的延迟
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/**
* 向LCD1602写指令
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 0; //RW = 0代表写操作
LCD_Data = Command; //写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms();
LCD_EN = 0; //使能关闭
}
/**
* 向LCD1602写入数据
*/
void LCD_WriteData(unsigned char Data)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 1; //RS = 1代表数据
LCD_RW = 0; //RW = 0代表写操作
LCD_Data = Data; //写入数据
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms(); //等待tR +tPW
LCD_EN = 0; //使能关闭
}
/**
* LCD1602忙检测
*/
void LCD_BusyCheck(void)
{
unsigned char temp = 0;
unsigned char Count = 0;
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 1; //RW = 1代表读操作
do{
LCD_EN = 1; // 拉高
temp = LCD_Data; // 将 LCD 状态保存在temp中,用于判忙
LCD_EN = 0; // 负跳变使能
if(Count++ >= 40)
{
break;
}
}while(temp & 0x80); // 结果为 1,LCD 忙,继续循环;结果为 0,LCD 不忙,可以进行后面的操作
}
/**
* LCD1602的初始化
*/
void LCD_Init(void)
{
LCD_WriteCommand(0x38); //8位数据接口,2行,5*8点阵
LCD_WriteCommand(0x0c); //显示开,光标关,闪烁关
LCD_WriteCommand(0x06); //光标向右加1,画面不动
LCD_WriteCommand(0x80); //从第一行第一列开始显示
LCD_WriteCommand(0x01); //清屏
LCD_Delay1ms(); //延时,等待初始化完成
}
/**
* 确定行列
* 第1行的地址为:0x00,0x01,0x02....0x27
* 第2行的地址为:0x40,0x41,0x42....0x67
* Hang: 1~2
* Lie: 1~40
*/
void LCD_Position(unsigned char Hang, unsigned char Lie)
{
if(Hang == 1)
{
LCD_WriteCommand(0x80 | (Lie - 1));
}
else
{
LCD_WriteCommand(0x80 | (0x40 + (Lie - 1)));
}
}
/**
* LCD显示一个字符
*/
void LCD_ShowChar(unsigned char Hang, unsigned char Lie, char Byte)
{
LCD_Position(Hang, Lie);
LCD_WriteData(Byte);
}
/**
* LCD显示字符串
*/
void LCD_ShowString(unsigned char Hang, unsigned char Lie, char *Str)
{
LCD_Position(Hang, Lie);
while(*Str != '\0')
{
LCD_WriteData(*Str++);
}
}
/**
* X的Y次方函数
*/
unsigned int Pow_LCD(unsigned char X,unsigned char Y)
{
unsigned int Result = 1;
while(Y--)
{
Result *= X;
}
return Result;
}
/**
* LCD显示无符号整数
* Num:无符号整数0 到 4294967295。
* Length:数值长度
*/
void LCD_ShowNum(unsigned char Hang, unsigned char Lie, unsigned long Num, unsigned int Length)
{
unsigned int i;
unsigned long Num1;
LCD_Position(Hang, Lie);
for(i = 1; i<= Length; i++)
{
Num1 = (Num / Pow_LCD(10,Length - i)) % 10;
LCD_WriteData('0'+ Num1);
}
}
/**
* LCD显示有符号整数
* Num:无符号整数0 到 4294967295。
* Length:数值长度
*/
void LCD_ShowSignedNum(unsigned char Hang, unsigned char Lie, signed long Num, unsigned int Length)
{
unsigned int i ;
unsigned long Num1;
LCD_Position(Hang, Lie);
if(Num >= 0)
{
LCD_WriteData('+');
for(i = 1; i<= Length; i++)
{
LCD_WriteData('0'+ (Num / Pow_LCD(10,Length - i)) % 10);
}
}
else
{
LCD_WriteData('-');
for(i = 1; i<= Length; i++)
{
Num1 = -Num;
LCD_WriteData('0'+ (Num1 / Pow_LCD(10,Length - i)) % 10);
}
}
}
/**
* LCD显示小数
* Num:有符号小数
* Length:小数点后长度
*/
void LCD_ShowFloat(unsigned char Hang, unsigned char Lie, float Num, unsigned char LenInt,unsigned char LenFloat)
{
unsigned char j = 0;
unsigned short Inte = 0; //保存小数整数部分的变量
if(Num < 0)
{
Num = -Num; //转换为正数
LCD_Position(Hang, Lie);
LCD_WriteData('-'); //显示“-”
}
else
{
LCD_Position(Hang, Lie);
LCD_WriteData('+'); //显示“+”
}
/* 显示整数 */
LCD_ShowNum(Hang, Lie + 1, (unsigned short)Num, LenInt);
/* 将小数计算为整数 */
Num = Num - (unsigned short)Num; //获取小数部分
Inte = (unsigned short)(Num * Pow_LCD(10,LenFloat));//将小数计算为整数
/* 显示小数部分 */
LCD_ShowChar(Hang, Lie + 1 + LenInt, '.'); //显示小数点
LCD_ShowNum(Hang, Lie + 2 + LenInt, Inte, LenFloat);//显示小数部分
}
/**
* LCD显示16进制数字
* Number范围:0~0xFFFF
* Length长度:1~4
*/
void LCD_ShowHexNum(unsigned char Hang, unsigned char Lie,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_Position(Hang, Lie);
for(i=Length;i>0;i--)
{
SingleNumber = Number / Pow_LCD(16,i-1) % 16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* 将数值转换为二进制数值显示
* Number范围:0~1111 1111 1111 1111
* Length范围:1~16
*/
void LCD_ShowBinNum(unsigned char Hang, unsigned char Lie,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_Position(Hang, Lie);
for(i = Length; i > 0; i--)
{
LCD_WriteData(Number / Pow_LCD(2,i-1) % 2 + '0');
}
}
②LCD1602.h文件的代码如下:
#ifndef __LCD1602_H
#define __LCD1602_H
#include <REGX52.H> //包含51头文件,里面全是寄存器地址
#define LCD_Data P0
sbit LCD_RS = P2^5;
sbit LCD_RW = P2^6;
sbit LCD_EN = P2^7;
/* 底层驱动函数 */
void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_BusyCheck(void);
void LCD_Position(unsigned char Hang, unsigned char Lie);
unsigned int Pow_LCD(unsigned char X,unsigned char Y);
/* 用户调用函数 */
void LCD_Init(void);
void LCD_ShowChar(unsigned char Hang, unsigned char Lie, char Byte);
void LCD_ShowString(unsigned char Hang, unsigned char Lie, char *Str);
void LCD_ShowNum(unsigned char Hang, unsigned char Lie, unsigned long Num,unsigned int Length);
void LCD_ShowSignedNum(unsigned char Hang, unsigned char Lie, signed long Num, unsigned int Length);
void LCD_ShowFloat(unsigned char Hang, unsigned char Lie, float Num, unsigned char LenInt,unsigned char LenFloat);
void LCD_ShowHexNum(unsigned char Hang, unsigned char Lie,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Hang, unsigned char Lie,unsigned int Number,unsigned char Length);
#endif
③main.c文件的代码如下:
#include "LCD1602.h"
void main(void)
{
LCD_Init();
LCD_ShowChar(1, 1, 'A');
LCD_ShowString(1, 3, "nihao");
LCD_ShowNum(1,9,123,3);
LCD_ShowSignedNum(1, 13, -21, 2);
LCD_ShowFloat(2,1,-15.321,2,3);
LCD_ShowHexNum(2, 9,15,2); //将15转化为十六进制为F显示
LCD_ShowBinNum(2, 13,4,4); //将4转化为二进制为0100显示
while(1)
{
}
}
4、自定义字符显示
LCD1602将固定的字模数据保存再CGROM 中,而LCD1602的CGRAM用于保存用户自定义字模的数据。其中通过发送下列的指令来确定字模数据存储在哪一个CGRAM中。
其中D5 ~ D3位确定哪一个CGRAM。由此可得一共由2^3 = 8个CGRAM。D2~D0位确定数据存放在CGRAM中哪个位置(一共有8个位置,即对应了8点阵,如下图所示)。一般D2~D0用户不用管他,只需要发送一个CGRAM的初始地址,然后发送8个字模数据,数据就会依次从上往下存放在CGRAM中。
例如:
指令00 0100 0000(0x40)表示存放在第1个CGRAM中,而第1个CGRAM又与CGROM中的0x00相关联。
指令00 0100 1000(0x48)表示存放在第2个CGRAM中,而第2个CGRAM又与CGROM中的0x01相关联。
指令00 0101 0000(0x50)表示存放在第3个CGRAM中,而第3个CGRAM又与CGROM中的0x02相关联。
指令00 0101 1000(0x58)表示存放在第4个CGRAM中,而第4个CGRAM又与CGROM中的0x03相关联。
指令00 0110 0000(0x60)表示存放在第5个CGRAM中,而第5个CGRAM又与CGROM中的0x04相关联。
指令00 0110 1000(0x68)表示存放在第6个CGRAM中,而第6个CGRAM又与CGROM中的0x05相关联。
指令00 0111 0000(0x70)表示存放在第7个CGRAM中,而第7个CGRAM又与CGROM中的0x06相关联。
指令00 0111 1000(0x78)表示存放在第8个CGRAM中,而第8个CGRAM又与CGROM中的0x07相关联。
/**
* 用户自定义字符
* CGRAM起始地址:0x40 0x48 0x50 0x58 0x60 0x68 0x70 0x78
* CGROM对应代码:0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
* Character:字模数据的数组指针
* Len:需要自动几个字符(1~8)
*/
void LCD_Customcharacter(unsigned char *Character, unsigned char Len)
{
unsigned char i = 0;
unsigned char j = 0;
for(i = 0; i < Len; i++)
{
LCD_WriteCommand(0x40 + i * 8); //指令:写入自定义字符的起始空间地址(从第一个CGRAM开始)
for(j = 0; j < 8; j++)
{
LCD_WriteData(*(Character + (i * 8) + j)); //写入字模数据
}
}
}
③main.c文件的代码如下:
#include "LCD1602.h"
unsigned char code Chara[][8] = {
{0x0f, 0x12, 0x0f, 0x0a, 0x1f, 0x02, 0x02, 0x02}, //字符年
{0x0f, 0x09, 0x0f, 0x09, 0x0f, 0x09, 0x0b, 0x11}, //字符月
{0x1f, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x1f, 0x00} //字符日
};
void main(void)
{
LCD_Init();
LCD_Customcharacter((unsigned char* )Chara, 3); //添加自定义字符"年月日"
LCD_ShowChar(1, 1, 0x00); //显示年
LCD_ShowChar(1, 2, 0x01); //显示月
LCD_ShowChar(1, 3, 0x02); //显示日
while(1)
{
}
}
5、滚屏显示
③main.c文件的代码如下:
#include "LCD1602.h"
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main(void)
{
LCD_Init();
LCD_ShowString(1,1,"Hello World");
while(1)
{
LCD_WriteCommand(0x18); //整体向左移动
Delay500ms();
}
}
6、4路引脚显示
使用4路引脚来驱动LCD1602可以节约引脚的使用。
需要向数据线上发送数据时:先将需要发送的数据Data的高4位数据发送到D7~D4,然后再将需要发送的数据Data的低4位数据发送到D7 ~ D4上
只需要修改驱动函数,应用函数不需要改变,修改的代码如下。
①LCD1602.c文件的代码如下:
#include "LCD1602.h"
#include "intrins.h"
void LCD_Delay1ms(void) //@11.0592MHz,1ms的延迟
{
unsigned char i, j;
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/**
* 向LCD1602写指令——4路
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 0; //RW = 0代表写操作
/* 写入指令的高4位数据 */
LCD_Data &= 0x0F; //先让引脚的高4位清零
LCD_Data |= (Command & 0xF0);//写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms();
LCD_EN = 0; //使能关闭
/* 写入指令的低4位数据 */
LCD_Data &= 0x0F; //先让引脚的高4位清零
LCD_Data |= (Command << 4); //写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms(); //等待tsp1 + tsp2
LCD_EN = 0; //使能关闭
}
/**
* 向LCD1602写入数据——4路
*/
void LCD_WriteData(unsigned char Data)
{
LCD_BusyCheck(); //判忙
LCD_EN = 0; //使能关闭
LCD_RS = 1; //RS = 1代表数据
LCD_RW = 0; //RW = 0代表写操作
/* 写入数据的高4位数据 */
LCD_Data &= 0x0F; //先让引脚的高4位清零
LCD_Data |= (Data & 0xF0); //写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms();
LCD_EN = 0; //使能关闭
/* 写入数据的低4位数据 */
LCD_Data &= 0x0F; //先让引脚的高4位清零
LCD_Data |= (Data << 4); //写入指令
_nop_(); //等待tsp1 + tsp2
LCD_EN = 1; //使能开启
LCD_Delay1ms(); //等待tsp1 + tsp2
LCD_EN = 0; //使能关闭
}
/**
* LCD1602忙检测——4路
*/
void LCD_BusyCheck(void)
{
unsigned char temp = 0;
unsigned char Count = 0;
LCD_EN = 0; //使能关闭
LCD_RS = 0; //RS = 0代表指令
LCD_RW = 1; //RW = 1代表读操作
do{
/* 读取高4位 */
LCD_EN = 1; // 拉高
temp |= (LCD_Data & 0xF0); // 将LCD的高4位保存到temp,用于判忙
LCD_EN = 0; // 使能关闭
/* 读取低4位 */
LCD_EN = 1; // 拉高
temp |= ((LCD_Data & 0xF0) >> 4); // 将LCD的低4位保存到temp,用于判忙
LCD_EN = 0; // 使能关闭
if(Count++ >= 40)
{
break;
}
}while(temp & 0x80); // 结果为 1,LCD 忙,继续循环;结果为 0,LCD 不忙,可以进行后面的操作
}
/**
* LCD1602的初始化——4路
*/
void LCD_Init(void)
{
LCD_WriteCommand(0x33);
LCD_WriteCommand(0x32); //注意:使用4路驱动时需要先发送0x33和0x32,然后再发送配置指令,具体参考参考手册
LCD_WriteCommand(0x28); //4位数据接口,2行,5*8点阵
LCD_WriteCommand(0x0c); //显示开,光标关,闪烁关
LCD_WriteCommand(0x06); //光标向右加1,画面不动
LCD_WriteCommand(0x80); //从第一行第一列开始显示
LCD_WriteCommand(0x01); //清屏
LCD_Delay1ms(); //延时,等待初始化完成
}
②main.c文件的代码如下:
#include "LCD1602.h"
void main(void)
{
LCD_Init();
LCD_ShowChar(1, 1, 'A');
LCD_ShowString(1, 3, "nihao");
LCD_ShowNum(1,9,123,3);
LCD_ShowSignedNum(1, 13, -21, 2);
LCD_ShowFloat(2,1,-15.321,2,3);
LCD_ShowHexNum(2, 9,15,2); //将15转化为十六进制为F显示
LCD_ShowBinNum(2, 13,4,4); //将4转化为二进制为0100显示
while(1)
{
}
}