美文网首页
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