LCD1602是一种用来显示字母、数字、符号的点阵型液晶模块,可显示 2 行字符信息,每行又能显示 16 个字符,每个字符由若干个( 5x7 或 5x10) 的点阵字符位组成, 每位之间有一个点距的间隔,每行之间也有间隔,起到字符间距和行间距的作用。其实物图如下所示:
LCD1602有 16 个管脚孔,从左至右管脚编号顺序是 1-16,其功能定义如下所示
管脚说明:
3 脚:VL,液晶显示偏压信号,用于调整 LCD1602 的显示对比度,通常外接电位器用以调整偏压信号。注意,电压为 0 时对比度最强。
4 脚:RS,数据/命令选择端。此脚为高电平时,可对LCD1602 进行数据字节(即使用在 LCD1602上显示的字节,且LCD1602 的数据是 8 位的)的传输操作;此脚为低电平时,则是进行命令字节(即用来对 LCD1602 的一些工作方式作设置的字节)的传输操作。
5 脚:R/W,读写选择端。此脚为高电平,可对 LCD1602 进行读数据操作, 反之进行写数据操作。
6 脚:E,使能信号,其实是 LCD1602 的数据控制时钟信号,利用该信号的上升沿实现对 LCD1602 的数据传输。
7~14 脚:8 位并行数据口。 51 单片机一组 IO 也是 8 位,使得对 LCD1602 的数据读写大为方便。 在 LCD1602 内部含有 80 个字节的数据存储器DDRAM,用来寄存显示字符。其地址和屏幕的对应关系如下表所示:
显示位置 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | .... | 40 | |
DDRAM地址 | 第一行 | 00H | 01H | 02H | 03H | 04H | 05H | 06H | .... | 27H |
第二行 | 40H | 41H | 42H | 43H | 44H | 45H | 46H | .... | 67H |
如图所示,并非所有的地址都可直接用来显示字符数据,只有第一行中的 00-0F、第二行中的 40-4F 才能显示,其他地址只能用于存储。要显示字符时,要先输入显示字符地址,也就是告诉模块在哪里显示字符。
例如第二行第一个字符的地址是 40H,那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位 D7 恒定为高电平 1 。
所以实际写入的数据应该是 01000000B(40H) +10000000B(80H)=11000000B(C0H)。在 LCD1602 中就用前 16 个就行了。第二行也一样用前 16 个地址。
使用 LCD1602 需要掌握的一些常用的指令:
指令功能 | 指令编码 | 执行时间/ms | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
清屏 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1.64 |
指令功能 | 指令编码 | 执行时间/us | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
进入模式设置 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | I/D | S | 40 |
指令功能 | 指令编码 | 执行时间/us | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
显示开关控制 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | C | B | 40 |
指令功能 | 指令编码 | 执行时间/us | |||||||||
RS | R/W | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | ||
功能设定 | 0 | 0 | 0 | 0 | 1 | DL | N | F | X | X | 40 |
使用 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=150ns 的 E 脉冲宽度。然后E引脚负跳变,RS电平变化,R/W 电平变化。这样便是一个完整的LCD1602 写命令的时序。
注意:这里介绍的是 8 位 LCD1602。
使用到硬件资源如下:
- LCD1602 液
开发板上集成了一个 LCD1602 液晶接口,电路如下所示:
如图所示,8 位数据口 DB0-DB7 与单片机的 P0.0-P0.7 管脚连接,RS、RW、E 脚与单片机的 P2.6、P2.5、P2.7 管脚连接。图中的RJ1 是一个电位器,用来调节 LCD1602 对比度即显示亮度,实物图如下所示:
注:这里原理图使用的是 8 位 LCD1602 接口设计,可兼容 4 位 LCD1602 。 4 位 LCD1602 在传输数据时,需要将 8 位的数据截成两段,先发送高四位,后发低四位。其它引脚操作方法不变。
所要实现的功能是:在 LCD1602 液晶上显示字符信息。
程序框架如下:
- 编写 LCD1602 显示函数
- 编写主函数
重点是对 LCD1602 进行写命令和数据。 这里分析几个重要函数:
在代码中可以看到有如下格式:
#if (LCD1602_4OR8_DATA_INTERFACE==0)
...(省略内容 1)
#else
...(省略内容 2)
#endif
这是 C 语言中的条件编译,如果条件表达式 LCD1602_4OR8_DATA_INTERFACE==0 为真, 则执行省略内容 1 部分,否则执行省略内容 2 部分。和 if...else 类似。
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 命令。
lcd1602_show_string 函数是 LCD1602 显示字符函数,该函数有 3 个入口参数,x 和 y 用来确定显示的坐标,x 范围是 0-15,y 范围是 0-1。str 参数是一个指针类型变量,用于传递显示的内容。函数内首先判断 x、y 坐标是否有效,无效则强制退出,然后根据 xy 值在对应的坐标位置显示内容,代码如下:
#include "lcd1602.h"
/*******************************************************************************
* 函 数 名 : lcd1602_write_cmd
* 函数功能 : LCD1602写命令
* 输 入 : cmd:指令
* 输 出 : 无
*******************************************************************************/
#if (LCD1602_4or8_DATA_INTERFACE==0)//8为LCD
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后负跳变完成写入
}
#else //4位LCD
/*对于 4 位 LCD1602 在传输数据的时候需要将 8 位的数据截成两段,先发
送高四位,在发送低四位。其它引脚操作方法不变。*/
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_DATAPORT=cmd<<4; //准备命令,发送低四位,此时将低四位左移至高四位再发送
delay_ms(1);
LCD1602_E=1; //使能脚E先上升沿写入
delay_ms(1);
LCD1602_E=0; //使能脚E后负跳变完成写入
}
#endif
/*******************************************************************************
* 函 数 名 : lcd1602_write_data
* 函数功能 : LCD1602写数据
* 输 入 : dat:数据
* 输 出 : 无
*******************************************************************************/
#if (LCD1602_4or8_DATA_INTERFACE==0) //8位LCD
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后负跳变完成写入
}
#else
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_DATAPORT=dat<<4; //准备新数据,发送低四位,此时将低四位左移至高四位再发送
delay_ms(1);
LCD1602_E=1; //使能脚E先上升沿写入
delay_ms(1);
LCD1602_E=0; //使能脚E后负跳变完成写入
}
#endif
/*******************************************************************************
* 函 数 名 : lcd1602_init
* 函数功能 : LCD1602初始化
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
#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)
{
lcd1602_write_cmd(0x28); //功能设定指令:数据总线4位,显示2行,5*7点阵/字符
lcd1602_write_cmd(0x0c); //显示开关控制指令:显示功能开,无光标,光标闪烁
lcd1602_write_cmd(0x06); //模式设置指令:写入新数据后光标右移,显示屏不移动
lcd1602_write_cmd(0x01); //清屏指令:清屏
}
#endif
/*******************************************************************************
* 函 数 名 : lcd1602_clear
* 函数功能 : LCD1602清屏
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void lcd1602_clear(void)
{
lcd1602_write_cmd(0x01);
}
/*******************************************************************************
* 函 数 名 : lcd1602_show_string
* 函数功能 : LCD1602显示字符
* 输 入 : x,y:显示坐标,x=0~15,y=0~1;
str:显示字符串
* 注 释 : 并非所有的地址都可直接用来显示字符数据,只有第一行中
的 00-0F、第二行中的 40-4F 才能显示,其他地址只能用于存储。
*******************************************************************************/
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')
{
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
{
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++;
}
}
}
首先调用外设驱动头文件,然后进入 main 函数初始化 LCD1602,并调用 LCD1602 显示函数让第一行显示"Hello World!",第二行显 示"0123456789"。
#include "public.h"
#include "lcd1602.h"
void main()
{
lcd1602_init(); //LCD1602初始化
lcd1602_show_string(0,0,"Hello world!"); //第一行显示
lcd1602_show_string(0,1,"0123456789"); //第二行显示
while(1)
{
}
}
这里说明下,在开发中通常会显示数值,比如温度数据,程序中我们获取的温度数据通常存储在 float 变量中,并非字符串,此时需要想办法将 float 变量中的值转换为字符串,比如 float temp=31.2,那么我们可以再定义一个数组 temp_buf[]用于保存 temp 中的每一个位,操作如下:
float temp=31.2;
u8 temp_buf[5];
u16 temp_value=temp*10;//将浮点数转换为整型变量,乘10得312
/*获取temp)value的各位,并存储在数组temp_value中*/
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 显示函数知道数组内结束位置。
根据 C 语言指针的特点,此时调用 LCD1602 显示函数时,只需要将数组名传递进去即可,因为数组名即为该数组的起始地址,然后显示函数内部通过指针访问数组的每一位。
实现现象如下:在 LCD1602 液晶上显示字符信息。