分享

12864学习(控制器ST7920)

 莽汉一枚 2013-03-25

在学习12864其他功能前先学习一下12864的几个基本函数

void delay(uint x)//延时函数

{

x=x*12;

while(x--)

{

_nop_(); //包含在intrins.h头文件

_nop_();

_nop_();

}

}

void write_lcd_cmd(uchar cmd)//写命令

{

RS=0;

RW=0;

EN=1;

P0=cmd;

// delay(1);

EN=0;

delay(1);

}

void write_lcd_date(uchar date)//写数据函数

{

RS=1;

RW=0;

EN=1;

P0=date;

// delay(1);

EN=0;

delay(1);

}

void display_pos(uchar x,uchary)//设置显示地址,用于显示中文字时设计显示地址

{

uchar pos;

switch(x)

{

case 0: pos=0x80; break;//当x为0指向第一行

case 1: pos=0x90; break;//当x为1指向第二行

case 2: pos=0x88; break;//当x为2指向第三行

case 3: pos=0x98; break;//当x为3指向第四行

default: pos=0x80; //x为其它值时指向第一行

}

pos=pos+y;

write_lcd_cmd(pos);

}

/*===========================================================

函数功能:读取12864液晶繁忙标志BF位状态BF=1繁忙,BF=0不忙

注意:与读取数据不同读取繁忙标志位是是属于写指令,要将RS置低

============================================================*/

void read_lcd_bf()

{

uchar BF=1;

P0=0xff;

do

{

RS=0;

RW=1;

EN=0;

EN=1;

_nop_();

BF=P0;

BF=BF>>7;

}

while(BF);

}

12864显示图片

要让一个点有显示就要让相应的点置高电平。要想知道如何控制一个点的高低电平就要知道它的地址是怎样排列的。我们看着12864液晶是连在一起的一块屏幕,就会想当然的认为它的地址也是一直地增的,但是你错了。12864是分为上下两个半屏的。

12864液晶模块显示地址如图所示。

注意:12864显示分为上下两屏,而不是一个整屏。什么意思呢?就是说它的地址是分成两大部分(既上下两大部分)。如图,上半屏的水平地址X是从0x80—0x87并且它的每个水平地址都包含了十六个点的显示区(如图红色标注)。下半屏水平地址是从0x88—0x8f,而上下半屏的纵坐标Y都是从0—31。

这个是我定义的坐标关系简图

坐标对应关系简图

向12864 GDROM写如数据来绘图的过程:

1、关闭绘图显示功能

2、先打开扩充指令功能

3、连续写入X坐标地址,和y坐标地址(说明书上是写着先写X在写y坐标,但是程序是却是先写Y再写X坐标,否则会出错。还有这里写地址使用写命令函数不是用写数据函数)

4、向GDROM 中写入数据

5、打开绘图显示功能

/*====================================================

函数功能:清除GDROM的内容,如果不清除会出现花屏现象

清除方法:向GDROM中写入0x00来清除内容

========================================================*/

void clear_gcrom()

{

uchar i,j,k,lcd_x,lcd_y;

lcd_x=0x80;

lcd_y=0x80;

write_lcd_cmd(0x34);//打开扩充指令关闭绘图显示(绘图指令为扩充指令,并且在绘图期间必须关闭绘图显示功能)

for(i=0;i<2;i++)//分为上下两半屏清除显示

{

for(j=0;j<32;j++)

{

write_lcd_cmd(lcd_y+j);

write_lcd_cmd(lcd_x);

for(k=0;k<16;k++)

{

write_lcd_date(0x00);

}

}

lcd_x=0x88;//将x指向下半屏

}

write_lcd_cmd(0x36);//打开绘图指令

write_lcd_cmd(0x30);//操作恢复为常用指令

}

/*===========================================================

函数功能:向12864中写入一幅图片

函数参数:*p为指向图片数据的指针lcd_x为水平显示位置,lcd_y为垂直显示位置,

函数将12864屏分成上下两屏写入,上半屏LCD_x=0x80,下半屏lcd_x=0x88

===========================================================*/

void write_image(uchar *p)

