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的一般步骤如下:
- 打开/dev/fb设备文件。
- 用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
- 将屏幕缓冲区映射到用户空间。
- 映射后就可以直接读写屏幕缓冲区,进行绘图和图片显示了。
在屏幕上画一条线:
#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的理解和使用的主要内容,如果未能解决你的问题,请参考以下文章