17. 屏幕显示(framebuffer)

17.1. LubanCat-sg200x系列板卡

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

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

17.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|

17.3. framebuffer应用程序

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
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <unistd.h>  
#include <stdio.h>  
#include <fcntl.h>  
#include <linux/fb.h>  
#include <sys/mman.h>  
#include <stdlib.h>  
#include <string.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <errno.h>

#define ARGB8888

#define PAUSE()                                                                                                        \
    do {                                                                                                           \
        printf("---------------press Enter key to continue!---------------\n");                                    \
        getchar();                                                                                             \
    } while (0)


#if defined(ARGB8888)	// 32bits
#define RED      0xFFFF0000
#define GREEN    0xFF00FF00
#define BLUE     0xFF0000FF
#define YELLOW   0xFFFFFF00
#define WHITE    0xFFFFFFFF 
#define BLACK    0xFF000000
void fill_color(uint32_t *fb_addr, uint32_t bit_map, int psize)
{
    int i;
    int pix_count = psize / 4;
    for(i=0; i<pix_count; i++) {
        *fb_addr = bit_map;
        fb_addr++;
    }
}
#else	// 16bits
#define RED      0xFC00
#define GREEN    0x83E0
#define BLUE     0x801F
#define YELLOW   0xFFE0
#define WHITE    0xFFFF 
#define BLACK    0x8000
void fill_color(short *fb_addr, short bit_map, int psize)
{
    int i;
    for(i=0; i<psize; i++) {
        *fb_addr = bit_map;
        fb_addr++;
    }
}
#endif

void _fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
    long screensize=0;  

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

    if(ioctl(fp, FBIOGET_VSCREENINFO, vinfo)){  
        printf("Error reading variable information/n");  
        exit(3);  
    }  

    screensize = finfo->line_length * vinfo->yres;

    printf("The ID=%s\n", finfo->id);
    printf("The phy mem = 0x%x, total size = %d(byte)\n", finfo->smem_start, finfo->smem_len);  
    printf("line length = %d(byte)\n", finfo->line_length);  
    printf("xres = %d, yres = %d, bits_per_pixel = %d\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel);  
    printf("xresv = %d, yresv = %d\n", vinfo->xres_virtual, vinfo->yres_virtual);  
    printf("vinfo.xoffset = %d, vinfo.yoffset = %d\n", vinfo->xoffset, vinfo->yoffset);  
    printf("vinfo.vmode is :%d\n", vinfo->vmode);  
    printf("finfo.ypanstep is :%d\n", finfo->ypanstep);  
    printf("vinfo.red.offset=0x%x\n", vinfo->red.offset);
    printf("vinfo.red.length=0x%x\n", vinfo->red.length);
    printf("vinfo.green.offset=0x%x\n", vinfo->green.offset);
    printf("vinfo.green.length=0x%x\n", vinfo->green.length);
    printf("vinfo.blue.offset=0x%x\n", vinfo->blue.offset);
    printf("vinfo.blue.length=0x%x\n", vinfo->blue.length);
    printf("vinfo.transp.offset=0x%x\n", vinfo->transp.offset);
    printf("vinfo.transp.length=0x%x\n", vinfo->transp.length);
    printf("Expected screensize = %d(byte), using %d frame\n", screensize, finfo->smem_len/screensize);
}

