18. 屏幕显示(framebuffer)

18.1. LubanCat-RK系列板卡

该教程适用于LubanCat-RK系列板卡, 可以接入野火mipi屏以及HDMI屏幕进行使用。

本章的示例代码目录为:base_linux/screen/framebuffer

18.2. framebuffer介绍

FrameBuffer中文译名为帧缓冲驱动,它是出现在2.2.xx内核中的一种驱动程序接口。 主设备号为29,次设备号递增。

Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。 FrameBuffer机制模仿显卡的功能,将显卡硬件结构抽象掉, 可以通过FrameBuffer的读写直接对显存进行操作。 用户可以将FrameBuffer看成是显示内存的一个映像, 将其映射到进程地址空间之后,就可以直接进行读写操作, 而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。

用户不必关心物理显存的位置、换页机制等等具体细节, 这些都是由FrameBuffer设备驱动来完成的。

FrameBuffer实际上就是嵌入式系统中专门为GPU所保留的一块连续的物理内存, LCD通过专门的总线从framebuffer读取数据,显示到屏幕上。

FrameBuffer本质上是一块显示缓存, 往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。 所以说FrameBuffer就是一块白板。

屏幕位置从上到下,从左至右与内存地址是顺序的线性关系

未找到图片

屏幕除了RGB888的格式外,还具有其他的格式

1
2
3
4
ARGB888:|AAAAAAAAA|RRRRRRRRR|GGGGGGGG|BBBBBBBB|
RGB888: |         |RRRRRRRRR|GGGGGGGG|BBBBBBBB|
RGB565: |RRRRRRGGG|GGGBBBBB|
RGB555: | RRRRRGGG|GGGBBBBB|

18.3. framebuffer应用程序

前言:

注意

如果你使用的是带桌面版的镜像,在使用framebuffer前要注意先把图形界面关闭, 不然会出现触摸后屏幕不断闪烁的情况

1
2
3
4
5
6
7
8
9
#关闭用户图形界面

sudo systemctl set-default multi-user.target
sudo reboot

#开启用户图形界面

sudo systemctl set-default graphical.target
sudo reboot
base_linux/screen/framebuffer/framebuffer.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <sys/types.h>		//open需要的头文件
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>	        //write
#include <sys/types.h>
#include <sys/mman.h>		//mmap  内存映射相关函数库
#include <stdlib.h>	        //malloc free 动态内存申请和释放函数头文件
#include <string.h> 	
#include <linux/fb.h>
#include <sys/ioctl.h>

//32位的颜色
#define Black 	0x00000000
#define White 	0xffFFFFFF
#define Red 	0xffFF0000
#define Green 	0xff00ff00
#define Blue 	0xff99ffff

int fd;
unsigned int *fb_mem  = NULL;	//设置显存的位数为32位
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;

int main(void)
{
	unsigned int i;
	int ret;

	/*--------------第一步--------------*/
	fd = open("/dev/fb0",O_RDWR);			//打开framebuffer设备
	if(fd == -1){
		perror("Open LCD");
		return -1;
	}
	/*--------------第二步--------------*/
 
 	//获取屏幕的可变参数
	ioctl(fd, FBIOGET_VSCREENINFO, &var);
	//获取屏幕的固定参数
	ioctl(fd, FBIOGET_FSCREENINFO, &fix);
   
  	//打印分辨率
	printf("xres= %d,yres= %d \n",var.xres,var.yres);
 	//打印总字节数和每行的长度
	printf("line_length=%d,smem_len= %d \n",fix.line_length,fix.smem_len);
	printf("xpanstep=%d,ypanstep= %d \n",fix.xpanstep,fix.ypanstep);
 
	/*--------------第三步--------------*/
	
  fb_mem = (unsigned int *)mmap(NULL, var.xres*var.yres*4, 		//获取显存,映射内存
			PROT_READ |  PROT_WRITE, MAP_SHARED, fd, 0);   
								  
	if(fb_mem == MAP_FAILED){
		perror("Mmap LCD");
		return -1;	
	}

	memset(fb_mem,0xff,var.xres*var.yres*4);		//清屏
	sleep(1);
	/*--------------第四步--------------*/
	//将屏幕全部设置成蓝色
	for(i=0;i< var.xres*var.yres ;i++)
		fb_mem[i] = Blue;
	sleep(2);
	memset(fb_mem,0x00,var.xres*var.yres*4);		//清屏
	
	munmap(fb_mem,var.xres*var.yres*4); //映射后的地址,通过mmap返回的值	
	close(fd); 			//关闭fb0设备文件
	return 0;			
}

