Linux Framebuffer应用编程

LCD控制原理

Linux系统通过Framebuffer(帧缓冲)驱动程序控制LCD。显示设备也被称为帧缓冲设备。Frame表示帧,buffer表示缓冲。这就是说,Framebuffer是一块内存,里面存放着一帧图像,每帧图像包含每个像素颜色值。BPP,像素深度,指存储每个像素所用的位数,通常值为16,24,或32。

假设LCD分辨率1024 x 768,每个像素颜色用32bit表示,那么Framebuffer大小为1024 * 768 * 32 / 8 = 3145728 byte。

Framebuffer应用编程的本质:修改指定位置像素点颜色值。

控制LCD主要步骤:
1)驱动程序设置好LCD控制器
根据LCD参数设置LCD控制器的时序、信号极性;
根据LCD分辨率、BPP分配Framebuffer。

2)APP使用ioctl获得LCD分辨率、BPP。

3)APP通过mmap映射Framebuffer,彺Framebuffer写入数据。

假设需要设置LCD中坐标(x,y)处像素的颜色,首先要找到这个像素对应的内存(地址),然后根据它的BPP值设置颜色。假设fb_base是APP执行mmap后得到的Framebuffer地址,如下图所示:

可以用下面公式算出(x,y)坐标处像素对应的Framebuffer地址:

(x,y)像素起始地址 = fb_base + (xres * bpp / 8) * y + x * bpp / 8

xres 表示x轴像素个数, yres表示y轴像素个数,bpp表示像素深度。

像素的颜色如何表示?
用RGB三原色(红、绿、蓝)表示,在不同的BPP格式中,用不同的位来表示R、G、B。如下图所示:

对于32BPP,一般只设置其中的低24bit,高8bit表示透明度,一般LCD不支持。
对于24BPP,硬件上为方便处理,在Framebuffer中也是用32bit表示,效果跟32BPP一样。
对于16BPP,常用RGB565;很少用到RGB555,这可以通过ioctl读取驱动程序中的RGB位偏移来确定使用哪种格式。


Framebuffer编程有关API

通过一个示例程序,进行Framebuffer编程。目的:打开LCD设备节点,获取分辨率等参数,映射Framebuffer,实现描点函数。

open

open用于打开设备文件。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

函数参数说明:

  • pathname 表示打开文件的路径;
  • flags 表示打开文件的方式,常用有6种:

a. O_RDWR 可读可写方式打开;
b. O_RDONLY 只读方式打开;
c. O_WRONLY 只写方式打开;
d. O_APPEND 如果文件原来有内容,则新写入内容会接续到原内容后面;
e. O_TRUNC 如果这个文件中本来有内容,则原内容截断;
f. O_CREAT 当前打开文件不存在,就创建并打开它,通常与O_EXCL结合使用;当没有文件时创建文件,有文件时报错并提醒我们。

  • mode 表示创建文件的权限,只有在flags中使用了O_CREAT时才有效;否则,忽略。

返回值:成功打开文件,返回文件描述符;打开失败,返回-1,并且errno被设置。

ioctl

ioctl用于获取设备参数。ioctl功能非常强大、灵活,不同驱动程序内部会实现不同ioctrl,APP可以使用各种ioctl跟驱动程序交互,如传数据给驱动程序,也可以从驱动程序中读取数据。

#include <sys/ioctl.h>

int ioctl(int fd, int request, ...);

函数参数说明:

  • fd 文件描述符
  • request 表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据

对于LCD设备来说,常用request有
(1)FBIOGET_VSCREENINFO:获取LCD的可变参数信息;
(2)FBIOPUT_VSCREENINFO:设置LCD的可变参数信息;
(3)FBIOGET_FSCREENINFO:获取LCD的不变参数信息

宏定义FBIOGET_VSCREENINFO、FBIOPUT_VSCREENINFO、FBIOGET_FSCREENINFO及保存结果的数据结构struct fb_var_screeninfo 和 struct fb_fix_screeninfo,都位于<linux/fb.h>。

  • ... 表示可变参数arg,根据request命令,保存设备驱动程序返回输出的数据

返回值:通常是成功时,返回0,也有少部分request,返回非负数;失败时,返回-1,并且errno被设置。

mmap

mmap用于建立内存映射。
munmap用于取消mmap建立的内存映射。

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
int munmap(void *addr, size_t length);

函数参数说明:

  • addr 表示指定映射的内存起始地址,通常设为NULL表示让系统自动选择地址,在成功建立映射后返回改地址

  • length 表示将文件中多大的内容映射到内存中

  • prot 表示映射区域的保护方式,可以为以下4种方式组合:
    a. PROT_EXEC 映射区域可被执行
    b. PROT_READ 映射区域可被读出
    c. PROT_WRITE 映射区域可被写入
    d. PROT_NONE 映射区域不能存取

  • flags 表示影响映射区域的不同特性,常用2种:
    a. MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
    b. MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。

返回值:若成功映射,则返回指向映射的区域指针;失败返回-1,errno被设置。