{

uchar i,j,k,lcd_x,lcd_y;

lcd_x=0x80;

lcd_y=0x80;

write_lcd_cmd(0x34);//打开扩充功能

for(i=0;i<2;i++) //分为上下两屏

{

for(j=0;j<32;j++)

{

write_lcd_cmd(lcd_y+j);

write_lcd_cmd(lcd_x);

for(k=0;k<16;k++) //写入显示数据

{

write_lcd_date(*p++);

}

}

lcd_x=0x88;//将x地址指向下半屏

}

write_lcd_cmd(0x36);//打开绘图功能

write_lcd_cmd(0x30);//操作恢复为常用指令

}

12864画图功能

如果你想在液晶上显示几条线的话用上面的画图方法可以实现,但是要画一条任意直线,而且还要让直线可以变化的话用上面的方法肯定不是最好的方法,因为你每一条线的数据都要事先保存起来,再调用。先不说程序存储空间会不够用,更重要的是相当麻烦。你是不是也想只要一种方法只要给出直线的起始和终点坐标就可以画出一条直线来呢?

在学习画直线之前我们先来往12864任意点上画一个点。画一点很简单只要将相应点置高电平就可以了。注意在12864液晶相应点上画一点,在向12864中画点时先要读取出该地址上的数据,再在读取到的数据中加入该点的数据,如果不先读取如果以前那个地址就有显示的数据时,可能就会破坏那个地址的显示

/*===========================================================

函数功能;读取12864数据

注意:读取数据时是在RS=1、RW=1、EN=1期间进行的。读数据一定要在EN为高电平

期间进行。还有就是不要想当然的认为读数据指令要将RS置低,恰恰相反要将RS置高。

=============================================================*/

uchar read_lcd_date()

{

uchar date;

P0=0xff;

RS=1;

RW=1;

EN=0;

EN=1;

date=P0;

_nop_();

EN=0;

return date;

}

/*===========================================================

函数功能:在12864液晶相应点上画一点,在向12864中画点时先要读取

出该地址上的数据,再在读取到的数据中加入该点的数据,如果不先读取如果以前

那个地址就有显示的数据时,可能就会破坏那个地址的显示

参数功能说明:x为水平方向上的坐标,y为垂直方向的坐标,

color为所画点的颜色,0画白点,1画黑点,2使相应位颜色取反

read_H,read_L分别用来保存从12864中读取到数据的高低字节数据

=============================================================*/

void write_dian(uchar x,uchar y,uchar color)

{

uchar x_locate,y_locate;

uchar x_bit;

uchar read_H,read_L;

x_locate=x/16;//求出要显示点在具体的哪一个地址上

x_bit=x%16;//求出要显示点在具体在地址的哪一位上

if(y<32)//说明在屏的上半屏

{

y_locate=y;

}

else

{

y_locate=y-32;

x_locate=x_locate+8;//将x_locate指向下半屏

}

write_lcd_cmd(0x34);//写扩充命令

write_lcd_cmd(y_locate+0x80);//写垂直方向地址

write_lcd_cmd(x_locate+0x80);//写水平方向的地址

read_lcd_bf();

read_lcd_date();//空读一次12864的数据(一定要空读一次否则会出错)至于为什么要空读我也不是很清楚

read_H=read_lcd_date(); //连续读取相应地址高低字节的数据

read_L=read_lcd_date();

if(x_bit<8)//判断x地址数据是在高字节还是低字节,x_bit<8在高字节

{ //为什么在高字节,应为地址为从左到右

switch(color)

{

case0:read_H&=(~(0x01<<(7-x_bit)));break;

case 1:read_H|=(0x01<<(7-x_bit));break;

case2:read_H^=(0x01<<(7-x_bit));break;

default:break;

}

write_lcd_cmd(y_locate+0x80);

write_lcd_cmd(x_locate+0x80);

write_lcd_date(read_H);

write_lcd_date(read_L);

}

else

{

switch(color)

{

case0:read_L&=(~(0x01<<(15-x_bit)));break;

case1:read_L|=(0x01<<(15-x_bit));break;

case2:read_L^=(0x01<<(15-x_bit));break;

default:break;

}

write_lcd_cmd(y_locate+0x80);

write_lcd_cmd(x_locate+0x80);

write_lcd_date(read_H);

write_lcd_date(read_L);

}

write_lcd_cmd(0x36);

write_lcd_cmd(0x30);

}

