美文网首页
Beaglebone Black开发实验——图形用户接口

Beaglebone Black开发实验——图形用户接口

作者: Neo_Zeng | 来源:发表于2019-12-13 01:45 被阅读0次

本次实验旨在熟悉VNC应用与FrameBuffer(帧缓冲)设备编程

本次报告由以下五部分内容组成:

  • 通过 sshserial 两种方式连接开发板,共享主机网络连接
  • 配置x11vnc,用VNCviewer连接开发板,显示图形化界面
  • FrameBuffer编程,实现画点、线、圆,移动圆和显示BMP文件等功能(这一部分不进行代码展示,所有代码都在第五部分)
  • 实验过程中遇到的问题心得体会
  • 源码展示(完整代码见附件,讨论动态库、 静态库、软件层次关系

1、通过USB端口/ SSH连接开发板,共享主机网络连接

开发板中预先安装了 Linux 3.8.13 Debian 系统

root用户密码默认为空
debian用户密码默认为temppwd

将开发版通过USB接口连入电脑之后,通过设备管理器查看端口为COM7

端口为COM7 于是我们可以在PuTTY上进行配置:选择Serial连接端口COM7Speed 115200 PuTTY配置 连接成功显示如下 COM7 - PuTTY
Xshell上通过ssh连接开发板,可以利用Xftp传输下载文件。在Xshell上进行配置:IP 192.168.7.2用户名:debian密码:temppwd,连接成功如下: ssh连接开发板 Xftp上传下载文件
配置usb0共享主机网络连接,连接成功后ping 百度,可以ping通,表示成功接入互联网 ping www.baidu.com

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 &
截图如下:

命令截图 配置VNC viewer 连接开发板 ip:192.168.7.2 端口号:5900
截图如下: VNC viewer连接远程虚拟桌面

3、FrameBuffer 编程,实现画圆等若干功能

帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。帧缓冲设备对应的设备文件为/dev/fb*,嵌入式系统一般为/dev/fb。在应用程序中,操作/dev/fb0的一般步骤如下:

  1. 打开/dev/fb设备文件
  2. 用ioctl函数获取显示屏的位深、分辨率等信息,两个重要的结构体类型是
    fb_fix_screeninfo 和fb_var_screeninfo ;
  3. 用mmap函数将FrameBuffer映射到用户空间;
  4. 应用程序读写FrameBuffer,进行绘图和图片显示等;
  5. 解除映射,关闭/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三色图

显示RGB图片 显示一张BMP测试图
任意显示一张BMP测试图片
2)画点、线、矩形、圆

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

画点、线、矩形、实心矩形

画点、线、矩形 画点、线、矩形 画一个实心圆与显示英文字符串(这里显示字符串应用的是老师提供的lcd.c里printlcd函数) 画实心圆
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)动态库, 静态库, 软件层次关系

我们通常把一些公用函数制作成函数库,供其他程序使用,从而优化整体的软件结构,使其更为合理与整洁。函数库分为静态库和动态库两种。

  1. 静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库。
  2. 动态库在程序编译时并不会被链接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

静态库和动态库各自都有优缺点,此处不详细说明,本次实验选择使用静态库。
代码源文件包括:(fb_showbmp是单独用于展示bmp文件,lcd则是画图)

  1. lcd.h fb_showbmp.h 函数库的头文件
  2. lcd.c fb_showbmp.c 函数库的源程序,包括所有公共函数
  3. 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;
    }

相关文章

网友评论

      本文标题:Beaglebone Black开发实验——图形用户接口

      本文链接:https://www.haomeiwen.com/subject/mcotnctx.html