上篇博文笔者分享了关于液晶1602基本的工作流程,本篇主要是通过逻辑分析仪来看一下程序使能的电平时序,是否符合产品文档给出 的时序逻辑。 先看一下1602的时序图
认识下时序图中各个标识的含义:
- Tc信号周期(E Cycle Time)指的是使能引脚E从本次上升沿到下次上升沿的最短时间是400ns,而51单片机因为速度较慢,本案用的晶振是11.0592M因此1个机器周期的时间是1us多,而一条C语言指令肯定是1个或者多个机器周期,所有这个条件天然满足。即 E = 1;这个指令就满足要求了,除非是高速单片机E指令后需要加一点延时时间。
- tpw脉冲宽度(E Pulse Width)指的是使能周期E高电平的持续时间最短是150ns,同样由于单片机比较慢这个条件是满足的,本案高脉冲的实现方法简单粗暴:
LCD1602_E = 0; //高脉冲 LCD1602_E = 1; //高脉冲 LCD1602_E = 0; //高脉冲
这个高脉冲时间刚好是1个机器周期即语句LCD1602_E = 1执行时间1us多一点。
- tR,tF上升沿下/降沿时间(E Rise/fall time)指的是使能引脚E的上升沿时间和下降沿时间,不能超过25ns,这个时间对于51单片机来说也是比较宽裕的,51单片机引脚的上升沿和下降沿时间大概是10~15ns之间。
- tsp1地址建立时间(Address Setup Time)指的是RS和R/W引脚使能后至少保持30ns,使能引脚E才可以变成高电平,本案的实现方案是:
一般都会在在其后加1句总线操作语句因此这个条件也是天然满足的,即使你不加这句也是天然满足的,因为LCD1602_RW=0要保持1个机器周期即1us。
-
tHD1地址保持时间(address Hold Time)指的是使能引脚E变成低电平后,至少保持10ns之后,RS和R/W才能进行变化,这个条件也是天然满足的。E使能语句本身就要持续1个机器周期。
-
tD(读操作)数据建立时间(Data Output Delay Time)指的是使能引脚E变成高电平后,最多100ns,1602就把数据送出来了,就可以正常去读状态或者数据了
-
tHD2(读操作)数据保持时间(data hold time)指的是读操作过程中,使能引脚E变成低电平后,至少保持20ns,DB数据总线才可以进行变化,这个条件也是天然满足的。本案读操作只有一个就是读状态:
void LcdWaitReady() { unsigned char sta; LCD1602_DB = 0xFF; LCD1602_RS = 0; LCD1602_RW = 1; do{ LCD1602_E = 1; sta = LCD1602_DB; //读取状态字 LCD1602_E = 0; }while(sta & 0x80);//bit7等于1表示液晶正忙,重复检测直到等于0为止 }
-
tsp2(写操作)数据建立时间(data setup time)指的是DB数据总线准备好后,至少保持40ns,使能引脚E才可以从低到高进行使能变化,这个条件也是天然满足的。
-
tHD2(写操作)数据保持时间(data hold Time) 指的是写操作过程中,要引脚E变成低电平后至少保持10ns,DB数据总线才可以变化。这个条件也是天然满足的本案写指令和数据的过程:
void LcdWriteDat(unsigned char dat) { LcdWaitReady(); LCD1602_RS = 1; LCD1602_RW = 0; LCD1602_DB = dat; //忙状态的时候LCD1602_E就被拉成低电平因此这边无需再加1句了 LCD1602_E = 1; //高脉冲 LCD1602_E = 0; //高脉冲 } void LcdWriteCmd(unsigned char cmd) { LcdWaitReady(); LCD1602_RS = 0; LCD1602_RW = 0; LCD1602_DB = cmd; // LCD1602_E = 1; //高脉冲 LCD1602_E = 0; //高脉冲 }
从结论上来看对于51单片机来说1602LCD液晶很多细节上是可以忽视的它是天然满足,只有在高速单片机上可能要多注意一点,看来速度太快也不都是好事。
然后总结下上篇博文给出一些小结论然后看一下是否需要补充的:
1:在液晶E端是低电平的情况下DB0-DB7都是高电平。
2:51单片机IO口默认是高电平,由于本案接了下拉电阻,E端电压最终会被拉成低电平。
3:写指令和写数据是需要高脉冲使能的。
4:液晶更改功能的时候需要先关闭液晶即写指令08H
看下本案用到的程序其实和上篇博文没有什么太大的区别,主要是这篇为了防止产生歧义把整个初始化过程都写出来了,按照规格文件:
上代码:
# include<reg52.h>
# define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
void InitLcd1602();
void LcdShowStr(unsigned char x ,unsigned char y, unsigned char* str);
void main()
{
unsigned char str[] = "Firewood 2024";
unsigned int i =1500;
unsigned int j = 500;
while(i--);
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = 0x38;
LCD1602_E = 0; //高脉冲
LCD1602_E = 1; //高脉冲
LCD1602_E = 0; //高脉冲
while(j--);
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = 0x38;
LCD1602_E = 0;
LCD1602_E = 1; //高脉冲
LCD1602_E = 0; //高脉冲
j = 500;
while(j--);
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = 0x38;
LCD1602_E = 0;
LCD1602_E = 1; //高脉冲
LCD1602_E = 0; //高脉冲
InitLcd1602();
LcdShowStr(2,0,str);
LcdShowStr(0,1,"I love You BeBy");
while(1);
}
/*等待液晶准备好 */
void LcdWaitReady()
{
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do{
LCD1602_E = 1;
sta = LCD1602_DB; //读取状态字
LCD1602_E = 0;
}while(sta & 0x80);//bit7等于1表示液晶正忙,重复检测直到等于0为止
}
/*向LCD1602液晶写入一字节命令,cmd为待写入的命令值 */
void LcdWriteCmd(unsigned char cmd)
{
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
// LCD1602_E = 0;
LCD1602_E = 1; //高脉冲
LCD1602_E = 0; //高脉冲
}
/*向LCD1602液晶写入一字节数据,dat为待写入数据 */
void LcdWriteDat(unsigned char dat)
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
//LCD1602_E = 0;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 设置显示RAM起始地址,亦即光标位置,(x,y)为对应屏幕上的字符坐标*/
void LcdSetCursor(unsigned char x,unsigned char y)
{
unsigned char addr;
if(y == 0)
addr = 0x00+x;
else
addr = 0x40+x;
LcdWriteCmd(addr|0x80);//0x80 = 1000 0000
}
/*在液晶上显示字符串,(x,y)为对应屏幕上的起始坐标,str为字符串指针 */
void LcdShowStr(unsigned char x,unsigned char y,unsigned char* str)
{
LcdSetCursor(x,y); //设置其实地址
while(*str != '\0')
{
LcdWriteDat(*str++);
}
}
/*初始化1602液晶 */
void InitLcd1602()
{
LcdWriteCmd(0x38);//0x38 = 0011 1000 16*2显示,5*7点阵,8位数据接口
LcdWriteCmd(0x08);//显示关闭
LcdWriteCmd(0x01);//清屏
LcdWriteCmd(0x06);//0x04 = 0000 0100 文字不动,地址自动加1
LcdWriteCmd(0x0C);//显示器开 ,光标关闭
}
结果和上篇博文是一样的
然后先看下逻辑分析仪的输出图
共11个通道,我们要观察的是这些电平信号,是否是按照程序执行的,并且是符合之前文档给出的时序要求的。这个图被压缩了很多,看不同清楚,这篇博文主要就是一点一点的看完所有的时序信号。
由上图知:前4个通道分别是液晶使能端口E,状态字端口bit7即是P0.7/DB7端口,RS数据指令选择端口,R/W读写选择端口.后续7个就是数据端口即P0端口的低7位。
- 由上图可以看到2个很短的高脉冲信号,其实是3个第三个和后面的电平信号连在一起,在图上不易看出,而且三个脉冲信号之间的时间间隔是5ms左右,这是符合我们初始化过程的
- 当然最前面的15ms以及一开始上电电平被下拉电阻拉低的过程没显示出来,笔者采用的是上升沿触发但是不影响分析这个时序图。由于液晶E端口与单片机P1.5端口相连并且接了下拉电阻,因此电平信号最终会被拉成低电平,从上述图看出它确实是低电平信号突然产生了个高脉冲 信号。我们把图放大仔细看一下
- 这个图就是第一个高脉冲的时序图,我们挨个看一下这个时序图是否符合我们的要求,首先这个高脉冲时间是1us左右,右上角图片放大看一下。这个图对应程序哪个部分呢?
- 就是上图这个过程,可以看到高脉冲使能语句是LCD1602_E = 1;它的执行时间是1个机器周期鉴于本案用的晶振1个机器周期的时间就是1us符合时序图的结果。
- 由上图可知在使能高脉冲前就已经使能了RS,RW,以及数据端口。从程序结构来说RS端口是最早发送电平信号,接着是R/W端口,最后是数据端口。
- 从时序图我们可以看出确实是这样的,并且我们发现即使程序上没有进行“忙”判断,在时序图上bit7确实在总线写入的时候拉低了;看下图RS语句在时序延时了1us(仅指语句执行时间),RW在时序上是延时2us(按理是只延时1us的)才进行总线赋值的LCD1602 = 0x38;而且是在2us后bit7从高电平变成低电平总线才开始赋值。事实上RW该语句是只使能了1us,所谓2us的产生是由于总线在等待BIt7从高电平变成低电平,总线电平的信号才开始发生变化。这是符合文档给出只有在bit7为低电平的时候才允许进行读写使能。
- 上述说法是不对的,“忙”判断结束后bit7和“忙”状态检测就没有关系了,纯粹是因为赋值运算把bit7拉低了,而且这个时间恰好发生在赋值语句执行一半的时候发生,(整个语句要2个机器周期即2us)且bit7这个端口既是忙状态检测也是数据输入输出端口这个要分开,当然这个前端初始化也没有“忙”判断,可能是15ms的延时时间使得无需再忙判断。“忙”判断读出的是液晶给出的电平信息,而这个bit7信号是我们写入程序的电平信号。要区分电平信号的来源。
- 看一下数据线的输出是否是符合要求的,时序图可知它写入的是X011 1000,而最高位x其实是Bit7的电平,它是低电平0,因此写入的就是0011 1000=0x38;是符合程序以及文档初始化要求的。
- 这是一个写指令的过程,写执行过程中我们要关注的时序要求是如下
- tsp1地址建立时间(Address Setup Time)指的是RS和R/W引脚使能后至少保持30ns,使能引脚E才可以变成高电平,其它的时序要求要不不适用,要不是天然满足的,其实从时序图上就可以看出来RS和R/W引脚的持续时间比高脉冲的时间还要长。
第一个高脉冲分析结束,看下第二个高脉冲:
可以看到第二个高脉冲其他通道没有电平变化,一直保持着之前的电平信号。
- 使能端E只有一个高脉冲,时间是1us。
- bit7一直是低电平
- RS,R/W端口都是低电平。即处在写指令状态
- 数据端口依然写入0x38,中键那个0的白线不是电平信号线是测试时间线下面那根橙的(要放大看)才是电平线。
- 它是符合初始化这个过程
然后看一下前端初始化的最后一个高脉冲:
- 从结果上看它和第二个高脉冲的电平信号相同它是前端初始化的最后一个部分
- ,可以看出在这个高脉冲后一段时间电平信号发生了很多变化,其实就是1602LCD液晶真正的初始化过程。
在分析后续的时序先看一下接下来的过程可以看到在完成前端初始化,程序正式进入初始化。从程序的执行顺序来说是要执行
LcdWriteCMd(0x38);语句因此进入相关函数
第1句就是“忙”判断函数LcdWaitReady();因此进入相关函数
然后看时序图:
- 我们接下写的指令,有两个关键的指示电平信号。1是总线写入时的高脉冲,2是“忙”判断的bit7电平信号拉低,
- 从上图可以看到时序在第一次“忙”判断的时候它的高电平时间是3us即3个机器周期,其实就是如下图红框两句程序语句导致的,E端赋值是1个机器周期,总线读状态是2个机器周期,从时序图看出在执行sta = LCD1602_DB语句的时候bit7就引进被拉成低电平了,但是在while()函数判断的时候并没有通过,又再次执行了do--while()函数里的内容,这次在LCD1602_E = 1:赋值的时候bit7立马就被拉成低电平了,这次才通过了“忙”判断,从这个现象来说:赋值运算只能读取前面语句使能的端口信号,在本句内发生的电平信号变化,它无法即刻读取。这才造成了又进行了一次“忙”判断。
“忙”判断后进入写指令的函数,我们看下写指令的过程,写入0x38,然后接个高脉冲使能看下图在高脉冲使能前:
- E端是低电平
- BIt7是低电平
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x011 1000,x是最高位bit7因此写入的指令是0011 1000= 0x38;
- 然后一个高脉冲使能指令写入成功,即液晶接收0x38指令
接着进行下一次写指令LCDWriteCmd(0x08);看下时序图
从结果上看在下一个高脉冲前它发生了三次忙判断,只有在最后一次“忙”判断才成功。前面两次判断都是高电平,而且这次没有发生上个“忙”判断节点发生的现象。即在读总线状态的时候bit7才拉低。通过下图的时序图看下写入的指令是什么:
- E端是低电平,为高脉冲准备
- BIt7是低电平。这个低电平由总线写入语句造成。
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x000 1000,x是最高位bit7因此写入的指令是0000 1000= 0x08;
- 然后一个高脉冲使能指令写入成功,即液晶接收0x08指令;
- 而且我们看到PO端口这次是P0.3/DB3端口在写指令的时候即使写入前和写入后的电平都是高电平,它依然会先拉低再很快的拉高,如果放大看它其实有一个80ns的低电平保持时间。
我们已经看了几个时序图了,可以得出一些结论。
- “忙”判断 后E端在高脉冲来临前一直是低电平的(其实这个不强制要求的只是我们这个程序现象是这个,关于这个问题可以看下我上篇博文的说法)
- “忙”判断后bit7它就恢复高电平了。
- “忙”判断结束后,bit7这个端口的电平信号就和“忙”判断没关系了,它的后续电平信号就由写入的指令或者地址决定,后续我们可以看到在写地址的时候bit7是高电平。
接着下一个指令
LcdWriteCmd(0x01);//清屏
这是个清屏指令,看时序图:
这次进行了两次“忙”判断才成功,接着看下输入的指令是什么?
- E端是低电平,为高脉冲准备
- BIt7是低电平。这个低电平由总线写入语句造成。
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x000 0001,x是最高位bit7因此写入的指令是0000 0000= 0x01;
- 然后一个高脉冲使能指令写入成功,即液晶接收0x01指令;
- 这里要看一下液晶是怎么清屏它的具体表现是什么?
这个清屏效果大概持续了1.38ms,在这个过程中E端是周期7.6us的方波,bit7是高电平,RS选择端是低电平,RW选择端是高电平。
接着下一个指令
LcdWriteCmd(0x06);//文字不动,地址自动加1
这个式初始化过程中主要的使能语句,前面的博文笔者说到,如果不照按产品规格书的写法即在使能这个指令前关闭液晶,这句指令就不会起作用。
看下时序图:
由标志的电平信号我们找到了写指令的信号电平的位置,看下写入的指令是什么是否是0x06,
- E端是低电平,为高脉冲准备
- BIt7是低电平。这个低电平由总线写入语句造成。
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x000 0011,x是最高位bit7因此写入的指令是0000 0011= 0x06;
- 然后一个高脉冲使能指令写入成功,即液晶接收0x06指令;确定是0x06位置没有错,也确定了前面的波形是清屏功能造成的。
最后一个初始化指令
LcdWriteCmd(0x0C);//显示器开 ,光标关闭
看下时序图:这次进行了三次“忙”判断看下写入的指令。
- E端是低电平,为高脉冲准备
- BIt7是低电平。这个低电平由总线写入语句造成。
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x000 1100,x是最高位bit7因此写入的指令是0000 1100=0x0C;
- 至此初始化完成。
初始化完成后我们就要开始写入字符信息了,进入该函数。
void LcdShowStr(unsigned char x,unsigned char y,unsigned char* str)
{
LcdSetCursor(x,y); //设置其实地址
while(*str != '\0')
{
LcdWriteDat(*str++);
}
}
从这个函数又发现要进入LcdSetCursor(x,y)函数
void LcdSetCursor(unsigned char x,unsigned char y)
{
unsigned char addr;
if(y == 0)
addr = 0x00+x;
else
addr = 0x40+x;
LcdWriteCmd(addr|0x80);//0x80 = 1000 0000
}
然后发现该函数又要进入我们熟悉的写指令过程,不过这次是写地址了。理下程序
LcdShowStr(2,0,str);
该句写入的地址是:0x00+2=0x02;0x02+0x80=0x82,这个“或”运算其实就是加法因为不会发生进位。也就是说要写入一个0x82的地址以写指令的方式好的看下时序图。
找到标志的电平信号图像,然后看下写入的是否符合我们之前程序要求的,
- E端是低电平,为高脉冲准备
- BIt7是高电平。这个高电平由总线写入语句造成。可以看到写地址和写指令区分开了用这个高低电平
- RS、R/W都是低电平说明是写指令
- 数据端口提前写入数据是x000 0010,x是最高位bit7因此写入的指令是1000 0010=0x82;
- 确实是写入了1个0x82的地址。然后高脉冲使能
在写入这个地址后就要写入数据了看下这个数据写入函数
void LcdWriteDat(unsigned char dat)
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
//LCD1602_E = 0;
LCD1602_E = 1;
LCD1602_E = 0;
和写指令区别是RS现在是高电平了,从函数看我们即将写入的是
F这个字符的ASCII码是070 =0x46看时序图
找到关键电平信号点,只进行了1次忙判断,看一下写入的代码是什么?
- E端是低电平,为高脉冲准备
- BIt7是低电平。这个低电平由总线写入语句造成。
- RS是1 ,R/W是0说明是写数据。
- 数据端口提前写入数据是x100 0110,x是最高位bit7因此写入的指令是0100 0110=0x46;
- 确实是写入了1个0x46的数据。然后高脉冲使能。
- 后续的写入过程和这个类似就不再赘述
至此本案结束,通过上述的时序图,对1602LCD液晶数据传输应该有些理解了。
- 可以有一点总结:除了前端初始化过程,一般来说任何操作前都要进行“忙”判断。
- bit在完成“忙”判断后这个端口的功能后只是用来做为输入输出端口。
- 作为51单片机来说液晶传输的时序要求是天然满足的。
- 在写指令、数据的时候最后都需要高脉冲使能。