LCD1602

简介

1602 液晶也叫 1602 字符型液晶,它能显示 2 行字符信息,每行又能显示 16 个字符。它是一种专门用来显示字母、数字、符号的点阵型液晶模块。它是由若干个 5x7 或者 5x10 的点阵字符位组成,每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此,所以它不能很好的显示图片。

从左至右管脚编号顺序是 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 个地址。

LCD1602 常用命令

注意:这些指令对于 LCD1602 初始化是必须的。

  • 清屏指令

    功能:

    <1> 清除液晶显示器,即将 DDRAM 的内容全部填入"空白"的 ASCII 码 20H;
    <2> 光标归位,即将光标撤回液晶显示屏的左上方;
    <3> 将地址计数器(AC)的值设为 0

    注:下面软件编程那个0x01是DB0~DB7:0 0 0 0       0 0 0 1

  • 模式设置指令

    功能:

    ​ 设定每次写入 1 位数据后 光标的移位方向,并且设定每次写入的一个字符是否移动。

    I/D:0=写入新数据后光标 左移 1=写入新数据后光标 右移

    S:0=写入新数据后 显示屏不移动 1=写入新数据后 显示屏整体右移 1 个字符

    注:下面软件编程那个0x06是DB0~DB7:0 0 0 0        0 1 1 0

  • 显示开关控制指令

    功能:

    控制显示器开/关、光标显示/关闭以及光标是否闪烁

    D:0=显示功能关 1=显示功能开
    C:0=无光标 1=有光标
    B:0=光标闪烁 1=光标不闪烁

    注:下面软件编程那个0x0c是DB0~DB7:0 0 0 0       1 1 0 0

  • 功能设定指令

    功能:

    设定数据总线位数、显示的行数及字型。

    DL:0=数据总线为 4 位 1=数据总线为 8 位
    N:0=显示 1 行 1=显示 2 行
    F:0=5×7 点阵/每字符 1=5×10 点阵/每字符

注:下面软件编程那个0x38是DB0~DB7:0 0 1 1       1 0 0 0

LCD1602 使用

要使用 LCD1602,首先需要对其 初始化,即通过写入一些特定的指令实现。然后选择要在 LCD1602 的哪个位置显示并将所要显示的数据发送到 LCD 的DDRAM。使用 LCD1602 通常都是用于写数据进去,很少使用读功能

LCD1602 操作步骤如下所示:

在此,不需要读出它的数据的状态或者数据本身。所以只需要看两个写时序:

  • 当要写 指令字,设置 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写命令的时序。

注意

使用 4 位 LCD1602 时,应该多看手册,找到不同点,对原有程序加以修改

从上图中可知,LCD1602 的 8 位数据口 DB0-DB7 与单片机的 P0.0-P0.7 管脚连接,LCD1602 的 RS、RW、E 脚与单片机的 P2.6、P2.5、P2.7 管脚连接。RJ1是一个电位器,用来调节 LCD1602 对比度即显示亮度。

注意

这里原理图是使用的 8 位 LCD1602 接口设计,是可以兼容 4 位 LCD1602的。对于非标准接口的 LCD1602,我们也会通过转接板将其转接为对应开发板接口的。 对于 4 位 LCD1602 在传输数据的时候需要将 8 位的数据截成两段,先发送高四位,在发送低四位。其它引脚操作方法不变。

软件编写

main.c

# include "kd1602.h"


void main()
{
	 lcd1602_init();
	lcd1602_show_string(0,0,"Hello World!");
	lcd1602_show_string(0,1,"0123456789");
	
    while(1)
    {
       
    }    
}

kd1602.c

# include "kd1602.h"


void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
	{
		for(j=110;j>0;j--);
	}
}

