【嵌入式模块】LCD1602&LCD12864

前言

  作为最为常见的显示模块LCD1602和LCD12864常常会被用来调试,也曾遇到用LCD作为显示器显示传感器测量结果的小项目,这篇博客简单总结一下LCD的使用。

一个小问题

如何判断自己拿的是不是带字库的LCD?

  • 找到型号,看数据手册
  • 如果没有找到数据手册,可以看看模块背后有几个芯片,有说三个芯片(三坨黑色的东西)就是带字库的,两个芯片就是不带字库的。
  • 看引脚:有说如果有PSB引脚的为带字库的,不带字库的有两个CS1和CS2引脚,用来选择左右半屏的。

  我之前一直以为1602和12864是一样的,只是屏幕大小的区别,但后来发现,其实使用方法上也不一样,12864具有串行数据传输的功能,而1602只能使用并行数据传输。

LCD1602

参考链接

  • LCD1602液晶显示屏的入门级应用(一)- CSDN
  • lcd1602使用手册,LCD1602的使用详解 - 电子发烧友
  • LCD1602液晶使用介绍–(完整版)- CSDN

引脚定义

  先来看看1602的引脚定义,如下图所示:
【嵌入式模块】LCD1602&LCD12864_第1张图片
  使用时,将VDD、BLA接5V电源,VSS、BLK接地,VL接一个0-5V的电压信号,其大小会影响实际显示效果,需要根据实际情况调整。

  信号方面,RS、R/W、E为控制信号,D0~D7为数据传输引脚,用来输入或输出指令(状态)和数据。

操作时序

读操作时序
【嵌入式模块】LCD1602&LCD12864_第2张图片
写操作时序
【嵌入式模块】LCD1602&LCD12864_第3张图片
时序参数:
【嵌入式模块】LCD1602&LCD12864_第4张图片
总结来说:
【嵌入式模块】LCD1602&LCD12864_第5张图片
其中,读取到的状态字定义如下:
【嵌入式模块】LCD1602&LCD12864_第6张图片

指令介绍

  除显示数据的传输外,LCD1602的使用就是靠写入不同的指令来实现,其指令总结如下:
【嵌入式模块】LCD1602&LCD12864_第7张图片
【嵌入式模块】LCD1602&LCD12864_第8张图片
初始化的顺序:
【嵌入式模块】LCD1602&LCD12864_第9张图片

51程序示例

#include    //包含头文件

#define uint unsigned int  //预定义
#define uchar unsigned char 

sbit rs=P2^6;    //1602的数据/指令选择控制线 
sbit rw=P2^5;        //1602的读写控制线 
sbit en=P2^7;        //1602的使能控制线 
/*P0口接1602的D0~D7*/ 
uchar code table[]="1234";             //要显示的内容放入数组table
void delay(uint n)       //延时函数                       
{ 
    uint x,y;  
    for(x=n;x>0;x--) 
        for(y=110;y>0;y--); 
} 
void lcd_wcom(uchar com)  //1602写命令函数                 
{ 
    rs=0;            //选择指令寄存器 
    rw=0;            //选择写 
    P0=com;            //把命令字送入P2 
    delay(5);            //延时一小会儿,让1602准备接收数据 
    en=1;           //使能线电平变化,命令送入1602的8位数据口 
    en=0; 
} 
void lcd_wdat(uchar dat)        //1602写数据函数       
{ 
    rs=1;        //选择数据寄存器 
    rw=0;        //选择写 
    P0=dat;        //把要显示的数据送入P2 
    delay(5);        //延时一小会儿,让1602准备接收数据 
    en=1;        //使能线电平变化,数据送入1602的8位数据口 
    en=0; 
} 
void lcd_init()              //1602初始化函数       
{ 
    lcd_wcom(0x38);       //8位数据,双列,5*7字形       
    lcd_wcom(0x0c);      //开启显示屏,关光标,光标不闪烁 
    lcd_wcom(0x06);    //显示地址递增,即写一个数据后,显示位置右移一位 
    lcd_wcom(0x01);    //清屏 
} 
void main()            //主函数 
{     
    uchar m=0; 
    lcd_init();       //液晶初始化 
    lcd_wcom(0x80);   //显示地址设为80H(即00H,)上排第一位       
    for(m=0;m<4;m++)     //将table[]中的数据依次写入1602显示 
    {
    	lcd_wdat(table[m]);           
        delay(200); 
    } 
    while(1);        //动态停机 
} 

显示汉字或自定义字符

  根据上面判断显示模块是否带字库的方法,我们可以发现1602只有两个芯片,即不带字库,那有没有办法可以显示汉字和自定义的字符呢?还真有。
  在LCD1602模块中,不同位置显示的字符实际上是来自于DDRAM中不同地址的数据,在某个位置显示内容即在对应地址的DDRAM中写入数据。因此,这样显示出来的数据都是其自带的数据,也就是ASCII中的字符。
  除此之外,LCD1602模块中还有CGRAMCGROM两个储存位置。其中CGROM可以看作是储存ASCII字库的位置,不能更改,掉电信息不消失。而CGRAM可随机读写,有8个字节的空间,用来存放自定义字符的代码。
  仔细观察LCD1602的显示背景可以发现,它所有显示的内容都是在一个5x8的点阵中显示的,而且最底下那行没有使用,即5x7点阵,这也是关于LCD显示的指令中5x7点阵的来源。
  因此,如果需要显示自定义的字符,那就需要将设置5x8点阵的数据传递给LCD显示模块,如下图就是一个自定义的°C的符号:
