NanoPi NEO Air使用十四:FrameBuffer的理解和使用

Posted 【ql君】qlexcel

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NanoPi NEO Air使用十四:FrameBuffer的理解和使用相关的知识,希望对你有一定的参考价值。

FrameBuffer的介绍

应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显存是需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。

为了解决上述问题, Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb,因此大家在以后的 Linux 学习中见到“Framebuffer”或者“fb”的话第一反应应该想到 RGBLCD或者显示设备。 fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通过访问/dev/fbX 这个设备就可以访问 LCD。 /dev/fb0 是个字符设备,因此肯定有file_operations 操作集, fb 的 file_operations 操作集定义在 drivers/video/fbdev/core/fbmem.c 文件中,如下所示:

static const struct file_operations fb_fops = 
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \\
	(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \\
	 !defined(CONFIG_MMU))
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
;

Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。

FrameBuffer在Linux中的实现和机制

Framebuffer设备驱动程序架构如下图,用户空间应用程序主要通过read、write、mmap、ioctl这四个接口与Framebuffer设备打交道。但是一般都是采用mmap方式直接操作内存,所以下文也主要介绍mmap方式。

Framebuffer主要结构体

用户空间可见的FrameBuffer设备的主要结构体几乎都是在include/uapi/linux/fb.h这个中文件定义的。这些结构包括:fb_var_screeninfo和fb_fix_screeninfon

fb_var_screeninfo
主要是记录用户可以修改的控制器可变参数:

struct fb_var_screeninfo    
    /*可见解析度*/  
    __u32 xres;            
    __u32 yres;   
    /*虚拟解析度*/  
    __u32 xres_virtual;        
    __u32 yres_virtual;   
    /*虚拟到可见之间的偏移*/  
    __u32 xoffset;             
    __u32 yoffset;             
    __u32 bits_per_pixel;       /*每像素的位数,BPP*/  
    __u32 grayscale;        /*非0时指灰度*/  
    /*fb缓存的R\\G\\B位域*/  
    struct fb_bitfield red;        
    struct fb_bitfield green;      
    struct fb_bitfield blue;   
    struct fb_bitfield transp;  /*透明度*/    
    __u32 nonstd;           /*!=0非标准像素格式*/  
    __u32 activate;            
    __u32 height;           /*高度*/  
    __u32 width;            /*宽度*/  
    __u32 accel_flags;         
    /*除pixclock本身外,其他都以像素时钟为单位*/  
    __u32 pixclock;         /*像素时钟(皮秒)*/  
    __u32 left_margin;      /*行切换:从同步到绘图之间的延迟*/  
    __u32 right_margin;     /*行切换:从绘图到同步之间的延迟*/  
    __u32 upper_margin;     /*帧切换:从同步到绘图之间的延迟*/  
    __u32 lower_margin;     /*帧切换:从绘图到同步之间的延迟*/  
    __u32 hsync_len;        /*水平同步的长度*/  
    __u32 vsync_len;        /*垂直同步的长度*/  
    __u32 sync;            
    __u32 vmode;               
    __u32 rotate;           /*顺时针旋转的角度*/  
    __u32 reserved[5];      /*保留*/  
;  

fb_fix_screeninfon
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。

struct fb_fix_screeninfo    
    char id[16];            /*字符串形式的标识符*/  
    unsigned long smem_start;   /*fb缓冲内存的开始位置(物理地址)*/  
    __u32 smem_len;         /*fb缓冲的长度*/  
    __u32 type;              /*FB_TYPE_* */  
    __u32 type_aux;         /*Interleave*/  
    __u32 visual;           /*FB_VISUAL_* */    
    __u16 xpanstep;         /*如果没有硬件panning,赋0*/  
    __u16 ypanstep;            
    __u16 ywrapstep;           
    __u32 line_length;      /*一行的字节数*/  
    unsigned long mmio_start;   /*内存映射I/O的开始位置*/  
    __u32 mmio_len;         /*内存映射I/O的长度*/  
    __u32 accel;               
    __u16 reserved[3];      /*保留*/  
;  

fb_var_screeninfo与fb_fix_screeninfo的关系见下图:

在framebuffer驱动框架中相关的重要数据结构:

struct fb_ops 
    struct module *owner;
    //检查可变参数并进行设置
    int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
    //根据设置的值进行更新,使之有效
    int (*fb_set_par)(struct fb_info *info);
    //设置颜色寄存器
    int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
                unsigned blue, unsigned transp, struct fb_info *info);
    //显示空白
    int (*fb_blank)(int blank, struct fb_info *info);
    //矩形填充
    void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
    //复制数据
    void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
    //图形填充
    void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
;

从设备驱动程序结构看,这个驱动主要跟fb_info结构体有相应的关系,这个结构体记录了这个设备驱动的全部相关的信息,
其中就包括设备的设置参数,状态,还有对应底层硬件操作的回调函数。在Linux中,每一个帧缓冲设备都必须对应一个fb_info,fb_info在/linux/fb.h中。

上面的fp_ops这个结构体就是常用的非常重要的一个。和其它的内核代码中的字符驱动类似,同样,如果你要使用这个驱动,你必须去注册这个设备驱动。打开linux3.5的内核代码,显示驱动的分析都是由 drivers/video/fbmem.c开始 。我们可以查阅到fbmem.c里定义 register_framebuffer这个函数。通过 查看这个函数,我们可以发现其实这个函数在drivers/video/s3c-fb.c中的probe函数中调用了 register_framebuffer,在s3c-fb.c这个文件注册了一个平台总线设备的驱动程序。这里要注意了,所谓的平台总线驱动其实是由linux内核本身去实现的,是一种总线驱动的机制。
以下是fb_info结构体:

