ARM-驱动-09-LCD FrameBuffer

一、核心概念:Frame Buffer 是什么

裸机 vs Linux 的显示方式

方式 做法
裸机 直接向 LCD 控制器物理地址(如 0x56000000)写像素数据
Linux 通过 /dev/fb0 设备,用 mmap 映射显存到用户空间,再写虚拟地址

Linux 的内存保护机制禁止应用层直接访问物理地址,Frame Buffer 就是内核提供的合法通道。

mmap 的本质

复制代码
open("/dev/fb0")
    ↓
mmap()   →   用户空间拿到虚拟地址指针 p_mem
    ↓
写 p_mem   →   内核页表   →   真实显存物理地址   →   LCD 自动刷新

mmap 之后,写内存 = 写屏幕,不需要任何驱动调用,效率极高。


二、初始化流程(三步固定写法)

代码(来自 pro1/framebuffer.c 的 fb_init)

c 复制代码
int fb_init(void)
{
    // 第一步:打开设备
    int fd_fb = open("/dev/fb0", O_RDWR);

    // 第二步:获取屏幕参数
    ioctl(fd_fb, FBIOGET_VSCREENINFO, &info);

    // 第三步:映射显存
    int len = info.xres_virtual * info.yres_virtual * info.bits_per_pixel / 8;
    pfb = (unsigned char *)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
}

关键参数说明(fb_var_screeninfo 结构体)

字段 含义 例子
xres / yres 屏幕实际可见分辨率 800 / 600
xres_virtual / yres_virtual 显存虚拟分辨率(通常更大) 1024 / 600
bits_per_pixel 每个像素占多少位 32

为什么要用 xres_virtual 而不是 xres

显存实际宽度是 xres_virtual,不是屏幕可见宽度 xres。用错了偏移计算就会错位。

两个地方必须用 xres_virtual:① mmap 大小计算;② 像素偏移计算。

退出时释放

c 复制代码
void fb_deinit(void)
{
    munmap(pfb, len);
    close(fd_fb);
}

三、画点------所有绘图的基础

代码(pro1/framebuffer.c)

c 复制代码
void draw_point(unsigned short x, unsigned short y, unsigned int col)
{
    if ((x > info.xres_virtual) || (y > info.yres_virtual))
        return;   // 越界保护
    unsigned int *p = (unsigned int *)(pfb + (y * info.xres_virtual + x)
                                       * info.bits_per_pixel / 8);
    *p = col;
}

偏移公式推导

复制代码
显存是一维内存,按行存储:
第 0 行:像素(0,0) (1,0) (2,0) ... (xres_virtual-1, 0)
第 1 行:像素(0,1) (1,1) ...

像素(x, y) 的字节偏移 = (y × xres_virtual + x) × (bits_per_pixel / 8)
                                                  ↑
                                              把位转成字节,32位色 = 4字节

颜色格式(32位 ARGB)

c 复制代码
0xFF0000   // 红色
0x00FF00   // 绿色
0x0000FF   // 蓝色
0xFFFFFF   // 白色
0x000000   // 黑色
0xFFFF00   // 黄色

四、想换背景色怎么做

用 lcd_fill 填充整个屏幕

c 复制代码
// 把屏幕全部填成黑色(用来清屏)
lcd_fill(0, 0, 799, 599, 0x000000);

// 把屏幕全部填成白色
lcd_fill(0, 0, 799, 599, 0xFFFFFF);

// 函数原型(来自 framebuffer.h)
void lcd_fill(unsigned short x0, unsigned short y0,
              unsigned short x1, unsigned short y1,
              unsigned int color);

用 draw_bmp 显示图片背景(来自 mouse.c)

c 复制代码
// 把 bg.bmp 从 (0,0) 开始铺满屏幕
draw_bmp(0, 0, "./bg.bmp");

典型用法:切换背景时先清屏再画

c 复制代码
// main.c 里的写法
lcd_fill(0, 0, 799, 599, 0xffff);       // 先清屏
lcd_show_string(100, 200, 200, 50, 32, "hello", 0xffffff);  // 再显示文字
sleep(3);
lcd_fill(0, 0, 799, 599, 0xffff);       // 清屏(换背景)
lcd_show_string(100, 200, 200, 50, 32, "abcde", 0xffffff);  // 显示新内容

五、想显示字符串怎么做

函数原型(framebuffer.h)

c 复制代码
void lcd_show_string(unsigned short x,       // 起始 X 坐标
                     unsigned short y,       // 起始 Y 坐标
                     unsigned short width,   // 显示区域宽度(超出自动换行)
                     unsigned short height,  // 显示区域高度(超出截断)
                     unsigned char size,     // 字体大小:12/16/24/32
                     char *p,               // 字符串内容
                     unsigned int col);      // 字体颜色

示例

c 复制代码
// 在 (100, 100) 位置显示 "hello",字体 16,白色
lcd_show_string(100, 100, 200, 50, 16, "hello", 0xffffff);

// 在 (100, 200) 位置显示 "world",字体 32,红色
lcd_show_string(100, 200, 200, 50, 32, "world", 0xff0000);

字体大小对应像素

