最近接触了一些关于Linux下framebuffer方面的东西和LCD的framebuffer驱动,所以去了解了一些相关基础知识。为了避免其他人走我走过的弯路,所以就把我的一些很少的心得,和大家分享一下,希望对有些人有帮助。
【什么是FrameBuffer】
FrameBuffer直译就是,帧缓冲。
Frame帧:你所看到的屏幕的图像,或者在一个窗口中的图像,就叫一帧。
Buffer缓冲:一段RAM,用来暂存图像数据,这些数据会被直接写入到显示设备。
帧缓冲就相当于介于 图形操作 和 图像输出中间的一个中间人。将程序对图形数据的处理操作,反馈到显示输出上。
显卡(显存中的数据) <-> 帧缓冲(程序对其中的数据进行处理) <-> 显示器(输出图像)
帧缓冲可用于,实现原先视频卡并不支持的分辨率。
显卡可能并不支持你当前某个更大分辨率的显示器,但是可以通过帧缓冲获取显卡的显存中的数据,处理之后,实现更大的分辨率的图像,然后将数据直接输出到显示器上。
【双显示器例子】
一个例子,可能就是双显示,最近刚刚看到实际某开发者的系统,就是两个显示器,鼠标移动超过单个显示器,到最右边的时候,就跑到另一个显示器了。对于常常用多系统或者需要打开很多东西的开发人员,这个功能很实用。
帧缓冲可以用于 页面交换page flipping(也常叫做 双缓冲double buffering),许多游戏都是采用此技术,以实现更流畅的视频输出,以便用户获得更好的游戏体验。此技术也被用于3D图形加速。
【双缓冲的主要实现原理】
假如你的显示器是VGA模式,640×400,也就是虚拟的分辨率是640X800,也就是800线(每一行的数据,称为一条线,也就是640X1的数据了)。800线的数据存储于Framebuffer,而实际的显示内容,只是400线,Linux内核中的Framebuffer模型中,对应有个变量yoffset,就是表示的这个具体的纵坐标,默认是0,所以显示的内容就是,0-399线,由于和实际显示 页面大小等同,所以此处可以简称为第一帧。如果yoffset改变了,比如此例中变为400,那就是显示剩余的部分,400-799线。此处简称为第二帧。
在系统显示第一帧的时候,系统在后台悄悄地准备第二帧的数据,所以,等第一帧显示完成,多数时候,第二帧的数据也准备好了,就可以直接显示,同时系统又在准备接下来的一帧的数据,这样就可以大大提高显示效率。
【平滑地滚动页面的实现原理】
同上,在显示完第一帧数据的时候,也就是0-399线的时候,将yoffset设置为1,就可以显示1-400线的数据了,显示完成后,再设置yoffset为2,就显示2-401线的数据,这样,就可以一点点地,平滑地显示整个滚动画面了。其实也就是画面在垂直方向的滚动。其中yoffset的增加,可以使用定时器,各个一段时间,比如10us,增加1,系统自动会更新显示对应的内容,这样我们所看到的内容就是滚动的画面了。
此外,Linux中的Framebuffer模型中,提供了一些ioctl功能,给定一些参数,然后系统可以实现对应的功能,其中有个参数就是FBIOPAN_DISPLAY。具体也就是类似如下调用:
ioctl (framebuffer_handler, FBIOPAN_DISPLAY, &variable_info);
而这个调用,如果显示不支持framebuffer的双缓冲的话,那么其framebuffer的缓冲大小,就是和物理上的显示器大小等同,那么对应的yoffset也就不会像双缓冲那样变化了。
也就是说,如果显卡/显示屏控制器不支持双缓冲,那么yoffset就应该一直为0,并且在运行时候,也不应该改变,也不应该去给FBIOPAN_DISPLAY的参数调用ioctl。
小tip:
【测试显示屏是否正常工作】
在加载了显示屏驱动后,不知道是否已经工作正常的话,
可以通过在某个文件夹下有稍微多些文件的地方,去ls,以便显示有出来东西,
然后通过
ls > /dev/fb0
将当前文件夹列表出来的一些数据,送到framebuffer的默认设备/dev/fb0,如果显示屏驱动已经正常加载,显示屏可以正常工作的话,那么你会在显示屏左上角看到一些乱码花屏一类的东西,这就是你刚刚ls送过去的数据。
发现其他教程上的命令,效果更好:
cat screenshot >/dev/fb0
即将当前截屏内容送给LCD显示。
【LCD液晶显示器的坐标轴】
左上角为(0,0),水平向左是X轴正向,垂直向下,是Y轴正向。即:
(0,0) X→
Y
↓
【写驱动前预先要了解的知识】
1. 软件方面:
要搞懂Linux的Framebuffer驱动的框架。
其中就包括熟悉Linux下,为了framebuffer专门实现的数据结构,尤其是
- struct fb_fix_screeninfo {
- char id[16]; /* identification string eg "TT Builtin" */
- unsigned long smem_start;/* Start of frame buffer mem(physical address)*/
- __u32 smem_len; /* Length of frame buffer mem */
- __u32 type; /* see FB_TYPE_* */
- __u32 type_aux; /* Interleave for interleaved Planes */
- __u32 visual; /* see FB_VISUAL_*/
- __u16 xpanstep; /* zero if no hardware panning */
- __u16 ypanstep; /* zero if no hardware panning */
- __u16 ywrapstep; /* zero if no hardware ywrap */
- __u32 line_length; /* length of a line in bytes */
- unsigned long mmio_start;/* Start of Memory Mapped I/O(physical address)*/
- __u32 mmio_len; /* Length of Memory Mapped I/O */
- __u32 accel; /* Type of acceleration available */
- __u16 reserved[3]; /* Reserved for future compatibility */
- };
和
- /* more kernel header files copied shamelessly */
- struct fb_bitfield {
- __u32 offset; /* beginning of bitfield */
- __u32 length; /* length of bitfield */
- __u32 msb_right;/* != 0 : Most significant bit is right */
- };
- struct fb_var_screeninfo {
- __u32 xres; /* visible resolution*/
- __u32 yres; __u32 xres_virtual;/* virtual resolution*/
- __u32 yres_virtual;
- __u32 xoffset;/* offset from virtual to visible */
- __u32 yoffset;/* resolution*/
- __u32 bits_per_pixel;/* guess what */
- __u32 grayscale; /* != 0 Graylevels instead of colors */
- struct fb_bitfield red;/* bitfield in fb mem if true color, */
- struct fb_bitfield green; /* else only length is significant */
- struct fb_bitfield blue;
- struct fb_bitfield transp; /* transparency*/
- __u32 nonstd; /* != 0 Non standard pixel format */
- __u32 activate;/* see FB_ACTIVATE_* */
- __u32 height;/* height of picture in mm */
- __u32 width;/* width of picture in mm */
- __u32 accel_flags;/* acceleration flags (hints) */
- /* Timing: All values in pixclocks, except pixclock (of course) */
- __u32 pixclock; /* pixel clock in ps (pico seconds) */
- __u32 left_margin; /* time from sync to picture */
- __u32 right_margin; /* time from picture to sync */
- __u32 upper_margin; /* time from sync to picture */
- __u32 lower_margin;
- __u32 hsync_len; /* length of horizontal sync */
- __u32 vsync_len; /* length of vertical sync */
- __u32 sync; /* see FB_SYNC_* */
- __u32 vmode; /* see FB_VMODE_* */
- __u32 reserved[6]; /* Reserved for future compatibility */
- };
此处简单描述一下,fb_fix_screeninfo对应的是物理设备的相关属性,包括映射后的显存的起始地址,长度,类型等。
fb_var_screeninfo包含了很多具体驱动运行时候的,framebuffer,即显示内容的具体属性,包括实际的显示器上可见的分辨率大小,xres和yres,Framebuffer的分辨率大小xres_virtual和 yres_virtual,如果是上面提到的双缓冲,那么这个分辨率就是可以显示的分辨率的两倍了。
xoffset 和yoffset就是上面提到的,如果是双缓冲,对应不同的应用,就会在运行时刻,改变对应的xoffset 或yoffset,以实现不同的显示效果。
其他一些成员,具体看注释就知道了。
关于Framebuffer的使用,详情请参考第二个链接,里面已经说得很好很详细了,我就没必要再一一翻译了。
2. 硬件方面
了解自己的开发板上的LCD显示屏的大小,具体支持的哪些特性,比如是否支持双缓冲等。
把这些相关信息,在驱动编写的时候,进行对应的赋值。
【如何写framebuffer的LCD驱动】
主要是实现Linux的Framebuffer框架下的,一些函数,最基本的一些就是,以Linux内核中的S3c2410举例,在s3c2410fb.c中,有个对应的结构体:
- static struct fb_ops s3c2410fb_ops = {
- .owner = THIS_MODULE,
- .fb_check_var = s3c2410fb_check_var,
- .fb_set_par = s3c2410fb_set_par,
- .fb_blank = s3c2410fb_blank,
- .fb_setcolreg = s3c2410fb_setcolreg,
- .fb_fillrect = cfb_fillrect,
- .fb_copyarea = cfb_copyarea,
- .fb_imageblit = cfb_imageblit,
- };
可以看到,这里,将自己实现的那些对framebuffer的操作函数,包括设置LCD的参数,剪切,拷贝,清空等,赋值给那个结构体变量,这样上层framebuffer框架中的函数调用的时候,就会调用你自己实现的那些函数了。
有些函数,如fb_pan_display等,如果你没有实现,那么系统就会调用上层框架中默认实现的函数来去做对应的操作。
上层的默认的函数实现在 drivers/video/fbmem.c中。相关具体的细节,感兴趣的自己去看吧。
关于fb驱动中的probe函数,在insmod对应驱动后执行,主要就是一些初始化,其中就包括了上面提到的几个全局变量,结构体的初始化,比如此例中的s3c2410fb_probe()中:
- fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
- fbinfo->fix.type_aux = 0;
- fbinfo->fix.xpanstep = 0;
- fbinfo->fix.ypanstep = 0;
- fbinfo->fix.ywrapstep = 0;
- fbinfo->fix.accel = FB_ACCEL_NONE;
- fbinfo->var.nonstd = 0;
- fbinfo->var.activate = FB_ACTIVATE_NOW;
- fbinfo->var.accel_flags = 0;
- fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
设置了fb和var的各自属性,是FB_ACTIVATE_NOW 立即激活类型,FB_VMODE_NONINTERLACED非交互模式(不支持双缓冲?)等等。
本人目前了解到的就是这么多,欢迎其他高手指出不妥之处和互相交流。
【参考资料】
【2】Console programming HOWTO - 7. framebuffer
【3】Linux Framebuffer Driver Writing HOWTO