/*******************************************************************************
* 函 数 名       : lcd1602_write_cmd
* 函数功能		 : LCD1602写命令
* 输    入       : cmd:指令
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_write_cmd(u8 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_write_data
* 函数功能		 : LCD1602写数据
* 输    入       : dat:数据
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_write_data(u8 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后负跳变完成写入	
}
/*******************************************************************************
* 函 数 名       : lcd1602_init
* 函数功能		 : LCD1602初始化
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_init()
{
	lcd1602_write_cmd(0x38);//数据总线8位,显示2行,5*7点阵/字符
	lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
	lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
	lcd1602_write_cmd(0x01);//清屏	
}


/*******************************************************************************
* 函 数 名       : lcd1602_clear
* 函数功能		 : LCD1602清屏
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void lcd1062_clear()
{
	lcd1602_write_cmd(0x01);//清屏
}

/*******************************************************************************
* 函 数 名       : lcd1602_show_string
* 函数功能		 : LCD1602显示字符
* 输    入       : x,y:显示坐标,x=0~15,y=0~1;
				   str:显示字符串
* 输    出    	 : 无
*******************************************************************************/
void lcd1602_show_string(u8 x,u8 y,u8 *str)
{
	u8 i=0;
	if(y>1 || x>15)//行列参数不对则强制退出
		return;
	if(y<1)//第一行开始
	{
		while(*str!='\0')//字符串是以'\0'结尾,只要前面有内容就显示
		{
			if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
			{
				lcd1602_write_cmd(0x80+x+i);//第一行显示地址设置	
			}
			else
			{
				lcd1602_write_cmd(0x40+0x80+x+i-16);//第二行显示地址设置
			}
			lcd1602_write_data(*str);//显示内容
			str++;//指针递增
			i++;
		}
	}
	else//第二行开始
	{
		while(*str!='\0')//字符串是以'\0'结尾,只要前面有内容就显示
		{
			if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
			{
				lcd1602_write_cmd(0x40+0x80+x+i);//第二行显示地址设置	
			}
			else
			{
				lcd1602_write_cmd(0x80+x+i-16);//第一行显示地址设置
			}
			lcd1602_write_data(*str);//显示内容
			str++;//指针递增
			i++;
		}
	}
}

kd1602.h

# ifndef _kd1602_H
# define _kd1602_H

# include "reg52.h"

typedef unsigned char u8;
typedef unsigned int u16;

sbit LCD1602_RS=P2^6;//数据命令选择
sbit LCD1602_RW=P2^5;//读写选择
sbit LCD1602_E=P2^7;//使能信号
# define LCD1602_DATAPORT P0  //宏定义LCD1602数据端口

void delay_ms(u16 ms);
void lcd1602_write_cmd(u8 cmd);
void lcd1602_write_data(u8 dat);
void lcd1602_init();
void lcd1062_clear();
void lcd1602_show_string(u8 x,u8 y,u8 *str);

# endif

例子

在开发中通常会显示数值,比如温度数据,程序中我们获取的温度数据通常存储在 float 变量中,并非字符串,此时需要想办法将 float 变量中的值转换为字符串,比如 float temp=31.2,那么我们可以再定义一个数组 temp_buf[] 用于保存 temp 中的每一个位,操作如下:

float temp=31.2;
u8 temp_buf[5];
u16 temp_value=temp*10;

temp_buf[0]=temp_value/100+0x30;
temp_buf[1]=temp_value%100/10+0x30;
temp_buf[2]='.';
temp_buf[3]=temp_value%100%10+0x30;
temp_buf[4]='\0';
lcd1602_show_string(0,0,temp_buf);

解释

将浮点数转换为整型变量,然后将整型值的每一个位取出来存储在数组temp_buf 中,细心的朋友会发现取出的每一位后面加了 0X30,这个值是将数字0-9 转换为 ASCII 码,因为液晶是以ASCII 码显示的。最后一个数组元素赋值了’\0’,这是为了让 LCD1602 显示函数知道数组内结束位置。

此时调用 LCD1602 显示函数时,只需要将数组名传递进去即可,因为数组名即为该数组的起始地址,然后显示函数内部通过指针访问数组的每一位。

LCD显示时分秒