size 字宽 字高 适用场景
12 6px 12px 小字,密集信息
16 8px 16px 普通正文
24 12px 24px 标题
32 16px 32px 大标题

注意:字符堆叠问题

连续显示不同字符串时,如果不清屏,新字符串会和旧字符串叠在一起。原因是 lcd_showchar 里只画"有笔画"的点,不画背景。

解决方法:显示前先用 lcd_fill 清屏 (或用 copy_mem 局部恢复背景,见第七节)。


六、想显示图片怎么做

版本一:简单版(fb/fb.c,硬编码尺寸 120×120)

c 复制代码
int draw_bmp(int x0, int y0, const char *bmp_name)
{
    int fd = open(bmp_name, O_RDWR);
    unsigned char head[54] = {0};
    read(fd, head, sizeof(head));       // 跳过 54 字节 BMP 文件头

    for (j = 0; j < 120; j++) {
        for (i = 0; i < 120; i++) {
            unsigned char c[3] = {0};
            read(fd, c, sizeof(c));
            // BMP 存储顺序是 BGR,转换为 RGB
            unsigned int col = (c[2] << 16) | (c[1] << 8) | c[0];
            draw_point(i + x0, 120 - j - 1 + y0, col); // BMP 是倒序,需要翻转
        }
    }
}

// 调用:把图片显示在 (679, 0)
draw_bmp(679, 0, "./123.bmp");

版本二:通用版(mouse.c,自动读取宽高)

c 复制代码
// 解析 BMP 文件头,自动获取图片宽高
BitMapFileHeader file_head;
BitMapInfoHeader info_head;
read(fd, &file_head, sizeof file_head);
read(fd, &info_head, sizeof info_head);

// info_head.biWidth  → 图片宽度
// info_head.biHeight → 图片高度(正数 = 倒序存储)

BMP 文件头结构

c 复制代码
// 文件头 14 字节
typedef struct {
    unsigned char  bfType[2];    // "BM" 标识
    unsigned int   bfSize;       // 文件总大小
    unsigned short bfReserved1;  // 保留,必须为 0
    unsigned short bfReserved2;  // 保留,必须为 0
    unsigned int   bfOffBits;    // 像素数据相对文件头的偏移量
} BitMapFileHeader;              // 共 14 字节(需 #pragma pack(1))

// 信息头 40 字节
typedef struct {
    unsigned int   biSize;       // 信息头大小(40)
    int            biWidth;      // 图片宽度(像素)
    int            biHeight;     // 图片高度,正数=倒序存储,负数=正序
    unsigned short biPlanes;     // 位面数,恒为 1
    unsigned short biBitCount;   // 每像素位数:24=RGB,32=ARGB
    unsigned int   biCompression;// 压缩类型:0=不压缩
    ...
} BitMapInfoHeader;

为什么 BMP 是上下颠倒的?

BMP 标准规定图像数据从底部向上存储(最后一行数据在文件最前面),所以显示时需要 height - j - 1 翻转 y 坐标。


七、想显示鼠标并让鼠标移动怎么做

核心思路:移动前先恢复背景

鼠标移动 = 在新位置画鼠标图片,但旧位置的鼠标覆盖了背景,需要先恢复。

复制代码
每次移动:
① copy_mem(旧x, 旧y, 16, 16)  ← 从 p_save 恢复旧位置的背景
② draw_bmp(新x, 新y, mouse.bmp) ← 在新位置画鼠标

save_fb 和 copy_mem(来自 mouse.c)

c 复制代码
// save_fb:把当前整个显存复制到 p_save(程序开始时调用一次)
int save_fb()
{
    unsigned int *pdst = (unsigned int *)p_save;
    unsigned int *psrc = (unsigned int *)p_mem;
    for (j = 0; j < info.yres_virtual; j++)
        for (i = 0; i < info.xres_virtual; i++)
            *pdst++ = *psrc++;
}

// copy_mem:从 p_save 把指定矩形区域恢复到 p_mem(每次移动前调用)
int copy_mem(int x0, int y0, int w, int h)
{
    for (j = y0; j < y0 + h; j++)
        for (i = x0; i < x0 + w; i++)
            *(pdst + j * xres_virtual + i) = *(psrc + j * xres_virtual + i);
}

内存分配(fb_init 里额外 malloc)

c 复制代码
// p_save 是额外分配的内存,和显存等大,用来保存背景快照
p_save = malloc(info.xres_virtual * info.yres_virtual * info.bits_per_pixel / 8);

读取鼠标坐标(来自 mouse.c,使用绝对坐标设备)

c 复制代码
int fd = open("/dev/input/event2", O_RDWR);  // 触摸屏/绝对鼠标

struct input_event event;
read(fd, &event, sizeof event);

if (event.type == EV_ABS) {
    if (event.code == ABS_X)
        x = event.value / 65535.0 * 800;   // 原始值 0~65535 → 屏幕 0~800
    else if (event.code == ABS_Y)
        y = event.value / 65535.0 * 600;   // 原始值 0~65535 → 屏幕 0~600
}

读取相对鼠标(来自 fb/main.c,使用 /dev/input/mice)