到这里画点函数就结束了,在现在我们来应用画点函数向12864里面画垂直和水平直线,利用画点函数就很好实现的,只要将直线所在水平线或垂直线上的点全部置成高电平就可以了。

/*============================================================

函数功能:画水平直线

参数说明:x0为水平线起始点,x1为水平直线终止点,y图画在第几行

color为 0时为白线, 为1时为黑线,2时取反该直线上的点

==============================================================*/

void write_x_line(uchar x0,uchar x1,uchar y,uchar color)

{

uchar temp;

if(x0>x1) //当X0在X1后面时将X1作为起始点

{

temp=x0;

x0=x1;

x1=temp;

}

for( ;x0<x1;x0++)

write_dian(x0,y,color);

}

/*================================================================

函数功能:画垂线

参数说明:y0 为起始点 y1为结束点,x为所在第几列 ,color同上为颜色选择

==================================================================*/

void write_y_line(uchar x,uchar y0,uchar y1,uchar color)

{

uchar temp;

if(y0>y1) //当y0在X1后面时将y1作为起始点

{

temp=y0;

y0=y1;

y1=temp;

}

for( ;y0<y1;y0++)

write_dian(x,y0,color);

}

到这里我相信你以经可以灵活的画出直线和垂线了吧。

在画直线之前先来学习一下直线的基本画法原理。在这里使用Bresenham算法。

下面是我找到得几种直线画法的原理。

由直线的斜率确定选择在x方向或y方向上每次递增(减)1个单位,另一变量的递增(减)量为0或1,它取决于实际直线与最近光栅网格点的距离,这个距离的最大误差为0.5。

假定直线斜率k在0~1之间。此时,只需考虑x方向每次递增1个单位,决定y方向每次递增0或1。

直线当前点为(xi,y)
直线当前光栅点为(xi,yi)

下一个直线的点应为(xi+1,y+k)
下一个直线的光栅点
或为右光栅点(xi+1,yi)(y方向递增量0)
或为右上光栅点(xi+1,yi+1)(y方向递增量1)

记直线与它垂直方向最近的下光栅点的误差为d,有:d=(y+k)–yi,且

0≤d≤1
当d<0.5:下一个象素应取右光栅点(xi+1,yi)
当d≥0.5:下一个象素应取右上光栅点(xi+1,yi+1)

如果直线的(起)端点在整数点上,误差项d的初值:d0=0,
x坐标每增加1,d的值相应递增直线的斜率值k,即:d=d + k。
一旦d≥1,就把它减去1,保证d的相对性,且在0-1之间。

令e=d-0.5,关于d的判别式和初值可简化成:

e的初值e0= -0.5,增量亦为k;
e<0时,取当前象素(xi,yi)的右方象素(xi+1,yi);
e>0时,取当前象素(xi,yi)的右上方象素(xi+1,yi+1);
e=0时,可任取上、下光栅点显示。

Bresenham算法的构思巧妙:它引入动态误差e,当x方向每次递增1个单位,可根据e的符号决定y方向每次递增 0 或 1。

e<0,y方向不递增
e>0,y方向递增1
x方向每次递增1个单位,e = e + k

因为e是相对量,所以当e>0时,表明e的计值将进入下一个参考点(上升一个光栅点),此时须:e = e - 1

通过(0,0)的所求直线的斜率大于0.5,它与x=1直线的交点离y=1直线较近,离y=0直线较远,因此取光栅点(1,1)比(1,0)更逼近直线;
如果斜率小于0.5,则反之;
当斜率等于0.5,没有确定的选择标准,但本算法选择(1,1)

1、浮点型的算法

x=xs
y=ys
x = xe -xs
y = ye -ys
//initialize e to compensate for a nonzero intercept
Error =?y/?x-0.5
//begin the main loop
for i=1 to ?x
WritePixel (x, y, value)
if (Error ≥0) then
y=y+1
Error = Error -1
end if
x=x+1
Error = Error +?y/?x
next i
finish

2整数Bresenham算法

· 上述Bresenham算法在计算直线斜率和误差项时要用到浮点运算和除法,采用整数算术运算和避免除法可以加快算法的速度。

由于上述Bresenham算法中只用到误差项(初值Error =?y/?x-0.5)的符号

