测试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;
}