1.认识LCD1602
1、概述:
- LCD1602(Liquid Crystal Display)是一种工业字符型液晶,能够同时显示 16×02,32个 字符(16列两行)。是我们接触引脚最多的模块。
- LCD1602我们的非标准协议(标准协议有IIC、IIS、SPI)中比较容易懂的玩法。
2、引脚说明:翻阅LCD1602说明书
- 共有16根引脚,如下表:
编号 符号 引脚说明 编号 符号 引脚说明 1 VSS 电源地 9 D2 双向数据线 2 VDD 电源正极,接5V正电源 10 D3 双向数据线 3 V0 液晶显示偏压。是液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高会产生“鬼影”(四方框影像),使用时可以通过一个10K电位器调整对比度。 11 D4 双向数据线 4 RS 数据/命令寄存器选择,高电平时选择数据寄存器,低电平时选择指令寄存器。一会看时序图 12 D5 双向数据线 5 R/W 读/写选择。高电平时进行读操作,低电平时进行写操作。一会看时序图 13 D6 双向数据线 6 E 使能信号。当E端由高电平跳变为低电平时,液晶模块执行命令。 14 D7 双向数据线 7 D0 双向数据线 15 BLA 背光源正极 8 D1 双向数据线 16 BLK 背光源负极 -
数据线占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根数据线实现的,如下表:
序号 | 指令 | RS | R/W | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 指令说明 |
1 | 清除显示 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 清显示,指令码01H,光标复位到地址00H位置 |
2 | 光标返回 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | * | 光标复位,光标返回到地址00H |
3 | 置输入模式 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | I/F | S | 光标和显示模式设置。I/D:光标移动方向,高电平右移,低电平左移。实际上就是控制从左到右写入还是从右至左的写入顺序。S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。S=1 当写一个字符,整屏显示左移(ID=1)或者右移(I/D=0),以得到光标不移动而屏幕移动的效果。S=0 当写一个字符,整屏显示不移动。 |
4 | 显示开/关控制 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | D | C | B | 显示开关控制。D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示. C:控制光标的开与关,高电平表示有光标,低电平表示无光标. B:控制光标是否闪烁,高电平闪烁,低电平不闪烁 |
5 | 光标或字符移位 | 0 | 0 | 0 | 0 | 0 | 1 | S/C | R/L | * | * | 光标或显示移位S/C:高电平时移动显示的文字,低电平时移动光标。R/L:文字或者光标移动方向,R表示右移,L表示左移 |
6 | 置功能 | 0 | 0 | 0 | 0 | 1 | DL | N | F | * | * | 功能设置命令DL:高电平时为8位总线,低电平时为4位总线。N:低电平时为单行显示,高电平时双行显示。F:低电平时显示5×8的点阵字符,高电平时显示5×0的点阵字符 |
7 | 置字符发生存储器地址 | 0 | 0 | 0 | 1 | 字符发生存储器地址(自定义字符) | 字符发生器RAM地址设置 | |||||
8 | 置数据存储器地址 | 0 | 0 | 1 | 显示数据存储器地址(在哪里显示) | DDRAM地址设置 | ||||||
9 | 读忙标志或地址 | 0 | 1 | BF | 计算器地址 | 读忙信号和光标地址。BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。 | ||||||
10 | 写数到CGRAM或DDRAM | 1 | 0 | 要写的数据内容(显示什么) | 写数据 | |||||||
11 | 从CGRAM或DDRAM读数 | 1 | 1 | 读出的数据内容 | 读数据 |
2.开发逻辑
1、和单片机的接线方法:
- 两组电源线:
- VSS——GND
- VDD——5V
- A——5V
- K——GND
- 对比度:V0——GND
- 控制线:
- RS——P1.0
- R/W——P1.1
- E——P1.4
- 数据线:D0到D7——P0.0到P0.7
- 注:之前讲过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显示模块都是被写入的那个,所以我们要学习写操作时序图。
- RS:引脚功能回看1.2引脚说明:数据/指令寄存器选择位。
- 看时序图:写操作时RS可以是高电平也可以是低电平,暂时不确定
- 回看1.2引脚说明:RS只看开始和结束,在R/W为0的前提下,RS为1代表写内容,RS为0代表写指令(地址),中间不需要转折。
- 根据功能,封装写内容和写指令这两个函数即可。
- R/W:引脚功能回看1.2引脚说明:读/写选择位。
- 看时序图:写操作开始(可以是高电平也可以是低电平)——> 中间在数据传输时必须是低电平 ——> 写操作结束(可以是高电平也可以是低电平)
- 回看1.2引脚说明:只有在R/W是低电平时,才能保证RS在高低电平切换时写入内容或者写入指令(地址)。
- 因此,全程把R/W配置成0就可以了,不用去管时序图中的时间。
- DB0~DB7:就相当于SBUF(回顾串口的接收/发送时序图),只不过这里LCD写操作的一帧数据是由引脚E控制的。
- 看时序图:可高可低,取决于写入的数据。
- 看时序图和时序参数图:有一个数据建立时间tSP2,也就是说在E=0(没被拉高前)时,就开始往LCD写数据;同时有一个数据保持时间tHD2,也就是在E变回0后,还保持一段数据写入的时间。
- E:引脚功能回看1.2引脚说明。使能信号从高电平到低电平时,LCD模块执行命令。
- 看时序图:开始是低电平——> 延时一段时间 ——> 中间转折为高电平(上升时间是tR) ——> 延时一段时间 ——> 中间转折为低电平(下降时间是tF) ——> 结束是低电平。
- 看时序参数图,tR和tF都是25ns,转折处持续的时间tpw是150ns。我们知道51单片机在11.0592MHz的晶振频率下的机器周期是1.085μs,所以给一个_nop_()就够了。
- 在写入数据(不论内容还是指令)时:E开始为0 ——> _nop_(); 延时1微秒 ——> P0口写数据 ——> _nop_(); 延时1微秒 ——> E转折为1 ——> _nop_(); 延时1微秒(不够就2微秒) ——> E结束为0 ——> _nop_(); 延时1微秒。
2、读操作时序分析:液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志为低电平,表示不忙,否则此指令失效。检测忙信号就是一个让LCD显示模块被读的过程,因此需要学习LCD的读操作时序分析。
- RS:引脚功能回看1.2引脚说明:数据/指令寄存器选择位。
- 看时序图:读操作时RS可以是高电平也可以是低电平,暂时不确定
- 回看1.2引脚说明:RS只看开始和结束,在R/W为1的前提下,RS为0代表读忙信号,中间没什么好转折的。
- 所以:全程把RS配置成0。
- R/W:引脚功能回看1.2引脚说明:读/写选择位。
- 看时序图:读操作开始(可以是高电平也可以是低电平)——> 中间在读数据时必须是高电平 ——> 读操作结束(可以是高电平也可以是低电平)
- 回看1.2引脚说明:只有在R/W是高电平时,才能保证在读忙信号。
- 因此,全程把R/W配置成1就可以了,不用去管时序图中的时间。
- DB0~DB7:就相当于SBUF(回顾串口的接收/发送时序图),只不过这里LCD读操作的一帧数据是由引脚E控制的。
- 看图:可高可低,取决于读取的数据。
- 看时序图和时序参数图:有一个数据建立时间tD,也就是说在E拉高一段时间后才去读数据;同时有一个数据保持时间tHD2,也就是在E变回0后,还保持一段数据读取的时间。
- E:引脚功能回看1.2引脚说明。使能信号从高电平到低电平时,LCD模块执行命令。
- 看时序图:开始是低电平——> 延时一段时间 ——> 中间转折为高电平(上升时间是tR) ——> 延时一段时间 ——> 中间转折为低电平(下降时间是tF) ——> 结束是低电平。
- 看时序参数图,tR和tF都是25ns,转折处持续的时间tpw是150ns。我们知道51单片机在11.0592MHz的晶振频率下的机器周期是1.085μs,所以给一个_nop_()就够了。
- 在读忙信号时: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. 定义符号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_();
- 代码:
#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. 定义符号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_();
- 代码:
#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; } }