LCD1602液晶显示模块

1.认识LCD1602

1、概述:

  1. LCD1602(Liquid Crystal Display)是一种工业字符型液晶,能够同时显示 16×02,32个 字符(16列两行)。是我们接触引脚最多的模块。
  2. LCD1602我们的非标准协议(标准协议有IIC、IIS、SPI)中比较容易懂的玩法。

2、引脚说明:翻阅LCD1602说明书

  • 共有16根引脚,如下表:
    编号符号引脚说明编号符号引脚说明
    1VSS电源地9D2双向数据线
    2VDD电源正极,接5V正电源10D3双向数据线
    3V0液晶显示偏压。是液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高会产生“鬼影”(四方框影像),使用时可以通过一个10K电位器调整对比度。11D4双向数据线
    4RS数据/命令寄存器选择,高电平时选择数据寄存器,低电平时选择指令寄存器。一会看时序图12D5双向数据线
    5R/W读/写选择。高电平时进行读操作,低电平时进行写操作。一会看时序图13D6双向数据线
    6E使能信号。当E端由高电平跳变为低电平时,液晶模块执行命令。14D7双向数据线
    7D0双向数据线15BLA背光源正极
    8D1双向数据线16BLK背光源负极
  • 数据线占8根,有点像串口的SBUF(8位数据缓冲寄存器),单片机和LCD之间的数据交互也需要类似于SBUF的东西,但是不幸的是LCD1602没有串口,所以我们用一组I/O口(D0~D7)表示。

  • RS和R/W引脚可以配合使用:
    RS高电平RS低电平
    R/W高电平读忙信号
    R/W低电平写入内容写入指令或者写入显示地址

3、控制指令:翻阅LCD1602说明书。

  • LCD1602液晶显示模块的读写操作,屏幕和光标的操作都是通过指令编程来实现的。
  • LCD1602液晶显示模块内部的控制器共有11条控制指令,是配合RS、R/W和8根数据线实现的,如下表:
