上个月调完最后一块板子,总算把MIPI CSI-2摄像头采集链路和MIPI DSI屏幕输出,在Xilinx Zynq-7035上同时跑通了。这套东西从看协议文档到实际出图,前后折腾了大概三周,期间把"MIPI DPHY"这几个字来来回回翻了个遍。回头看,真正卡人的反而不是Verilog代码本身,而是对物理层那几个电气参数、时序切换逻辑的理解,以及Xilinx FPGA上不同实现路线的取舍。
这篇文章就以我手头这个项目作为案例,把MIPI DPHY接口在Xilinx FPGA上的实现思路、选型依据、硬件布局、调试方法完整记录下来。适合谁看?一种是刚到类似项目、正在纠结"FPGA怎么接MIPI摄像头/屏幕"的工程师;另一种是已经有FPGA基础、想搞清楚DPHY物理层到底在传输什么东西的朋友。文章不打算从零教Verilog怎么写,而是直接给出一套经过验证的决策框架和避坑清单,让你在动手前就能把方案定稳。
1. 项目背景:为什么要用FPGA去碰MIPI DPHY
1.1 这个项目要解决什么实际问题
先讲清楚这个案例的"任务边界"。我这边是一套工业视觉检测设备的前端采集和中转显示,摄像头是MIPI CSI-2接口的CMOS Sensor,屏幕是MIPI DSI接口的工业屏,中间的处理核心是一块Xilinx Zynq-7000系列的FPGA。任务需求拆开有四条:
- 把CSI-2的RAW10图像数据逐帧采集到DDR里;
- 在FPGA逻辑里做一次图像处理,这里做的是坏点校正和直方图均衡;
- 处理完的图像通过DSI接口送到屏幕上实时显示;
- 整个链路端到端延迟控制在两帧以内。
这套需求如果后端不是要求"做处理+低延迟",直接用一颗带MIPI CSI/DSI的SoC平台(比如RK、全志的方案)会轻松太多。但工业现场往往有一些自研算法、Sensor兼容、非标分辨率的要求,SoC的ISP链路反而不够灵活,这时候FPGA的价值就体现出来了。
我在查资料的时候也看到,不少团队并不是非要用FPGA,而是手上的SoC平台点不亮某些屏、接不了某些Sensor,才被迫回到FPGA层面去兜底。所以这篇文章虽然主要讲FPGA,但很多思路对RK平台点MIPI屏、Linux DRM调试竖屏改横屏也有参考价值,后面我会专门延伸。
1.2 从热词里看到的共性困惑
在准备这篇案例分析之前,我特意翻了一圈和MIPI相关的热门搜索问题,发现最密集的问题集中在几类。
第一类是"FPGA怎么实现MIPI"。这个问题其实很笼统,不同人问的可能是完全不同的东西。有的人问的是物理层D-PHY怎么出信号,有的人问的是CSI-2协议怎么解包,还有人问的是"我用GTP能不能直接接摄像头"。问题笼统的背后,是对协议分层没有概念——MIPI CSI-2/DSI本身就是分层的:物理层D-PHY负责比特流,协议层负责包结构和高层语义。如果连自己要解决的是哪一层都没分清,看再多资料也找不对方向。
第二类是"帮忙看一下波形/时序",比如"mipi信号波形"、"mipi时钟波形"这种搜索。这类说明人已经到调试阶段了,示波器上能看到波形,但不确定波形是否合格。这部分我在后面的调试章节会给出实际判断标准。
第三类是"mipi接口引脚定义图"、"mipi布线"、"st7701s mipi"。一看就是硬件工程师在器件选型阶段,需要确定管脚复用、电平标准、PCB走线方式的工具型问题。这类问题看似基础,实际最容易翻车,因为DPHY的电气特性和普通LVDS、LVCMOS接口完全不一样。
1.3 先对号入座:你的项目属于哪一类DPHY应用
在选方案之前,至少要对号入座。我在案例里把DPHY应用按方向与速率分成几类,方便大家对照:
- CSI-2(摄像头输入):从图像Sensor接收数据,常见分辨率从VGA到4K不等。
- DSI(显示输出):往MIPI屏幕发送图像数据和命令,常见应用于手机屏、工业屏、车载屏。
速率上,VGA级别分辨率用2 lane/1Gbps就够;1080p@60的CSI-2一般需要4 lane,每lane在1Gbps到1.5Gbps之间。如果你的需求是4 lane×1.5Gbps以上,直接绕过SelectIO自研方案,优先考虑高速收发器或者外部桥接芯片。
我后来和几个做外包的同行聊,发现很多人最后走的都是"CPU/SoC + FPGA"的组合:SoC负责MIPI协议,FPGA负责算法;或者FPGA只做预处理,输出RGB/LVDS给后端SoC。真正需要在FPGA上直接出MIPI信号的场景并没有想象中多,但既然要出,方案就得定稳。
2. DPHY物理层到底在传输什么:写代码前的必修课
2.1 Lane结构:时钟与数据的DDR关系
MIPI DPHY最容易被忽视的一点是:它的时钟通道永远是DDR模式的。也就是说,在HS高速传输期间,Clock Lane发送的是差分时钟,数据Lane在时钟的上升沿和下降沿都会输出数据。这条规则决定了你在FPGA侧不能用简单的单沿逻辑去采样,要么用ISERDESE2这类DDR解串原语,要么让bit clock跑到数据率的两倍频率然后单沿采样——后者在FPGA里通常不现实。
一组DPHY链路一般是一条Clock Lane加一条或多条Data Lane。CSI-2和DSI都支持1、2、4条数据Lane的配置。多Lane之间采用"每Lane字节交错"的方式传输协议数据,所以接收侧需要先把每个Lane的串行比特流解成字节,再按Lane数量做字节级的对齐重组。
Clock Lane在HS模式发送的DDR时钟和数据是源同步关系,也就是说,时钟跳变沿和数据有效沿之间有一个严格的训练相移,接收侧完全可以用"时钟采样数据"的方式恢复比特流。我在FPGA里一般把"采样时钟相位"当成一个可调参数,通过IDELAYE2或者MMCM的相移能力去搜索最佳采样点。这块后面会详细说。
2.2 LP和HS:一根线上的两种人格
DPHY的每条Lane(包括Clock Lane)实际上共享同一对物理引脚。它有两种工作模式:LP(Low Power)和HS(High Speed)。
- LP模式:单端信号,逻辑电平在0到1.2V附近,速率低,主要用于传输命令、状态切换和总线进出;
- HS模式:差分信号,摆幅典型值只有200mV左右,共模电压约200mV,速率高,用于真正的图像数据流。
这两种模式在硬件上的信号特征是截然不同的。LP状态机里有一组定义:LP-00、LP-01、LP-10、LP-11,分别由P/N两条线上的电平组合而成。HS传输的启动序列SoT(Start of Transmission)就是先看到LP-11到LP-01的跳变,然后进入HS-0,再发同步序列00011101(也就是0x1D),最后才跟着包数据;传输结束时进入HS-Trail,回到LP-11。
这段描述和我在示波器上看到的波形完全对得上。如果你在调试时发现某条Lane一直"卡死"在某个电平,或者HS进去了出不来,多半就是LP状态机与HS时序切换没配对。
2.3 几组必须提前列出来的参数
协议文档里参数很多,但工程上真正要记住的其实就几组。以线速率(Bit Rate)为核心的口诀是:
- 1Gbps/lane时,bit time是1ns,DDR时钟频率是500MHz;
- 数据Lane之间的skew要求非常苛刻,同一时钟沿驱动的各Lane数据,在接收端允许的时间偏差通常在0.2个UI以内。按1Gbps算,就是200ps量级;
- HS差分摆幅典型200mV,接收端需要能识别最低约70mV到100mV的有效信号,这意味着用普通LVDS输入阈值(约±100mV)勉强可行,但余量很小。
我自己在硬件规格书阶段就把这些数值列成一张表,投板前和Layout工程师逐项核对。后面调试时,这张表直接决定了先从物理层查还是先从协议层查,省了无数冤枉路。
3. 基于Xilinx FPGA的DPHY实现:三条路线怎么选
3.1 原厂IP核、SelectIO自研、高速收发器
在Xilinx平台上做MIPI DPHY,大概有三条路线。
第一条是使用Xilinx官方的MIPI D-PHY IP核。这个IP核支持特定器件系列,覆盖UltraScale/UltraScale+为主,对7系列的支持比较有限。它在里面封装了物理层的收发功能,但协议层(CSI-2/DSI包解析)还得自己写。我在Zynq-7035上没有直接用官方IP,主要原因是器件支持不顺,而且我需要对采样相位做更灵活的干预。
第二条是用7系列HP Bank的SelectIO原语自己搭D-PHY收发。ISERDESE2、OSERDESE2、IDELAYE2、ODDR这些原语组合起来,确实能做到1Gbps/lane级别的收发链路。这条路线我最终采纳了,因为它能完全掌控采样相位和链路训练逻辑,便于对接非标Sensor或者低分辨率屏幕。缺点也很明确:工作量最大,时序收敛全靠自己。
第三条是用GTP/GTH等高速收发器。不过有个前提:GTP/GTH的物理层是CML电平,和MIPI DPHY的HS电平不直接兼容,通常需要在外部加电平转换芯片,或者用AC耦合加偏置的方式把电平拉到收发器可识别的范围,链路协商和LP信号处理也要在外部完成。这个方案适合4K高帧率场景,PCB复杂度和BOM成本都明显更高。
3.2 我最终选择的RX链路结构
在7系列FPGA上,一条完整的MIPI D-PHY接收Lane,电路结构大致是这样的:
- 差分输入先从板级进入FPGA的HP Bank引脚;
- HP Bank内部通过IBUFDS将差分信号转成单端逻辑,保留差分输入特征;
- 数据进入IDELAYE2做180度范围内的细调相,这是我用来搜索眼图中心的关键工具;
- 再用ISERDESE2做DDR解串,将高速串行数据转成8位并行数据;
- 并行数据进入一个自建的bit alignment状态机,搜索SoT同步序列(0x1D),完成bit slip;
- 多条Lane汇合后做lane alignment,再把字节流喂给上层的CSI-2或DSI协议解析模块。
这里有个细节非常容易被忽略:ISERDESE2在DDR模式下解出的并行数据,和实际比特流之间的对应关系是"对齐后"才成立的。你必须在接收端先跑一个训练过程,找到正确的8bit相位,这个动作就是bit slip。如果发现解出来的包头永远是乱的,先检查bit slip有没有真正锁住,而不是急着怀疑协议解析代码。
3.3 为什么我给TX端加了一颗外置PHY
RX我用SelectIO硬扛下来了,但TX(DSI到屏幕)我选择了一个混合方案:FPGA内部做DSI协议数据包的组织和像素流转换,把并行RGB/命令数据交给一颗支持MIPI DSI输出的外置PHY芯片,由它完成真正的D-PHY物理信号驱动。
原因是:DSI到屏幕这条链路需要同时处理LP命令和HS视频流。如果全部在FPGA里用普通IO模拟,LP信号的时序精度、HS差分信号质量都很难保证,尤其屏幕那头还涉及读回状态寄存器,需要真正的双向LP通信。外置PHY把最麻烦的物理层隔离出去,FPGA只需要把协议逻辑写对,整体风险大幅下降。
如果你想在FPGA里全自研DSI输出,必须同时具备三块能力:并行像素转字节、DSI包封装(含短包/长包、ECC/CRC计算)、以及物理层的LP/HS状态机控制。这个工作量比只做RX接收要大得多,所以我最终给出的组合是"FPGA做协议,外置PHY做物理承载",实测下来也是最稳的。
4. 案例核心:CSI-2摄像头采集链路的设计与实现
4.1 Sensor配置与Lane速率估算
拿到Sensor之后,第一步不是写代码,而是翻开datasheet确认输出格式和速率。我这个项目用的Sensor是RAW10格式,分辨率1920×1080@30fps。可以先算一下基本吞吐:1920×1080×30×10bit,约622Mbps。
如果用4条Data Lane,每Lane大约155.5Mbps,这在SelectIO能力范围内绰绰有余;改成2条Lane,每Lane约311Mbps,也依然可行。最终我选了4 Lane,原因并不是带宽不够,而是给后续升级留余量。这里一定要把格式算清楚,很多人看到"1Gbps/lane"就说"每秒能传1Gbit图像",实际上RAW10、RAW12、YUV422编码后的有效数据率差异非常大,包开销(长度头、校验、消隐区)也会占掉一部分带宽。
Sensor初始化方面,CSI-2的Sensor一般通过I2C配置寄存器,输出是连续的MIPI数据流。FPGA侧需要做的是等待链路稳定后,从数据流里识别帧起始(FS)、帧结束(FE)、行起始(LS)和行结束(LE)这几个短包,然后才开始取数。行消隐期间(HBlank/VBlank)数据Lane不会有有效像素,但物理层仍然在跑,解包模块必须能正确处理空闲状态。
4.2 字节解包与像素重组
CSI-2的包结构是:先发32bit packet header,里面包含8bit Data Type、16bit Word Count(WC)、8bit ECC;后面跟着WC个字节的有效数据;最后是16bit CRC。对RAW10来说,4个像素被打包成5个字节。这是从字节流恢复像素时的关键:pixel[9:0]需要按位拆散到5个字节里。我的做法是先写一个位拼接模块,把连续5字节重排成4个10bit像素,再去做后续的图像处理。
如果解出来的像素颜色不对、出现"红绿蓝乱序",多半是Byte-to-Pixel的映射反了,或者Lane重排顺序和Sensor手册中对不上。CSI-2规定Lane0是基准Lane,其他Lane可以任意映射,但多数Sensor默认按物理顺序输出。调试时我习惯用一块纯色板先做白平衡,一旦发现色偏,基本就能快速定位是不是Lane映射或者位序问题。
下面是一段结构示意,展示RAW10位拼接模块的核心思路,不是完整代码,但方向是对的:
// 每5个输入字节,产出4个10bit像素 // 输入:byte_stream[7:0],同步信号byte_valid // 输出:pixel_out[9:0],同步信号pixel_valid reg [39:0] shift_buf; always @(posedge clk) begin if (byte_valid) begin shift_buf <= {shift_buf[31:0], byte_stream}; if (byte_cnt == 4) begin pixel_out[0] <= shift_buf[9:0]; pixel_out[1] <= shift_buf[19:10]; pixel_out[2] <= shift_buf[29:20]; pixel_out[3] <= shift_buf[39:30]; pixel_valid <= 1'b1; end end end4.3 帧同步与DDR缓存衔接
Zynq平台下,我习惯把解包后的像素数据打上hsync/vsync/den信号,转成标准视频流送入VDMA或者自研的帧缓存控制器。这样做的最大好处是:后续所有图像处理模块、显示输出模块都可以复用同一套视频时序接口,不需要关心源头是MIPI还是别的接口。
帧同步要注意跨时钟域处理。像素时钟和MIPI接收端的恢复时钟不是同一个域,需要用异步FIFO做缓冲,并且把FS/FE信号也同步到输出侧,用来复位帧计数器和行计数器。我在项目里还额外加了一个"帧失步检测"逻辑:如果超过100ms没有检测到新的FS包,就触发报警并重新初始化接收链路。这个功能在连续运行验证中帮我抓到了好几次Sensor偶发丢帧的现场。
5. 案例核心:DSI屏幕点亮与显示方向的处理
5.1 DSI命令模式与视频模式的选择
DSI屏幕有两种基本工作模式:命令模式(Command Mode)和视频模式(Video Mode)。命令模式需要屏幕带GRAM,主机通过写命令刷新局部区域,适合低功耗静态画面;视频模式则类似传统RGB接口时序,主机端持续地向屏幕推像素流,刷新率由主机控制。
我用的工业屏同时支持两种模式,但因为业务需要实时显示检测画面,我选了视频模式。视频模式下的时序参数(HFP/HBP/HSYNC/VFP/VBP/VSYNC)和传统VESA时序本质是一回事,只是把这些参数封装进DSI包的blanking包(BLLP)里发送。FPGA端只需要保证自己生成的视频时序参数和屏幕规格书一致,屏幕就能正常显示。
5.2 让FPGA的VTC时序匹配屏幕要求的porch
这里特别想讲一个工程细节:同一颗屏幕,竖屏和横屏的时序参数往往完全不一样。有的屏幕物理分辨率是1080×1920,你以1920×1080去驱动它,显示方向不对是小事,更多时候是把屏幕直接驱动到异常状态,出现闪屏、半屏、花屏。
正确做法是:先查屏幕规格书里的"display resolution"方向和推荐的porch值,再把FPGA里的VTC(Video Timing Controller)参数改成和它一致。我的经验是写一组寄存器,开放给软件侧动态修改这些值,这样换屏或者改分辨率时不用重新综合整个工程。这在产线换屏验证时非常实用。
5.3 竖屏改横屏背后的数据重映射逻辑
热词里有一条"mipi dsi drm竖屏改横屏显示",这更多是Linux DRM子系统的话题。在FPGA/SoC协同的项目里,竖屏改横屏可以有两个层次的实现方式:一是Linux DRM层面通过rotation属性让驱动做扫描方向旋转,这需要SoC的显示控制器和内存带宽支持,适合有操作系统的平台;二是在FPGA内部做旋转。
如果在FPGA内部做90度或270度旋转,本质上是一个图像转置操作,必须先把整帧数据缓存进DDR,然后按转置后的地址顺序读出来送给DSI。这个操作的存储带宽代价大概是正常扫描的两倍,而且需要精确控制行缓存分块。对带宽紧张的项目,另一个取巧的办法是"方向感知显示":屏幕物理上固定为横屏,但内容渲染时主动用横屏坐标系,省掉旋转步骤。这在实际项目里往往比死磕旋转更省资源。
6. 调试实录:从波形到图像,一步步定位问题
6.1 第一眼先看HS时钟是否干净
拿到板子第一次上电,我习惯不先接Sensor的完整数据,而是给Sensor发配置后直接抓Clock Lane波形。看两个指标:一是HS模式时的时钟差分摆幅是否足够、频率是否稳定;二是从LP进入HS的跳变过程是否符合SoT时序。如果时钟波形都不对,数据Lane基本不用往下查了。
示波器上MIPI HS波形长什么样?差分探头挂到Clock Lane的P/N上,正常情况下能看到一个稳定的差分方波,差分幅度在200mV峰峰值左右,频率等于DDR时钟频率。比如1Gbps数据率时是500MHz。如果幅度偏低到100mV以下,或者波形拖着明显的过冲/振铃,先怀疑PCB走线阻抗和端接,不要急着调FPGA里的相位。
6.2 三大经典问题:花屏、无图、闪屏
说一下这三大问题我在实际项目里是怎么定位的。
无图:先看链路是否建立SoT。用ILA监测解串模块的sync_locked信号,如果一直拉不起来,检查Sensor是否出图、配置是否正确、时钟相位是否在眼图中心。现场我一般先把IDELAYE2的tap值全扫一遍,找出最佳相位区间,然后取中间值。这个扫描过程可以写成一个小状态机,自动完成。
花屏:花屏大概率是字节对齐或Lane对齐出了问题。如果图像像被打乱一样,先查Header里WC解出来是否合理。如果WC是乱的,查bit slip;如果WC是对的但像素内容乱,再查Lane映射顺序和Byte-to-Pixel位序。
闪屏:闪屏通常不是物理层问题,而是显示端时序参数或数据带宽不足。DSI视频模式下,如果porch参数和屏幕要求的对不上,屏幕会周期性闪烁或出现滚动条。另外,如果帧缓存DDR带宽不够,也会在某个固定位置出现撕裂现象。
6.3 高速信号在FPGA内部怎么观测
很多人会问:MIPI数据率那么高,FPGA内部的ILA采样率根本跟不上,怎么办?我的做法是在解串模块后面加一个"整包抓拍"逻辑:把进入协议解析的字节流和包头信息缓存到一块BRAM里,触发后把整包数据dump出来,通过JTAG或者AXI接口读回上位机分析。这样虽然看不到每个bit的波形,但能非常高效地定位协议层的字节错位、降序错误、CRC错误。
真正要看清模拟波形,还是得靠示波器。如果你手边没有差分探头,还有一个土办法:用两个普通探头分别挂到P和N,然后用示波器的数学通道A-B近似差分信号。注意探头的GND要尽量短,否则测量结果会有很大的振铃噪声,容易误导判断。
| 测试项目 | 判断标准 | 常见异常 |
|---|---|---|
| HS时钟频率 | 稳定在DDR时钟频率(如500MHz) | 频率偏低或漂移,检查Sensor配置 |
| HS差分摆幅 | 约200mV峰峰值 | 摆幅过低,查阻抗和端接 |
| HS起始序列 | LP-11变LP-01后进入HS-0 | 起始序列异常,查LP状态机 |
| 数据Lane抗偏斜 | 各Lane间skew小于0.2UI | 相差过大,查PCB等长 |
| 同步序列0x1D | 解串后能稳定检测到 | 检测不到,查bit slip和相位 |
7. 硬件层面对FPGA时序的隐性影响:PCB设计协同
7.1 阻抗、等长、过孔:DPHY布线的几个硬性要求
DPHY的差分对差分阻抗通常要求100Ω(有些规范按85Ω评估),走线层最好是完整参考平面,避免跨分割。等长方面,同一对差分线P/N之间的长度差尽量控制在5mil以内;数据Lane之间、数据Lane与时钟Lane之间的长度差,在有FPGA相位调节兜底的情况下,可以放宽到15~20mil,但越短越好。
AC耦合电容的位置也有讲究。放在靠近连接器端时,后面接FPGA的走线要保持完整参考;放在靠近FPGA端时,连接器到电容之间的走线也是完好差分。我在项目中习惯把100nF的0402电容放在距连接器5mm以内,这样既能隔直流,又能尽量减少高速段的断点。电容选型要留意ESR和自谐振频率,尽量选高频特性好的MLCC。
7.2 端接与共模偏置:LVDS口接收MIPI信号的老话题
如果你没有用外置PHY,而是直接用FPGA的LVDS接收MIPI HS信号,共模偏置是需要自己搭的。MIPI HS的共模电平约200mV,而7系列HP Bank的LVDS_25输入共模要求是1.2V左右,直接用LVDS标准接收会偏。常见的解决方法是AC耦合后,在FPGA输入端用两颗电阻从1.2V分压出一个约200mV到400mV的偏置,让共模落在LVDS接收器的线性区。这个偏置电阻网络必须并一个小电容滤波,否则噪声会直接进到采样沿上,导致误码率飙升。
7.3 FPGA与PCB开发的几个扯皮点
做FPGA的人容易忽略PCB,做PCB的人容易把FPGA当黑盒。实际协作中,我总结出几个必查项:
- pin assignment时,差分P/N不能接反,HP Bank的VCCO电压要和接口电平匹配;
- DPHY Lane所在的Bank内时钟资源是否够,ISERDESE2需要用到MMCM、PLL和BUFIO,引脚分配时要提前留好这些资源;
- 不要在差分线上加过大容性负载的ESD保护器件,某些ESD器件的结电容会把1Gbps信号的上升沿磨圆,导致眼图大幅劣化;
- 如果板上还有Sensor,Sensor的MCLK时钟源要离FPGA不远,走线尽量短且包地,否则很容易把干扰耦合进MIPI链路。
这些点如果等板子回来再发现,改版周期至少两到三周。在我这个项目里,因为前期checklist逼得紧,这些问题基本都在投板前提前规避掉了,后面调试过程少受了很多罪。
跑通这套链路之后,我最大的体会是:MIPI DPHY在FPGA上能不能稳定工作,物理层的余量远比协议逻辑重要。协议层出了问题,ILA和仿真就能定位;物理层出了问题,很多时候只能靠耐心和示波器一点点熬出来。如果你正准备接一个MIPI项目,建议先把第2节和第7节里的参数列成一张checklist,在选型阶段和Layout阶段各过一遍,能省下后面一半的调试时间。换一个思路说,MIPI这套东西本质上没有太多高深的算法,难就难在它把协议、半导体工艺、板级信号完整性全部绑在了一根差分线上,任何一个环节抠不细,最后都会反馈到屏幕上那一团乱糟糟的像素里。