因此只需作如下的简单变换:

NError = 2*Error*?x

即可得到整数算法,这使本算法便于硬件(固件)实现。

程序

o //Bresenham's integer line resterizationalgorithm for the first octal.
//The line end points are (xs,ys) and (xe,ye) assumed not equal. All variablesare assumed integer.
//initialize variables
x=xs
y=ys
x = xe -xs
y = ye -ys
//initialize e to compensate for a nonzero intercept
NError =2*?y-?x //Error=?y/?x-0.5
//begin the main loop
for i=1 to ?x
WritePixel (x, y)
if (NError >=0) then
y=y+1
NError = NError –2*?x //Error = Error -1
end if
x=x+1
NError = NError +2*?y //Error = Error +?y/?x
next i
finish

接下来就开使画任意角度的直线了。

/*======================================================

函数功能:判断整数的正负及是否为零

函数说明:当形参X大于零返回1,等于零返回零,小于零返回-1.

========================================================*/

uchar pan_duan_fu_hao(uchar x)

{

if(x>0)

{

return 1;//当x大于零返回1

}

else if(x==0)

{

return 0;//当x为零返回0

}

else

{

return -1;//当x小于零返回-1

}

}

/*===========================================================

函数功能:画一条任意斜率的直线

函数思想:利用Bresenham画法基础上自己稍加改进。既当直线dy>dx时将y坐标依次加一(相当于将y轴

于x轴互换)。这里选择的是浮点型的画圆方法。会减慢运算速度。也可使用整形的运算方法运算速度更快

参数说明:start_x,start_y,end_x,end_y,分别为直线起始坐标和终点坐标,color为直线颜色。

length为画点时循环的次数,falg为dy与dx的大小的标志,当dy>dx时flag置1.

===========================================================*/

void write_line(uchar start_x,uchar start_y,uchar end_x,ucharend_y,uchar color)

{

uint i,length;

float temp,dx,dy;

char incx,incy,flag;

dx=end_x-start_x; //求开始x坐标和结束X坐标的距离

dy=end_y-start_y; //求开始y坐标和结束y坐标的距离

incx=pan_duan_fu_hao(dx);//判断整数的正负及是否为零

incy=pan_duan_fu_hao(dy);

if(incx==0)//当x轴没有增加时画一条垂线

{

write_y_line(start_x,start_y,end_y,color);

}

if(incy==0)//当y轴没有增加时画一条水平线

{

write_x_line(start_x,end_x,start_y,color);

}

dx=abs(dx);//求绝对值函数包含在math.h头文件里

dy=abs(dy);

if(dx>=dy)//当dx>=dy时循环画点次数以x轴为标准

{

length=dx;//画点循环次数为dx

flag=0;

}

else//当dx<dy时循环画点次数yiY轴为标准

{

length=dy;//画点次数为dy

flag=1;//标志位置1

}

if((incx!=0)||(incy!=0))//当在x方向或Y方向有增量时画直线

{

if(flag==0) //当以x轴为标准时画直线,求斜率于0.5的差

temp=dy/dx-0.5;//用以选择下一个点取右上方或左下方的点

else //当以y轴为标准是画直线

temp=dx/dy-0.5;//用以选择下一个点取左上方或右下方的点

if(end_y<start_y)//当结束的的y坐标大于开始点时将incy取反

{ //为什么要取反,应为写的程序是以12864右下角的点为坐标原点

incy=incy*(-1);//而实际上12864是以右上方的那个点为原点所以Y轴相当于反过来了

} //因此当end_y>start_y时要用减1而不是加1

for(i=0;i<=length;i++)//画点的循环

{

write_dian(start_x,start_y,color);

if(flag==0)

{

if(temp>=0)

{

start_y=start_y+incy;

temp=temp-1;

}

start_x=start_x+incx;//x轴坐标加(减)1

temp=temp+dy/dx; //temp加上直线的斜率

}

else

{

if(temp>=0)

{

start_x=start_x+incx;

temp=temp-1;

}

start_y=start_y+incy;//y轴坐标加(减)1

temp=temp+dx/dy;//temp加上直线的斜率

}

}

}

read_lcd_bf();//等待LCD空闲时退出

}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多