main.c

/*	
实验现象:
	LCD1602上显示秒表
*/

# include<reg52.h>
# include "lcd.h"

typedef unsigned char uint8;
typedef unsigned int uint16;

uint8 a[]="LCD Design of Tm";  //要显示的字符
uint8 num[]="0123456789";
uint8 b[8],shi,fen,miao;
void delay(uint16 i)
{
	while(i--);
}
void wrc(uint8 c)	 //写命令
{
	LcdWriteCom(c);
}
void wrd(uint8 dat)	  //写数据
{
	LcdWriteData(dat);
}
void lcd_init()
{
	LcdInit();	
}
void time0init()
{
	TMOD=0X01;
	TH0=0XFC;
	TL0=0X18; //1ms
	TR0=1;
	ET0=1;
	EA=1;
}
void timepros()
{
	b[0]=num[shi/10];
	b[1]=num[shi%10];
	b[2]=0x3a;
	b[3]=num[fen/10];
	b[4]=num[fen%10];
	b[5]=0x3a;
	b[6]=num[miao/10];
	b[7]=num[miao%10];
}
void display()
{
	uint8 i;
	timepros();
	wrc(0x00+0x80);
	for(i=0;i<16;i++)
	{
		wrd(a[i]);
	}
	wrc(0x44+0x80);
	wrd(b[0]);
	wrd(b[1]);
	wrd(b[2]);
	wrd(b[3]);
	wrd(b[4]);
	wrd(b[5]);
	wrd(b[6]);
	wrd(b[7]);
}
void main()
{
	lcd_init();
	time0init();
	while(1)
	{
		display();
	}	
}
void tim0() interrupt 1
{
	static uint16 n;
	TH0=0XFC;
	TL0=0X18;//1ms
	n++;
	if(n==1000)
	{
		n=0;
		miao++;
		if(miao==60)
		{
			miao=0;
			fen++;
			if(fen==60)
			{
				fen=0;
				shi++;
				if(shi==60)
				{
					shi=0;
					miao=0;
					fen=0;
				}
			}
		}
	}
//	display();
}

lcd.c

# include "lcd.h"

/*******************************************************************************
* 函 数 名         : Lcd1602_Delay1ms
* 函数功能		   : 延时函数,延时1ms
* 输    入         : c
* 输    出         : 无
* 说    名         : 该函数是在12MHZ晶振下,12分频单片机的延时。
*******************************************************************************/

void Lcd1602_Delay1ms(uint c)   //误差 0us
{
    uchar a,b;
	for (; c>0; c--)
	{
		 for (b=199;b>0;b--)
		 {
		  	for(a=1;a>0;a--);
		 }      
	}
    	
}

