●从零理解 DSI 屏幕撕裂:一条数据流水线的故事
1. 整个系统在干什么
你的板子在做一件事:把 HDMI 输入的画面缩小后,显示在一块 MIPI DSI 小屏幕上。
数据流是这样的:
HDMI 源(笔记本) → rk628 芯片 → DSI 面板
720×720 (缩小到) 360×360
41.877 MHz 360×360 11 MHz
rk628 内部有一个缩放器(Scaler),负责把 720×720 的帧缩小成 360×360。缩放器输入侧跑 HDMI
时钟(41.877MHz),输出侧跑 DSI 时钟(11MHz)——这就是一切撕裂的根源。
---
2. 为什么要缩放?为什么时钟不一样?
HDMI 源输出的是 720×720@60fps,像素时钟 41.877MHz。你的面板只有 360×360 像素,它的
datasheet 规定像素时钟必须是 11MHz。如果让面板直接接收 720×720
的信号,它根本显示不了——分辨率不匹配,时钟也不匹配。
所以缩放器必须介入:用 41.877MHz 的速度读入一帧,用 11MHz
的速度缩放后写出到面板。两个速度不一样,缩放器内部有一个叫 FIFO(先入先出缓冲区)
的东西来缓冲数据。
读写比值是 41.877/11 ≈ 3.8:1,意味着缩放器每 1 个像素写出,就要读入约 3.8 个像素(其中
2.8 个被缩小算法丢弃了,因为 720→360 是 2:1 缩小)。
---
3. 撕裂是怎么发生的?
直观类比:想象两台跑步机并排放着,左边跑 41.877 km/h,右边跑 11
km/h。你在左边的跑步机上画一幅画,画完传给右边继续画。左边的人画得快,右边的人画得慢。如
果两边没有任何同步信号,右边的人可能在你画到一半就开始往面板上"发表"——结果就是:
面板显示的画面:
┌──────────────────┐
│ ← 第 N 帧内容 │ ← 上半部分正常
│ │
├──────────────────┤ ← 这就是"撕裂线"
│ ← 第 N+1 帧内容 │ ← 下半部分是下一帧
└──────────────────┘
在 DSI video mode 下,缩放器不需要等面板说"我准备好了"——它按固定的 60fps
节奏持续往面板灌数据。如果缩放器内部的 FIFO 在某一个瞬间恰好读完旧数据、塞入新数据的时候
和面板的扫描线重合,屏幕上就会出现新旧帧各占一半的撕裂。
轻微撕裂是因为 FIFO 大小足够深(缓存了若干行像素),新旧数据切换的时机绝大多数时候落在消
隐区(面板不显示的区域),偶尔被扫描线抓到。
---
4. Command Mode vs Video Mode
这是 DSI 协议的两种传输模式:
Video Mode(视频模式):
- DSI 控制器像 HDMI
一样,按固定时序(hfront/hsync/hback/vfront/vsync/vback)不停地发像素
- 面板被动接收,不受面板内部控制
- 优点:简单,不需要额外同步信号
- 缺点:不理会面板内部刷新状态,容易撕裂
Command Mode(指令模式):
- DSI 控制器只有收到 write_memory_start 指令时才发一帧数据
- 面板刷完一帧后通过 TE(Tearing Effect)引脚发信号:"可以发下一帧了"
- 优点:永不撕裂(严格同步)
- 缺点:需要 TE 引脚硬件连接
你一开始的 dsi,video-mode 走的是 video mode。去掉之后走 command mode。但你没有 TE
引脚——面板虽然内部配置了 TE,但 rk628 芯片收不到——所以 command mode
变成了"没有同步信号的盲发模式",撕裂仍然存在(只是位置变了)。
---
5. Blanking Porch 到底在干什么?
每显示一帧,面板都会经历这样的过程:
← visible area → ← 消隐 →
┌──────────────────────────────┐
Scan ──→│======= 显示区 =======│ HSync │←── 一行结束,回扫
└──────────────────────────────┘
hdisplay hfp/hsync/hbp
← visible area → ← 消隐 →
┌──────────────────────────────┐
Frame──→│======= 显示区 =======│ VSync │←── 一帧结束,回扫
└──────────────────────────────┘
vdisplay vfp/vsync/vbp
消隐区(Blanking) 就是面板不显示的区域——电子束(或数字扫描指针)从一行末尾回到下一行开
头、从一帧底部回到顶部的时间。
那 blanking porch 和撕裂有什么关系?
缩放器的 FIFO 每次从"旧帧→新帧"切换数据时,会产生一个"脏数据窗口"。如果这个窗口能和消隐
区对齐,就算新旧数据混在一起也不会显示出来——因为消隐区面板本来就不显示。
这就是我调 vback-porch / vfront-porch 的原理:
- 调大 vback-porch:让消隐区更长,把撕裂线推到消隐区里藏起来
- 调大 vfront-porch:消隐区位置移动,看撕裂线能不能被覆盖
- 但 vtotal = vactive + vfp + vsync + vbp 是固定的(由 clock / htotal / 60fps
决定),所以加大 back 就要减 front
- 最终目标:把撕裂线推到 vfp+vsync+vbp 覆盖的区域里,不让它在显示区出现
---
6. 为什么自测模式不撕裂?
rk628 的自测模式(Color Bar)是芯片内部生成纯色条纹,直接输出到 DSI
发送器——完全绕过了缩放器和 HDMI 接收器。没有跨时钟域问题,没有
FIFO,没有新旧帧切换——所以永远不撕裂。
这也反证了:你的 DSI PHY、lane
配置、面板本身的初始化都是对的。问题只在缩放器的跨时钟域同步上。
---
7. 为什么改了 hsync-active / vsync-active 撕裂位置会变?
缩放器内部有一个函数叫
calc_dsp_frm_hst_vst,它计算缩放器从输入帧的哪个位置开始读取数据。这个计算依赖于 src/dst
的 hsync/vsync 极性、htotal、vtotal、时钟频率等参数。
改变极性相当于改变了缩放器识别的"帧起点"。帧起点变了,FIFO
新旧数据切换的时机就变了,撕裂线的位置也跟着变了。这在调试上是一个信号:你对时序的任何微
调都在影响撕裂位置,说明你还没有找到精确的对齐点,但方向是对的。
---
8. 最终的解决方案分层
┌─────────────┬─────────────────────────────┬─────────────────┬────────────────────┐
│ 方案 │ 原理 │ 效果 │ 前提 │
├─────────────┼─────────────────────────────┼─────────────────┼────────────────────┤
│ 接 TE 线 │ 面板每帧结束发脉冲,主机严 │ 彻底消除 │ 需要硬件 TE 引线 │
│ │ 格同步 │ │ │
├─────────────┼─────────────────────────────┼─────────────────┼────────────────────┤
│ 调 blanking │ 让 FIFO │ 可消除肉眼可见 │ 需要精确计算/反复 │
│ │ 脏数据窗口落入消隐区 │ 撕裂 │ 调试 │
├─────────────┼─────────────────────────────┼─────────────────┼────────────────────┤
│ 改 dst │ 让输入输出时钟域成整数倍 │ 降低 FIFO │ 面板 datasheet │
│ clock │ │ 碰撞概率 │ 通常不允许 │
├─────────────┼─────────────────────────────┼─────────────────┼────────────────────┤
│ 双缓冲/帧缓 │ 写完一帧再读 │ 彻底消除 │ rk628 │
│ 冲 │ │ │ 硬件不一定支持 │
└─────────────┴─────────────────────────────┴─────────────────┴────────────────────┘
你的情况是:TE 没有,clock 不能改,只能调
blanking。这个调试过程本质是用消隐区去"盖住"撕裂线,反复调 vback/vfront/vsync
就是在移动盖子。