【嵌入式模块】LCD1602&LCD12864_第10张图片
  其中,每一行对应一个8位的数据(高三位没有使用,固定为0),一共需要8个数据,正好可以放在CGRAM中。因此,显示自定义字符时,首先要在CGRAM中写入字符代码,然后再设置CGRAM中的数据传输到DDRAM的位置。其中,写入CGRAM的指令如下图所示:
【嵌入式模块】LCD1602&LCD12864_第11张图片

  注意:上图为12864的CGRAM指令格式,而1602的CGRAM的地址只有从000~111共8个地址为有效地址,对应指令为0x40 ~ 0x47。

概况来说,这三者之间的关系大概如下所示
【嵌入式模块】LCD1602&LCD12864_第12张图片

其51程序如下所示:

#include    //包含头文件

#define uint unsigned int  //预定义
#define uchar unsigned char 

sbit rs=P2^6;    //1602的数据/指令选择控制线 
sbit rw=P2^5;        //1602的读写控制线 
sbit en=P2^7;        //1602的使能控制线 
/*P0口接1602的D0~D7*/ 
uchar code table[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00};             //要显示的内容放入数组table
void delay(uint n)       //延时函数                       
{ 
    uint x,y;  
    for(x=n;x>0;x--) 
        for(y=110;y>0;y--); 
} 
void lcd_wcom(uchar com)  //1602写命令函数                 
{ 
    rs=0;            //选择指令寄存器 
    rw=0;            //选择写 
    P0=com;            //把命令字送入P2 
    delay(5);            //延时一小会儿,让1602准备接收数据 
    en=1;           //使能线电平变化,命令送入1602的8位数据口 
    en=0; 
} 
void lcd_wdat(uchar dat)        //1602写数据函数       
{ 
    rs=1;        //选择数据寄存器 
    rw=0;        //选择写 
    P0=dat;        //把要显示的数据送入P2 
    delay(5);        //延时一小会儿,让1602准备接收数据 
    en=1;        //使能线电平变化,数据送入1602的8位数据口 
    en=0; 
} 
void lcd_init()              //1602初始化函数       
{ 
    lcd_wcom(0x38);       //8位数据,双列,5*7字形       
    lcd_wcom(0x0c);      //开启显示屏,关光标,光标不闪烁 
    lcd_wcom(0x06);    //显示地址递增,即写一个数据后,显示位置右移一位 
    lcd_wcom(0x01);    //清屏 
} 
void main()            //主函数 
{     
    uchar m; 
    lcd_init();       //液晶初始化 

    lcd_wcom(0x40);//设定CGRAM地址,把自定义字符存储进去     

    for(m=0;m<8;m++)     //将table[]中的数据依次写入1602显示 
    { 
	    lcd_wdat(table[m]);           
	    delay(200); 
    } 

    lcd_wcom(0x85);   //显示地址设为85H,上排中间位

    lcd_wdat(0);

    while(1);        //动态停机 
} 

  需要注意:在最后写入自定义字符时,还写入了一个0,我的理解是,这一步的就是正常的写入显示内容的指令,但没有从D0~D7引脚输入数据,模块就自动调用CGRAM中的数据,这个0不能换成其他任何数据!
  经过测试发现,如果要显示两个不同的自定义字符很有可能会发生冲突的情况,显示效果较差。

LCD12864

引脚定义

  对于LCD12864,有两种工作模式,串行和并行,当PSB引脚为低电平时,其工作在串行模式下,此时其通信模式类似于SPI,靠三根引脚CS(片选)、SID(数据输入端)、CLK(时钟输入端) 来进行通信,因此其数据传输端口DB0~DB7无效;当PSB引脚为高电平时,其工作在并行模式下,此时RS(CS)、R/W(SID)、E(CLK) 为控制信号输入端,DB0~DB7为数据输入输出端。
  因此,串行和并行方式下使用的引脚也不相同,如下图所示:
【嵌入式模块】LCD1602&LCD12864_第13张图片
【嵌入式模块】LCD1602&LCD12864_第14张图片

  其中,由于并行引脚工作方式与LCD1602十分接近,而且目前串行操作更加流行,因此这里只介绍串行控制方法,并行控制方法可以参考1602

指令集

  在LCD12864中,具有两套指令:基本指令扩展指令,选择哪一套指令可以通过输入指令来选择,指令具体如下所示:
