3. 显示链路设计:双屏独立显示原理、帧缓冲机制与双屏合成方案

好,咱们进入第三章。这一章讲的是显示链路,说白了就是——屏幕上的像素到底是怎么画出来的?

双屏互动,听起来高大上,但底层原理其实很朴实。我当年第一次接触双屏项目时,以为要搞什么黑科技,结果发现核心就三件事:独立显示、帧缓冲管理、合成输出。搞懂这三样,双屏系统你就拿下了七成。

3.1 双屏独立显示原理

先问一个问题:仪表和中控,是不是两个独立的显示器?

答案是:物理上独立,逻辑上可以独立,也可以共享

在嵌入式系统里,双屏独立显示通常有两种架构:

  • 单SoC双显示控制器:一个芯片,两个显示接口(比如DSI0和DSI1)。每个接口独立输出视频流。
  • 双SoC方案:仪表一颗芯片,中控一颗芯片,通过以太网或LVDS通信。这种更贵,但隔离性好。

我个人习惯用单SoC方案,成本低,同步也容易。但要注意——两个显示控制器是独立的,但内存带宽是共享的。我在项目中遇到过一个问题:仪表和中控同时刷新高帧率动画,结果内存带宽爆了,画面卡成PPT。后来怎么解决的?给仪表分配固定带宽,中控用动态分配。嗯,这个坑你们以后大概率也会踩。

关键点:双屏独立显示,本质上是两个独立的显示时序发生器(Timing Controller)在并行工作。每个屏幕有自己的分辨率、刷新率、色彩格式。互不干扰,但共享系统资源。

3.2 帧缓冲(Framebuffer)机制

帧缓冲,英文叫Framebuffer。说白了就是——一块内存区域,专门用来存一帧画面的像素数据

你想想看,屏幕每秒刷新60次,每次刷新时,显示控制器就从这块内存里读数据,然后一行一行地推给屏幕。这就是帧缓冲最朴素的工作原理。

在Linux系统里,帧缓冲设备通常叫/dev/fb0/dev/fb1。每个fb设备对应一个显示层。双屏系统里,一般会有两个fb设备:

# 查看帧缓冲设备信息
$ fbset -fb /dev/fb0

# 输出示例
mode "1920x720-60"
    # D: 74.25 MHz, H: 67.5 kHz, V: 60 Hz
    geometry 1920 720 1920 720 32
    timings 13468 148 88 36 4 44 5
    accel false
    rgba 8/16,8/8,8/0,0/0
endmode

这里有个细节:geometry那一行,第一个1920x720是可视分辨率,第二个1920x720是虚拟分辨率。虚拟分辨率可以比可视分辨率大,用来实现滚动或双缓冲。

我的经验:双缓冲是帧缓冲的经典用法。一个缓冲在前台显示,一个在后台绘制。绘制完成后交换。这样不会出现撕裂。我曾经在项目里偷懒用了单缓冲,结果用户一滑动地图就撕裂,被测试怼得哑口无言。从那以后,双缓冲成了我的标配。

帧缓冲的典型操作流程:

  1. open("/dev/fb0", O_RDWR) —— 打开设备
  2. ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) —— 获取当前显示参数
  3. mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) —— 映射到用户空间
  4. 直接往映射的内存里写像素数据
  5. ioctl(fd, FBIOPAN_DISPLAY, &vinfo) —— 切换显示缓冲(双缓冲时用)

代码示例(简化版):

#include <linux/fb.h>
#include <sys/mman.h>

int fb_fd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo vinfo;
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);

// 计算一帧大小
size_t screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

// 映射帧缓冲
char *fbp = (char *)mmap(NULL, screensize, 
                         PROT_READ | PROT_WRITE, 
                         MAP_SHARED, fb_fd, 0);

// 画一个红色像素
*(fbp + 0) = 0x00;     // Blue
*(fbp + 1) = 0x00;     // Green
*(fbp + 2) = 0xFF;     // Red
*(fbp + 3) = 0x00;     // Alpha (if 32bpp)

munmap(fbp, screensize);
close(fb_fd);

