12864液晶——读写、划点、划线、汉字、32*16的字符

//左半屏幕和右半屏幕的列号是一样的,页号也是一样的。
//选择整个屏幕,在给DDRAM中写数据时,会同时写到两个屏幕中,即两个屏幕中将会显示一样的数据。
//在清屏的时候可以选择整个屏幕。
//在滚动的时候可以选择整个屏幕,此时如果分别选屏幕滚动,可以实现两个屏幕滚动方向相反。

#define LCD_OFF 0x3E //关显示
#define LCD_ON 0x3F//开显示

#define Add_X 0xB8 //页初始地址,共8页
#define Add_Y 0x40 //Y初始地址,0到63,0x40到0x7f
#define Add_Z 0xC0 //DDRAM的初始地址

#define UPLINE 0x01 //上划线就是每个字节的第一位都是1
#define UNDERLINE 0x80//下划线就是每个字节的最后一位都是1

#define LCD12864_DATA_PORT P0//数据端口DB0~7接P0口

sbit LCD12864_E=P2^4; //E使能端
sbit LCD12864_RW=P2^3; //RW为0是写,为1是读 
sbit LCD12864_RS=P2^2; //RS为0输入的为命令,为1输入的为数据
sbit LCD12864_CS1=P2^0; //CS1,低电平有效
sbit LCD12864_CS2=P2^1; //CS2,低电平有效
sbit LCD12864_RST=P2^5; //复位端口

void delayus(unsigned int us)//延时函数
{
while(us--);
}

void LCDSel(unsigned char sel)//选择屏幕
{

switch(sel) 
{
case 0: LCD12864_CS1=0;LCD12864_CS2=0;break; //选择两个屏幕 
case 1: LCD12864_CS1=0;LCD12864_CS2=1;break; //选择左半屏幕
case 2: LCD12864_CS1=1;LCD12864_CS2=0;break; //选择右半屏幕
default:break;
}

}

