测试framebuffer

技术说明

在 T113 这类全志 SoC 上,Framebuffer 并不是孤立存在的组件,而是显示引擎(Display Engine,简称 DE)体系的一部分。 这块芯片内部有 DE、TCON(时序控制器)、以及 LCD/HDMI 等模块,负责图像合成、时序输出和背光控制。 Linux 内核中的 disp 驱动在启动时会根据设备树(DTS)里的参数初始化硬件,比如输出类型、分辨率、像素格式和背光逻辑。在我们的设备树中:

&disp {
    fb0_format = <10>;   // ARGB8888,每像素32bit
    fb0_width  = <800>;
    fb0_height = <480>;
    screen0_output_type = <1>; // LCD输出
};
&lcd0 {
    lcd_x = <800>;
    lcd_y = <480>;
    lcd_dclk_freq = <33>;
};

这些配置告诉显示驱动:创建一个 800×480 的主屏,使用 ARGB8888 格式输出到 LCD。驱动在底层会据此分配一块显存区作为 framebuffer,注册成字符设备 /dev/fb0。 当系统启动后,用户空间通过 /dev/fb0 访问的其实是一块映射自 DE 内部缓冲区的共享内存。

在 TinaSDK5 的构建体系中,Framebuffer 与 Allwinner 的 disp 子系统紧密结合。 内核中不仅支持基本的单缓冲,还定义了虚拟分辨率机制:yres_virtual 可以配置为实际高度的 2 倍,从而生成两个显存页面。 这时 /dev/fb0 的总显存大约是 800 × 480 × 4 × 2≈3 MB,用户程序可以利用 vinfo.yoffset 在两页之间切换,实现双缓冲动画。 我们在下面的双缓冲的示例中调用 ioctl(FBIOPAN_DISPLAY) 翻页,就是使用了这一特性。 硬件层的 DE 在接到 PAN 命令后,仅修改扫描地址指针,而不需要重新拷贝数据,因此画面切换非常平滑。

从上层来看,Framebuffer 在 T113 上既可以作为 LVGL 这种图形库的底层输出接口,也可以单独承担启动画面、诊断模式等独立显示任务。 它的色深、分辨率和刷新率直接由 lcd0 节点的参数定义,而时钟频率、同步极性、背光 PWM 信号同样在设备树中配置好。 这使得整个显示路径——从 用户程序的 mmap 调用,一直到 DE 合成、TCON 输出 RGB 信号给 LCD 屏——都完全打通而且透明,不需要窗口系统的参与。

T113 板卡上的 Framebuffer 实际上是 Allwinner 显示驱动提供的最底层显示平面。 它直接对应设备树中的 fb0 定义,受 disp/lcd0 节点控制,位于 DE 显存之中。 在这个平台上使用 Framebuffer 进行绘图或动画,既能充分发挥硬件的性能,又能保持系统轻量,这是 TinaSDK 常用的显示方式,也是 LVGL 驱动 sunxifb.c 实现的基础。

下面我们来构建测试工程

makefile文件

# ==========================================================
# T113 TinaSDK5 Buildroot Framebuffer Test Makefile
# ==========================================================

# 交叉编译器
CROSS_COMPILE = /home/zqboard/TinaSDK5/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-
CC             = $(CROSS_COMPILE)gcc

# Buildroot sysroot / staging 目录
STAGING_DIR = /home/zqboard/TinaSDK5/out/t113/zqboard/buildroot/buildroot/staging
SYSROOT_OPT = --sysroot=$(STAGING_DIR)

# 头文件和库路径
INC = -I$(STAGING_DIR)/usr/include
LIB = -L$(STAGING_DIR)/usr/lib

# 通用编译选项
CFLAGS = $(SYSROOT_OPT) $(INC) -Wall -O2
LDFLAGS = $(LIB)

# 目标程序
TARGET = fb_test
SRCS = fb_test.c

all: $(TARGET)

$(TARGET): $(SRCS)
    $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

install:
    @echo "Copying to board..."
    scp $(TARGET) root@192.168.1.100:/usr/bin/