注意:直接操作帧缓冲虽然高效,但不同平台的像素格式可能不同。有的用RGB565,有的用ARGB8888。写代码前一定要确认bits_per_pixelred/green/blue offset。我曾经在换平台时忘了改这个,结果画面颜色全乱套,排查了一整天。

3.3 双屏合成方案:SurfaceFlinger vs Wayland

帧缓冲搞定了,但问题来了——如果中控屏幕上同时显示导航、音乐、空调三个应用,怎么把它们合成一帧?

这就轮到合成器登场了。在汽车嵌入式领域,主流方案有两个:SurfaceFlinger(Android)Wayland(Linux)

3.4.1 SurfaceFlinger(Android系统)

SurfaceFlinger是Android的显示合成服务。它的工作流程大致是:

  • 每个应用把自己的UI渲染到独立的Surface
  • SurfaceFlinger收集所有Surface,按照Z-order排序
  • 用GPU或硬件合成器(HWC)把它们合成一帧
  • 输出到帧缓冲

双屏场景下,SurfaceFlinger支持多Display。每个Display有自己的合成链。比如:

// 伪代码示意
sp<IBinder> display1 = SurfaceComposerClient::getBuiltInDisplay(
    ISurfaceComposer::eDisplayIdMain);   // 中控屏
sp<IBinder> display2 = SurfaceComposerClient::getBuiltInDisplay(
    ISurfaceComposer::eDisplayIdHdmi);   // 仪表屏(通过HDMI)

我记得在做一个双屏项目时,遇到一个坑:仪表屏和中控屏的刷新率不同(仪表60Hz,中控30Hz)。SurfaceFlinger默认用主屏的刷新率同步所有合成。结果仪表屏出现微卡顿。解决方案是给仪表屏单独设置refreshRate,并启用独立VSync。

核心要点:SurfaceFlinger的优势在于生态成熟,应用层开发方便。但缺点是对底层控制力弱,定制化难度大。如果你做的是深度定制车机,可能会觉得它有点「重」。

3.4.2 Wayland(Linux系统)

Wayland是Linux下的新一代显示协议。它比SurfaceFlinger更轻量,也更灵活。

Wayland的核心思想是:每个客户端自己渲染自己的窗口,合成器(Compositor)只负责把各个窗口的缓冲区拼起来。没有X11那种复杂的C/S架构,也没有SurfaceFlinger那种厚重的Java层。

在双屏场景下,Wayland的典型配置:

# weston.ini 配置示例
[core]
shell=desktop-shell.so

[output]
name=DSI-1
mode=1920x720@60
transform=normal

[output]
name=DSI-2
mode=1280x480@60
transform=normal

每个output对应一个物理屏幕。应用可以通过wl_output接口获取屏幕信息,然后决定把窗口放到哪个屏幕上。

Wayland有个我很欣赏的特性——直接硬件合成。如果硬件支持,Wayland可以把多个客户端的缓冲区直接交给显示控制器做硬件叠加,完全不经过GPU。这在仪表这种对延迟敏感的场景下特别有用。

避坑指南:我曾经在一个项目里用Wayland做仪表显示,结果发现切换窗口时会有短暂黑屏。排查下来,是客户端在重新申请缓冲区时,旧缓冲区被提前释放了。解决方案是启用wl_buffer的release回调机制,确保新旧缓冲区平滑过渡。

3.4.3 方案对比

特性 SurfaceFlinger Wayland
系统平台 Android Linux(Yocto、Buildroot等)
合成方式 GPU + HWC硬件合成 GPU / 直接硬件叠加
双屏支持 原生支持多Display 通过多个output配置
延迟 中等(有Java层开销) 低(直接协议交互)
定制难度 高(需修改Android框架) 中(需熟悉Wayland协议)
典型场景 中控娱乐系统 仪表盘、Linux车机

我个人建议:如果你的项目是基于Android的车机,老老实实用SurfaceFlinger,别折腾。但如果你做的是仪表+Linux中控的混合架构,Wayland会是更好的选择。它轻、快、可控。

嗯,这一章的内容就到这里。显示链路设计,说白了就是搞清楚「像素从哪里来,到哪里去」。帧缓冲是地基,合成器是框架,双屏独立显示是目标。下一章我们会聊到更具体的交互设计——双屏之间的触控反馈和动画同步。到时候见。