【嵌入式模块】LCD1602&LCD12864_第15张图片
【嵌入式模块】LCD1602&LCD12864_第16张图片
【嵌入式模块】LCD1602&LCD12864_第17张图片
初始化流程
【嵌入式模块】LCD1602&LCD12864_第18张图片
  可以对照上述指令表根据自己的需要来设置。

串行工作时序图

【嵌入式模块】LCD1602&LCD12864_第19张图片
  这张图需要仔细看。首先是CS信号,在传输数据时必须为高电平,如果不需要考虑那么多的话,可以直接连接VCC,使其始终有效。
  然后是SCLK信号,仔细观察可以发现,在SCLK上升沿产生数据传输,即SCLK上升沿之前要把数据准备好。
  最后是SID信号,从图中可以看出,每次传输一个字节的数据,需要24个时钟,即传输3个字节。其中第一个字节为选择传输数据还是传输指令第二个字节为数据字节的高4位加4个0第三个字节为数据字节的低4位加4个0

51例程

#include "LCD.h"

void delay_us(uint8_t time)
{
	time *= 0.9;   //晶振为11.0592MHz
	while(time--);
}

void delay_ms(uint8_t times)
{
	while(times--)
	{
		delay_us(1000);
	}
}

void send_byte(uint8_t byte)
{
	uint8_t i;
	for(i=0; i<8; i++)
	{
		if((byte << i) & 0x80)  //从最高位开始
		{
			LCD_SID = 1;
		}
		else
		{
			LCD_SID = 0;
		}
		LCD_SCK = 0;
//		delay_us(5);
		LCD_SCK = 1;
	}
}

void write_cmd(unsigned char cmdcode)
{
//	delay_ms(1);
    send_byte(0xf8);    //告诉12864接下来传送指令
    send_byte(cmdcode & 0xf0);     //先传输高4位
    send_byte((cmdcode << 4) & 0xf0);  //后传输低4位
//    delay_us(100);       //延时待数据写入
}

void write_data(unsigned char Dispdata)
{
//	delay_ms(1);
    send_byte(0xfa);         //告诉12864接下来传送数据
    send_byte(Dispdata & 0xf0);   //先传输高4位
    send_byte((Dispdata << 4) & 0xf0);  //后传输低4位
//    delay_us(100);       //延时待数据写入
}

void LCD_Init(void)
{
    delay_ms(200);     //等待液晶自检,延时50ms
    write_cmd(0x30);   //基本指令操作,8bit
//    delay_us(150);    //延时137us以上
    write_cmd(0x0c);  //显示开关闭光标
//    delay_us(110);    //延时100us以上
    write_cmd(0x01);  //清屏
    delay_ms(100);     //清屏后等待一段时间实现稳定
//	write_cmd(0x06);
}

void write_str(char *s)
{
//    while(*s > 0)
//    {
//        write_data(*s);
//        s++;
//        delay_ms(5);
//    }
	unsigned char  i = 0;  
    while(s[i]!='\0')  
    {   
        write_data(s[i]);  
        i++;  
        delay_ms(5);  
    }  
}

void write_title(void)
{
    write_cmd(0x80);    //第一行首位
    write_str("距离为");
    write_cmd(0x90);    //第二行首位
    write_str("速度为");
    write_cmd(0x88);    //第三行首位
    write_str("角度为");
    write_cmd(0x98);    //第四行首位
    write_str("加速度为");
    delay_ms(50);
}

  上述例程中有一点需要注意:由于Keil_C51的编译器太垃圾,经过测试,传输字符串函数中的指针部分无法识别,对应那被注释掉的部分代码。

问题与解决

  在调试上面那部分代码时,发现一个很严重的问题,那就是LCD12864一旦显示中文,总是显示乱码
  在网上查找资料时发现,有说Keil缺少某一个文件的,需要把它添加到根目录下的bin文件夹中,但我试了并不管用;还有说需要把Keil中含有中文字符的代码文件转换为ASCII编码格式的文件,这一点我也试了【而且不知道ASCII是个什么编码格式】,并不管用。
  但第二种方法启发了我,我试着将我的Keil编码格式改为GB2312【原来为了更好看的字体改为了UTF-8】,然后通过Notepads将文件以GB2312编码格式保存,意外发现问题已经解决!

高阶应用:显示自定义字符

  和1602一样,12864中也具备显示自定义字符的功能,而且使用方法也非常类似,也是向CGRAM中写入自定义字符的代码,然后再写入到DDRAM,从而显示出来。
  值得一提的是,12864中的CGRAM有4组16x16的空间,共128个字节,可以显示4个16x16的自定义汉字或符号。其指令如下图所示:
在这里插入图片描述
  参考指令表可以得出:该四个汉字的指令地址为0x40~0x4F、0x50 ~ 0x5F、0x60 ~ 0x6F、0x70 ~ 0x7F,配合取模软件,即可得到自定义的字符,其中,每一行的16位拆分为高8位和低8位,两个字节,然后开启下一行。

你可能感兴趣的:(#,嵌入式设备,嵌入式硬件,51单片机,LCD1602,LCD12864)