嵌入式 图形实验报告.docx
《嵌入式 图形实验报告.docx》由会员分享,可在线阅读,更多相关《嵌入式 图形实验报告.docx(19页珍藏版)》请在冰豆网上搜索。
嵌入式图形实验报告
图形用户接口
1、实验目的
(1)了解嵌入式系统图形界面的基本编程方法
(2)学习图形库的制作
2、实验原理
(1)FrameBuffer
显示屏的整个显示区域,在系统内会有一段存储空间与之对应。
通过改变该存储空间的内容达到改变显示信息的目的。
该存储空间被称为FrameBuffer,或显存。
显示屏上的每一点都与FrameBuffer里的某一位置对应。
所以,解决显示屏的显示问题,首先要解决的是FrameBuffer的大小以及屏上的每一像素与FrameBuffer的映射关系。
影响空间大小的因素:
由于FrameBuffer空间的计算大小是以屏幕的大小和显示模式决定的,所以显示模式(单色或彩色)、显示屏的性能、显示的需要均会影响FrameBuffer空间的大小。
另外显示屏还有单屏幕、双屏幕两种工作模式:
单屏幕模式代表屏幕的显示范围是整个屏幕,只需一个FrameBuffer和一个通道;双屏幕模式则将整个屏幕划分为两个部分,这两个部分各自有FrameBuffer,且他们的地址无需连续,并同时具有两个各自独立的通道将FrameBuffer的数据传送到显示屏。
显示操作及映射连续性:
由于FrameBuffer通常就是从内存空间分配所得,并且他是有连续的字节空间组成,屏幕的显示操作通常是从左到右逐点像素扫描,从上到下逐行扫描,直到扫描到右下角,然后再折返到左上角。
又由于FrameBuffer里的数据是按地址递增的顺序被提取,所以屏幕上相邻的两像素被映射到FrameBuffer里是连续的,并且屏幕最左上角的像素对应FrameBuffer的第一空间单元,屏幕最右下角则对应最后一个单元空间。
(2)FrameBuffer与色彩
计算机反映自然界的颜色是通过RGB(Red-Green-Blue)值来表示的。
如果要在屏幕某点显示某种颜色,则必须给出相应的RBG值。
FrameBuffer是由所有像素的RGB值或RGB值的部分位所组成,本系统使用的16位/像素的模式下,FrameBuffer里的每个单元16位,每个单元代表一个像素的RGB值,如下图
D15
D14
D13
D12
D11
D10
D9
D8
D7
D6
D5
D4
D3
D2
D1
D0
R
R
R
R
R
G
G
G
G
G
G
B
B
B
B
B
有了以上的分析,就可以用下面的计算公式
FrameBufferSize=Width*Height*Bitperpixel/8
计算FrameBuffer的大小(以字节为单位)。
(三)LCD控制器
在FrameBuffer与显示屏之间还需要一个中间件,该中间件负责从FrameBuffer里提取数据,进行处理,并传输到显示屏上。
PXA270处理器内部集成LCDC,他提供了一个从PXA270处理器到显示屏的接口,LCDC的作用是将FrameBuffer里的数据传输到LCDC的内部,然后经过处理,输出数据到LCD的输入引脚上。
本实验系统使用的是16位TFTLCD,像素分辨率是640X480。
(4)FrameBuffer操作
FrameBuffer是一种驱动程序接口,这种接口将显示设备抽象为帧缓冲区。
帧缓冲区为图像硬件设备提供了一种抽象化处理,它代表了一些视频硬件设备,允许应用软件通过定义明确的界面来访问图像硬件设备。
于是,将帧缓冲区映射到进程地址空间之后,就可以直接进行读写和I/O控制等操作,而写操作可以立即显示在屏幕上。
了解这个设备的参数可以通过FBIOGET_FSCREENINFO、FBIOGET_VSCREENINFO命令,从中可以获取显示器的色味、分辨率等信息(vinfo.bits_per_pixel、vinfo.xres、vinfo.yres)。
3、实验内容
(1)实现基本画图功能
在FrameBuffer基础上编写画点、画线的API函数,供应用程序调用,实现任意曲线的画线功能。
(二)合理的软件结构
将调用设备驱动的基本API函数独立地构成一个函数库,为用户程序屏蔽底层硬件信息,直接提供一些简单的画图调用。
函数库可以是独立编译后的“.o”文件或由归档管理器ar生成的库文件,或是将“.o”文件链接而承认那个的共享库“.so”。
四、实验过程及相关程序
(一)设备的初始化(LCD_INIT)
FrameBuffer设备是/dev/fb(它通常是/dev/fb0),对于该设备的初始化包括设备的打开,通过ioctl函数获得设备的相关信息,计算FrameBuffer缓冲区的大小以及使用mmap函数获取FrameBuffer缓冲区的首地址。
具体程序如下:
fd=open("/dev/fb0",O_RDWR);//打开设备
ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);//获取设备的相关信息
printf("%dx%d,%dbpp\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);//打印相关信息
screensize=vinfo.xres*vinfo.yres*vinfo.bits_per_pixel/8;//计算缓冲区大小
fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//获取缓冲区首地址
设备的初始化基本就结束了。
(二)设备的关闭(LCD_END)
设备关闭前获得的缓冲区应先被释放,接着关闭设备。
具体程序如下:
munmap(fbp,screensize);//缓冲区释放
close(fd);//关闭设备
(三)清屏操作(LCD_CLEAR)
实验中的清屏操作,可通过memset(fbp,0,screensize)来实现,此时屏幕为黑色。
(4)画图程序
1.画点程序(draw_point)
画点程序程序是整个画图实验的基础,其他的画图程序都是建立在画点程序上的。
画点程序主要是解决两个问题:
a、点的坐标;b、点的颜色。
点的位置的获取,我们只需要知道该点相对于缓冲区首地址的偏移量即可。
例如要在(x,y)处显示一个点,可通过下面的方法获取其偏移量:
offset=(x+y*vinfo.xres)*vinfo.bits_per_pixel/8;
在这之前,我们最好先对x和y的范围进行一个判断,即x和y的值不满足LCD屏坐标范围时,则对改点不进行操作,方这便我们下面的编程。
if(x<0||x>639||y<0||y>479)
return;
而对于点得颜色,由于我们使用的LCD屏是16位色的,所以首先必须根据格式要求将RGB压缩到16位,再填充对应字节。
相应的程序如下:
color=(Red<<11)|((Green<<5)&0x07E0)|(Blue&0x1F);
*(unsignedchar*)(fbp+location+0)=color&0xFF;
*(unsignedchar*)(fbp+location+1)=(color>>8)&0xFF。
到此为止,一个完整的画点程序就完成了。
其实,我们完全可以把画点的颜色也加入其中。
在这里,我只列举了九种颜色,如果想要更多的颜色,可自行加入。
intcolour_choose(intnum)
{
intr,b,g;
intcol;
switch(num)
{
case0:
r=255;b=0;g=0;break;//red
case1:
r=255;b=255;g=0;break;//yellow
case2:
r=0;b=255;g=0;break;//green
case3:
r=160;b=32;g=240;break;//purpie
case4:
r=184;b=143;g=143;break;//rose
case5:
r=0;b=0;g=255;break;//blue
case6:
r=25;b=15;g=80;break;//
case7:
r=255;b=255;g=255;break;//write
case8:
r=0;b=0;g=0;break;//black
}
col=(r<<11)|((g<<5)&0x07E0)|(b&0x1F);
returncol;
}
把返回值送到上面画点程序中的color,这样颜色选择就完成了。
为了使这个程序的实用性和兼容性能够得到提高,我在后来的实验中在这里做出了修改,由于我们现在所使用的LCD是16位色的,但是上面已经说到了,我们可以通过设备的初始化得到屏幕的基本信息。
所以,如果得到的screensize中vinfo.bits_per_pixel为24位色的时候,我们画点程序应该做出修改。
此时画点程序为:
offset=(x+y*vinfo.xres)*vinfo.bits_per_pixel/8;
*(unsignedchar*)(fbp+offset+0)=r;
*(unsignedchar*)(fbp+offset+1)=g;
*(unsignedchar*)(fbp+offset+2)=b;
所以我们在画点之前先做一个判断。
如果初始化屏幕得到16位色时,就用上面那个程序,若为24位色就用下面这个程序。
2.划线程序(draw_line)
有了画点程序后,画线程序就比较简单了。
已知两个点,画一条连接这两点的直线,我们只许求出它们的斜率,得到直线方程,再通过for循环语句即可画出这条线。
不过有两个地方必须要注意:
a、当直线的斜率非常大的时候,这时候在屏幕上划出的点在Y轴方向上会断断续续,所以必须要改进,我们可以将斜率绝对值大于1和小于等于1这两种情况分开考虑,当斜率较大时,以Y轴为基准画X轴方向的点;当斜率较小时,以X轴为基准画Y轴方向的点。
b、当直线斜率为0(即Y0=Y1)或无穷大时(即X0=X1)应特殊考虑,因为此时直线斜率不存在。
相应的程序如下:
voiddraw_line(intx1,inty1,intx2,inty2,intm)
{
inti;floatj;
if(x1==x2)
{
if(y1>=y2)
for(i=y2;i>=y1;i--)
draw_point(x1,i,m);
else
for(i=y1;i<=y2;i++)
draw_point(x1,i,m);
}
else
if(y1==y2)
{
if(x1>=x2)
for(i=x2;i>=x1;i--)
draw_point(y1,i,m);
else
for(i=x1;i<=x2;i++)
draw_point(y1,i,m);
}
else
{
j=(float)(y2-y1)/(float)(x2-x1);
if(j>1||j<-1)
{
j=(float)(x2-x1)/(float)(y2-y1);
if(y1for(i=y1;i<=y2;i++)
draw_point((int)((i-y1)*j+x1),i,m);
else
for(i=y1;i>=y2;i--)
draw_point((int)((i-y1)*j+x1),i,m);
}
else
{
if(x1for(i=x1;i<=x2;i++)
draw_point(i,(int)((i-x1)*j+y1),m);
else
for(i=x1;i>=x2;i--)
draw_point(i,(int)((i-x1)*j+y1),m);
}
}
}
3.画圆程序(draw_circle)
画圆程序其实也是来自于画点程序,一个圆由1000个点组成,而这1000个点正好满足同一个圆方程,这样由于各个点间隔非常小,所有看上去就是一个完整的圆了。
相应的程序如下:
voiddraw_circle(intR,intx0,inty0,intm)
{
intx,y,i;
for(i=0;i<1000;i++)
{
x=R*sin(2*PI/1000*i)+x0;
y=R*cos(2*PI/1000*i)+y0;
draw_point(x,y,m);
}
}
画实心圆可以由上面的画圆程序得到,我们可以用一个for循环将圆的半径由0递增到所需要的实心圆半径R,这样一个半径为R的实心圆就能得到了。
不过我们上面已经提到了,所画的圆其实是由1000个点组成的,说白了,它其实并不是一个真正的圆,这样,当我们将大量的圆和在一起组成一个实心圆时,实心圆内部会出现很多中心对称的黑色点,这其实就是由于一些点断断续续没有被着色到而造成的。
有一个改进的方法就是,我们先选定实心圆的范围,再一个一个判断屏幕上的点是否落在这个范围之内,如果是,则对其着色,反之则放弃着色。
不过这必须对屏幕上所有的点都进行判断,工作量大,所以本程序还有待提高。
相应的程序如下:
voiddraw_shi_circle(intR,intx0,inty0,intm)
{
intx,y;
for(x=0;x<=vinfo.xres;x++)
{
for(y=0;yif((x-x0)*(x-x0)+(y-y0)*(y-y0)<=R*R)
{
draw_point(x,y,m);
}
}
}
4.画旋转五角星程序(draw_star)
画五角心其实就是先求出五角星五个角所相对应的坐标值,再把这些点连接起来。
而让五角星旋转的话,我们只要让这五个点沿着一个角度偏移就可以了,期间再延迟一段时间并刷一下屏即可。
相应的程序如下:
voiddraw_star(intl,intx0,inty0)
{
floatx1,y1;
floatx2,y2;
floatx3,y3;
floatx4,y4;
floatx5,y5;
inti;
for(i=0;i<360;i=i+30)
{
x1=x0+l*sin((PI*0+PI*i)/180);
y1=y0+l*cos((PI*0+PI*i)/180);
x2=x0+l*sin((PI*72+PI*i)/180);
y2=y0+l*cos((PI*72+PI*i)/180);
x3=x0+l*sin((PI*144+PI*i)/180);
y3=y0+l*cos((PI*144+PI*i)/180);
x4=x0+l*sin((PI*216+PI*i)/180);
y4=y0+l*cos((PI*216+PI*i)/180);
x5=x0+l*sin((PI*288+PI*i)/180);
y5=y0+l*cos((PI*288+PI*i)/180);
draw_line(x3,y3,x5,y5);
draw_line(x1,y1,x3,y3);
draw_line(x1,y1,x4,y4);
draw_line(x2,y2,x4,y4);
draw_line(x2,y2,x5,y5);
}
usleep(1000000);
lcd_clear();
}
5.画振动的正弦波(draw_zhenxian)
这个程序的原理和上面基本相似,所以不再做具体的解释。
相应的程序如下:
voiddraw_zhenxian(intu,intm)
{
longinti,j,y;
{
for(j=u;j>=0;j--)
{
for(i=0;i<640;i++)
{
y=j*sin(PI*i/80)+240;
draw_point(i,y,m);
}
usleep(10000);
lcd_clear();
}
}
}
6.画正十二边形(draw_dodecagon)
这个程序是实现画正十二边形,由于我准备能在屏幕上模拟一个钟表,这个十二边形的每个顶点正好是12个整点值。
相应程序如下:
voiddraw_dodecagon(intx0,inty0,intR,intm)
{
intx[12],y[12];
inti=0;
for(i=0;i<12;i++)
{
x[i]=x0+R*cos(i*PI/6);
y[i]=y0+R*sin(i*PI/6);
}
for(i=0;i<11;i++)
{
draw_line(x[i],y[i],x[i+1],y[i+1],m);
}
draw_line(x[11],y[11],x[0],y[0],m);
}
7画钟表程序(draw_clock)
其实这个程序本身并不烦,我先在屏幕上画了一个实心圆,用于做钟表的表盘。
然后再调用划线函数,画三条直线,不过三条直线的长度各不相同,分别做时针、分针和秒针,并且它们都延时一段时间然后移动到下一个位置。
同时,它们原来的位置会被和表盘相同颜色的直线盖住。
这样,我们就会看到一个时钟显示在屏幕上。
具体程序如下:
voiddraw_clockline(intx0,inty0)
{
intclock_x_miao,clock_y_miao,clock_x_fen,clock_y_fen,clock_x_shi,clock_y_shi,i;
intl=180;
for(i=0;i<3600;i++)
{
clock_x_fen=x0+l*sin(i*PI/1800)/1.2;
clock_y_fen=y0-l*cos(i*PI/1800)/1.2;
clock_x_shi=x0+l*sin(i*PI/108000)/1.5;
clock_y_shi=y0-l*cos(i*PI/108000)/1.5;
clock_x_miao=x0+l*sin(i*PI/30);
clock_y_miao=y0-l*cos(i*PI/30);
draw_line(x0,y0,clock_x_shi,clock_y_shi);
draw_line(x0,y0,clock_x_miao,clock_y_miao);
draw_line(x0,y0,clock_x_fen,clock_y_fen);
usleep(1000000);
draw_line_one(x0,y0,clock_x_miao,clock_y_miao);
draw_line_one(x0,y0,clock_x_fen,clock_y_fen);
draw_line_one(x0,y0,clock_x_shi,clock_y_shi);
}
}
voiddraw_clock()
{
draw_shi_circle(190,320,240);
draw_dodecagon(320,240,200);
}
由于这个程序是一个死循环,所以最好在后台操作。
但是如果把程序送到后台时,键盘输入就会不起作用。
因此,我并没有把这段程序放到动态库之中。
但是,在之前的调试中,这个程序能够显示,并且达到了我想要的效果。
8.主函数(main)
由于我们所需要的画图程序以及设备驱动程序等都已经在子函数中给出了,所以主函数的任务就是调用子函数来实现相应的功能。
在这里,为了能够将子函数的功能一一显示出来,我在这里用了一个switch的结构,我们在键盘上输入0-5之间的任意数字,主函数就做出相应的选择,LCD屏上也会出现相应的图形,当输入数字为9时,程序结束。
当输入完成0-5之间的选择后,此时,我们就可以输入相应图形的信息,比如画圆时,我们就输入圆的半径和圆心得位置,划线则输入直线的两个端点。
最后,我们再输入0-8之间的一个数,完成颜色的选择。
这样,整个程序就完成了。
我们可以实现图形形状可选,所在位置可选,颜色可选等功能。
功能相对比较强大。
相应的程序如下:
intmain()
{
intc=0;
intx1=0,x2=0,y1=0,y2=0,r=0,m=0;
lcd_init();
while(c!
=9)
{
scanf("%d",&c);
switch(c)
{
case0:
lcd_clear();printf("Drawtheline:
");
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&m);
draw_line(x1,y1,x2,y2,m);break;
case1:
lcd_clear();printf("Drawtheline:
");
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&m);
draw_line(x1,y1,x2,y2,m);break;
case2:
lcd_clear();printf("Drawthecircle:
");
scanf("%d%d%d%d",&r,&x1,&y1,&m);
draw_circle(r,x1,y1,m);break;
case3:
lcd_clear();printf("Drawthesolid_circle:
");
scanf("%d%d%d%d",&r,&x1,&y1,&m);
draw_shi_circle(r,x1,y1,m);break;
case4:
lcd_clear();printf("Drawthezhenxianbo:
");
scanf("%d%d",&x1,&m);
draw_zhenxian(x1,m);break;
case5:
lcd_clear();printf("Drawthedodecagon:
");
scanf("%d%d%d%d",&x1,&y1,&r,&m);
draw_dodecagon(x1,y1,r,m);break;
default:
printf("Wrong\n");
}
}
}
(5)显示一幅图片
图片有jpg、bmp、tiff等格式,本实验中采用bmp格式的图片作为显示样本。
采用bmp格式图片的原因如下:
这种格式的图片包含的图像信