/*******************************************************************************
* 函 数 名         : LcdWriteCom
* 函数功能		   : 向LCD写入一个字节的命令
* 输    入         : com
* 输    出         : 无
*******************************************************************************/
# ifndef 	LCD1602_4PINS	 //当没有定义这个LCD1602_4PINS时
void LcdWriteCom(uchar com)	  //写入命令
{
	LCD1602_E = 0;     //使能
	LCD1602_RS = 0;	   //选择发送命令
	LCD1602_RW = 0;	   //选择写入
	
	LCD1602_DATAPINS = com;     //放入命令
	Lcd1602_Delay1ms(1);		//等待数据稳定

	LCD1602_E = 1;	          //写入时序
	Lcd1602_Delay1ms(5);	  //保持时间
	LCD1602_E = 0;
}
# else 
void LcdWriteCom(uchar com)	  //写入命令
{
	LCD1602_E = 0;	 //使能清零
	LCD1602_RS = 0;	 //选择写入命令
	LCD1602_RW = 0;	 //选择写入

	LCD1602_DATAPINS = com;	//由于4位的接线是接到P0口的高四位,所以传送高四位不用改
	Lcd1602_Delay1ms(1);

	LCD1602_E = 1;	 //写入时序
	Lcd1602_Delay1ms(5);
	LCD1602_E = 0;

	LCD1602_DATAPINS = com << 4; //发送低四位
	Lcd1602_Delay1ms(1);

	LCD1602_E = 1;	 //写入时序
	Lcd1602_Delay1ms(5);
	LCD1602_E = 0;
}
# endif
/*******************************************************************************
* 函 数 名         : LcdWriteData
* 函数功能		   : 向LCD写入一个字节的数据
* 输    入         : dat
* 输    出         : 无
*******************************************************************************/		   
# ifndef 	LCD1602_4PINS		   
void LcdWriteData(uchar dat)			//写入数据
{
	LCD1602_E = 0;	//使能清零
	LCD1602_RS = 1;	//选择输入数据
	LCD1602_RW = 0;	//选择写入

	LCD1602_DATAPINS = dat; //写入数据
	Lcd1602_Delay1ms(1);

	LCD1602_E = 1;   //写入时序
	Lcd1602_Delay1ms(5);   //保持时间
	LCD1602_E = 0;
}
# else
void LcdWriteData(uchar dat)			//写入数据
{
	LCD1602_E = 0;	  //使能清零
	LCD1602_RS = 1;	  //选择写入数据
	LCD1602_RW = 0;	  //选择写入

	LCD1602_DATAPINS = dat;	//由于4位的接线是接到P0口的高四位,所以传送高四位不用改
	Lcd1602_Delay1ms(1);

	LCD1602_E = 1;	  //写入时序
	Lcd1602_Delay1ms(5);
	LCD1602_E = 0;

	LCD1602_DATAPINS = dat << 4; //写入低四位
	Lcd1602_Delay1ms(1);

	LCD1602_E = 1;	  //写入时序
	Lcd1602_Delay1ms(5);
	LCD1602_E = 0;
}
# endif
/*******************************************************************************
* 函 数 名       : LcdInit()
* 函数功能		 : 初始化LCD屏
* 输    入       : 无
* 输    出       : 无
*******************************************************************************/		   
# ifndef		LCD1602_4PINS
void LcdInit()						  //LCD初始化子程序
{
 	LcdWriteCom(0x38);  //开显示
	LcdWriteCom(0x0c);  //开显示不显示光标
	LcdWriteCom(0x06);  //写一个指针加1
	LcdWriteCom(0x01);  //清屏
	LcdWriteCom(0x80);  //设置数据指针起点
}
# else
void LcdInit()						  //LCD初始化子程序
{
	LcdWriteCom(0x32);	 //将8位总线转为4位总线
	LcdWriteCom(0x28);	 //在四位线下的初始化
	LcdWriteCom(0x0c);  //开显示不显示光标
	LcdWriteCom(0x06);  //写一个指针加1
	LcdWriteCom(0x01);  //清屏
	LcdWriteCom(0x80);  //设置数据指针起点
}
# endif

lcd.h

# ifndef __LCD_H_
# define __LCD_H_
/**********************************
当使用的是4位数据传输的时候定义,
使用8位取消这个定义
**********************************/
//# define LCD1602_4PINS

/**********************************
包含头文件
**********************************/
# include<reg52.h>

//---重定义关键词---//
# ifndef uchar
# define uchar unsigned char
# endif

# ifndef uint 
# define uint unsigned int
# endif

/**********************************
PIN口定义
**********************************/
# define LCD1602_DATAPINS P0
sbit LCD1602_E=P2^7;
sbit LCD1602_RW=P2^5;
sbit LCD1602_RS=P2^6;

/**********************************
函数声明
**********************************/
/*在51单片机12MHZ时钟下的延时函数*/
void Lcd1602_Delay1ms(uint c);   //误差 0us
/*LCD1602写入8位命令子函数*/
void LcdWriteCom(uchar com);
/*LCD1602写入8位数据子函数*/	
void LcdWriteData(uchar dat)	;
/*LCD1602初始化子程序*/		
void LcdInit();						  

# endif