LCD1602液晶屏(续)
作者:互联网
从前面的分析中知道,在HD44780控制芯片忙的时候,是不能对其进行写入操作的,所以在写入指令或数据时都需要进行判忙的操作,其时序如下图所示(8位数据模式)。
从上图中可看到,当HD44780在执行内部操作时,其数据的最高位DB7为高电平,表示忙,只有内部操作结束时,DB7才为低电平,表示空闲,这时才能对HD44780进行写入操作。为了方便,一般通过C语言把以上“判忙”这一过程封装成一个函数,如下所示(C51,以下同)。
bit Busy(void) { unsigned char value; LCD_EN = 1; //使能端拉高电平 _nop_(); //调用汇编指令延时一个空指令周期 value = DataPort; //读取数据 LCD_EN = 0; //忙信号结束,拉低使能端电平 _nop_(); //调用汇编指令延时一个空指令周期 return (bit)(value & 0x80);//返回1为忙,0为不忙 }
一般情况下,判忙是为了等待,即忙则等待,直到不忙为止,因此,还可以进一步封装成一个“忙等待”函数,如下所示。
void WaitForEnable(void) { DataPort = 0xff; //数据线电平拉高 LCD_RS = 0; //选择指令寄存器 LCD_RW = 1; //选择读方式 while(Busy()); //忙等待 }
有了忙等待函数,就可以封装出其他函数了。接下来封装”写指令“函数,写指令可根据需要进行判忙或不判忙选择,依据前面给出的写操作时序,函数代码如下所示。
void LcdWriteCommand(unsigned char cmd, unsigned char busy) { if(busy) //若busy为1则判忙,否则不判忙 WaitForEnable(); //忙等待 LCD_RS = 0; //选择指令寄存器 LCD_RW = 0; //选择写方式 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 1; //使能端拉高电平 _nop_(); //调用汇编指令延时一个空指令周期 DataPort = cmd; //把命令数据送到数据线上 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 0; //拉低使能端电平,完成写入 _nop_(); //调用汇编指令延时一个空指令周期 }
以同样的方式,可以封装出一个”写数据“的函数,写数据都需要先判忙,函数代码如下所示。
void LcdWriteData(unsigned char data) { WaitForEnable(); //忙等待 LCD_RS = 1; //选择数据寄存器 LCD_RW = 0; //选择写方式 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 1; //使能端拉高电平 _nop_(); //调用汇编指令延时一个空指令周期 DataPort = data; //把显示数据送到数据线上 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 0; //拉低使能端电平,完成写入 _nop_(); //调用汇编指令延时一个空指令周期 }
有了写指令的函数之后,”初始化“函数也可以写了,依据前面的初始化过程,函数代码如下所示(8位数据模式)。
void LcdInit(bit N, bit ID, bit S, bit D, bit C, bit B) { unsigned char cmd = 0x30; LcdWriteCommand(cmd, 0); //写第一次 Delay_nms(5); //延时5ms LcdWriteCommand(cmd, 0); //写第二次 Delay_nms(1); //延时1ms LcdWriteCommand(cmd, 0); //写第三次 if(N) cmd |= 0x08; //双行 else cmd &= ~0x08; //单行 LcdWriteCommand(cmd, 1); //确定显示行数及字形大小,检测忙信号 LcdWriteCommand(0x08, 1); //关闭显示,检测忙信号 LcdWriteCommand(0x01, 1); //清屏,检测忙信号 cmd = 0x04; if(ID) cmd |= 0x02; //AC递增 else cmd &= ~0x02; //AC递减 if(S) cmd |= 0x01; //画面移动 else cmd &= ~0x01; //光标移动 LcdWriteCommand(cmd, 1); //配置进入模式,检测忙信号 cmd = 0x08; if(D) cmd |= 0x04; //开启显示 else cmd &= ~0x04; //关闭显示 if(C) cmd |= 0x02; //显示光标 else cmd &= ~0x02; //不显示光标 if(B) cmd |= 0x01; //开启闪烁 else cmd &= ~0x01; //关闭闪烁 LcdWriteCommand(cmd, 1); //屏幕显示、光标显示、闪烁等配置,检测忙信号 LcdWriteCommand(0x02, 1); //光标复位 }
接下来需要封装一个”光标定位“函数,用于确定欲显示字符的起始位置,函数代码如下所示。
void LocateXY(char posX, char posY) { unsigned char temp; temp = posX & 0x0f; //屏蔽高4位,限定横坐标X的范围为0~15 posY &= 0x01; //屏蔽高7位,限定纵坐标Y的范围为0~1 if(posY) temp |= 0x40; //第二行显示,地址码+0x40,因第二行起始地址为0x40 temp |= 0x80; //设定DDRAM地址的指令DB7恒为1(即0x80) LcdWriteCommand(temp, 1); //把命令temp写入LCD中,检测忙信号 }
接下来封装一个“显示单字符”函数,用于在确定位置显示一个字符,函数代码如下所示。
void DisplayOneChar(unsigned char x, unsigned char y, unsigned char data) { LocateXY(x, y); //定位欲显示字符的位置 LcdWriteData(data); //将要显示的数据data写入LCD }
最后可以封装一个“显示字符串”函数,用于一次性显示多个字符,函数代码如下所示。
void DisplayString(unsigned char x, unsigned char y, unsigned char const *ptr) { unsigned char i, j=0; while(ptr[j] > 31) j++; //ptr[j]>31时为ASCII码,j累加,计算出字符串长度 for(i=0; i<j; i++) { DisplayOneChar(x++, y, ptr[i]); //显示单个字符,同时x坐标递增 if(x == 16) { x = 0; y ^= 1; //当每行显示超过16个字符时换行继续显示 } } }
一般应用拥有以上函数就够了,但也可以根据需要再封装一下其他函数,这里再封装一个获取当前光标所在地址(AC)的函数,即”读取光标位置“函数,依据前面给出的读操作时序,函数代码如下所示。
unsigned char LcdReadAC(void) { unsigned char value; WaitForEnable(); //忙等待 DataPort = 0xff; //数据线电平拉高 LCD_RS = 0; //选择指令寄存器 LCD_RW = 1; //选择读方式 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 1; //使能端拉高电平 _nop_(); //调用汇编指令延时一个空指令周期 value = DataPort; //读取数据 LCD_EN = 0; //拉低使能端电平 _nop_(); //调用汇编指令延时一个空指令周期 return (value & 0x7f); //返回低7位的AC值 }
以同样的方式,可以封装出一个”读数据“的函数,函数代码如下所示,其参数为欲读取的内容所在地址。
unsigned char LcdReadData(unsigned char AC) { unsigned char value; LcdWriteCommand((AC | 0x80), 1); //定位欲读取的地址 WaitForEnable(); //检测忙信号 DataPort = 0xff; //数据线电平拉高 LCD_RS = 1; //选择数据寄存器 LCD_RW = 1; //选择读方式 _nop_(); //调用汇编指令延时一个空指令周期 LCD_EN = 1; //使能端拉高电平 _nop_(); //调用汇编指令延时一个空指令周期 value = DataPort; //读取数据 LCD_EN = 0; //拉低使能端电平 _nop_(); //调用汇编指令延时一个空指令周期 return value; //返回读取到的内容 }
接下来再封装一个“光标移动”函数,用于移动光标,其参数为0时光标左移,为1时光标右移,函数代码如下所示。
void CursorMove(bit dir) { if(dir) LcdWriteCommand(0x14, 1); //光标右移 else LcdWriteCommand(0x10, 1); //光标左移 }
以同样的方式,可以再封装出一个”画面移动“的函数,用于移动画面,其参数为0时画面左移,为1时画面右移,函数代码如下所示。
void ImageMove(bit dir) { if(dir) LcdWriteCommand(0x1c, 1); //画面右移 else LcdWriteCommand(0x18, 1); //画面左移 }
最后还需要封装一个上面初始化函数中用到的毫秒级延时函数,函数代码如下所示,该函数的延时时间与单片机相关,这里是51单片机12MHz晶振为例,若用其他单片机应酌情更改。
void Delay_nms(unsigned int ms) { unsigned int x,y; for(x=ms; x>0; x--) for(y=110; y>0; y--); }
实际使用时,上面的所有函数可写在一个C语言文件,然后把相关的定义放在头文件中,主程序只需要把该文件添加进工程,即可调用所有的函数了。头文件一般作如下定义。
//========================引脚宏定义======================== sbit LCD_RS = P1^0; //RS脚输出高电平 sbit LCD_RW = P1^1; //RW脚输出高电平 sbit LCD_EN = P2^5; //EN脚输出高电平 //========================端口宏定义======================== #define DataPort P0 //P0为数据端口 //========================初始化参数的宏定义======================== #define SINGLE 0 //单行显示 #define DOUBLE 1 //双行显示 #define INC 1 //AC递增 #define DEC 0 //AC递减 #define SHIFT 1 //画面滚动 #define NOSHIFT 0 //画面不动 #define OPEN 1 //打开显示 #define CLOSE 0 //关闭显示 #define SHOW 1 //显示光标 #define NOSHOW 0 //不显示光标 #define BLINK 1 //光标闪烁 #define NOBLINK 0 //光标不闪烁 #define LEFT 0 //向左移动 #define RIGHT 1 //向右移动 //===========================函数声明============================ void Delay_nms(unsigned int ms); //延时n毫秒 bit Busy(void); //判忙 void WaitForEnable(void); //忙等待 void LcdWriteData(unsigned char data); //写数据 unsigned char LcdReadData(unsigned char AC); //读某地址的数据 void LcdWriteCommand(unsigned char cmd, unsigned char busy); //写命令 unsigned char LcdReadAC(void); //读当前地址 void LcdInit(bit N, bit ID, bit S, bit D, bit C, bit B);//初始化 void LocateXY(char posx, char posy); //定位显示位置 void DisplayOneChar(unsigned char x, unsigned char y, unsigned char data); //显示单字符 void DisplayString(unsigned char x, unsigned char y, unsigned char const *ptr);//显示字符串 void CursorMove(bit dir); //光标移动 void ImageMove(bit dir); //画面滚动
在使用时,根据实际的连接情况,只需要更改上述头文件中的“引脚宏定义”和“端口宏定义”部分,其余均不需要改动。
标签:void,cmd,液晶屏,unsigned,char,LCD,指令,LCD1602 来源: https://www.cnblogs.com/fxzq/p/16214841.html