Framebuffer程序

1.open打开设备

首先,打开设备节点

fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0) {
    printf("Can't open /dev/fb0\n");
    return -1;
}

2.ioctl获取LCD参数

LCD驱动程序给APP提供2类参数:可变的参数fb_var_screeninfo,固定的参数fb_fix_screeninfo。编写APP时,主要关系可变参数,定义如下:

#include <linux/fb.h>

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 = 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 */
};

fb_bar_screeninfo关键参数:
分辨率:xres, yres;
bpp:bits_per_pixel,像素深度;
red、green、blue成员,表示RGB分别用多少位表示从哪位开始。

如何获取当前fb_bar_screeninfo类型的var?
可以使用ioctl函数:

static struct fb_var_screeninfo var; /* current var */
...

if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
    printf("Can't get var\n");
    return -1;
}

注意:这里ioctl用到的命令是FBIOGET_VSCREENINFO,表示get var screen info,即获得屏幕的可变信息。也可以用FBIOPUT_VSCREENINFO来调整这些参数,但很少用到。

对于固定参数fb_fix_screeninfo,APP编程中很少用到,定义如下:

#include <linux/fb.h>

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;                 /* Indicate to driver which    */
                                 /*  specific chip/card we have    */
    __u16 capabilities;          /* see FB_CAP_*            */
    __u16 reserved[2];           /* Reserved for future compatibility */
};

可以用ioctl FBIOGET_FSCREENINFO读出这些信息,但很少用到。

3.mmap映射Framebuffer

通过mmap调用,来映射一块内存。需要知道它的地址,由驱动程序来设置;需要知道它的大小(占用多少byte),由APP设置。
Framebuffer占用多少byte内存(screen_size),取决于LCD分辨率(xres * yres)+ 像素深度(bpp)。

line_width = var.xres * var.bits_per_pixel / 8; // 一行多少byte
pixel_width = var.bits_per_pixel / 8;           // 一个像素多少byte
screen_size = var.xres * var.yres * var.bits_per_pixel / 8; // 一屏多少byte
fb_base = (unsigned char*)mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char*)MAP_FAILED) {
    printf("Can't mmap\n");
    return -1;
}

screen_size是整个Framebuffer大小(bytes);PROT_READ | PROT_WRITE表示该区域可读、可写;MAP_SHARED表示该区域共享的,APP写入数据时,会直达驱动程序。

4.定义描点函数

所谓描点函数,就是根据指定lcd坐标(x, y),修改Framebuffer对应内存的值,从而修改颜色值。
能在LCD上描绘指定像素后,就可以写字、画图,描点函数是基础。

void lcd_put_pixel(int x, int y, unsiged int color)
{
    unsigned char *pen_8 = fb_base + y * line_width + x * pixel_width;
    unsigned short *pen_16;
    unsigned int *pen_32;

    unsigned int red, green, blue;
    
    pen_16 = (unsigned short *)pen_8;
    pen_32 = (unsigned int *)pen_8;

    switch(var.bits_per_pixel) { /* bpp 像素深度 */
        case 8:
        {
            *pen_8 = color;
            break;
        }
        case 16:
        {
            /* RGB565 */
            red = (color >> 16) & 0xFF;
            green = (color >> 8) & 0xFF;
            blue = (color >> 0) & 0xFF;
            color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
            *pen_16 = color;
            break;
        }
        case 32:
        {
            /* RGB888 */
            *pen_32 = color;
            break;
        }
        default:
        {
            printf("Can't support %d bpp\n", var.bits_per_pixel);
            break;
        }
    }
}

传入的color表示颜色,格式是0x00RRGGBB,即RGB888。当LCD是16bpp时,要把color变量中的R、G、B抽出来,再合并成RGB565格式。

pen_8的计算公式,实际上也是计算(x, y)坐标像素对应的Framebuffer地址。

pen_8 = fb_base + y * line_width + x * pixel_width

对于8bpp,color不再表示RGB三原色,这涉及到调色板的概念,color是调色板的值。

将RGB888转换为RGB565时,要从color变量中把R、G、B抽取出来,然后以RGB565格式合成新的color值。

通过赋值语句*pen_16 = color ,将16bit颜色值写入Framebuffer。

对于32bpp,颜色格式跟color参数一致,可以直接写入Framebuffer。

5. 示例:lcd_put_pixel画点

main函数中,在最后简单画几个点:

/* 清屏: 全部设为白色 */
memset(fbmem, 0xFF, screen_size);

/* 设置100个像素为红色 */
for (i = 0; i < 100; i++) {
    lcd_put_pixel(var.xres / 2 + i, var.yres / 2, 0xFF0000);
}

完整程序:show_pixel.c | gitee


参考

嵌入式Linux应用开发完全手册V4.0_韦东山全系列视频文档-IMX6ULL开发板, 韦东山.

posted @ 2022-08-19 15:10  明明1109  阅读(799)  评论(0编辑  收藏  举报