• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Recommended Posts

从前面的分析我们知道,HD44780控制芯片忙的时候,是写不了的。因此在写指令或数据时,需要判断忙操作,其时序如下图所示(8位数据模式)。

fkucif5yc0k4167.png

从上图可以看出,HD4780在执行内部操作时,其数据的最高位DB7处于高电平,表示繁忙。只有内部操作完成,DB7处于低电平,表示空闲,才能写入HD4780。为方便起见,上述“判断繁忙”的过程一般被C语言封装成一个函数,如下图(C51,下同)。

位忙(无效)

{

无符号字符值;

LCD _ EN=1;//使能端子拉高。

_ nop _();//调用汇编指令延迟一个空指令周期。

value=DataPort//读取数据

LCD _ EN=0;//当busy信号结束时,下拉enable端的电平。

_ nop _();//调用汇编指令延迟一个空指令周期。

返回(位)(值0x 80);//返回1表示忙,返回0表示不忙

}

一般来说,判断忙就是等,也就是忙的时候,等到不忙的时候。因此,它可以进一步封装成一个“忙等待”函数,如下所示。

void WaitForEnable(void)

{

数据端口=0xff//数据线电平被拉高

LCD _ RS=0;//选择指令寄存器

LCD _ RW=1;//选择阅读模式

while(Busy());//忙着等待

}

使用忙等待功能,您可以封装其他功能。接下来封装了“写指令”函数,写指令可以根据需要选择是否判断繁忙。根据前面给出的写操作顺序,功能代码如下。

void LcdWriteCommand(无符号字符cmd,无符号字符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(无符号字符数据)

{

WaitForEnable();//忙着等待

LCD _ RS=1;//选择数据寄存器

LCD _ RW=0;//选择书写方式

_ nop _();//调用汇编指令延迟一个空指令周期。

LCD _ EN=1;//使能端子拉高。

_ nop _();//调用汇编指令延迟一个空指令周期。

DataPort=数据;//将显示数据发送到数据线

_ nop _();//调用汇编指令延迟一个空指令周期。

LCD _ EN=0;//拉低使能端的电平,完成写入。

_ nop _();//调用汇编指令延迟一个空指令周期。

}

有了写指令的功能,还可以写“初始化”功能。根据前面的初始化过程,函数代码如下(8位数据模式)。

void LCD init(N位、id位、S位、D位、C位、B位)

{

无符号字符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);      //画面滚动

在使用时,根据实际的连接情况,只需要更改上述头文件中的“引脚宏定义”和“端口宏定义”部分,其余均不需要改动。 

Link to comment
Share on other sites