全志H3上移植framebuffer驱动的完整示例
2026/4/2 19:44:45 网站建设 项目流程

在全志H3上点亮第一块屏幕:从零开始移植Framebuffer驱动

你有没有过这样的经历?手里的开发板通电后,串口输出一切正常,U-Boot顺利加载内核,系统也成功启动——可屏幕就是黑的。没有Logo,没有光标,甚至连一个像素都不显示。

如果你正在用全志H3(比如Orange Pi PC、NanoPi Fire等),这个问题很可能不是硬件坏了,而是framebuffer没起来

别急,这不是玄学问题,而是一个典型的嵌入式图形系统配置任务。本文将带你一步步“点亮”这块沉默的屏幕,完整还原我在某款工业面板项目中为H3移植framebuffer驱动的真实过程。不讲空话,只说实战。


为什么是 framebuffer?

在谈“怎么搞”之前,先回答一个问题:我们非得用 framebuffer 吗?

现在主流都在推 DRM/KMS,听起来更现代、功能更强。但现实是,在很多资源受限的设备上,你根本跑不动完整的图形栈。X11太重,Wayland又太复杂,连Qt都可能卡成幻灯片。

而 framebuffer 不一样。它就像裸机编程里的 GPIO 控制——直接、高效、可控。

当你只需要显示一张图片、画几个按钮、或者跑个简单的操作界面时,framebuffer 就是你最可靠的战友。它绕过了所有中间层,把显存暴露给你,让你可以像写内存一样往屏幕上“塞”像素。

更重要的是,它是后续引入 DirectFB、Qt Embedded 或轻量级 GUI 框架的基础。没有 fb0,什么都免谈。


H3 的显示架构:sunxi-display 是什么?

全志系列 SoC 并不像标准 ARM 架构那样使用通用 DRM 驱动。相反,它有一套自己的显示管理框架,叫做sunxi-display,有时也叫disp2

这个框架藏在内核源码的drivers/staging/sunxi/目录下,虽然标记为“staging”(不稳定),但在 H3 这种老平台上却是唯一能稳定出图的方案

它的核心组件包括:

  • TCON(Timing Controller):负责生成 LCD 所需的同步信号(HSYNC/VSYNC)、时钟和数据使能。
  • LCD控制器接口:连接TCON与外部LCD屏或HDMI PHY。
  • Framebuffer绑定机制:将一段内存区域注册为显示缓冲区,并交由TCON扫描输出。

整个流程就像这样:

[显存] ←→ [TCON] → [LVDS/HDMI] → [显示屏] ↑ 内核驱动初始化 ← 设备树配置

所以,我们的目标很明确:通过设备树告诉内核“我要在哪块内存里放图像”,以及“怎么把这块内存刷到屏幕上”。


第一步:确认你的内核支持 framebuffer

再完美的配置,也得建立在正确的编译选项之上。打开你的.config文件,检查以下几项是否启用:

CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FB=y CONFIG_FB_SUNXI=y CONFIG_DRM_SUN4I=n # 关键!避免与fbdev冲突 CONFIG_STAGING=y CONFIG_SUNXI_DISP2=y # 如果用了 disp2 模块

⚠️ 特别提醒:CONFIG_DRM_SUN4ICONFIG_FB_SUNXI不能共存。如果你发现/dev/fb0总是创建失败,八成是因为 DRM 抢占了显示资源。

如果这些选项不在配置中,你需要重新执行make menuconfig,路径大致如下:

Device Drivers ---> Graphics support ---> <*> Support for frame buffer devices [*] Allwinner sunXi family SoCs support [ ] DRM Support for Sun4i A10 and compatible (关闭它!)

保存后重新编译并烧写内核镜像。


第二步:编写设备树节点——让内核知道怎么配屏

这才是真正的“动手环节”。

假设你要接一块常见的 800x480 分辨率的RGB LCD屏,以下是关键的 DTS 配置片段。我会逐段解释每行的意义,而不是让你复制粘贴完还不知道发生了什么。

1. 声明默认 framebuffer 输出

这是最关键的一步。你得告诉内核:“嘿,我要用这块显存来当屏幕。”