序号指令RSR/WD7D6D5D4D3D2D1D0指令说明
1清除显示0000000001清显示,指令码01H,光标复位到地址00H位置
2光标返回000000001*光标复位,光标返回到地址00H
3置输入模式00000001I/FS光标和显示模式设置。I/D:光标移动方向,高电平右移,低电平左移。实际上就是控制从左到右写入还是从右至左的写入顺序。S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。S=1 当写一个字符,整屏显示左移(ID=1)或者右移(I/D=0),以得到光标不移动而屏幕移动的效果。S=0 当写一个字符,整屏显示不移动。
4显示开/关控制0000001DCB显示开关控制。D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示.  C:控制光标的开与关,高电平表示有光标,低电平表示无光标. B:控制光标是否闪烁,高电平闪烁,低电平不闪烁
5光标或字符移位000001S/CR/L**光标或显示移位S/C:高电平时移动显示的文字,低电平时移动光标。R/L:文字或者光标移动方向,R表示右移,L表示左移
6置功能00001DLNF**功能设置命令DL:高电平时为8位总线,低电平时为4位总线。N:低电平时为单行显示,高电平时双行显示。F:低电平时显示5×8的点阵字符,高电平时显示5×0的点阵字符
7置字符发生存储器地址0001字符发生存储器地址(自定义字符)字符发生器RAM地址设置
8置数据存储器地址 0 0 1 显示数据存储器地址(在哪里显示DDRAM地址设置
9读忙标志或地址 0  1 BF计算器地址读忙信号和光标地址。BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
10写数到CGRAM或DDRAM 1 0要写的数据内容(显示什么写数据
11从CGRAM或DDRAM读数11读出的数据内容读数据


2.开发逻辑

1、和单片机的接线方法:

 

  1. 两组电源线
    1. VSS——GND
    2. VDD——5V
    3. A——5V
    4. K——GND
  2. 对比度:V0——GND
  3. 控制线
    1. RS——P1.0
    2. R/W——P1.1
    3. E——P1.4
  4. 数据线:D0到D7——P0.0到P0.7
    1. 注:之前讲过51单片机的P0口组没有上拉电阻,作为总线扩展不用加上拉电阻,作为I/O口使用时需要上拉电阻。

2、搞清在哪显示:

  • 在哪里显示是依靠D0~D7这8根数据线来实现的。以前PC和51单片机用串口通讯,51单片机上有专门的SBUF寄存器,现在LCD1602上没有专门的SBUF寄存器,所以说只能用8根数据线来替代。
  • 如下左图是LCD的内部显示地址,我们只要告知LCD1602将来要在哪个位置写什么数据就能实现如右图的效果,图中的地址码是16进制的。DDRAM 是显示用RAM,直接和屏幕上的点相对应,屏幕上的一个点和DDRAM中的一个位对应。

 

  • 注意到LCD1602共有32个字符,而25=32,是否用5bit就能表示显示的地址呢?答:确实是这样的,但是除了屏幕上可以显示的32个字符数据以外,还有我们肉眼看不到的显示地址(如上左图所示),实际上LCD1602通过指令可以实现数据移位的效果(1602液晶屏一行显示16个,对应于00-0F/40-4F,而DDRAM可以储存80个,如果需要显示10-27以及50-67的内容就需要用到左移右移来实现),但是我们目前用不到这么复杂。
  • 真正的显示地址:如上左图的显示地址并不是我们编程时需要的显示地址。真正的地址要求bit7一定是1(在1.3控制指令表中提到:写入显示地址时要求最高位D7恒定为高电平1),剩下的才是我们需要设置的位置,比如我们想在05这个显示地址显示字符'N',05的二进制是0000 0101,但实际上规定显示地址的bit7是1,所以说05位置真正的显示地址是0x80(1000 0000)+ 0x05(0000 0101)= 0x85(1000 0101),所以编程时写0x85。

3、搞清显示什么:

  • 显示什么也是依靠D0~D7这8根数据线来实现的。
  • 如下表是LCD1602模块字库表。1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,这些字符有:阿拉伯数字,英文字母的大小写,常用的符号,和日文假名等,每一个字符都有一个固定的代码,对于我们常用的数字和字母来说,这些代码就是ASCII码。比如小写字母'a',我们查表得到高位是0110,低位是0001。转换成十进制就是97,也就是'a'的ASCII码。所以说让单片机告诉LCD显示模块显示什么就变得很简单,对编程来说只需要直接写字母即可,不需要像显示地址一样写16进制数。

 

  • CGRAM : 允许用户自建字模区存储器,从LCD1602模块字库表的最左侧可以看见。具体信息可以百度。

4、如何区分显示地址和显示内容:

  • 51单片机的P0这个I/O口组有两个任务,一个是通过LCD上的8根数据线来告诉LCD显示模块在哪里显示,另一个是通过LCD上的8根数据线来告诉LCD显示模块要显示什么字符。那么LCD显示模块如何区分51单片机发送来的显示位置和显示内容呢?答:RS,它是数据/命令选择位。也就是说确定这8位是字符'a'的ASCII码时,把RS配置成1,选择数据寄存器;当确定这8位是显示的地址时,把RS配置成0,选择指令寄存器

3.读/写操作时序

阅读时序图,需要关心以下三点:开始、结束、转折。

0、时序参数:

1、写操作时序分析:51单片机无论是给LCD模块输入地址还是内容,LCD显示模块都是被写入的那个,所以我们要学习写操作时序图。

  1. RS:引脚功能回看1.2引脚说明:数据/指令寄存器选择位。
    1. 看时序图:写操作时RS可以是高电平也可以是低电平,暂时不确定
    2. 回看1.2引脚说明:RS只看开始和结束,在R/W为0的前提下,RS为1代表写内容,RS为0代表写指令(地址),中间不需要转折。
    3. 根据功能,封装写内容和写指令这两个函数即可。
  2. R/W:引脚功能回看1.2引脚说明:读/写选择位。
    1. 看时序图:写操作开始(可以是高电平也可以是低电平)——> 中间在数据传输时必须是低电平 ——> 写操作结束(可以是高电平也可以是低电平)
    2. 回看1.2引脚说明:只有在R/W是低电平时,才能保证RS在高低电平切换时写入内容或者写入指令(地址)。
    3. 因此,全程把R/W配置成0就可以了,不用去管时序图中的时间。
  3. DB0~DB7:就相当于SBUF(回顾串口的接收/发送时序图),只不过这里LCD写操作的一帧数据是由引脚E控制的。
    1. 看时序图:可高可低,取决于写入的数据。
    2. 看时序图和时序参数图:有一个数据建立时间tSP2,也就是说在E=0(没被拉高前)时,就开始往LCD写数据;同时有一个数据保持时间tHD2,也就是在E变回0后,还保持一段数据写入的时间。
  4. E:引脚功能回看1.2引脚说明。使能信号从高电平到低电平时,LCD模块执行命令。
    1. 看时序图:开始是低电平——> 延时一段时间 ——> 中间转折为高电平(上升时间是tR) ——> 延时一段时间 ——> 中间转折为低电平(下降时间是tF) ——> 结束是低电平。
    2. 看时序参数图,tR和tF都是25ns,转折处持续的时间tpw是150ns。我们知道51单片机在11.0592MHz的晶振频率下的机器周期是1.085μs,所以给一个_nop_()就够了。
    3. 在写入数据(不论内容还是指令)时:E开始为0 ——> _nop_(); 延时1微秒 ——> P0口写数据 ——>  _nop_(); 延时1微秒 ——> E转折为1 ——> _nop_(); 延时1微秒(不够就2微秒) ——> E结束为0 ——> _nop_(); 延时1微秒。

2、读操作时序分析:液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志为低电平,表示不忙,否则此指令失效。检测忙信号就是一个让LCD显示模块被读的过程,因此需要学习LCD的读操作时序分析。

  1. RS:引脚功能回看1.2引脚说明:数据/指令寄存器选择位。
    1. 看时序图:读操作时RS可以是高电平也可以是低电平,暂时不确定
    2. 回看1.2引脚说明:RS只看开始和结束,在R/W为1的前提下,RS为0代表读忙信号,中间没什么好转折的。
    3. 所以:全程把RS配置成0。
  2. R/W:引脚功能回看1.2引脚说明:读/写选择位。
    1. 看时序图:读操作开始(可以是高电平也可以是低电平)——> 中间在读数据时必须是高电平 ——> 读操作结束(可以是高电平也可以是低电平)
    2. 回看1.2引脚说明:只有在R/W是高电平时,才能保证在读忙信号。
    3. 因此,全程把R/W配置成1就可以了,不用去管时序图中的时间。
  3. DB0~DB7:就相当于SBUF(回顾串口的接收/发送时序图),只不过这里LCD读操作的一帧数据是由引脚E控制的。
    1. 看图:可高可低,取决于读取的数据。
    2. 看时序图和时序参数图:有一个数据建立时间tD,也就是说在E拉高一段时间后才去读数据;同时有一个数据保持时间tHD2,也就是在E变回0后,还保持一段数据读取的时间。
  4. E:引脚功能回看1.2引脚说明。使能信号从高电平到低电平时,LCD模块执行命令。
    1. 看时序图:开始是低电平——> 延时一段时间 ——> 中间转折为高电平(上升时间是tR) ——> 延时一段时间 ——> 中间转折为低电平(下降时间是tF) ——> 结束是低电平。
    2. 看时序参数图,tR和tF都是25ns,转折处持续的时间tpw是150ns。我们知道51单片机在11.0592MHz的晶振频率下的机器周期是1.085μs,所以给一个_nop_()就够了。
    3. 在读忙信号时:E开始为0 ——> _nop_(); 延时1微秒 ——> E转折为1 ——>  _nop_(); 延时1微秒(不够就2微秒)——> P0口读数据 ——>  _nop_(); 延时1微秒 ——> E结束为0 ——> _nop_(); 延时1微秒。

3、忙信号检测:

  • 回看1.3控制指令:指令9中提到忙信号的检测标志位是BF,也就是D7这根数据总线。BF高电平为忙,低电平为不忙,只有在不忙的时候我们才能做动作(写操作,不论是写内容还是写指令)。
  • 编程时判断LCD是否在忙,就是判断51单片机从P0这个I/O口组的P0.7这一位是否是1,有两种方法(我们以下的程序中选用第二种方式):
    • 方式1:由于P0这个寄存器可位寻址,所以直接找到P0.7这一位,对它做判断即可
    • 方式2:不通过位寻址,对P0做位与运算,进行取位操作,方法是将除了bit7以外的位都“置0”,bit7保持不变,即 P0 & 0x80,如果P0 & 0x80 的运算结果是0,说明P0的bit7是0,如果运算结果不是0,说明P0的bit7是1。

4.LCD1602的初始化

1、LCD1602初始化过程(8bit):手册中总结好了,我们一会儿只需要封装一下初始化LCD的函数即可。

(1)延时15ms

(2)写指令38H(不检测忙信号)

(3)延时5ms

(4)以后每次写指令,读/写数据操作均需要检测忙信号

(5)写指令38H:显示模式设置

(6)写指令08H:显示关闭

(7)写指令01H:显示清屏

(8)写指令06H:显示光标移动设置

(9)写指令0CH:显示开及光标设置

5.LCD显示demo

习题1(LCD显示一个字符:在LCD的第一行第六列的位置显示一个字符'N'

  • 注:data是关键字,不要去用这个当做变量名
  1. 思路:
    宏定义:
    1. 定义符号dataBuffer,用它代表P0这个I/O口组: #define dataBuffer P0
    //dataBuffer的传递路线为: 
    //路线1:main函数: position ——> API1. LCD_write_cmd(char cmd); ——> cmd ——> dataBuffer ——> LCD
    //路线2:main函数:dataShow ——> API2. LCD_write_data(char datashow); ——> datashow ——> dataBuffer ——> LCD
    //路线3:API6. check_busy() ——> LCD ——> dataBuffer ——> temp
    全局变量:
    1. sbit指令找到P1这个I/O口组的第0位P1^0,把它与LCD的RS(LCD的数据/指令寄存器选择位)相连,用来输出指令给LCD: sbit RS = P1^0;
    2. sbit指令找到P1这个I/O口组的第0位P1^1,把它与LCD的RW(LCD的读/写选择位)相连,用来输出指令给LCD: sbit RW = P1^1;
    3. sbit指令找到P1这个I/O口组的第0位P1^4,把它与LCD的E(LCD的使能信号)相连,用来输出指令给LCD: sbit EN = P1^4;
    1. 根据LCD内部地址码,令显示地址为05H(第一行第六列),保存在字符变量position中:
    	char position = 0x80 + 0x05;
    2. 确认position这个地址上显示的字符是'N',保存在字符变量dataShow中: char dataShow = 'N';
    3. 调用API3. 初始化LCD1602: LCD1602_Init();
    4. 调用API1. 往LCD显示模块中写地址,告诉LCD字符'N'要显示的地址: LCD_write_cmd(position);
    5. 调用API2. 往LCD显示模块中写内容,紧接着告诉LCD显示的内容是'N': LCD_write_data(dataShow);
    /* 一级函数:f1、f2、f3 */
    f1. 封装往LCD液晶显示模块写指令的API: void LCD_write_cmd(char cmd); 
    //形参cmd是要写入的指令(地址)
    	f1.1 调用API6,对LCD操作前检测忙信号: check_busy();
        f1.2 RS低电平时,指令寄存器选择: RS = 0;
    	f1.3 RW低电平,表示写操作: RW = 0;
        f1.4 根据写操作的时序分析,总结出如下过程:
    		EN = 0;
    		_nop_();
    		dataBuffer = cmd;
    		_nop_();
    		EN = 1;
    		_nop_();
    		EN = 0;
    		_nop_();
    f2. 封装往LCD液晶显示模块写内容的API: void LCD_write_data(char datashow);	
    //形参datashow是要写入的内容
    	f2.1 调用API6,对LCD操作前检测忙信号: check_busy();
        f2.2 RS高电平时,数据寄存器选择: RS = 1;
    	f2.3 RW低电平,表示写操作: RW = 0;
        f2.4 根据写操作的时序分析,总结出如下过程:
    		EN = 0;
    		_nop_();
    		dataBuffer = datashow;
    		_nop_();
    		EN = 1;
    		_nop_();
    		EN = 0;
    		_nop_();
    f3. 封装初始化LCD液晶显示模块的API: void LCD1602_Init(); 
    	f3.1 调用API4,软件延时15ms: Delay15ms();
    	f3.2 调用API1,写指令38H(不检测忙信号): LCD_write_cmd(0x38);
    	f3.3 调用API5,软件延时5ms: Delay5ms();
    	f3.4 显示模式设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令38H: LCD_write_cmd(0x38);	
    	f3.5 显示关闭:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令08H: LCD_write_cmd(0x08);
    	f3.6 显示清屏:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令01H: LCD_write_cmd(0x01);
    	f3.7 显示光标移动设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令06H: LCD_write_cmd(0x06);
    	f3.8 显示开机光标设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令0CH: LCD_write_cmd(0x0C);
    /* 二级函数:f1、f2、f4、f5、f6 */
    f1. 同上,也是一级函数: void LCD_write_cmd(char cmd);
    f2. 同上,也是一级函数: void LCD_write_data(char datashow);
    f4. 封装软件延时15ms的API,用于LCD初始化: void Delay15ms();
    f5. 封装软件延时5ms的API,用于LCD初始化: void Delay5ms();
    f6. 封装检测(读取)忙信号的API: void check_busy();
    	f6.1 将从P0这个I/O口组获取到的LCD的8位数据线的数据,保存在字符变量temp中: char temp = 0x80;
    	//内在逻辑:temp中包含了忙信号的标志位(bit7),标志位是1则表示LCD正忙,我们还没读取前
    	//就让51单片机认为LCD正忙,所以初始化为0x80(1000 0000),这样方便一会儿进入循环
    	f6.2 把LCD的busy这个状态做的更彻底一点,让P0这个I/O口组的bit7是1: dataBuffer = 0x80;
        f6.3 while循环,一直检测忙信号,直到检测到不忙,判据是:!((temp & 0x80)==0)
        //语法逻辑:用!表示“直到”,用 (temp & 0x80)==0 表示不忙。也就是说不忙就退出循环,不再检测
            f6.3.1 RS低电平时,指令寄存器选择: RS = 1;
    		f6.3.2 RW高电平,表示读操作: RW = 1;
            f6.3.3 根据读操作的时序分析,总结出如下过程:
    			EN = 0;
    			_nop_();
    			EN = 1;
    			_nop_();
    			temp = dataBuffer;
    			_nop_();
    			EN = 0;
    			_nop_();

  2. 代码:
    #include "reg52.h"
    #include "intrins.h"
    
    #define dataBuffer P0	//LCD的8位数据线,刚好用dataBuffer这个I/O口组
    sbit RS = P1^0;			//LCD的数据/指令寄存器选择位
    sbit RW = P1^1;			//LCD的读/写选择位
    sbit EN = P1^4;			//LCD的使能信号
    
    /* API1. LCD液晶显示模块写指令 */
    void LCD_write_cmd(char cmd);
    /* API2. LCD液晶显示模块写内容 */
    void LCD_write_data(char datashow);
    /* API3. 初始化LCD1602 */
    void LCD1602_Init();
    /* API4. 软件延时15ms,用于LCD初始化 */
    void Delay15ms();
    /* API5. 软件延时5ms,用于LCD初始化 */
    void Delay5ms();
    /* API6. 检测忙信号 */
    void check_busy();
    
    void main(void)
    {
    	char position = 0x80 + 0x05;	//显示地址:05H,第一行第六列
    	char dataShow = 'N';
    	LCD1602_Init();					//初始化LCD1602
    	LCD_write_cmd(position);		//选择要显示的地址
    	LCD_write_data(dataShow);		//发送要显示的字符
    }
    
    void LCD_write_cmd(char cmd)
    {
    	check_busy();
    	RS = 0;		//RS低电平时,指令寄存器选择,将1个字符写在数据线上告诉LCD这是指令
    	RW = 0;
    	EN = 0;
    	_nop_();
    	dataBuffer = cmd;
    	_nop_();
    	EN = 1;
    	_nop_();
    	EN = 0;
    	_nop_();
    }
    
    void LCD_write_data(char datashow)
    {
    	check_busy();
    	RS = 1;		//RS高电平时,数据寄存器选择,将1个字符写在数据线上告诉LCD这是内容
    	RW = 0;
    	EN = 0;
    	_nop_();
    	dataBuffer = datashow;
    	_nop_();
    	EN = 1;
    	_nop_();
    	EN = 0;
    	_nop_();
    }
    
    void LCD1602_Init()
    {
    	Delay15ms();			//(1)延时15ms
    	LCD_write_cmd(0x38);	//(2)写指令38H(不检测忙信号)
    	Delay5ms();				//(3)延时5ms
    	//(4)以后每次写指令,读/写数据操作均需要检测忙信号
    	check_busy();
    	LCD_write_cmd(0x38);	//(5)写指令38H:显示模式设置
    	check_busy();
    	LCD_write_cmd(0x08);	//(6)写指令08H:显示关闭
    	check_busy();
    	LCD_write_cmd(0x01);	//(7)写指令01H:显示清屏
    	check_busy();
    	LCD_write_cmd(0x06);	//(8)写指令06H:显示光标移动设置
    	check_busy();
    	LCD_write_cmd(0x0C);	//(9)写指令0CH:显示开及光标设置
    }
    
    void Delay15ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 27;
    	j = 226;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void Delay5ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 9;
    	j = 244;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void check_busy()
    {
    	char temp = 0x80;				//一开始就busy
    	dataBuffer = 0x80;
    	while( !((temp & 0x80)==0) ){	//一直检测忙信号,直到检测到不忙(temp的bit7为高电平代表忙)
    		RS = 0;
    		RW = 1;
    		EN = 0;
    		_nop_();
    		EN = 1;
    		_nop_();
    		temp = dataBuffer;
    		_nop_();
    		EN = 0;
    		_nop_();
    	}	
    }

习题2(LCD显示一行字符

  1. 思路:
    宏定义:
    1. 定义符号dataBuffer,用它代表P0这个I/O口组: #define dataBuffer P0
    //dataBuffer的传递路线为: 
    //路线1:main函数: position ——> API1. LCD_write_cmd(char cmd); ——> cmd ——> dataBuffer ——> LCD
    //路线2:main函数:dataShow ——> API2. LCD_write_data(char datashow); ——> datashow ——> dataBuffer ——> LCD
    //路线3:API6. check_busy() ——> LCD ——> dataBuffer ——> temp
    全局变量:
    1. sbit指令找到P1这个I/O口组的第0位P1^0,把它与LCD的RS(LCD的数据/指令寄存器选择位)相连,用来输出指令给LCD: sbit RS = P1^0;
    2. sbit指令找到P1这个I/O口组的第0位P1^1,把它与LCD的RW(LCD的读/写选择位)相连,用来输出指令给LCD: sbit RW = P1^1;
    3. sbit指令找到P1这个I/O口组的第0位P1^4,把它与LCD的E(LCD的使能信号)相连,用来输出指令给LCD: sbit EN = P1^4;
    1. 调用API3. 初始化LCD1602: LCD1602_Init();
    2. 调用API7. 在LCD模块的第1行的第5列位置开始显示字符串"NO.1": LCD1602_showLine(1,5,"NO.1");
    3. 调用API7. 在LCD模块的第2行的第0列位置开始显示字符串"chenlichen shuai":
    	LCD1602_showLine(2,0,"chenlichen shuai");
    /* 一级函数:f3、f7 */
    f3. 封装初始化LCD液晶显示模块的API: void LCD1602_Init(); 
    	f3.1 调用API4,软件延时15ms: Delay15ms();
    	f3.2 调用API1,写指令38H(不检测忙信号): LCD_write_cmd(0x38);
    	f3.3 调用API5,软件延时5ms: Delay5ms();
    	f3.4 显示模式设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令38H: LCD_write_cmd(0x38);	
    	f3.5 显示关闭:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令08H: LCD_write_cmd(0x08);
    	f3.6 显示清屏:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令01H: LCD_write_cmd(0x01);
    	f3.7 显示光标移动设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令06H: LCD_write_cmd(0x06);
    	f3.8 显示开机光标设置:
    		调用API6,检测忙信号: check_busy();
    		调用API1,写指令0CH: LCD_write_cmd(0x0C);
    f7. 封装在LCD液晶上显示一行字符串的API: void LCD1602_showLine(char row,char column,char *str);
    	形参row是行,column是列,str是要显示的字符串
        f7.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
    	f7.2 switch选择语句,表达式为row
        	f7.2.1 当row为1时:表示在第一行显示字符串
            	f7.2.1.1 往LCD显示模块中写起始地址:
    				调用API6,检测忙信号: check_busy();
    				调用API1,告知显示地址为第1行第column列,LCD_write_cmd(0x80+column);
    			f7.2.1.2 while循环,控制循环的变量是*p,当*p != '\0' 时进入循环,发送要显示的内容:
    				调用API6,检测忙信号: check_busy();
    				调用API2. 往LCD显示模块中写当前字符指针p所在位置的字符: LCD_write_data(*p);
    				修改循环变量p的值,让指针p偏移: p++;
    			f7.2.1.3 break提前退出当前选择控制语句: break;
    		f7.2.2 当row为2时:表示在第二行显示字符串
            	f7.2.2.1 往LCD显示模块中写起始地址:
    				调用API6,检测忙信号: check_busy();
    				调用API1,告知显示地址为第2行第column列,LCD_write_cmd(0x80+0x40+column);
    			f7.2.2.2 while循环,控制循环的变量是*p,当*p != '\0' 时进入循环,发送要显示的内容:
    				调用API6,检测忙信号: check_busy();
    				调用API2. 往LCD显示模块中写当前字符指针p所在位置的字符: LCD_write_data(*p);
    				修改循环变量p的值,让指针p偏移: p++;
    			f7.2.2.3 break提前退出当前选择控制语句: break;
    /* 二级函数:f1、f2、f4、f5、f6 */
    f1. 封装往LCD液晶显示模块写指令的API: void LCD_write_cmd(char cmd); 
    //形参cmd是要写入的指令(地址)
    	f1.1 调用API6,对LCD操作前检测忙信号: check_busy();
        f1.2 RS低电平时,指令寄存器选择: RS = 0;
    	f1.3 RW低电平,表示写操作: RW = 0;
        f1.4 根据写操作的时序分析,总结出如下过程:
    		EN = 0;
    		_nop_();
    		dataBuffer = cmd;
    		_nop_();
    		EN = 1;
    		_nop_();
    		EN = 0;
    		_nop_();
    f2. 封装往LCD液晶显示模块写内容的API: void LCD_write_data(char datashow);	
    //形参datashow是要写入的内容
    	f2.1 调用API6,对LCD操作前检测忙信号: check_busy();
        f2.2 RS高电平时,数据寄存器选择: RS = 1;
    	f2.3 RW低电平,表示写操作: RW = 0;
        f2.4 根据写操作的时序分析,总结出如下过程:
    		EN = 0;
    		_nop_();
    		dataBuffer = datashow;
    		_nop_();
    		EN = 1;
    		_nop_();
    		EN = 0;
    		_nop_();
    f4. 封装软件延时15ms的API,用于LCD初始化: void Delay15ms();
    f5. 封装软件延时5ms的API,用于LCD初始化: void Delay5ms();
    f6. 封装检测(读取)忙信号的API: void check_busy();
    	f6.1 将从P0这个I/O口组获取到的LCD的8位数据线的数据,保存在字符变量temp中: char temp = 0x80;
    	//内在逻辑:temp中包含了忙信号的标志位(bit7),标志位是1则表示LCD正忙,我们还没读取前
    	//就让51单片机认为LCD正忙,所以初始化为0x80(1000 0000),这样方便一会儿进入循环
    	f6.2 把LCD的busy这个状态做的更彻底一点,让P0这个I/O口组的bit7是1: dataBuffer = 0x80;
        f6.3 while循环,一直检测忙信号,直到检测到不忙,判据是:!((temp & 0x80)==0)
        //语法逻辑:用!表示“直到”,用 (temp & 0x80)==0 表示不忙。也就是说不忙就退出循环,不再检测
            f6.3.1 RS低电平时,指令寄存器选择: RS = 1;
    		f6.3.2 RW高电平,表示读操作: RW = 1;
            f6.3.3 根据读操作的时序分析,总结出如下过程:
    			EN = 0;
    			_nop_();
    			EN = 1;
    			_nop_();
    			temp = dataBuffer;
    			_nop_();
    			EN = 0;
    			_nop_();

  2. 代码:
    #include "reg52.h"
    #include "intrins.h"
    
    #define dataBuffer P0	//LCD的8位数据线,刚好用dataBuffer这个I/O口组
    sbit RS = P1^0;			//LCD的数据/指令寄存器选择位
    sbit RW = P1^1;			//LCD的读/写选择位
    sbit EN = P1^4;			//LCD的使能信号
    
    /* API1. LCD液晶显示模块写指令 */
    void LCD_write_cmd(char cmd);
    /* API2. LCD液晶显示模块写内容 */
    void LCD_write_data(char datashow);
    /* API3. 初始化LCD1602 */
    void LCD1602_Init();
    /* API4. 软件延时15ms,用于LCD初始化 */
    void Delay15ms();
    /* API5. 软件延时5ms,用于LCD初始化 */
    void Delay5ms();
    /* API6. 检测忙信号 */
    void check_busy();
    /* API7. LCD1602显示一行 */
    void LCD1602_showLine(char row, char column, char *str);
    
    void main(void)
    {
    	LCD1602_Init();			//初始化LCD1602
    	LCD1602_showLine(1,5,"NO.1");
    	LCD1602_showLine(2,0,"chenlichen shuai");
    }
    
    void LCD_write_cmd(char cmd)
    {
    	check_busy();
    	RS = 0;		//RS低电平时,指令寄存器选择,将1个字符写在数据线上告诉LCD这是指令
    	RW = 0;
    	EN = 0;
    	_nop_();
    	dataBuffer = cmd;
    	_nop_();
    	EN = 1;
    	_nop_();
    	EN = 0;
    	_nop_();
    }
    
    void LCD_write_data(char datashow)
    {
    	check_busy();
    	RS = 1;		//RS高电平时,数据寄存器选择,将1个字符写在数据线上告诉LCD这是内容
    	RW = 0;
    	EN = 0;
    	_nop_();
    	dataBuffer = datashow;
    	_nop_();
    	EN = 1;
    	_nop_();
    	EN = 0;
    	_nop_();
    }
    
    void LCD1602_Init()
    {
    	Delay15ms();			//(1)延时15ms
    	LCD_write_cmd(0x38);	//(2)写指令38H(不检测忙信号)
    	Delay5ms();				//(3)延时5ms
    	//(4)以后每次写指令,读/写数据操作均需要检测忙信号
    	check_busy();
    	LCD_write_cmd(0x38);	//(5)写指令38H:显示模式设置
    	check_busy();
    	LCD_write_cmd(0x08);	//(6)写指令08H:显示关闭
    	check_busy();
    	LCD_write_cmd(0x01);	//(7)写指令01H:显示清屏
    	check_busy();
    	LCD_write_cmd(0x06);	//(8)写指令06H:显示光标移动设置
    	check_busy();
    	LCD_write_cmd(0x0C);	//(9)写指令0CH:显示开机光标设置
    }
    
    void Delay15ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 27;
    	j = 226;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void Delay5ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 9;
    	j = 244;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void check_busy()
    {
    	char temp = 0x80;				//一开始就busy
    	dataBuffer = 0x80;
    	while( !((temp & 0x80)==0) ){	//一直检测忙信号,直到检测到不忙(temp的bit7为高电平代表忙)
    		RS = 0;
    		RW = 1;
    		EN = 0;
    		_nop_();
    		EN = 1;
    		_nop_();
    		temp = dataBuffer;
    		_nop_();
    		EN = 0;
    		_nop_();
    	}	
    }
    
    void LCD1602_showLine(char row, char column, char *str)
    {
    	char *p = str;
    	switch(row){
    		case 1:
    			check_busy();
    			LCD_write_cmd(0x80+column);			//选择要显示的地址
    			while(*p != '\0'){		
    				check_busy();			
    				LCD_write_data(*p);		//发送要显示的字符(不用发指令让光标移动,光标会自动后移)
    				p++;
    			}
    			break;
    		case 2:
    			check_busy();
    			LCD_write_cmd(0x80+0x40+column);	//选择要显示的地址
    			while(*p != '\0'){
    				check_busy();
    				LCD_write_data(*p);		//发送要显示的字符(不用发指令让光标移动,光标会自动后移)
    				p++;
    			}
    			break;
    		default :
    			break;
    	}
    }

  • 8
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCD1602液晶显示模块是一种常用的显示器件,可以显示文字和图形,广泛应用于各种电子设备中。下面介绍一些调试LCD1602液晶显示模块的具体方法: 1. 检查电路连接。首先要确保电路连接正确,电源电压稳定,各个引脚连接正确。在连接时,可以参考LCD1602液晶显示模块的连接图。 2. 初始化液晶显示模块。在程序中,需要先对液晶显示模块进行初始化,即设置显示模式、光标位置、显示内容等。常用的初始化命令有0x38、0x0C、0x01等。 3. 发送显示数据。在程序中,使用相应的命令和数据,向液晶显示模块发送需要显示的文字、数字、符号等信息。常用的显示命令有0x80、0x81、0x82等,对应不同的显示位置。 4. 观察显示效果。在程序中,可以通过调整显示内容、光标位置等参数,观察液晶显示模块的显示效果。同时,可以使用万用表、示波器等工具,观察引脚电平、信号波形等信息。 5. 调整对比度。对于LCD1602液晶显示模块,对比度的调整非常重要,可以通过旋转可变电阻或者改变电压等方法进行调整。对比度调整得好,可以使得显示效果更加清晰、鲜明。 需要注意的是,在调试过程中,要注意保护液晶显示模块和电路的稳定性和可靠性,避免连接错误、电源波动等问题,同时还要注意操作的准确性,避免意外操作造成的损坏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值