struct fb_info 
 int node;
 struct fb_var_screeninfo var; /* Current var */
 struct fb_fix_screeninfo fix;  /* Current fix */
 struct fb_videomode *mode; /* current mode */

 struct fb_ops *fbops;
 struct device *device;   /* This is the parent */
 struct device *dev;   /* This is this fb device */

 char __iomem *screen_base; /* Virtual address */
 unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ 
 …………
;

FrameBuffer的应用

帧缓冲设备对应的设备文件为 /dev/fb*,如果系统有多个显示卡,Linux下还可支持多个帧缓冲设备,最多可达32 个,分别为/dev/fb0到/dev/fb31,而/dev/fb则为当前缺省的帧缓冲设备,通常指向/dev/fb0。当然在嵌入式系统中支持一个显 示设备就够了。帧缓冲设备为标准字符设备,主设备号为29,次设备号则从0到31。分别对应/dev/fb0-/dev/fb31。通过/dev/fb, 应用程序的操作主要有这几种:

1、读/写(read/write)/dev/fb:相当于读/写屏幕缓冲区。
例如用 cp /dev/fb0 tmp命令可将当前屏幕的内容拷贝到一个文件中,而命令cp tmp > /dev/fb0 则将图形文件tmp显示在屏幕上。

2、映射(map)操作:由于Linux工作在保护模式,每个应用程序都有自己的虚拟地址空间,在应用程序中是不能直接访问物理缓冲区地址的。为此, Linux在文件操作 file_operations结构中提供了mmap函数,可将文件的内容映射到用户空间。对于帧缓冲设备,则可通过映射操作,可将屏幕缓冲区的物理地址 映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区,在屏幕上绘图了。实际上,使用帧缓冲设备的应用程序都是通过映射操 作来显示图形的。由于映射操作都是由内核来完成,下面我们将看到,帧缓冲驱动留给开发人员的工作并不多。

3、 I/O控制:对于帧缓冲设备,对设备文件的ioctl操作可读取/设置显示设备及屏幕的参数,如分辨率,显示颜色数,屏幕大小等等。ioctl的操作是由底层的驱动程序来完成的。
在应用程序中,操作/dev/fb的一般步骤如下:

  1. 打开/dev/fb设备文件。
  2. 用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
  3. 将屏幕缓冲区映射到用户空间。
  4. 映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。

在屏幕上画一条线:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

int main () 

    int fp=0;
    struct fb_var_screeninfo  vinfo;
    struct fb_fix_screeninfo  finfo;
    long screensize=0;
    char *fbp = 0;
    long location = 0;

    fp = open ("/dev/fb0",O_RDWR);   // 打开/dev/fb设备文件

    if (fp < 0)
    
        printf("Error : Can not open framebuffer device/n");
        exit(1);
    

    if (ioctl(fp,FBIOGET_FSCREENINFO,&finfo))  // fb_fix_screeninfo
    
        printf("Error reading fixed information/n");
        exit(2);
    

    if (ioctl(fp,FBIOGET_VSCREENINFO,&vinfo))
    
        printf("Error reading variable information/n");
        exit(3);
    
    //显示结构体信息
    printf("The mem is :%d\\n",finfo.smem_len);  
    printf("The line_length is :%d\\n",finfo.line_length);  
    printf("The xres is :%d\\n",vinfo.xres);  
    printf("The yres is :%d\\n",vinfo.yres);  
    printf("bits_per_pixel is :%d\\n",vinfo.bits_per_pixel);  

    //计算显存大小
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
    /*这就是把fp所指的文件中从开始到screensize大小的内容给映射出来,得到一个指向这块空间的指针*/
    fbp =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp,0);
    if ((int) fbp == -1)
    
        printf ("Error: failed to map framebuffer device to memory./n");
        exit (4);
    

    /*从(0,0)到(100,100)画线,(0,0)点在屏幕左上角*/
    int i,j;
    for(i=0,j=0;i<100;i++,j++)
    
        location = i * (vinfo.bits_per_pixel / 8) + j  *  finfo.line_length;

        *(fbp + location) = 0;              /*直接赋值来改变屏幕上某点的颜色*/
        *(fbp + location + 1) = 0xf8;       /* RGB565格式,全红=0xf800 */
    

    munmap (fbp, screensize); /*解除映射*/
    close (fp);    /*关闭文件*/

    return 0;


    //头文件 <sys/mman.h>
    //函数原型:void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
    //start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
    //length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理
    //prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
    //flags:相关的标志,就跟open函数的标志差不多的,自己百度去查
    //fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
    //off_toffset:被映射对象内容的起点。

    //PROT_READ //页内容可以被读取
    //PROT_WRITE //页可以被写入
    //MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

编译
arm-linux-gcc fb_test.c -o fb_test
编译后上传scp fb_test root@192.168.0.101:/lib/modules/4.14.111/
执行

显示效果:

参考:
2016/1/9:深度剖析安卓Framebuffer设备驱动
【原创】IP摄像头技术纵览(三)—图像数据在帧缓存设备(framebuffer)上的显示

以上是关于NanoPi NEO Air使用十四:FrameBuffer的理解和使用的主要内容,如果未能解决你的问题,请参考以下文章

NanoPi NEO Air使用二:固件烧录

NanoPi NEO Air使用一:介绍

NanoPi NEO Air使用四:操作GPIO

NanoPi NEO Air使用十:自己编写驱动来控制LED

NanoPi NEO Air使用六:使用摄像头

NanoPi NEO Air使用十三:移植RTL8723BU驱动