在 Linux Framebuffer (FB) 应用编程中,主要涉及到三个核心的系统调用,它们共同完成了设备打开、配置获取和内存访问的功能。
以下是对这些关键 API 函数的详细解析:
1. open 函数:打开 Framebuffer 设备
open 函数用于打开 /dev/fbX 设备文件,获取文件描述符,这是后续操作的基础。
A. 头文件 (Header Files)
c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
B. 函数原型 (Function Prototype)
c
int open(const char *pathname, int flags, ...);
C. 参数说明 (Parameters)
| 参数 | 类型 | 说明 (FB 场景) |
|---|---|---|
pathname |
const char * |
要打开的设备文件路径,例如 /dev/fb0(通常是主显示设备)。 |
flags |
int |
打开方式标志。在 FB 编程中通常使用 O_RDWR(读写模式),以便进行读(获取配置)和写(绘图)。 |
... |
变长参数 | 可选参数 mode_t mode,只有在 flags 中包含 O_CREAT 时才需要,在打开设备文件时通常不使用。 |
D. 返回值 (Return Value)
| 返回值 | 说明 |
|---|---|
| 非负整数 | 成功,返回设备的文件描述符 (fd)。该描述符将在 ioctl 和 mmap 中使用。 |
| -1 | 失败,并设置全局变量 errno。常见的错误是权限不足或设备文件不存在。 |
2. ioctl 函数:控制和配置 Framebuffer
ioctl (Input/Output Control) 函数是 Linux 设备驱动程序和用户应用交互的主要方式。在 FB 编程中,它用于获取和设置屏幕的配置信息。
A. 头文件 (Header File)
c
#include <sys/ioctl.h>
B. 函数原型 (Function Prototype)
c
int ioctl(int fd, unsigned long request, ...);
C. 参数说明 (Parameters)
| 参数 | 类型 | 说明 (FB 场景) |
|---|---|---|
fd |
int |
通过 open("/dev/fb0", ...) 返回的文件描述符。 |
request |
unsigned long |
控制命令 。这是 ioctl 的核心,它告诉驱动程序执行什么操作。FB 驱动定义了特定的命令宏。 |
... |
变长参数 | 通常是一个指针,指向用户空间定义的结构体,用于在用户和内核之间传递数据(获取/设置配置)。 |
常见的 Framebuffer request 宏:
| request 宏 | 描述 | 对应结构体 | 作用 |
|---|---|---|---|
FBIOGET_FSCREENINFO |
获取固定的屏幕信息(Immutable Screen Info)。 | struct fb_fix_screeninfo |
包含 VRAM 物理地址、映射长度、设备名称等。 |
FBIOGET_VSCREENINFO |
获取可变的屏幕信息(Variable Screen Info)。 | struct fb_var_screeninfo |
包含分辨率、色深、时序等可修改的参数。 |
FBIOPUT_VSCREENINFO |
设置可变的屏幕信息。 | struct fb_var_screeninfo |
根据用户设置的参数(如分辨率、色深)来配置显示。 |
D. 返回值 (Return Value)
| 返回值 | 说明 |
|---|---|
| 0 | 成功。 |
| -1 | 失败,并设置全局变量 errno。常见的错误是命令无效或参数结构体不匹配。 |
3. mmap 函数:映射 Framebuffer 内存
mmap 函数将 Framebuffer 的物理显存(VRAM)直接映射到应用程序的虚拟地址空间,允许用户通过指针直接读写像素数据,这是实现高性能绘图的关键。
A. 头文件 (Header File)
c
#include <sys/mman.h>
B. 函数原型 (Function Prototype)
c
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
C. 参数说明 (Parameters)
| 参数 | 类型 | 说明 (FB 场景) |
|---|---|---|
addr |
void * |
通常设为 NULL,让内核自动选择虚拟地址。 |
length |
size_t |
要映射的显存长度 。应通过 FBIOGET_FSCREENINFO 获取的 smem_len 字段来确定。 |
prot |
int |
保护权限。通常设为 `PROT_READ |
flags |
int |
映射标志 。必须使用 MAP_SHARED,因为所有进程的修改都需要反映到实际的显存中,并最终显示在屏幕上。 |
fd |
int |
通过 open("/dev/fb0", ...) 获得的设备文件描述符。 |
offset |
off_t |
映射在文件中的起始偏移量。对于 FB 设备,通常设为 0,从显存的起始位置开始映射。 |
D. 返回值 (Return Value)
| 返回值 | 说明 |
|---|---|
void \* |
成功,返回映射到进程虚拟地址空间的起始地址(即显存的起始地址)。 |
MAP_FAILED |
失败(等于 (void *)-1),并设置全局变量 errno。 |
核心步骤总结
一个典型的 Framebuffer 应用编程流程是:
- 打开设备:
fd = open("/dev/fb0", O_RDWR); - 获取固定信息:
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);(获取smem_len) - 获取可变信息:
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);(获取xres,yres,bits_per_pixel等) - 映射显存:
fbp = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - 绘图/操作: 使用
fbp指针直接读写像素。 - 解除映射:
munmap(fbp, finfo.smem_len); - 关闭设备:
close(fd);
📄 Framebuffer 绘图示例代码 (fb_test.c)
下面这段代码实现了以下功能:
- 打开 LCD 设备 (
/dev/fb0)。 - 获取屏幕参数(分辨率、位深)。
- 通过
mmap将显存映射到用户空间。 - 根据屏幕的位深(16bpp 或 32bpp)智能适配颜色格式。
- 在屏幕上绘制一个全屏红色背景 ,并在中间画一个蓝色的矩形。
您可以直接在您的 Linux 开发环境(Ubuntu)中保存为 fb_test.c,然后使用交叉编译器编译。
c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stdint.h>
int main()
{
int fp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
long int screensize = 0;
char *fbp = 0;
int x = 0, y = 0;
long int location = 0;
// 1. 打开 Framebuffer 设备
fp = open("/dev/fb0", O_RDWR);
if (fp == -1) {
perror("Error: cannot open framebuffer device");
return 1;
}
printf("The framebuffer device was opened successfully.\n");
// 2. 获取不可变屏幕信息 (FINFO) - 为了得到 smem_len (显存总大小) 和 line_length (跨度)
if (ioctl(fp, FBIOGET_FSCREENINFO, &finfo) == -1) {
perror("Error reading fixed information");
return 1;
}
// 3. 获取可变屏幕信息 (VINFO) - 为了得到分辨率 (xres, yres) 和 位深 (bpp)
if (ioctl(fp, FBIOGET_VSCREENINFO, &vinfo) == -1) {
perror("Error reading variable information");
return 1;
}
// 打印检测到的屏幕信息
printf("Display info: %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("Line length (stride): %d bytes\n", finfo.line_length);
// 4. 计算显存大小
// 虽然可以用 vinfo.xres * vinfo.yres * bpp / 8 计算,但使用 finfo.smem_len 更安全
screensize = finfo.smem_len;
// 5. 内存映射 (mmap)
fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0);
if ((long)fbp == -1) {
perror("Error: failed to map framebuffer device to memory");
return 1;
}
printf("The framebuffer was mapped to memory successfully.\n");
// 6. 绘图操作
// 遍历屏幕的每一个像素点
for (y = 0; y < vinfo.yres; y++) {
for (x = 0; x < vinfo.xres; x++) {
// 核心计算:计算坐标 (x,y) 在显存中的字节偏移量
// finfo.line_length 是每一行占用的字节数(可能包含对齐填充)
location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) +
(y + vinfo.yoffset) * finfo.line_length;
// 定义颜色 (R, G, B)
// 默认画红色背景
int r = 255, g = 0, b = 0;
// 如果在屏幕中间区域,画蓝色矩形 (200x200)
if (x >= (vinfo.xres/2 - 100) && x < (vinfo.xres/2 + 100) &&
y >= (vinfo.yres/2 - 100) && y < (vinfo.yres/2 + 100)) {
r = 0; g = 0; b = 255;
}
// 根据不同的位深 (BPP) 写入不同的像素格式
if (vinfo.bits_per_pixel == 32) {
// 32bpp: ARGB8888 或 BGRA8888
// 指针偏移到当前像素位置
*(fbp + location) = b; // Blue
*(fbp + location + 1) = g; // Green
*(fbp + location + 2) = r; // Red
*(fbp + location + 3) = 0; // Alpha (透明度)
}
else if (vinfo.bits_per_pixel == 16) {
// 16bpp: 通常是 RGB565 (5位红, 6位绿, 5位蓝)
// i.MX6ULL 的 LCD 经常是这种格式
unsigned short int t = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
*((unsigned short int*)(fbp + location)) = t;
}
else {
// 其他情况暂不处理 (如 24bpp)
// printf("Unsupported bpp: %d\n", vinfo.bits_per_pixel);
}
}
}
// 7. 解除映射并关闭文件
munmap(fbp, screensize);
close(fp);
printf("Drawing finished. Check your screen!\n");
return 0;
}
💡 核心知识点解析
finfo.line_length(Stride) 的重要性 :- 我们在计算
location时使用了y * finfo.line_length,而不是y * xres * bpp。 - 原因 :有些屏幕为了硬件对齐,每一行的末尾可能会有填充字节 (Padding) 。比如一行只有 1366 像素,但在显存里可能占用了 1400 像素的空间。使用
line_length是最安全、最标准的做法。
- 我们在计算
- RGB565 (16bpp) vs ARGB8888 (32bpp) :
- i.MX6ULL 的裸 RGB 接口通常连接 16位 或 24位 屏幕。Linux 驱动经常默认配置为 16bpp (RGB565)。
- RGB565 算法 :
((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)- 红色 8bit 丢弃低3位,变成 5bit,左移到最高位。
- 绿色 8bit 丢弃低2位,变成 6bit,放在中间。
- 蓝色 8bit 丢弃低3位,变成 5bit,放在最低位。
- 代码中增加了
if (vinfo.bits_per_pixel == ...)判断,这样无论您的屏幕配置如何,都能显示出正确的颜色,而不会变成花屏或噪点。