编译运行

1
2
3
4
5
6
7
8
gcc framebuffer.c -o framebuffer
./framebuffer

#运行结果
#打印屏幕信息
#屏幕会先变白
#等待1s
#屏幕变蓝

18.3.1. 代码分析

操作屏幕一共需要四步就可以操作整个屏幕

第一步
1
2
3
4
5
6
	/*--------------第一步--------------*/
	fd = open("/dev/fb0",O_RDWR);			//打开framebuffer设备
	if(fd == -1){
		perror("Open LCD");
		return -1;
	}
  • 这一步是打开framebuffer设备,这一步成功后会返回文件描述符– fd, 我们后面可以操作这个文件描述符操作该设备。

第二步
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	/*--------------第二步--------------*/
 
 	//获取屏幕的可变参数
	ioctl(fd, FBIOGET_VSCREENINFO, &var);
	//获取屏幕的固定参数
	ioctl(fd, FBIOGET_FSCREENINFO, &fix);
   
  	//打印分辨率
	printf("xres= %d,yres= %d \n",var.xres,var.yres);
 	//打印总字节数和每行的长度
	printf("line_length=%d,smem_len= %d \n",fix.line_length,fix.smem_len);
	printf("xpanstep=%d,ypanstep= %d \n",fix.xpanstep,fix.ypanstep);
  • 这一步获取屏幕的参数以及设置屏幕

  • 获取屏幕的可变参数 ioctl(fd, FBIOGET_VSCREENINFO, &var);

  • 获取屏幕的固定参数 ioctl(fd, FBIOGET_FSCREENINFO, &fix);

  • 这两个目的是获取fb_var_screeninfo,fb_fix_screeninfo结构体

  • 我们看一下结构体所包含的内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
 //固定参数
 struct fb_fix_screeninfo {
     char id[16];                        /* identification string eg "TT Builtin" */
     unsigned long smem_start;       /* mmap后内存的首地址*/
                                     /* (physical address) */
     __u32 smem_len;                     /* framebuffer mmap的最大长度*/
     __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;                  /* 每行像素的长度  */
     unsigned long mmio_start;       /* Start of Memory Mapped I/O   */
                                                 /* (physical address) */
     __u32 mmio_len;                     /* Length of Memory Mapped I/O  */
     __u32 accel;                        /* Indicate to driver which */
                                     /*  specific chip/card we have  */
     __u16 capabilities;                 /* see FB_CAP_*                     */
     __u16 reserved[2];                  /* Reserved for future compatibility */
 };

 //可变参数
 struct fb_var_screeninfo {
         __u32 xres;                     /* x轴的可视区域--x轴实际像素*/
         __u32 yres;             /* y轴的可视区域--y轴实际像素*/
         __u32 xres_virtual;         /* x轴的虚拟区域,可用于双缓冲设计*/
         __u32 yres_virtual;     /* y轴的虚拟区域,可用于双缓冲设计*/
         __u32 xoffset;                      /* x轴偏移量-默认为零,可用于双缓冲设计*/
         __u32 yoffset;                      /* x轴偏移量-默认为零,可用于双缓冲设计*/

         __u32 bits_per_pixel;               /* 每个像素占用的字节*/
         __u32 grayscale;            /* 0 = color, 1 = grayscale,>1 = FOURCC*/

         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;          /* (OBSOLETE) see fb_info.flags */

         /* 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 rotate;                       /* angle we rotate counter clockwise */
         __u32 colorspace;           /* colorspace for FOURCC-based modes */
         __u32 reserved[4];          /* Reserved for future compatibility */
 };
  • 更多详细的内容可以阅读内核源码以及查看手册位置kernel/Documentation/fb/api.txt

api.txt

  • ioctl :在framebuffer里除了 FBIOGET_VSCREENINFO, FBIOGET_FSCREENINFO 还有其他的选项进行配置

framebuffer ioctl选项

选项名

功能

用法

FBIOGET_VSCREENINFO

ioctl(fd, FBIOGET_VSCREENINFO, &var);

获取屏幕可变参数

FBIOPUT_VSCREENINFO

ioctl(fd, FBIOPUT_VSCREENINFO, &var);

修改屏幕的可变参数

FBIOGET_FSCREENINFO

ioctl(fd, FBIOGET_FSCREENINFO, &fix);

