LCD1602液晶显示模块

news2024/11/28 14:53:10

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;
    	}
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用暴力攻击破解登陆密码

长久以来,入侵远程计算机系统的工具和技术并没有发生翻天覆地的变化。例如,在许多情况下,普通用户只要知道了相关密码,就能立刻变身为管理员。虽然这些情形听起来不够曲折,但在大多数情况下,暴力攻击是通过…

Kubernetes第3天

第五章 Pod详解 本章节将详细介绍Pod资源的各种配置(yaml)和原理。 Pod介绍 Pod结构 每个Pod中都可以包含一个或者多个容器,这些容器可以分为两类: 用户程序所在的容器,数量可多可少 Pause容器,这是每个…

JavaScript实现水印效果

效果 实现思路 利用canvas绘制出文字将canvas作为遮罩层背景图, 将背景x轴和y轴重复 实现步骤 动态生成canvas并画出文字 const canvas document.createElement("canvas"); canvas.width len * fontSize; // canvas宽度, 目前是根据文字长度和大小来调整的, 自…

Postman调试依赖登录接口的3种方法

在接口测试种, 我们经常会遇到有些接口登录后才能访问.我们在使用Postman调试这种接口时一般有3种方法: 1.依次请求 如果有登录接口的文档,或者通过抓包比较容易抓出登录请求的参数和格式,可以先使用Postman请求一下登录接口,这时Cookies会存到本地(可以通过Postman Cookies管理…

JVM学习(十一):对象的实例化内存布局与访问定位

目录 一、对象的实例化 1.1 创建对象的方式 1.2 创建对象的步骤 二、对象的内存布局 2.1 对象头 2.1.1 运行时元数据(Mark Word) 2.1.2 典型指针 2.2 实例数据(Instance Data) 2.3 对齐填充(Padding&#…

一个奇葩的网络问题,把技术砖家“搞蒙了“

问题现象 客户反馈有一个server端S, 两个client端C1, C2, S的iptables规则对C1, C2都是放通的,但是C2无法连接上S,客户很着急,催我们尽快解决。 这里解释一下,iptables规则是防火墙规则,是linux系统实现防…

【C++刷题集】-- day1

目录 选择题 单选 编程题 组队竞赛⭐ 【题目解析】 【解题思路】(排序 贪心) 删除公共字符⭐ 【题目解析】 【解题思路】(哈希映射) 选择题 单选 1、 以下for循环的执行次数是 ( ) for(int x 0, y 0; (y 123) && (x < 4); x); 是无限循环 循环次数不…

Leetcode刷题之有效的括号

我们的内心和心智&#xff0c;是决定我们未来命运的最强劲的力量。 -- 奥普拉温弗瑞目录 &#x1f341;一.有效的括号 &#x1f34d;1.使用栈实现 &#x1f352;2.完整代码&#xff1a; 题目描述&#xff1a; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0…

Sonar Qube代码质量检测工具安装及基本使用

Sonar介绍 Sonar Qube是一个开源的代码分析平台&#xff0c;支持Java、Python、PHP、JavaScript、CSS等25种以上的语言&#xff0c;可以检测出重复代码、代码漏洞、代码规范和安全性漏洞的问题。 Sonar Qube可以与多种软件整合进行代码扫描&#xff0c;比如Maven&#xff0c;…

算法套路十七——买卖股票问题:状态机 DP

算法套路十七——买卖股票问题&#xff1a;状态机 DP 状态机DP是一种将动态规划方法应用于有限状态机&#xff08;Finite State Machine&#xff09;的问题求解方法。 状态机DP&#xff08;State Machine DP&#xff09;是一种动态规划的思想&#xff0c;它通常用于解决一些具…

行动元宣布推出面向精密制造领域的智能运动控制解决方案

近日&#xff0c;AI 工业工程化平台行动元宣布推出面向精密制造领域的智能运动控制解决方案。该方案融合大数据模型、数字孪生以及人工智能技术&#xff0c;通过数字化建模、适配、调试等过程&#xff0c;极大提升终端设备集成方案的设计、选型与测试效率&#xff0c;并通过算法…

你不学,我不学,谁来网安,谁来保卫国家!

一、为什么选择网络安全&#xff1f; 这几年随着我国**《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》**等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前…

PCL点云库(6) — Filters模块空间裁剪器类

目录 6.1 3D包围盒裁剪器Class BoxClipper3D< PointT > 6.2 平面裁剪器Class pcl::PlaneClipper3D< PointT > 6.3 立方体过滤Class pcl::CropBox< PointT > 6.4 曲面或多边形过滤 Class pcl::CropHull< PointT > 6.5 完整代码 6.1 3D包围盒裁剪器…

C语言基础:static关键字

本文结合工作经验&#xff0c;研究C语言中static关键字的用法。 文章目录 1 static关键字概念2 用法与使用场景2.1 修饰全局变量2.1.1 代码示例2.1.2 使用场景 2.2 修饰函数2.2.1 代码示例2.2.2 使用场景 2.3 修饰局部变量2.3.1 代码示例2.4.2 使用场景 3 总结 1 static关键字…

第13章:存储过程和存储函数

一、存储过程 1.1理解 含义&#xff1a; 存储过程stored procedure&#xff0c;思想是一组经过预先编译的SQL语句的封装。 存储过程预先存储在MySQL服务器上&#xff0c;需要执行的时候&#xff0c;客户端向服务器端发出调用存储过程的命令&#xff0c;服务器段把这组SQL执…

当我和ChatGPT-4聊完后,我觉得一切可能已经来不及了

飞机上有wifi&#xff0c;了然无味&#xff0c;在万米高空&#xff0c;和ChatGPT-4开始了一场坦诚的沟通&#xff0c;它全程都表现出高情商&#xff0c;以及不断尽量安抚我的情绪&#xff0c;而这&#xff0c;恰恰令我脊背发凉。 部分文字截取 ZM&#xff1a;我能不能理解每次对…

k8s学习-CKS真题-ImagePolicyWebhook容器镜像扫描

目录 题目环境搭建imagePolicyWebhook搭建 解题任务二任务三任务一检查 模拟考题参考 题目 Context cluster上设置了容器镜像扫描器&#xff0c;但尚未完全集成到 cluster 的配置中。 完成后&#xff0c;容器镜像扫描器应扫描并拒绝易受攻击的镜像的使用。 Task 注意&#xff…

5.17 ARM 作业

1. 2.用for循环实现1~100之间的和 13BA 3.xmind

可以找工作的C端的低代码产品,终于让我找到了

目录 写在前面 低代码平台 平台怎么选 各平台区别 为什么选它 写在前面 大家都知道低代码这个叫法是从B端叫起来的&#xff0c;也就是说不管是业务人员还是开发人员&#xff0c;都是企业内部使用。那么有没有C端的&#xff0c;且免费使用的低代码产品呢&#xff1f; 低代码…

一次性能优化思考过程

前言 最近业务上空闲了下来&#xff0c;也是把之前在开发时自身感受比较大的白屏时间放在了主线上去排查优化&#xff0c;这里记录一下笔者对于移动端vConsole脚本的引入问题全过程。 网络脚本与问题定位 对于白屏时间&#xff0c;与网络传输有很大关系&#xff0c;如图&…