c 复制代码
int fd = open("/dev/input/mice", O_RDWR);

char data[3] = {0};
read(fd, data, 3);
// data[0]:按键状态(9=左键按下,10=右键按下,8=左键松开)
// data[1]:X 轴位移(有符号,负=向左)
// data[2]:Y 轴位移(有符号,负=向上)

x += data[1];   // 累加位移
y += data[2];
if (x < 0) x = 0;    // 边界保护
if (x > 799) x = 799;

注意:save_fb 的局限性

save_fb 只保存调用那一刻的画面。如果之后又画了字符串,再 copy_mem 会把字符串也擦掉,因为快照里没有这些字符串。全屏重绘(清屏 + 重画所有内容)是最简单但效率最低的解决方案。


八、基本图形绘制(pro1/framebuffer.c)

函数速查表

函数 用途 参数
lcd_fill(x0,y0,x1,y1,col) 填充矩形区域 左上角、右下角坐标、颜色
lcd_drawline(x1,y1,x2,y2,col) 画直线 起点、终点坐标、颜色
lcd_draw_rectangle(x1,y1,x2,y2,col) 画矩形边框 左上角、右下角坐标、颜色
lcd_draw_Circle(x0,y0,r,col) 画圆形边框 圆心坐标、半径、颜色
lcd_showchar(x,y,ch,size,mode,col) 显示单个字符 坐标、字符、大小、叠加模式、颜色
lcd_show_string(x,y,w,h,size,str,col) 显示字符串 坐标、区域宽高、大小、内容、颜色
lcd_shownum(x,y,num,len,size,col) 显示整数(高位0不显示) 坐标、数值、位数、大小、颜色
lcd_showxnum(x,y,num,len,size,mode,col) 显示整数(可控制高位0) 同上+模式

lcd_showchar 的 mode 参数

c 复制代码
mode = 0;   // 非叠加:背景色为黑色(字符区域黑底)
mode = 1;   // 叠加:背景透明,只画有笔画的点,不覆盖底色

九、三色条纹测试(最简单的全屏渲染,验证 FB 是否工作)

来自 pro1/main.c 的 DEBUG 版本,验证 Frame Buffer 初始化是否正常:

c 复制代码
for (j = 0; j < info.yres; j++) {
    for (i = 0; i < info.xres; i++) {
        if (j < info.yres / 3)
            draw_point(i, j, 0xff);         // 上 1/3:蓝色
        else if (j < info.yres * 2 / 3)
            draw_point(i, j, 0xff00);       // 中 1/3:绿色
        else
            draw_point(i, j, 0xff0000);     // 下 1/3:红色
    }
}

十、编译运行

bash 复制代码
# PC 上测试(需要 root 权限,Ctrl+Alt+F2 切到纯文本终端)
gcc fb.c -o fb_test
sudo ./fb_test

# 交叉编译(开发板运行)
arm-linux-gnueabihf-gcc fb.c -o fb_test
# 传到开发板,直接运行(开发板不需要切终端)
./fb_test

# pro1 多文件编译
arm-linux-gnueabihf-gcc main.c framebuffer.c -o main

PC 运行注意 :图形界面(X11/Wayland)独占显存,必须先 Ctrl+Alt+F2 切换到 TTY 纯文本终端后再运行,否则没有效果或权限报错。


十一、常见问题

问题 原因 解决
运行没有任何显示 在图形界面下运行 Ctrl+Alt+F2 切换到 TTY
颜色显示不对 bpp 不是 32,颜色格式不同 ioctl 查看 bits_per_pixel,RGB565 需要位操作转换
图片显示上下颠倒 忘记翻转 y 坐标 height - j - 1 + y0 代替 j + y0
图片颜色偏 BMP 是 BGR 存储,直接用成了 RGB 用 `(c[2]<<16)
字符显示堆叠 没清屏就切换内容 显示前先调用 lcd_fill 清屏
鼠标移动有残影 没有恢复旧位置背景 移动前先调用 copy_mem 恢复背景
相关推荐
乐迪信息1 小时前
乐迪信息:智慧港口AI防爆摄像机实现船舶违规靠岸自动抓拍
大数据·人工智能·算法·安全·目标跟踪
winxp-pic1 小时前
图片校正软件 操作说明及算法介绍
算法
wayz111 小时前
Day 6 编程实战:决策树与过拟合分析
算法·决策树·机器学习
SuperEugene1 小时前
Vue3 配置文件管理:按模块拆分配置,提升配置可维护性|配置驱动开发实战篇
前端·javascript·vue.js·驱动开发
永不复还1 小时前
Windows 驱动开发(四)—— IRP Pending
windows·驱动开发
ฅ ฅBonnie2 小时前
vLLM 推理后端简介
人工智能·python·算法
贾斯汀玛尔斯2 小时前
每天学一个算法--堆排序(Heap Sort)
数据结构·算法
programhelp_2 小时前
ZipRecruiter CodeSignal OA 2026|最新真题分享 + 速通攻略
数据结构·经验分享·算法·面试
liuyao_xianhui2 小时前
map和set_C++
java·开发语言·数据结构·c++·算法·宽度优先