/ { chosen { bootargs = "console=tty0 console=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 rootwait"; stdout-path = "serial0:115200n8"; framebuffer@0 { compatible = "simple-framebuffer"; allwinner,pipeline = "tcon0-lcd0"; // 数据流路径:TCON0 → LCD0 clocks = <&pll_de>, <&tcon_ch0>; // 显示引擎时钟源 width = <800>; height = <480>; stride = <3200>; // 每行字节数 = 800 * 4 (ARGB8888) format = "a8r8g8b8"; // 像素格式 status = "okay"; }; }; };

重点说明几个字段:

  • allwinner,pipeline:必须准确对应你的硬件链路。H3 上通常是tcon0-lcd0tcon0-hdmi
  • stride:非常重要!它不一定是width × bpp / 8。有些驱动要求对齐到32字节边界。这里我们按4字节对齐计算:800×4=3200。
  • format:常见有"x8r8g8b8"(32位,透明通道无效)、"r5g6b5"(16位)。务必与后续绘图代码匹配。

2. 启用 TCON 和 LCD 接口

&tcon0 { status = "okay"; }; &lcd0 { status = "okay"; compatible = "allwinner,simple-panel"; panel-timing { clock-frequency = <33000000>; // ~33MHz 像素时钟 hactive = <800>; // 有效水平像素 vactive = <480>; // 有效垂直像素 hfront-porch = <40>; // 行前肩 hback-porch = <88>; // 行后肩 hsync-len = <48>; // 行同步宽度 vfront-porch = <13>; // 场前肩 vback-porch = <32>; // 场后肩 vsync-len = <3>; // 场同步宽度 hsync-active = <0>; // 低电平有效 vsync-active = <0>; // 低电平有效 de-active = <1>; // DE 高电平有效 pixelclk-active = <0>; // 像素时钟下降沿采样 }; };

这些参数从哪来?查你所用LCD模组的规格书!例如一款名为AT070TN92的屏,其 timing spec 中会明确列出上述数值。

💡 小技巧:若无确切数据,可参考 Armbian 提供的boot/dtbs/sun8i-h3-*.dts中的配置进行微调。


第三步:验证驱动是否加载成功

烧录新内核和 dtb 后重启,进入系统第一时间执行:

ls /dev/fb* # 应该看到:/dev/fb0

如果没有,查看内核日志:

dmesg | grep -i fb # 或者 dmesg | grep -i display

理想输出应包含类似内容:

[ 2.123456] simple-framebuffer.0: [drm] fb0: simpledrmfbd dev [ 2.123500] Console: switching to colour frame buffer device 100x30

如果有错误信息如failed to find pipeline 'tcon0-lcd0',说明pipeline字符串拼错了;如果是clock not found,检查clocks引用是否正确。

一旦/dev/fb0出现,恭喜你,已经完成了80%的工作


第四步:写个小程序测试显存访问

下面这段代码是我每次调试必用的“红方块测试程序”。它不做任何依赖库的事,纯粹用系统调用验证 framebuffer 是否可用。

#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <linux/fb.h> #include <unistd.h> #include <string.h> int main() { int fd; struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; char *fbp = NULL; long size; fd = open("/dev/fb0", O_RDWR); if (fd == -1) { perror("无法打开 /dev/fb0"); return -1; } if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo)) { perror("读取变量信息失败"); close(fd); return -1; } if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo)) { perror("读取固定信息失败"); close(fd); return -1; } printf("分辨率: %dx%d, 位深: %d bpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel); size = vinfo.xres * vinfo.yres * (vinfo.bits_per_pixel / 8); fbp = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (fbp == MAP_FAILED) { perror("mmap 失败"); close(fd); return -1; } // 绘制红色矩形(适用于 ARGB8888) int x, y; for (y = 100; y < 200; y++) { for (x = 100; x < 300; x++) { long offset = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) + (y + vinfo.yoffset) * finfo.line_length; *(fbp + offset + 0) = 0x00; // B *(fbp + offset + 1) = 0x00; // G *(fbp + offset + 2) = 0xff; // R *(fbp + offset + 3) = 0xff; // A (仅影响某些合成器) } } // 强制刷新缓存(ARM平台尤其重要) __builtin___clear_cache(fbp, fbp + size); munmap(fbp, size); close(fd); printf("红色矩形已绘制完成。\n"); sleep(2); // 保持画面短暂可见 return 0; }

编译并运行:

gcc -o test_fb test_fb.c sudo ./test_fb

如果一切顺利,你应该能在屏幕上看到一个鲜红的矩形框!

🛠 调试提示:

  • 若出现花屏:检查bits_per_pixelformat是否一致;
  • 若偏移错位:确认line_length是否等于stride
  • 若无反应:尝试加msync()或使用O_SYNC打开设备。

常见坑点与应对秘籍

❌ 问题1:/dev/fb0根本不存在

排查清单
- 内核是否启用了CONFIG_FB_SUNXI
- 设备树中framebuffer@0是否被误删或拼错?
-status = "okay"写了吗?别忘了这句!

建议临时添加printkdrivers/video/fbdev/core/fbmem.c中的register_framebuffer()函数,看是否被调用。

❌ 问题2:屏幕闪一下就黑,或颜色异常

大概率是cache一致性问题。ARM处理器有数据缓存,你改了内存,但GPU看到的是旧数据。

解决办法有两个:

  1. 使用__builtin___clear_cache()主动清理(如上面代码所示);
  2. 或者在 mmap 后立即调用msync(fbp, size, MS_SYNC)

更好的做法是在内核中启用CONFIG_ARM_DMA_MEM_BUFFERABLE并确保 CMA 区域正确分配。

❌ 问题3:只能显示部分区域,边缘裁剪

这通常是因为stride设置不当。有些驱动会对行宽做对齐处理。比如你设了800*3(RGB888),但它实际用了800*4对齐。

解决方案:在设备树中显式指定stride,并与用户空间代码保持一致。


可以继续深入的方向

当你成功点亮第一帧画面后,下一步就可以考虑更高级的应用了:

  • 双缓冲机制:在内存中维护前后两帧,减少撕裂;
  • 背光控制:通过 GPIO 控制&backlight节点实现开关;
  • 开机Logo:利用logo/logo_linux_clut224.ppm替换启动图;
  • 集成Qt/E:设置QT_QPA_PLATFORM=linuxfb即可运行无窗口系统的Qt应用。

甚至可以把这个 framebuffer 当作视频播放器的输出端,配合 FFmpeg 解码直接渲染YUV数据。


最后一点经验分享

在整个过程中,我学到最重要的一课是:不要迷信别人的设备树

社区里流传的各种.dts文件,可能适配的是不同版本的uboot、不同频率的PLL,甚至是不同的电源管理模式。哪怕只是差了一个 bit,也可能导致初始化失败。

最好的方式是:

  1. 先拿官方 SDK 中的参考配置跑通;
  2. 再根据自己的硬件逐步调整 timing 参数;
  3. 最后固化成自己项目的专属 dtb。

记住,嵌入式开发没有“一键成功”,只有“一步步逼近真相”。

如果你也在折腾H3的显示问题,不妨试试上面这套方法。也许下一秒,那块沉默已久的屏幕就会为你亮起一抹红色。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询