void WaitLCD()//检测忙
{

 unsigned char flag;
LCD12864_DATA_PORT=0xFF;//P0口全部置1
LCD12864_RW=1;
LCD12864_RS=0;
LCD12864_E=1;
LCD12864_E=1;
  LCD12864_E=0;
LCD12864_DATA_PORT=0xFF; //读有效数据
LCD12864_RW=1;
  LCD12864_RS=0;
LCD12864_E=1;
do{
flag=LCD12864_DATA_PORT;
LCD12864_DATA_PORT=0xFF;
  }while(!((flag&0x80)==0x80));
LCD12864_E=0;



void WriteDatToLCD12864(unsigned char dat)//写数据函数
{

// WaitLCD();
LCD12864_RS=1; //the data
LCD12864_RW=0; //write
LCD12864_DATA_PORT=dat;
LCD12864_E=1;
LCD12864_E=0;

}

void WriteCmdToLCD12864(unsigned char cmd)//写命令函数
{

// WaitLCD();
LCD12864_RS=0; //the command
LCD12864_RW=0; //write
LCD12864_DATA_PORT=cmd;
LCD12864_E=1;
LCD12864_E=0;

}

unsigned char ReadDatFromLCD12864(void)//读数据
{

unsigned char dat;
WaitLCD();
LCD12864_DATA_PORT=0xFF; //读空操作
LCD12864_RS=1; //the data
LCD12864_RW=1; //read
LCD12864_E=1;
LCD12864_E=1;
LCD12864_E=0;
LCD12864_DATA_PORT=0xFF; //读有效数据
LCD12864_RS=1; 
LCD12864_RW=1; 
LCD12864_E=1;
dat=LCD12864_DATA_PORT;
LCD12864_E=0;
return dat;



void LCD12864_init(void)//初始化12864
{

LCD12864_RST=0;//液晶复位
delayus(50);
LCD12864_RST=1;
LCDSel(0); //选择整个屏幕
WriteCmdToLCD12864(LCD_OFF);//关显示
WriteCmdToLCD12864(LCD_ON);//开显示

}

void SetX(unsigned char x)//页选择0~7
{

WriteCmdToLCD12864(Add_X+x);

}

void SetY(unsigned char y)//ADD_Y是0x40,列初始地址,Y地址自动加一,DDRAM中的写入一字节数据的对应关系是竖着的八位,低位在上面,高位在下面。两个半屏幕的列号都是从0~63。当选中两个屏幕时,操作是对两个屏幕同时操作,并且进行一样的操作。由选屏、页和列就能指定唯一的一字节单元,只能写入一字节,不能一位一位的写,行号是指DDRAM和屏幕显示用的。
{

WriteCmdToLCD12864(Add_Y+y);



void SetZ(unsigned char z)//ADD_Z是0xC0,起始行地址,自动加一,SetZ(0)表示DDRAM中的第0行对应屏幕中的第1行,改变对应的行可以实现滚屏的效果。写数据是把DB0~DB7一字节的数据写到DDRAM中,Y地址指针自动加1,读数据是从DDRAM中读一个字节数据,Y地址指针自动加1。改变SetZ(z)中z的值只是改变了DDRAM与屏幕的对应关系。DDRAM中的数据到屏幕上的显示是一行一行进行的,因此有个自动加1。
{

WriteCmdToLCD12864(Add_Z+z);

}

void ClearLCD()//清屏
{

int i,j;
LCDSel(0);//选择两个屏幕
for(j=0;j<8;j++)
{
WriteCmdToLCD12864(LCD_ON);//开显示
SetX(j); //从0到7选择每一页 
WriteCmdToLCD12864(Add_Y); //写入列的初始地址,列地址会自动加1,diffrent from SetY(0),SetY(64);
SetZ(0);//DDEAM和屏幕的对应关系是DDRAM中第0行对应屏幕的第1行。
for (i=0;i<64;i++) 
{
WriteDatToLCD12864(0x00);//选中了两个半屏,向两个半屏相同列号处同时写入0x00,清屏。
}

}

//左上角第一个点为原点,向下Y为轴,向右为X轴
//x:0~63~127 列号 y:0~63行号
//由于是由页和列控制的,因此此处给出的x必须转化为页,即除以8即可,由于是处理一个点,还要得到这一页的第几行,即对8求余
//flag : 0:擦除某个点, 1:显示某个点 ,其本质就是赋值,赋值0是清除一个点,赋值1是显示一个点
unsigned char code Tab[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//一个字节中的8个位,此处要控制点,因此要处理八位中的一位。
void Dot(char x,char y,bit flag)
{
unsigned char dat = 0;
if(x<64)//x属于0~63就是左半屏幕,x是向右的X轴坐标,即在左半屏幕x是列号,在右半屏幕x-64是列号
{
LCDSel(1);//选择左半屏幕
SetX(y>>3); //y是行号,y属于0~63,除以8就得到当前的页,得到页号就可以设置页地址
SetY(x); //由列号设置列地址
dat = ReadDatFromLCD12864();//读数据,由于是一个字节一个字节操作的,别的点不能改变,因此要读数据,从而来保证其余7位的值
if(flag)//flag为1显示某个点,点显示为黑色的,液晶中每个字体的显示都是黑色的
{
dat = dat|(Tab[y&7]);//对8求余:y%8或y&7。对8求余得到当前页的第y%8行,由列和行可以确定唯一一个点。与0求或不变,与1求或变为1。
}
else
{
dat = dat&(~(Tab[y&7]));//清除这个点,与0求与为0,与1求与不变。
}
SetY(x); //列地址会自动加1,改变的点还要写入DDRAM中,因此将列地址仍设置为列号为x的列
WriteDatToLCD12864(dat);//写数据,仅仅只改变了一个点的状态
}
else if(x<128)//右半屏幕
{
LCDSel(2);//选择右半屏幕
SetX(y>>3);//设置页地址
SetY(x-64);//列地址,右屏幕的列号是从0到63
dat = ReadDatFromLCD12864();//读数据,由于是一个字节一个字节操作的,别的点不能改变,因此要读数据
if(flag)
{
dat=dat|(Tab[y&7]);
}
else
{
dat=dat&(~(Tab[y&7]));
}
SetY(x-64);//右半屏幕的列地址也是0~63
WriteDatToLCD12864(dat);//写数据

}

//左上角第一个点为原点,向下Y为轴,向右为X轴
//在两个点之间划线,draw a line between point(x1,y1) and point(x2,y2)
//flag为0是erase擦除线,为1是画线
void Line(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2,bit flag)
{
unsigned char i;
unsigned char temp;//临时变量,用于交换两个数
float k;//k为斜率
//if中是划竖线
if(x1==x2)//坐标系是左上角第一个点为原点,向下Y为轴,向右为X轴,x1等于x2则这两个点在同一列
{
if(y1>y2)//如果y1大于y2,交换y1和y2的值
{
temp = y1;
y1 = y2;
y2 = temp;
}
for(i=y1;i<=y2;i++)//y1等于y2就是画点
{
Dot(x1,i,flag);//列相同,从y1到y2划点形成线
}
}
//else中是划横线和斜线
else
{
if(x1>x2)//为了保证k为正值,当然也可以用绝对值函数
{
temp = x1;
x1 = x2;
x2 = temp;
}
if(y1>y2)
{
temp = y1;
y1 = y2;
y2 = temp;
}
k = (float)(y2-y1)/(float)(x2-x1);//计算斜率,k为0是划横线,由于x1不等于x2,所以不会是画点
temp = x2-x1;
for(i=0;i<temp;i++)
{
Dot(x1+i,(unsigned char)(y1+k*i),flag);
}
}


void Rect(unsigned char x1,unsigned char y1,unsigned char x2,unsigned char y2,bit flag)//画矩形
{

Line(x1,y1,x2,y1,flag);//y1行所在横线
Line(x2,y1,x2,y2,flag);//x2列所在竖线
Line(x2,y2,x1,y2,flag);//y2行所在横线
Line(x1,y2,x1,y1,flag);//x1列所在竖线



//汉字是16*16
//x是页0~7,y是列0~127
//n为要显示汉字的个数
//upline为0表示有上划线,underline为0表示有下划线
//flag为0表示汉字反白显示
void hz_disp(unsigned char x,unsigned char y,unsigned char n,unsigned char code *hz,bit flag,bit upline,bit underline)
{
unsigned char i,j;
for (j=0;j<n;j++)//要显示n个汉字,汉字编号0~n-1,j表示当前汉字编号
{
//显示上半个汉字
//每页是8行,汉字是16*16的,因此一个汉字分成上下两部分显示,即8*16,8行16列。
for(i=0;i<16;i++)//一个汉字占32个字节,这是前16个字节,每个字节占DDRAM中的一列
{
//点的位置是在左半屏幕还是右右半屏幕
if((y+(j<<4)+i)<64)//y表示要显示的起始列号,j表示当前汉字编号,j要乘以16,i表示当前汉字中0~16行的第i行,因此y+j<<4+i才是当前要写入数据的列号
{
LCDSel(1);//选择左半屏幕
WriteCmdToLCD12864(LCD_ON);//开显示
SetX(x);//设置页
SetZ(0);//DDRAM和屏幕的对应关系
SetY(y+(j<<4)+i);//设置列地址
if(upline)//如果没有上划线
{
if(flag)//汉字不反白显示
{
WriteDatToLCD12864(hz[(j<<5)+i]);//一个汉字占32个单元,j要乘以32,即左移5位
}
else//汉字反白显示
{
WriteDatToLCD12864(~hz[(j<<5)+i]);
}
}
else//如果有上划线
{
if(flag)//不反白显示
{
WriteDatToLCD12864(hz[(j<<5)+i]|UPLINE);//将每个字节的最低位置为1
}
else//反白显示
{
WriteDatToLCD12864(~hz[(j<<5)+i]|UPLINE);//将每个字节的最低位置为1 
}
}
}
else if((y+(j<<4)+i)<128)//在右半屏幕
{
LCDSel(2);//选择右半屏幕
WriteCmdToLCD12864(LCD_ON);//开显示
SetX(x);//设置页地址
SetZ(0);//DDRAM和屏幕对应关系
SetY(y+(j<<4)+i-64);//设置列地址,右半屏需要减去64
if(upline)
{
if(flag) WriteDatToLCD12864(hz[(j<<5)+i]);
else WriteDatToLCD12864(~hz[(j<<5)+i]);
}
else
{
if(flag) WriteDatToLCD12864(hz[(j<<5)+i]|UPLINE);
else WriteDatToLCD12864(~hz[j<<5+i]|UPLINE); 
}
}
}
//显示下半个汉字
for(i=16;i<32;i++)//一个汉字占32个单元,这是后16个单元
{
if((y+(j<<4)+i-16)<64)//汉字在左半屏幕
{
if(x+1<8)//页编号是0~7,共8页,如果x+1小于8,这说明这个汉字不会最后一页显示汉字的上半部分,第一页显示汉字的下半部分

LCDSel(1);//选择左半屏幕
WriteCmdToLCD12864(LCD_ON);//开显示
SetX(x+1);//设置页地址
SetZ(0);//DDRAM和屏幕对应关系
SetY(y+(j<<4)+i-16);//设置列地址
if(underline)//如果没有下划线
{
if(flag) WriteDatToLCD12864(hz[j<<5+i]);
else WriteDatToLCD12864(~hz[j<<5+i]);
}
else//有下划线
{
if(flag) WriteDatToLCD12864(hz[j<<5+i]|UNDERLINE);//将每个字节的最高位置为1
else WriteDatToLCD12864(~hz[j<<5+i]|UNDERLINE);
}
}
}
else if((y+(j<<4)+i-16)<127)//下半部分的汉字在右半屏幕
{
if(x+1<8)

LCDSel(2);
WriteCmdToLCD12864(LCD_ON);
SetX(x+1);
SetZ(0);
SetY(y-64+(j<<4)+i-16);
if(underline)
{
if(flag) WriteDatToLCD12864(hz[j<<5+i]);
else WriteDatToLCD12864(~hz[j<<5+i]);
}
else
{
if(flag) WriteDatToLCD12864(hz[j<<5+i]|UNDERLINE);
else WriteDatToLCD12864(~hz[j<<5+i]|UNDERLINE); 
}
}
}
}
}
}

//字母和数字是16行8列的
//x:行0~7 y:列0~127
//asc: 指向标准交换码ASCII
//string: 指向要显示的字符串
//flag: 0 反白显示
//online: 0 带上划线,underline : 0带下划线
//n: the number of the string
void en_disp(unsigned char x,unsigned char y,unsigned char n,unsigned char code *asc,const unsigned char *string,bit flag,bit online,bit underline)
{
unsigned char i,j,loc;
for (j=0;j<n;j++)//从第一个字符开始处理,字符串string长度为n
{
loc = string[j]-0x30;//0~9和:的ASCII码是从0x30到0x3A,:正好在9后面,0~9和:的ASCII码在Asc[]数组中对应,loc是指示第几个元素,loc*16才是Asc[]数组中对应的位置,因为每个字符占16个单元,如果建立一个二维数据更容易处理一些。
for(i=0;i<8;i++)//ASCII码的上半部分
{
if((y+(j<<3)+i)<64)//y是列地址,每一个字符占8列,j<<3表示前面有j个字符占了8*j列,还要加上i,即当前字符的第i列
{
LCDSel(1);//选择左半屏幕
WriteCmdToLCD12864(LCD_ON);//开显示
SetX(x);//设置页地址
SetZ(0);
SetY(y+(j<<3)+i);//设置列地址
if(online)//没有上划线
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]);//不反白显示 
else WriteDatToLCD12864(~asc[loc<<4+i]);//反白显示
}
else
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]|UPLINE);
else WriteDatToLCD12864(~asc[loc<<4+i]|UPLINE);
}
}
else if((y+(j<<3)+i)<128)
{
LCDSel(2);
WriteCmdToLCD12864(LCD_ON);
SetX(x);
SetZ(0);
SetY(y-64+(j<<3)+i);
if(online)
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]);
else WriteDatToLCD12864(~asc[loc<<4+i]);
}
else
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]|UPLINE);
else WriteDatToLCD12864(~asc[loc<<4+i]|UPLINE);
}
}
}
for(i=8;i<16;i++)//显示下半个字母
{
if((y+(j<<3)+i-8)<64)

if(x+1<8)
{
LCDSel(1);
WriteCmdToLCD12864(LCD_ON);
SetX(x+1);
SetZ(0);
SetY(y+(j<<3)+i-8);
if(underline)
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]);
else WriteDatToLCD12864(~asc[loc<<4+i]);
}
else
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]|UNDERLINE);
else WriteDatToLCD12864(~asc[loc<<4+i]|UNDERLINE);
}

}
}
else if((y+(j<<3)+i-8)<128)
{
if(x+1<8)

LCDSel(2);
WriteCmdToLCD12864(LCD_ON);
SetX(x+1);
SetZ(0);
SetY(y-64+(j<<3)+i-8);
if(underline)
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]);
else WriteDatToLCD12864(~asc[loc<<4+i]);
}
else
{
if(flag) WriteDatToLCD12864(asc[loc<<4+i]|UNDERLINE);
else WriteDatToLCD12864(~asc[loc<<4+i]|UNDERLINE);
}
}
}
}

}

//显示一个32行16列的字符,占用4页
//line是页
//column是列
//flag是反白标识
void Show16X32(unsigned char page,unsigned char column,unsigned char *pt,bit flag)
{
unsigned char i,j;
for(j=0;j<4;j++)
{
SetX(page+j); //设置页地址
LCDSel(1); //选左半屏
SetY(column); //一个字符占用4页,但每页的列首地址都是相同的,设置列地址 
for(i=0;i<16;i++)//一个字符占16列
{
if((column+i)>=64) //如果列大于等于64

LCDSel(2);//选右屏幕
SetY(column+i-64);//设置列地址,列地址会自动加一
}
if(flag) WriteDatToLCD12864(*pt);//不反白显示
else WriteDatToLCD12864(~(*pt)); 
pt++;
}
}
}

原文地址:https://www.cnblogs.com/wolf-man/p/6823279.html