clean:
    rm -f $(TARGET)

fb_test.c文件

测试蓝屏输出,并且控制台输出分辨率信息

# ==========================================================
# T113 TinaSDK5 Buildroot Framebuffer Test Makefile
# ==========================================================

# 交叉编译器
CROSS_COMPILE = /home/zqboard/TinaSDK5/out/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi-
CC             = $(CROSS_COMPILE)gcc

# Buildroot sysroot / staging 目录
STAGING_DIR = /home/zqboard/TinaSDK5/out/t113/zqboard/buildroot/buildroot/staging
SYSROOT_OPT = --sysroot=$(STAGING_DIR)

# 头文件和库路径
INC = -I$(STAGING_DIR)/usr/include
LIB = -L$(STAGING_DIR)/usr/lib

# 通用编译选项
CFLAGS = $(SYSROOT_OPT) $(INC) -Wall -O2
LDFLAGS = $(LIB)

# 目标程序
TARGET = fb_test
SRCS = fb_test.c

all: $(TARGET)

$(TARGET): $(SRCS)
    $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)

install:
    @echo "Copying to board..."
    scp $(TARGET) root@192.168.1.100:/mnt/UDISK

clean:
    rm -f $(TARGET)

编译

运行make命令编译好后,会再项目录下生成fb_test文件

使用scp上传

修改makefile文件中的scp行,修改为开发板的ip地址

运行make install上传,默认root密码为123

运行结果

运行后,控制台输出

root@zqboard:/mnt/UDISK# ./fb_test 
Display: 800x480, 32bpp

屏幕变为蓝色

其他测试文件

一下是其他测试文件,可以尝试一下效果

绘制彩色矩形块

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <string.h>

/* 绘制矩形块函数 */
void draw_rect(unsigned char *fbp, int x0, int y0, int w, int h,
               int r, int g, int b,
               struct fb_var_screeninfo vinfo,
               struct fb_fix_screeninfo finfo)
{
    int bytes_per_pixel = vinfo.bits_per_pixel / 8;
    long location = 0;
    for (int y = y0; y < y0 + h; y++) {
        if (y >= vinfo.yres) break;
        for (int x = x0; x < x0 + w; x++) {
            if (x >= vinfo.xres) break;

            location = (x + vinfo.xoffset) * bytes_per_pixel +
                       (y + vinfo.yoffset) * finfo.line_length;

            fbp[location + 0] = b;        // Blue
            fbp[location + 1] = g;        // Green
            fbp[location + 2] = r;        // Red

            if (bytes_per_pixel == 4)
                fbp[location + 3] = 0xFF;  // Alpha
        }
    }
}

int main()
{
    int fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd < 0) {
        perror("open fb0");
        return -1;
    }

    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
    ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);

    long screensize = finfo.smem_len;
    unsigned char *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((intptr_t)fbp == -1) {
        perror("mmap fb");
        close(fbfd);
        return -1;
    }

    printf("Framebuffer info:\n");
    printf(" Resolution: %dx%d\n", vinfo.xres, vinfo.yres);
    printf(" Bit Depth : %d\n", vinfo.bits_per_pixel);

    memset(fbp, 0, screensize);  // 清屏为黑色

    // 绘制几个矩形块
    draw_rect(fbp, 100, 100, 200, 150, 255, 0, 0, vinfo, finfo);   // 红色
    draw_rect(fbp, 200, 300, 150, 120, 0, 255, 0, vinfo, finfo);   // 绿色
    draw_rect(fbp, 450, 200, 180, 160, 0, 0, 255, vinfo, finfo);   // 蓝色
    draw_rect(fbp, 50,  400, 100, 60, 255, 255, 0, vinfo, finfo);  // 黄色
    draw_rect(fbp, 650, 100, 100, 100, 255, 0, 255, vinfo, finfo); // 紫色

    printf("Draw complete!\n");
    sleep(5);  // 显示 5 秒

    munmap(fbp, screensize);
    close(fbfd);
    return 0;
}

绘制渐变色背景

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <string.h>

