本次实验旨在熟悉VNC应用与FrameBuffer(帧缓冲)设备编程
本次报告由以下五部分内容组成:
- 通过 ssh 和 serial 两种方式连接开发板,共享主机网络连接
- 配置x11vnc,用VNCviewer连接开发板,显示图形化界面
- FrameBuffer编程,实现画点、线、圆,移动圆和显示BMP文件等功能(这一部分不进行代码展示,所有代码都在第五部分)
- 实验过程中遇到的问题和心得体会
- 源码展示(完整代码见附件),讨论动态库、 静态库、软件层次关系
1、通过USB端口/ SSH连接开发板,共享主机网络连接
开发板中预先安装了 Linux 3.8.13 Debian 系统
root用户密码默认为空
debian用户密码默认为temppwd
将开发版通过USB接口连入电脑之后,通过设备管理器查看端口为COM7。



Xshell上通过ssh连接开发板,可以利用Xftp传输下载文件。在Xshell上进行配置:IP 192.168.7.2,用户名:debian,密码:temppwd,连接成功如下:


配置usb0共享主机网络连接,连接成功后ping 百度,可以ping通,表示成功接入互联网

2、配置x11vnc,通过VNC viewer连接开发板
Beaglebone Black开发板已经预先安装了gnome(or xfce?)桌面系统,我们可以在BBB开发板通过x11vnc命令创建远程虚拟桌面,主机上用VNC viewer实现远程连接(关于 x11vnc 和 vncserver 的区别,我将在第四部分问题与体会中详细说明)。
将实验室主机 /srv/nfs4 文件夹挂载到开发板 /mnt 下,在内网通过ssh连接主机scp拷贝x11vnc命令到开发板上。使用以下命令创建远程虚拟桌面:
~$ ./x11vnc -rawfb /dev/fb0 -forever &
截图如下:

截图如下:

3、FrameBuffer 编程,实现画圆等若干功能
帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备对应的设备文件为/dev/fb*,嵌入式系统一般为/dev/fb。在应用程序中,操作/dev/fb0的一般步骤如下:
- 打开/dev/fb设备文件
- 用ioctl函数获取显示屏的位深、分辨率等信息,两个重要的结构体类型是
fb_fix_screeninfo 和fb_var_screeninfo ;- 用mmap函数将FrameBuffer映射到用户空间;
- 应用程序读写FrameBuffer,进行绘图和图片显示等;
- 解除映射,关闭/dev/fb 设备文件。
相应的关键语句如下:
“fd = open ( "/dev/fb", O_RDWR ); ”
“ioctl(fd, FBIOGET_FSCREENINFO, &finfo);”
“ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);”
“screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; ”
”fbp =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);”
“munmap(fbp,screensize);”
可以通过~$ dd if=/dev/zero of=/dev/fb0
来实现清屏。
1)显示BMP格式文件
BMP文件又称为Bitmap(位图)是广泛使用的图像文件,编码格式简单,解析也较容易。在Linux FrameBuffer 的基础上,通过编程来显示图片。文件格式为:位图文件头+位图信息头(+颜色表)+位图数据。主要步骤为:读取文件,判断是否满足BMP文件格式要求--->转化为16位RGB--->写到FrameBuffer 上。效果如下:
显示一张RGB三色图


2)画点、线、矩形、圆
显示器的坐标系方向一般如下图所示,所画点的坐标(x,y)与相对于FrameBuffer首地址的偏移量offset一一对应,计算公式为offset=(y*vinfo.xres+x)*vinfo.bits_per_pixel/8
。画线和画圆部分,应用了Bresenham直线算法与画圆算法。
画点、线、矩形、实心矩形



3)画动态圆
实际是屏幕刷新与右移圆心画圆同时进行的过程。这里应当放一个视频,证明是动的,但我只记得截了图……
画两个实心动态圆