获取屏幕的固定参数

FBIOPAN_DISPLAY

ioctl(fd, FBIOPAN_DISPLAY, &var);

平移显示,设置屏幕的显示,可用于双缓冲framebuffer设计

其他

ioctl(fd, xxxxxx, &xxx);

其他参数可以阅读内核源码:/kernel/drivers/video/fbdev/core/fbmem.c 1113行~1267行

第三步
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
unsigned int *fb_mem  = NULL;	//设置显存的位数为32位
	/*--------------第三步--------------*/
	
  fb_mem = (unsigned int *)mmap(NULL, var.xres*var.yres*4, 		//获取显存,映射内存
			PROT_READ |  PROT_WRITE, MAP_SHARED, fd, 0);   
								  
	if(fb_mem == MAP_FAILED){
		perror("Mmap LCD");
		return -1;	
	}
  • 这一步操作是把设备的内存映射到用户空间里, 用户只需要对着这块内存操作就可以改变屏幕的内容, 与传统的write函数相比, 数据传输的速度以及便捷性都得到了大幅的提高

  • 第一行设置fb_mem为32位, 可以让我能够方便的使用32位的数据操作24位屏幕数据RGB888, 如果使用想要节省空间的话,可以使用unsigned char型, 不过颜色分量就需要解析,然后分别改写。

1
2
mmap的原型如下
caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);
  • 返回值为地址

  • addr :申请的地址,如果为空,系统会自动分配地址空间

  • len :申请内存空间的大小,单位为字节

  • prot :prot参数指定访问权限,可取如下几个值的“或”:PROT_READ(可读)、 PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)

  • flags :两个参数可以选择:MAP_PRIVATE和MAP_SHARED 1. MAP_PRIVATE - 创建一个私人写时复制映射,映射的更新对映射相同文件的其他进程不可见,而且并不是通向底层文件。 2. MAP_SHARED - 分享该映射,映射的更新对映射此文件的其他进程可见,并被带到底层文件。

  • fd : 文件描述符,fd=-1,匿名映射

  • offset :映射文件的偏移量,从文件的哪里开始操作

注意

更详细的操作可以前往pc上的Ubuntu在命令行里输入 man 2 mmap 获得

与之相对应的是第64行的 munmap(fb_mem,var.xres*var.yres*4);

1
2
#函数功能:释放内存空间
int munmap(void *addr, size_t length);
  • addr :mmap返回的地址

  • length :要回收的内存空间大小

第四步
1
2
3
4
	/*--------------第四步--------------*/
	//将屏幕全部设置成蓝色
	for(i=0;i< var.xres*var.yres ;i++)
		fb_mem[i] = Blue;
  • 该操作就可以直接通过操作framebuffer就可以控制屏幕了。 i对应的数值就是屏幕中像素点的位置

18.4. 双缓冲framebuffer设计

18.4.1. 前言

  • 本小结仅提供双缓冲framebuffer的应用程序设计思路, 想在LubanCat-RK系列板卡中实现这个功能需要自行修改内核源码适配

  • 双缓冲的设计在drm中有较高的支持,我们推荐使用drm应用编程替代framebuffer, 如果不想修改内核源码,但是想体验双缓冲framebuffer,可以前往下一章节学习

18.4.2. 双缓冲

在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。 计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。 如果所需要绘制的图形很简单,那么这样也没什么问题。 但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。 让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂, 则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了, 但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。 也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。 如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。 这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。 即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域, 在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。

未找到图片

以横向扩充为例

18.4.3. 实现思路

内核修改:

  1. 修改 fb_fix_screeninfo.smem_len 这个参数,而固定参数不能在应用层修改,所以需要在内核里扩充它的长度

  2. 修改内核中mmap函数的分配内存的长度,dma的传输范围

  3. 修改内核中fb_pan_display,以适配显示

应用层修改:

  1. 在应用层需要把xres_virtual扩充到xres的两倍

  2. 如果想在主屏幕上显示的话,需要设置 fb_var_screeninfo.xoffset=0;在副屏幕上显示的话,需要设置 fb_var_screeninfo.xoffset=xres;

  3. 通过ioctl(fd, FBIOPUT_VSCREENINFO, &var);修改参数

  4. 通过ioctl(fd, FBIOPAN_DISPLAY, &var);把参数传入到内核里,就可以切换屏幕了

本次设计思路参考文章:https://blog.csdn.net/louiswangbing/article/details/6606849