/* 绘制矩形块函数 */
void draw_rect(unsigned char *fbp, int x0, int y0, int w, int h,
               int r, int g, int b,
               struct fb_var_screeninfo vinfo,
               struct fb_fix_screeninfo finfo)
{
    int bytes_per_pixel = vinfo.bits_per_pixel / 8;
    long location = 0;
    for (int y = y0; y < y0 + h; y++) {
        if (y >= vinfo.yres) break;
        for (int x = x0; x < x0 + w; x++) {
            if (x >= vinfo.xres) break;

            location = (x + vinfo.xoffset) * bytes_per_pixel +
                       (y + vinfo.yoffset) * finfo.line_length;

            fbp[location + 0] = b;        // Blue
            fbp[location + 1] = g;        // Green
            fbp[location + 2] = r;        // Red

            if (bytes_per_pixel == 4)
                fbp[location + 3] = 0xFF;  // Alpha
        }
    }
}

int main()
{
    int fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd < 0) {
        perror("open fb0");
        return -1;
    }

    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
    ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);

    long screensize = finfo.smem_len;
    unsigned char *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((intptr_t)fbp == -1) {
        perror("mmap fb");
        close(fbfd);
        return -1;
    }

    printf("Framebuffer info:\n");
    printf(" Resolution: %dx%d\n", vinfo.xres, vinfo.yres);
    printf(" Bit Depth : %d\n", vinfo.bits_per_pixel);

    memset(fbp, 0, screensize);  // 清屏为黑色

    // 绘制几个矩形块
    draw_rect(fbp, 100, 100, 200, 150, 255, 0, 0, vinfo, finfo);   // 红色
    draw_rect(fbp, 200, 300, 150, 120, 0, 255, 0, vinfo, finfo);   // 绿色
    draw_rect(fbp, 450, 200, 180, 160, 0, 0, 255, vinfo, finfo);   // 蓝色
    draw_rect(fbp, 50,  400, 100, 60, 255, 255, 0, vinfo, finfo);  // 黄色
    draw_rect(fbp, 650, 100, 100, 100, 255, 0, 255, vinfo, finfo); // 紫色

    printf("Draw complete!\n");
    sleep(5);  // 显示 5 秒

    munmap(fbp, screensize);
    close(fbfd);
    return 0;
}

小方块水平移动

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <string.h>
#include <time.h>

/* 在指定位置绘制矩形 */
static void draw_rect(unsigned char *fbp, int x0, int y0, int w, int h,
                      int r, int g, int b,
                      struct fb_var_screeninfo vinfo,
                      struct fb_fix_screeninfo finfo)
{
    int bytes_per_pixel = vinfo.bits_per_pixel / 8;
    long location = 0;

    for (int y = y0; y < y0 + h; y++) {
        if (y >= vinfo.yres) break;
        for (int x = x0; x < x0 + w; x++) {
            if (x >= vinfo.xres) break;

            location = (x + vinfo.xoffset) * bytes_per_pixel +
                       (y + vinfo.yoffset) * finfo.line_length;

            fbp[location + 0] = b;
            fbp[location + 1] = g;
            fbp[location + 2] = r;
            if (bytes_per_pixel == 4)
                fbp[location + 3] = 0xFF;
        }
    }
}

/* 清屏幕为黑 */
static void clear_screen(unsigned char *fbp, struct fb_fix_screeninfo finfo)
{
    memset(fbp, 0, finfo.smem_len);
}