4、遇到的问题与心得体会
1)x11vnc与vncserver
在起初的vnc实验中,我进展的并不顺利。我在开发板上~$ sudo apt-get vncserver
安装了vncserver之后,通过命令~$ vncserver
创建远程虚拟桌面,主机vncviewer实现远程连接虚拟桌面,我发现可以进行图形桌面的正常操作,比如打开文件、浏览器访问网页,就是一个普通的远程桌面。但是本身并不支持/dev/fb设备的,以致任何利用FrameBuffer编程的画点、线、圆,显示图片的功能都实现不了。一定要使用x11vnc。
x11vnc和vncserver的区别在于,x11vnc只是将已有的X11桌面通过vnc传送到远程,而不是使用虚拟的vnc服务。所以x11vnc显示的只是桌面图片,并不能进行任何操作,因而支持FrameBuffer应用。
2)关于动态圆
本次实验中动态圆实现使用如下方法:
画圆 -> sleep间隔 -> 屏幕刷新 -> 圆心位置横坐标+1画圆 -> sleep间隔 -> 屏幕刷新 -> ……
这个方法的问题在于清屏和右移画圆两者是同时进行的,会出现闪频的问题。空心圆因为像素点比较少,整体移动效果还显得比较流畅。但实心圆的话因为像素点很多,闪频现象严重,也就是一个实心圆还没有显示完全屏幕就开始刷新往右画圆,实心圆移动很不流畅。为了实现动态圆的效果,屏幕的刷新是必要的但是不应该和画图处于同一频率,屏幕刷新或许可以放到一个单独线程中,这样多像素点图形移动不会因为闪频而不流畅。
3)心得体会
本次实验主要是学习嵌入式系统(BBB开发板)图形界面的基本编程方法,在FrameBuffer基础上实现简单的画图和显示,关于图形用户接口有很多内容值得学习,但因为时间、精力所做的事也相对有限,但收获还是有的,至少对GUI相关有一个初步认识。关于字符串和汉字的显示,经查询有两种方法字模打点和用矢量字体计算。这一部分我参考了lcd.c里printlcd函数,没有做进一步改进。但是完整的图形用户接口应该包括字符与汉字,所以这一部分值得更多学习。
5、源码展示与说明
1)动态库, 静态库, 软件层次关系
我们通常把一些公用函数制作成函数库,供其他程序使用,从而优化整体的软件结构,使其更为合理与整洁。函数库分为静态库和动态库两种。
- 静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库。
- 动态库在程序编译时并不会被链接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
静态库和动态库各自都有优缺点,此处不详细说明,本次实验选择使用静态库。
代码源文件包括:(fb_showbmp是单独用于展示bmp文件,lcd则是画图)
- lcd.h fb_showbmp.h 函数库的头文件
- lcd.c fb_showbmp.c 函数库的源程序,包括所有公共函数
- main.c 测试库文件的主程序,调用了若干公干函数实现绘图功能
由arm-linux-gnueabihf-gcc -c lcd.c
创建lcd.o文件,由.o文件创建静态库,静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为lcd,则静态库文件名就是liblcd.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。在系统提示符下键入以下命令将创建静态库文件:~$ ar cr liblcd.a lcd.o
在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用arm-linux-gnueabihf-gcc命令生成目标文件时指明静态库名,arm-linux-gnueabihf-gcc将会从静态库中将公用函数连接到目标文件中。注意,arm-linux-gnueabihf-gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件:arm-linux-gnueabihf-gcc -o lcd main.c -L. -llcd
2)源码展示
1. 画点、线、圆
画点
void draw_dot(PFBDEV pFbdev,POINT p,uint8_t r,uint8_t g,uint8_t b)
{
uint32_t offset;
uint8_t color[4];
color[0] = b;
color[1] = g;
color[2] = r;
color[3] = 0x0; //透明度
offset = p.y * pFbdev->fb_fix.line_length + 4 * p.x;
//将操作映射到内存中
fb_memcpy((void*)pFbdev->fb_mem + pFbdev->fb_mem_offset + offset,color,4);
}
画线(画横线将xy对换)
//画竖线
void draw_v_line(PFBDEV pFbdev,POINT minY,POINT maxY,uint8_t r,uint8_t g,uint8_t b)
{
int m;
int length = maxY.y - minY.y;
for(m = 0;m < length;m++){
POINT tp;
tp.x = minY.x;
tp.y = minY.y + m;
draw_dot(pFbdev,tp,r,g,b);
}
}
画圆
draw_x_y_dot_with_trans函数是对draw_dot函数的一点改进,加入了可变坐标值和RGB值。被注释掉的部分是画实心圆,保留的是空心圆。
void _draw_circle_8(PFBDEV pFbdev, int xc, int yc, int x, int y,const char *color)
{
draw_x_y_dot_with_trans(pFbdev,xc + y, yc + y,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc + x, yc - y,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc + x, yc - y,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc - x, yc - y,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc + y, yc + x,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc - y, yc + x,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc + y, yc - x,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
draw_x_y_dot_with_trans(pFbdev,xc - y, yc - x,rgbt.r,rgbt.g,rgbt.b,rgbt.t);
}
void draw_circle(PFBDEV pFbdev,int xc,int yc,int r,const char *color)
{
printf("Drawing a circle. radius=%d\n", r);
if (xc + r < 0 || xc - r >= 800 || yc + r < 0 || yc - r >= 480)
return;
int x = 0, y = r, yi, d;
d = 3 - 2 * r;
while (x <= y) {
_draw_circle_8(xc, yc, x, yi);
if (d < 0) {
d = d + 4 * x + 6;
} else {
d = d + 4 * (x - y) + 10;
y--;
}
x++;
/* for (yi = x; yi <= y; yi++)
_draw_circle_8(xc, yc, x+yi, y);
if (d < 0) {
d = d + 4 * x + 6;
} else {
d = d + 4 * (x - y) + 10;
y--;
}
x++;*/
}
}
2. 画动态圆
画圆之后 -> usleep间隔然后屏幕刷新 -> 右移圆心画圆
int x = 200;
while(1){
draw_circle(&fbdev,x,400,100,RED);
draw_circle(&fbdev,x+250,400,100,RED);
usleep(100);
x++;
if(x == 768)
break;
clear_con(fbdev.fb_mem + fbdev.fb_mem_offset,1,fbdev.fb_fix.smem_len);
}
3. 展示bmp图片
更好的方法是利用画点函数,参考教学资源lcd.c里的display_bmp函数即可。
可以试试清屏后使用命令cat test.bmp > /dev/fb0
展示bmp图像。
显示屏设备节点认为是/dev/fb0,通过显示屏分辨率比如 800x480, 那么bpp为32, 可以直接计算一行的数据长度为: 800 * (32 / 8) 个字节, 同样可以使用struce fb_fix_screeninfo 中的 line_length 字段决定一行数据长度。
处理一行要显示的数据
/*处理一行要显示的数据*/
while(1) {
if (img_info.bpp == 32)
pix.reserved = *(src--);
pix.red = *(src--);
pix.green = *(src--);
pix.blue = *(src--);
val = 0x00;
val |= (pix.red >> (8 - fb_info.red.length)) << fb_info.red.offset;
val |= (pix.green >> (8 - fb_info.green.length)) << fb_info.green.offset;
val |= (pix.blue >> (8 - fb_info.blue.length)) << fb_info.blue.offset;
if (fb_info.bpp == 16) {
*(dst++) = *(p + 0);
*(dst++) = *(p + 1);
}
else if (fb_info.bpp == 24) {
*(dst++) = *(p + 0);
*(dst++) = *(p + 1);
*(dst++) = *(p + 2);
}
else if (fb_info.bpp == 32) {
*(dst++) = *(p + 0);
*(dst++) = *(p + 1);
*(dst++) = *(p + 2);
*(dst++) = *(p + 3);
}
/*超过图片长度或显示屏长度认为一行处理完了*/
img_len--;
fb_len--;
if (img_len <= 0 || fb_len <= 0)
break;
}
检测是否是bmp图像
//检测是否是bmp图像
if (memcmp(FileHead.cfType, "BM", 2) != 0) {
printf("it's not a BMP file[%c%c]\n", FileHead.cfType[0], FileHead.cfType[1]);
ret = -1;
goto err_showbmp;
}
ret = fread( (char *)&InfoHead, sizeof(BITMAPINFOHEADER),1, fp );
if ( ret != 1) {
printf("read infoheader error!\n");
ret = -1;
goto err_showbmp;
}
img_info.width = InfoHead.ciWidth;
img_info.height = InfoHead.ciHeight;
img_info.bpp = InfoHead.ciBitCount;
img_info.size = FileHead.cfSize;
img_info.data_offset = FileHead.cfoffBits;
printf("img info w[%d] h[%d] bpp[%d] size[%ld] offset[%d]\n", img_info.width, img_info.height, img_info.bpp, img_info.size, img_info.data_offset);
if (img_info.bpp != 24 && img_info.bpp != 32) {
printf("img bpp is not 24 or 32\n");
ret = -1;
goto err_showbmp;
}
一行一行处理
char *buf_img_one_line;
char *buf_fb_one_line;
char *p;
int fb_height;
long img_len_one_line = img_info.width * (img_info.bpp / 8);
long fb_len_one_line = fb_info.line_length;
printf("img_len_one_line = %ld\n", img_len_one_line);
printf("fb_len_one_line = %ld\n", fb_info.line_length);
buf_img_one_line = (char *)calloc(1, img_len_one_line + 256);
if(buf_img_one_line == NULL) {
printf("alloc failed\n");
ret = -1;
goto err_showbmp;
}
buf_fb_one_line = (char *)calloc(1, fb_len_one_line + 256);
if(buf_fb_one_line == NULL) {
printf("alloc failed\n");
ret = -1;
goto err_showbmp;
}
fseek(fp, img_info.data_offset, SEEK_SET);
p = fb_info.fbp + fb_info.yoffset * fb_info.line_length; /*进行y轴的偏移*/
fb_height = fb_info.yres;
while (1) {
memset(buf_img_one_line, 0, img_len_one_line);
memset(buf_fb_one_line, 0, fb_len_one_line);
ret = fread(buf_img_one_line, 1, img_len_one_line, fp);
if (ret < img_len_one_line) {
/*图片读取完成,则图片显示完成*/
printf("read to end of img file\n");
cursor_bitmap_format_convert(buf_fb_one_line, buf_img_one_line, img_len_one_line); /*数据转换*/
memcpy(fb_info.fbp, buf_fb_one_line, fb_len_one_line);
break;
}
cursor_bitmap_format_convert(buf_fb_one_line, buf_img_one_line, img_len_one_line); /*数据转换*/
memcpy(p, buf_fb_one_line, fb_len_one_line); /*显示一行*/
p += fb_len_one_line;
/*超过显示屏宽度认为图片显示完成*/
fb_height--;
if (fb_height <= 0)
break;
}
网友评论