跳转到正文
知识笔记
📖 学习中

Framebuffer 基本原理与显示编程 ​

想弄清的问题 ​

Linux 应用程序怎样通过 /dev/fb0 获取屏幕参数,并在 LCD 上画出指定颜色?

关键概念 ​

1. Framebuffer 是什么? ​

Framebuffer(帧缓冲)可以理解为保存屏幕像素数据的一块内存。应用程序向其中写入颜色,显示控制器按配置读取这些数据,最终显示在 LCD 上。

text
应用程序 → /dev/fb0 → Framebuffer(显存)→ 显示控制器 → LCD

本课是使用已有的 fbdev 显示接口,不是编写 LCD 内核驱动。部分新平台主要使用 DRM/KMS,不一定提供 /dev/fb0。

2. 操作流程 ​

text
open() → ioctl() → mmap() → 写入像素 → munmap() → close()
接口用途
open()打开 /dev/fb0
ioctl()获取屏幕分辨率、像素格式及显存布局
mmap()把显存映射到用户空间,便于直接访问
munmap() / close()解除映射、关闭设备

3. 两类屏幕参数 ​

c
struct fb_var_screeninfo var; // 可变参数
struct fb_fix_screeninfo fix; // 固定参数

ioctl(fd, FBIOGET_VSCREENINFO, &var);
ioctl(fd, FBIOGET_FSCREENINFO, &fix);
字段含义
var.xres / yres实际可见的宽度、高度
var.xres_virtual / yres_virtual显存画布的宽度、高度
var.xoffset / yoffset当前画面在虚拟画布中的偏移
var.bits_per_pixel每个像素的位数(bpp)
var.red / green / blueRGB 分量所在的位位置、位数
fix.line_length显存每行实际字节数,可能有对齐填充
fix.smem_len显存区域大小

例如实际分辨率 1024×600,虚拟分辨率 1024×1200,显存可以容纳上下两张画面;但能否通过 yoffset 和 FBIOPAN_DISPLAY 切换画面,取决于驱动支持情况。

4. RGB565 与像素地址 ​

RGB565:红色 5 位、绿色 6 位、蓝色 5 位,共 16 位(2 字节)。

颜色RGB565 值
红0xF800
绿0x07E0
蓝0x001F
白0xFFFF

16 bpp 不一定就是 RGB565,还要核对 var.red/green/blue 的 offset 和 length。

在常见的线性 16 bpp 显存中,像素 (x, y) 的字节偏移是:

text
偏移 = (y + yoffset) × line_length + (x + xoffset) × 2

注意:不能用 xres × 2 代替 line_length,因为显存每行可能存在对齐填充。24/32 bpp 则应按实际每像素字节数计算。

自己的理解 ​

ioctl() 告诉程序显存“怎样排列”,mmap() 让程序“可以访问显存”。画点、画线、画矩形其实都是计算像素地址并写入颜色。

用法与最小示例 ​

先检查设备是否存在:

bash
ls -l /dev/fb*
cat /sys/class/graphics/fb0/virtual_size
cat /sys/class/graphics/fb0/bits_per_pixel

下面是RGB565、线性显存、小端序平台的最小画点示例;先判断格式,避免误写其他像素布局:

c
#include <fcntl.h>
#include <linux/fb.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>

int main(void)
{
    /* 必须用 O_RDWR 打开:只读的 fd 无法把显存映射成可写 */
    int fd = open("/dev/fb0", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /* var 描述当前显示模式(可修改),fix 描述显存固定属性(只读) */
    struct fb_var_screeninfo var = {0};
    struct fb_fix_screeninfo fix = {0};
    /* FBIOGET_VSCREENINFO 取分辨率、bpp、RGB 位域和画面偏移;
     * FBIOGET_FSCREENINFO 取行长、显存大小和显存类型 */
    if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0 ||
        ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) {
        perror("ioctl"); close(fd); return 1;
    }

    /* line_length 是一行在显存中的真实字节数,可能大于 xres * 2(对齐填充) */
    printf("%ux%u, %u bpp, line_length=%u\n",
           var.xres, var.yres, var.bits_per_pixel, fix.line_length);

    /*
     * 逐项确认像素布局:16 bpp 且红在 bit11~15、绿在 bit5~10、蓝在 bit0~4 才是 RGB565;
     * PACKED_PIXELS 表示像素连续紧密排列,TRUECOLOR 表示直接色(不查调色板);
     * 分辨率或显存长度为 0 说明驱动状态异常,后面无法安全计算地址。
     */
    if (var.bits_per_pixel != 16 ||
        var.red.offset != 11 || var.red.length != 5 ||
        var.green.offset != 5 || var.green.length != 6 ||
        var.blue.offset != 0 || var.blue.length != 5 ||
        fix.type != FB_TYPE_PACKED_PIXELS ||
        fix.visual != FB_VISUAL_TRUECOLOR ||
        var.xres == 0 || var.yres == 0 || fix.smem_len == 0) {
        fprintf(stderr, "不支持的显存格式或屏幕参数\n");
        close(fd); return 1;
    }

    /* 长度用 fix.smem_len(不能用 xres*yres*2,行填充会漏算);
     * MAP_SHARED 才能把写入真正落到显存,MAP_PRIVATE 只改私有副本 */
    uint8_t *fb = mmap(NULL, fix.smem_len,
                       PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fb == MAP_FAILED) { perror("mmap"); close(fd); return 1; }

    /* 在可见画面左上角写入一个 200×100 的红色实心矩形 */
    for (uint32_t y = 0; y < 100; y++) {
        for (uint32_t x = 0; x < 200; x++) {
            /* 可见画面可能位于虚拟画布的 (xoffset, yoffset) 处;
             * 地址 = 行号 × line_length + 列号 × 每像素字节数(16 bpp 为 2) */
            size_t pos = (size_t)(var.yoffset + y) * fix.line_length +
                        (size_t)(var.xoffset + x) * 2;
            /* 防止矩形超出显存范围时越界写坏其他内存 */
            if (pos + 2 > fix.smem_len) break;

            /* 0xF800 在小端序内存中低字节在前:低字节 0x00、高字节 0xF8 */
            fb[pos]     = 0x00;
            fb[pos + 1] = 0xF8;
        }
    }

    /* 先解除映射再关闭设备;munmap 的长度要与 mmap 时一致 */
    munmap(fb, fix.smem_len);
    close(fd);
    return 0;
}

将代码保存为 fb_pixel.c。在有 Linux fbdev 头文件的环境中编译(目标板需使用对应交叉工具链):

bash
gcc -Wall -Wextra -O2 fb_pixel.c -o fb_pixel

实验状态:尚未在目标开发板验证。 运行前先确认目标 LCD 已点亮、/dev/fb0 可用;示例只在左上角绘制 200×100 的红色矩形,不会画满屏幕。

待弄清的问题 ​

参考与关联 ​