int main(void)
{
    int fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd < 0) {
        perror("open fb0");
        return -1;
    }

    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
    ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
    long screensize = finfo.smem_len;

    unsigned char *fbp = (unsigned char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((intptr_t)fbp == -1) {
        perror("mmap fb");
        close(fbfd);
        return -1;
    }

    printf("Framebuffer animation demo: %dx%d %dbpp\n",
           vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);

    int box_w = 80;
    int box_h = 80;
    int pos_x = 0;
    int pos_y = vinfo.yres / 2 - box_h / 2; // 居中高度
    int dir = 1; // 1 向右,-1 向左

    // 主动画循环
    for (int frame = 0; frame < 600; frame++) {  // 共显示约10秒钟
        clear_screen(fbp, finfo);

        // 根据位置绘制方块
        draw_rect(fbp, pos_x, pos_y, box_w, box_h,
                  255, 50, 50, vinfo, finfo);

        // 移动方块
        pos_x += dir * 5; // 每帧移动速度
        if (pos_x + box_w >= (int)vinfo.xres) dir = -1;
        if (pos_x <= 0) dir = 1;

        // 控制帧率
        usleep(16000); // 约 60 FPS
    }

    clear_screen(fbp, finfo); // 清空最后画面
    munmap(fbp, screensize);
    close(fbfd);
    printf("Animation finished.\n");
    return 0;
}

双缓冲动画

上面例子中的图片会闪动,因为是单缓冲,下面的代码中使用了双缓冲,消除了视觉上的闪动。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>

static void draw_rect(unsigned char *buf, int x0, int y0, int w, int h,
                      int r, int g, int b,
                      struct fb_var_screeninfo vinfo,
                      struct fb_fix_screeninfo finfo)
{
    int bytes_per_pixel = vinfo.bits_per_pixel / 8;
    long location;

    for (int y = y0; y < y0 + h; y++) {
        if (y >= vinfo.yres) break;
        for (int x = x0; x < x0 + w; x++) {
            if (x >= vinfo.xres) break;
            location = (x + vinfo.xoffset) * bytes_per_pixel +
                       y * finfo.line_length;
            buf[location + 0] = b;
            buf[location + 1] = g;
            buf[location + 2] = r;
            if (bytes_per_pixel == 4) buf[location + 3] = 0xFF;
        }
    }
}

static void clear_buf(unsigned char *buf, struct fb_fix_screeninfo finfo,
                      struct fb_var_screeninfo vinfo)
{
    memset(buf, 0, vinfo.yres * finfo.line_length);
}

int main()
{
    int fbfd = open("/dev/fb0", O_RDWR);
    if (fbfd < 0) {
        perror("open fb0");
        return -1;
    }

    struct fb_fix_screeninfo finfo;
    struct fb_var_screeninfo vinfo;
    ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
    ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);

    printf("Framebuffer: %dx%d virtual_y=%d bpp=%d\n",
           vinfo.xres, vinfo.yres, vinfo.yres_virtual, vinfo.bits_per_pixel);

    long screensize = finfo.smem_len;
    unsigned char *fbp = (unsigned char *)mmap(0, screensize,
        PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if ((intptr_t)fbp == -1) {
        perror("mmap fb");
        close(fbfd);
        return -1;
    }

    int bytes_per_frame = vinfo.yres * finfo.line_length;
    unsigned char *buf[2];
    buf[0] = fbp;
    buf[1] = fbp + bytes_per_frame;

    int fb_index = 0;
    int box_w = 80, box_h = 80;
    int pos_x = 0;
    int pos_y = vinfo.yres / 2 - box_h / 2;
    int dir = 1;

    // 如果驱动没设虚拟高度,则手动设置
    if (vinfo.yres_virtual < 2 * vinfo.yres) {
        vinfo.yres_virtual = 2 * vinfo.yres;
        ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo);
    }

    // 主循环
    for (int frame = 0; frame < 600; frame++) {
        unsigned char *back_buf = buf[!fb_index];

        clear_buf(back_buf, finfo, vinfo);
        draw_rect(back_buf, pos_x, pos_y, box_w, box_h, 255, 100, 50, vinfo, finfo);

        pos_x += dir * 5;
        if (pos_x + box_w >= (int)vinfo.xres) dir = -1;
        if (pos_x <= 0) dir = 1;

        // Pan 显示到 back buffer
        vinfo.yoffset = (!fb_index) * vinfo.yres;
        if (ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo) < 0)
            perror("FBIOPAN_DISPLAY");

        fb_index = !fb_index; // 交换缓冲
        usleep(16000); // 控制帧率
    }

    munmap(fbp, screensize);
    close(fbfd);
    printf("Animation finished (double buffer).\n");
    return 0;
}