int main ()   
{  
    int fp=0;  
    struct fb_var_screeninfo vinfo;  
    struct fb_fix_screeninfo finfo;  
    void *fbp = NULL;    
    uint32_t *test_fbp = NULL;
    int x = 0, y = 0;  
    long location = 0;
    int i;
    int num = 1;
    int pix_size=0;

    fp = open("/dev/fb0", O_RDWR);  

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

    printf("-- Default fb info --\n");
    _fb_get_info(fp, &finfo, &vinfo);

    fbp = mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
    if (fbp == MAP_FAILED)
    {
        printf ("Error: failed to map framebuffer device to memory.\n");
        exit (4);  
    }
    printf("Get virt mem = %p\n", fbp);  

    pix_size = finfo.line_length * vinfo.yres;
    /* using first frame, for FBIOPAN_DISPLAY
     * 当刷新需要调用FBIOPAN_DISPLAY, 要告知驱动刷哪块帧, 用到下面两个参数
     * 如果使用第二帧buffer -> vinfo.xoffset = 0; vinfo.yoffset = vinfo.yres;
     */
    vinfo.xoffset = 0;
    vinfo.yoffset = 0;

    /* show color loop */
    while(num--) {
        printf("\ndrawing YELLOW......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, YELLOW, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);

        printf("\ndrawing BLUE......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, BLUE, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);
        
        printf("\ndrawing RED......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, RED, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);
	PAUSE();
    }

    munmap(fbp, finfo.smem_len); /*解除映射*/  

    close (fp);
    return 0;
}  

编译运行

1
2
gcc framebuffer.c -o framebuffer
./framebuffer

运行结果:依次黄、蓝、红依次刷新,按下回车后退出。

17.3.1. 代码分析

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

第一步
1
2
3
4
5
6
    fp = open("/dev/fb0", O_RDWR);  

    if(fp < 0) {  
        printf("Error : Can not open framebuffer device/n");  
        exit(1);  
    }  
  • 这一步是打开framebuffer设备,这一步成功后会返回文件描述符– fd, 我们后面可以操作这个文件描述符操作该设备。

第二步
 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
void _fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo)
{
    long screensize=0;  

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

    if(ioctl(fp, FBIOGET_VSCREENINFO, vinfo)){  
        printf("Error reading variable information/n");  
        exit(3);  
    }  

    screensize = finfo->line_length * vinfo->yres;

    printf("The ID=%s\n", finfo->id);
    printf("The phy mem = 0x%x, total size = %d(byte)\n", finfo->smem_start, finfo->smem_len);  
    printf("line length = %d(byte)\n", finfo->line_length);  
    printf("xres = %d, yres = %d, bits_per_pixel = %d\n", vinfo->xres, vinfo->yres, vinfo->bits_per_pixel);  
    printf("xresv = %d, yresv = %d\n", vinfo->xres_virtual, vinfo->yres_virtual);  
    printf("vinfo.xoffset = %d, vinfo.yoffset = %d\n", vinfo->xoffset, vinfo->yoffset);  
    printf("vinfo.vmode is :%d\n", vinfo->vmode);  
    printf("finfo.ypanstep is :%d\n", finfo->ypanstep);  
    printf("vinfo.red.offset=0x%x\n", vinfo->red.offset);
    printf("vinfo.red.length=0x%x\n", vinfo->red.length);
    printf("vinfo.green.offset=0x%x\n", vinfo->green.offset);
    printf("vinfo.green.length=0x%x\n", vinfo->green.length);
    printf("vinfo.blue.offset=0x%x\n", vinfo->blue.offset);
    printf("vinfo.blue.length=0x%x\n", vinfo->blue.length);
    printf("vinfo.transp.offset=0x%x\n", vinfo->transp.offset);
    printf("vinfo.transp.length=0x%x\n", vinfo->transp.length);
    printf("Expected screensize = %d(byte), using %d frame\n", screensize, finfo->smem_len/screensize);
}
  • 这一步获取屏幕的参数以及设置屏幕

  • 获取屏幕的可变参数 ioctl(fp, FBIOGET_VSCREENINFO, vinfo)

  • 获取屏幕的固定参数 ioctl(fp, FBIOGET_FSCREENINFO, finfo)

  • 这两个目的是获取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
    fbp = mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
    if (fbp == MAP_FAILED)
    {
        printf ("Error: failed to map framebuffer device to memory.\n");
        exit (4);  
    }
    printf("Get virt mem = %p\n", fbp);  
  • 这一步操作是把设备的内存映射到用户空间里, 用户只需要对着这块内存操作就可以改变屏幕的内容, 与传统的write函数相比, 数据传输的速度以及便捷性都得到了大幅的提高

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 获得

与之相对应的是 munmap(fbp, finfo.smem_len);

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

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

第四步
 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
    while(num--) {
        printf("\ndrawing YELLOW......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, YELLOW, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);

        printf("\ndrawing BLUE......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, BLUE, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);
        
        printf("\ndrawing RED......\n");
        vinfo.xoffset = 0;
        vinfo.yoffset = 0;
        fill_color(fbp, RED, pix_size);
        if (ioctl(fp, FBIOPAN_DISPLAY, &vinfo) != 0) {
        	printf("\nPAN_DISPLAY err(%d)\n", errno);
	}
        sleep(1);
	PAUSE();
    }
  • 该操作就可以直接通过操作framebuffer就可以控制屏幕了。 i对应的数值就是屏幕中像素点的位置