
1. 为什么放着现成桥接芯片不用非要FPGA手撸DSI1.1 项目背景一块MIPI屏把整个方案卡住了去年做一块图像处理板卡视频源进来之后要走缩放、叠加OSD然后再送出去显示。主控用的是Xilinx Artix-7逻辑资源本来就被图像算法吃掉大半最后的显示输出却遇到了麻烦——客户指定了一块10.1寸、1280x800的MIPI DSI接口LCD接口上没有并行RGB也没有LVDS只有一组MIPI DSI的差分对。刚接到需求时我第一反应是上桥接芯片FPGA输出28bit RGB加时钟桥接芯片负责转成MIPI DSI信号比如TC358775这类方案FPGA端只需要把像素时钟和DE信号送对就行MIPI协议完全不用碰。但仔细一算账就发现问题桥接芯片单价不低供货周期也不稳定而且客户有屏参数后期要改的需求桥接芯片的寄存器一旦固化在EEPROM里换屏就得重新烧录配置。后来干脆决定直接在FPGA里做MIPI DSI的发送链路从像素输入到差分输出全部自己写。这条路折腾了大概三周踩了不少坑但最终点亮了屏幕而且后续换屏只需要改配置文件逻辑代码一行不动。这篇文章就是把这套方案的完整思路、关键计算和排错过程梳理出来。1.2 直驱DSI这条路风险点比想象中多决定自己写的时候我低估了一个问题Xilinx的7系列FPGA并没有官方的D-PHY物理层IP核更没有MIPI DSI的完整控制器。第三方IP倒是存在但价格不低还要花时间评估不如直接手撸。实际动手后才发现FPGA直驱DSI屏主要面临三个层面的问题第一是协议层。MIPI DSI的数据格式包括短包、长包、各种事件包、CRC、ECC校验数据是怎么组织的视频模式下每行信号什么时候发、发多长这些完全不看协议手册是搞不定的。第二是物理层。D-PHY物理层在高速模式下是差分小摆幅信号标准摆幅只有200mV左右FPGA的LVDS输出摆幅有350mV电平标准并不完全一样。实际项目里很多直接用LVDS硬驱动DSI屏的案例短距离内确实能工作但这属于“电气层灰色操作”不是规范做法后面会细说。第三个问题是调试手段局限。DSI信号是高速差分串行信号普通逻辑分析仪直接量不了示波器也得带宽足够才能看见。很多情况下只能靠屏幕现象倒推问题这在调试阶段非常考验耐心。1.3 什么样的项目适合自己写DSI如果只是做一两个样机验证或者项目周期非常紧买桥接芯片是最省事的选择。但如果产品要做大批量、BOM成本敏感或者屏幕型号可能有变更FPGA直驱的优势就体现出来了省掉一颗桥接芯片换屏时改配置文件即可甚至还能在FPGA里顺便做背光PWM、触摸IC控制这些杂活。另外如果你手头的FPGA是高云这类带D-PHY硬核的型号自然优先用硬核如果只有Xilinx 7系列那就得接受电气层的妥协并控制好PCB走线长度。2. 吃透MIPI DSI的时序轮廓不然代码无从下手2.1 D-PHY物理层HS和LP是两个世界MIPI DSI的物理层基于D-PHY每条lane是一对差分线P/N模块会有一对时钟lane和一到四对数据lane。数据lane的传输分成两种模式HSHigh Speed和LPLow Power。HS模式下差分线上跑的是高速串行数据速率可以达到数百Mbps甚至Gbps级别负责传输图像像素流和大块数据。LP模式下P和N两线是独立的单端信号电平一般为0到1.8V用来传输低速率控制信号比如进入高速模式前的握手序列、总线转向等。在FPGA里实现时HS模式可以直接用差分输出缓冲器来驱动LVDS电气特性虽然不完全等于D-PHY但短距离内问题不大。LP模式就麻烦一些因为FPGA引脚在差分输出时无法同时作为两个独立的单端信号来驱动。一个常见的妥协方案是用差分缓冲器的三态控制让输出变成高阻再通过外部电阻网络把lane拉成LP状态。这个做法在低速握手阶段基本够用但不是完整的D-PHY实现。如果你的应用只是video mode单向发数据很多屏实际工作流程是上电后屏侧保持LP状态FPGA给完复位和初始化命令后直接切HS模式持续送像素流LP状态只在开始时用外部电路凑合一下即可。如果要做command mode需要频繁读写屏内部寄存器甚至读回tearing effect信号那就必须认真对待LP状态和BTA总线转向复杂度会明显上升。2.2 DSI数据包短包和长包搞清楚谁是谁DSI链路上传输的基本单元是包包分为短包和长包。短包总长度固定4字节第一个字节的高2位是虚拟通道号低6位是数据类型DT接着2字节是数据载荷最后1字节是ECC校验码。短包适合传输命令、同步事件比如VSync、HSync事件包就是短包。长包则由三部分组成4字节包头、N字节数据载荷、2字节CRC校验。包头里同样有虚拟通道号、数据类型以及16bit的字计数WC注意WC的单位是字16bit不是字节。数据载荷之后就跟着CRC。对于视频模式最关键的数据类型码这么记就够了0x01VSync Start事件包0x11VSync End事件包0x21HSync Start事件包0x31HSync End事件包0x3ERGB888像素流长包还有几个命令包是初始化时要用的0x05DCS短写命令无参数0x15DCS短写命令带一个参数0x39DCS长写命令带多个参数这里有个容易混淆的地方0x29这类值是DCS层面的命令编号比如0x29是Display On而0x39是DSI包层面的数据类型两者不是一回事。发送Display On命令时包头的数据类型应该是0x05载荷数据是0x29。2.3 视频模式到底在传什么DSI屏按刷新方式分为命令模式和视频模式。命令模式需要屏幕内部有显存FPGA把图像数据写入屏内RAM屏幕自己负责刷新适合手机屏。视频模式则没有显存FPGA必须持续不断地把像素流送过来一旦断流屏幕就闪或者黑。视频模式又分三种非突发同步脉冲模式、非突发同步事件模式、突发模式。我在项目里用的是非突发同步事件模式这种模式的特点是每行开始不发HSync脉冲而是先发一个HSync Start事件包然后直接跟像素流长包。行与行之间的blanking时间发送空包或者进入LP状态。将video mode的时序在帧层面理解就是这样的结构一帧开始先发VSync Start事件包然后是若干行数据一帧结束发VSync End事件包。每一行内部则是HSync Start事件包加像素流长包。屏的驱动控制器会根据这些事件包自己恢复出行场同步逻辑驱动面板的栅极和源极。这里要特别注意屏厂规格书会给出HSync Start和VSync Start对应的时序参数有的屏同时要求发End事件包有的只认Start这个没有统一规律一定要看具体驱动IC的需求。我踩过一个坑换了块屏之后只发Start事件结果画面滚动加上End事件包就正常了。2.4 一个容易被忽视的细节EOTp和包间间隔MIPI DSI规范里有个可选的EoTpEnd of Transmission packet在HS传输结束时可以选择发送。并不是所有屏都校验这个但有些屏如果不发EOTp最后一个包的数据会出现偶发丢失表现为屏幕偶发性横纹。反过来有些屏如果发了EOTp反而工作异常屏的寄存器里通常有开关可以配置。遇到这类问题优先查初始化序列里EOTp相关的设置再决定是加还是停。包与包之间的间隔也不能太小。如果连续两个包之间只有几个时钟周期的间隔有些屏来不及切换状态会导致后续整个包被丢弃。我在状态机里每发完一个包至少插入4个字节时钟周期的idle时间。3. 时钟树设计一上来就算错了数据率3.1 从屏参到像素时钟不要只拿分辨率乘刷新率很多初学者算像素时钟时直接1280×800×60得出61.44MHz这个数字在实际工程里是偏小的。正确算法必须把行场blanking都算进去。以我做的10.1寸1280x800屏为例规格书给出的典型时序是Hactive1280HFP48HBP80HSYNC32Vactive800VFP3VBP33VSYNC10。算一下行总数 1280 48 80 32 1440帧总数 800 3 33 10 846像素时钟 1440 × 846 × 60 ≈ 73.1MHz所以最后Vivado工程里的pixel clock是73.1MHz不是61.44MHz。差了将近20%如果按错误值设计行场时序对不上屏要么画面偏移要么直接不亮。3.2 lane速率和串行时钟计算过程要清晰有了像素时钟接下来要算每条数据lane需要跑多高的速率。这里的关键点是像素时钟是连续跑的但DSI的像素数据只在Hactive期间发送。为了让像素数据能够在发送窗口内传完lane的峰值速率必须按Hactive期间的需求来设计。公式是每lane最低速率 像素时钟 × 每像素位数 ÷ lane数。这块屏是RGB888每像素24bit4条数据lane算出来每lane最低速率 73.1MHz × 24 ÷ 4 ≈ 438.6Mbps考虑到包开销、事件包、blanking间隔实际设计时留10%余量取整到480Mbps比较稳妥。接下来是FPGA内部时钟的分配。OSERDESE2做8:1串行化时工作模式是DDR意思是串行时钟CLK的每个上升沿和下降沿都输出一个bit所以串行数据率 2 × CLK频率。反推串行时钟CLK 480Mbps ÷ 2 240MHz并行时钟CLKDIV CLK ÷ 4 60MHz也就是说每个lane的字节吞吐率是60MB/s正好对应480Mbps的串行速率。OSERDESE2在240MHz的CLK下工作每个CLK周期输出2bitDDR模式下8bit并行数据在一个CLKDIV周期内全部发出。时钟lane上的差分时钟频率等于串行数据率的一半也就是240MHz。这就对应了MIPI DSI里时钟lane和数据lane频率的固定关系时钟lane频率 数据lane bit率 ÷ 2。3.3 PLL/MMCM怎么分配这三个时钟FPGA内部需要有三个主要时钟73.1MHz的像素时钟、60MHz的字节时钟、240MHz的串行时钟。其中60MHz和240MHz必须保持固定的相位关系否则OSERDESE2在DDR模式下会采错bit。我的做法是用一个MMCM同时产生这些时钟。VCO频率取1200MHz这样240MHz是5分频60MHz是20分频73.1MHz虽然不能从1200MHz直接分频得到但可以让MMCM用两个PLL级联或者直接给像素时钟单独用一个小PLL。不过更简单的做法是视频时序发生器本来就在字节时钟域工作像素时钟可以不单独产生直接用60MHz字节时钟驱动视频时序逻辑时序参数以字节时钟为单位换算一下就行。这个方案在工程上更容易实现因为OSERDESE2的CLKDIV就是60MHz视频时序逻辑和打包逻辑都跑在60MHz跨时钟域环节直接省掉了。唯一要注意的是60MHz时钟下的行场周期计数器要按字节时钟重新折算Hactive 1280像素在60MHz下依然是1280个周期因为像素速率本来就是73.1MHz而字节时钟60MHz慢一些中间要靠FIFO缓冲。这里想提醒一下如果嫌麻烦也可以把字节时钟设成83.3MHz对应lane速率666Mbps这样一行像素数据能在更短时间内发完blanking时间更充裕。但时钟频率越高对PCB和信号完整性要求越严除非屏幕对blanking要求特别苛刻否则没必要。4. FPGA架构落地模块划分与串行化实现4.1 顶层数据流从像素到差分线的完整通路整个DSI发送链路在FPGA里的数据流按模块拆分大概是这样的视频时序发生器产生行场同步信号、数据有效信号和RGB像素数据送入行缓冲FIFO。DSI打包状态机从FIFO读取像素数据按照video mode的事件时序拼装成DSI数据包。打包好的字节流进入串行化模块由OSERDESE2把并行字节转换成高速串行bit流最终通过差分输出缓冲器送到屏的lane上。模块之间的数据宽度在60MHz字节时钟域内统一按8bit设计。每四个字节时钟周期OSERDESE2能发出一字节正好匹配480Mbps的串行速率。整个链路最容易被忽视的是FIFO。因为像素时钟73.1MHz和字节时钟60MHz不同频两者之间必须有异步FIFO缓冲。FIFO深度我用了2KB1280像素×3字节约3.8KB其实偏小但因为我用了双缓冲模式一行像素一边写入一边发送只要写速度比读速度快就不会溢出。实际跑下来的结论是4KB深度更稳妥特别是后续要加OSD叠加时行数据量会变多。4.2 打包模块VSS/HSS事件包和像素包怎么拼视频模式下DSI打包模块的核心状态机要在一帧内完成这些动作IDLE等待帧开始发送VSS短包挂VSync Start数据载荷为0x0001发送VSE短包部分屏需要挂VSync End数据载荷为0x0001进入行循环每行开始发HSS事件包然后从FIFO读出整行像素拼成像素流长包发送行与行之间插入BLLP空包或者LP状态直到行时序走完像素流长包的拼法是这样4字节包头先发出去包头里数据类型是0x3EWC是1920个16bit字因为1280像素×24bit÷16bit等于1920后面依次跟3840字节的像素数据最后是2字节CRC。注意WC的单位是字。如果这里搞错把3840填进WC屏幕收到的数据长度就多了一倍画面必定错乱。4.3 OSERDESE2配置8:1串行化的具体细节Xilinx 7系列的OSERDESE2原生支持DDR模式下的8:1串行化。配置要点是DATA_RATE设为DDRDATA_WIDTH设为8SERDES_MODE设为MASTERCLK接240MHz串行时钟CLKDIV接60MHz字节时钟。并行数据从D1到D8输入串行输出时D1是最先发出的bit。这里有个非常关键且容易踩坑的地方MIPI DSI的数据传输是LSB first也就是每个字节的最低有效位最先发送。所以打包模块输出的字节必须满足“bit0对应D1”的映射关系否则图像显示出来是莫名的条纹或噪点。我在调试过程中发现图像颜色错乱查了很久最后就是这个问题打包逻辑按MSB first组包字节里最高位去了D1串行发送出来的bit顺序和MIPI要求的顺序相反屏端解析出来的像素数据全是乱的。调整方法也简单把字节bit倒序后再送入OSERDESE2即可。差分输出端用OBUFDS把OSERDESE2的单端输出转成差分对。这里还有一个LP控制的trickOBUFDS的三态控制脚T在拉高时差分输出是高阻配合板上的外部上下拉电阻网络可以把lane拉成LP状态。初始化阶段用这种方法发送LP唤醒序列进入HS模式后把T拉低正常发送高速数据。如果你的板子没做外部偏置网络LP状态完全不受控这可能就是你初始化命令发不出去的直接原因。4.4 时钟约束和综合要点这条链路在Vivado里综合时不需要什么特殊约束但有一点建议加对240MHz的串行时钟创建时钟约束确保综合器在做时序分析时把OSERDESE2的CLK和CLKDIV之间的相位关系算清楚。我遇到过一种诡异现象仿真全对上板后图像有时候正常有时候花。后来发现是MMCM的CLKOUT相位配置问题CLK和CLKDIV两个输出之间没有成倍数关系导致OSERDESE2在某些corner下建立时间不足。解决方案是在MMCM配置里对CLKOUT加固定相位偏移让CLK的边沿正好落在CLKDIV时钟周期的中部保证数据采样裕量。具体值取决于你的VCO频率和分频比没有统一答案但Vivado的MMCM配置界面可以直接调每调一版跑一遍时序报告看OSERDESE2相关路径的WNS最差负时序裕量只要为正就基本稳了。5. DCS初始化序列从屏厂例程到FPGA状态机5.1 屏厂给的C代码到底在干什么MIPI DSI屏内部有一颗显示驱动IC上电后默认处于Sleep模式需要主控通过DSI接口发一串初始化命令配置像素格式、显示方向、gamma、输出极性等参数最后开启显示。屏厂通常会提供一段MCU的初始化代码长这样Init_Sequence[] { 0x11, // Sleep Out 0x00, // 延时120ms 0x3A, 0x77, // 设置像素格式为24bit 0x00, // 延时10ms 0xB0, 0x03, 0x80, ... // 厂商私有寄存器配置 0x00, // 延时10ms 0x29, // Display On };这些命令看起来简单但在DSI链路上传输时要按DSI的包格式重新组织不能直接拿0x11、0x3A这些字节就往DSI TX里塞。5.2 命令转包的规则把屏厂初始化序列转成DSI包的规则其实不复杂无参数的命令比如Sleep Out0x11、Display Off0x28、Display On0x29用0x05类型短包发送2字节数据载荷里高字节填0低字节填命令编号。比如Sleep Out就是0x0005? 不对是数据载荷0x0011低字节是0x11。带一个参数的命令比如Set Pixel Format0x3A带参数0x77用0x15类型短包发送数据载荷的布局是低字节为参数高字节为命令编号。也就是0x773A这种写法。带多个参数的命令用0x39类型长包发送包的数据载荷第一个字节是命令编号之后才是命令参数。这里最容易搞错的就是字节顺序。DSI包头的字节序和DCS命令载荷的字节序是两回事前者按大端排列后者按特定规则排。我第一次写打包模块时没仔细看命令参数全部错位屏自然没有任何反应。5.3 状态机设计要能处理延时FPGA不像MCU那样按顺序执行代码初始化过程必须用一个状态机控制。我的实现是一个表驱动状态机初始化命令存在Block RAM里每一项记录命令类型、参数个数、数据值、延时值。状态机依次读取并发送遇到延时项就启动计数器等待。状态划分大概是这几种初始等待等屏上电稳定一般100ms写命令状态发送一个完整的DSI命令包长短包分别处理延时状态等待指定毫秒数完成状态所有命令执行完毕进入视频模式存命令表用Block RAM而非逻辑寄存器一个好处是换屏时只需要重新生成coe文件另外可以存几百条命令不占LUT资源。5.4 初始化序列里的隐性陷阱初始化序列最大的坑不是命令本身而是延时。我在调试中遇到过一个非常折磨的问题初始化命令发送后屏幕偶尔能亮偶尔黑屏而且时间节点完全随机。后来对比屏厂例程才发现Sleep Out之后的延时要求是120ms但我为了赶时间只等了50ms。屏幕驱动IC内部有个DCDC升压电路从Sleep状态恢复需要足够时间稳定时间不够就把后续命令忽略了且不会报任何错。另一个坑在复位时序。有的屏要求RESX引脚先拉低至少5ms再拉高然后等5ms才能发第一条命令。如果FPGA上电后立即开始初始化屏的复位还没完成后面所有命令都会被丢弃。解决思路是初始化状态机第一步先等至少10ms同时控制RESX引脚的IO在适当时机翻转。初始化完成不等于立即有画面。很多驱动IC在Display On命令发出后需要几十毫秒甚至更长时间内部数模模块才能锁定如果马上开始发像素数据画面可能先是雪花然后慢慢正常。建议在Display On之后再加一个50ms延时再切视频模式这个小技巧可以避免很多解释不清的闪屏问题。5.5 ECC和CRC是否真的要做短包的ECC和长包的CRC在DSI规范里都是必选字段但在实际项目中不少屏对这两个校验非常宽松填0也能正常工作。我最初验证时懒得算全填0大部分屏可以点亮。但换到另一块屏时就遇到了教科书级的场景黑屏示波器看着波形完全正常初始化序列也逐字节核对没问题折腾了两天最后发现是CRC全零导致屏端直接丢弃了初始化长包。从那以后我的建议是不要偷懒ECC和CRC必须实现。ECC只针对短包计算的是包头前3字节的汉明码实现逻辑很小CRC针对长包数据按MIPI规范的CRC-24多项式逐位计算在60MHz下消耗的资源也不多。如果不想自己查多项式去NXP或ST的公开参考实现里找现成的Verilog代码都行改一改接口就能用。这里分享一个我的做法初始阶段可以先用全零CRC把链路跑通确认其他功能都正常后再把CRC逻辑加上去。这样分工排查遇到的问题会少很多。6. 上板排错黑屏、闪屏、花屏的完整排查链路6.1 现象一完全黑屏示波器看差分对波形正常但屏幕无反应这是最常遇到的初装故障。当示波器已经看到差分对上有明显的高速信号屏幕却还是黑的问题大概率不在物理层而在初始化命令没有被屏正确接收。排查链路如下首先确认初始化状态机到底有没有跑完。在Vivado里挂ILA抓初始化状态机的状态寄存器看看是卡在延时状态还是已经跑完。如果卡住不动多半是计数器设计问题比如延时计数器的位宽不够或者时钟没起来。如果状态机跑完了接着抓TX侧发送的字节流与屏厂序列逐字节比对。这里要重点检查三件事短包的数据类型和载荷顺序、长包的WC值、CRC计算是否开启。任何一个错误屏都会选择静默丢包表现出来就是黑屏。另一个常见原因是虚拟通道号对不上。FPGA侧发送的包VC字段默认是0绝大多数屏默认也是0但有些屏内部设置为通道1或2这种情况需要看屏的寄存器配置或者尝试发命令时把VC改一下。如果这些都没问题就需要怀疑屏的上电时序。用示波器看RESX引脚和电源上电的先后顺序对照规格书里power on sequence的要求。我遇到过一次FPGA配置完成后立即拉高RESX但板的电源监控在FPGA逻辑起来之后才把屏的VCI供给起来导致屏在RESX拉高时根本没上电整个初始化全部无效。解决方法是让屏的电源由独立的IO控制上电稳定后再释放RESX。6.2 现象二有波形但不是DSI信号或者时钟lane上波形频率不对如果示波器看到的波形确实在跳但频率和计算的串行速率差得很远比如设计的240MHz时钟示波器看到的却是60MHz周期的pattern这种一般不是DSI的HS数据而是LP状态信号。LP状态是低速单端信号看起来像方波频率很低很容易和HS数据区分。产生这种问题的原因通常是OSERDESE2没有使能OBUFDS一直处于三态lane上的波形全部来自外部上下拉电阻的充放电。这时候回到FPGA内部排查确认OSERDESE2的OCLK和CLK是否都正确连接T控制信号初始化时是否拉低。很多方案里T控制信号被接成了固定高电平等于把差分输出永久关掉了。还有一个很隐蔽的问题OSERDESE2的复位信号RST。复位期间输出是确定的低电平如果RST一直不释放串行数据根本出不来。我在调试时用ILA抓复位信号发现它被某个逻辑错误地拉住了去掉之后波形立刻正常。6.3 现象三屏幕亮了但画面颜色错乱画面能出来说明DSI链路基本是通的问题大概率出在像素数据格式和物理层位序上。颜色错乱有三个层次需要区分第一层是RGB顺序反了。红色显示成蓝色绿色正常这就是RGB和BGR的swap。通常不需要动FPGA代码在初始化命令里找一个厂家私有的寄存器设置RGB order即可。有的屏默认RGB有的默认BGR换屏后一定要重新确认。第二层是每个字节内bit顺序反了。之前提过MIPI DSI是LSB firstOSERDESE2的D1首先发送字节的最低位。如果打包逻辑按MSB first组装屏幕显示出来是像噪点一样的彩色横条纹完全看不出真实图像。第三层是像素内字节顺序。对RGB888来说一个像素3字节MIPI规定的是字2字节为单位字节序要符合“低字节在前”还是“高字节在前”取决于具体实现。最有效的排查方式是用纯色测试图发一帧纯红、纯绿、纯蓝的图片看屏幕显示出来是什么颜色就能反推是哪一层的顺序错了。6.4 现象四画面闪烁、滚动、横纹闪烁和滚动本质上都是帧同步或者行同步信息没被屏正确恢复。先说滚动。画面能显示但整体上下滚动说明VSync信号和屏幕内部刷新不同步。排查的重点是一帧数据里有没有正确发送VSS事件包VSE事件包是否按屏的要求发送。在非突发同步事件模式下部分屏要求VSS和VSE都必须存在缺一个就会出现垂直滚动。横纹或者水波纹则多半是行方向和blanking不够。屏的驱动IC在每行数据之间需要一定时间去做锁存、极性反转如果BLLP时间太短栅极驱动就来不及稳定表现出来就是横纹。这时的调整方式是增大HFP或者HBP值让行blanking变长直到画面干净。还有一种被忽略的情况是LP状态和HS状态切换过于频繁导致屏的电源波动。特别是在行blanking期间如果每一行都退出HS进入LP屏的控制逻辑反复恢复锁定会加剧闪烁。解决方法是行间发BLLP空包不退出HS模式只在帧间或者初始化阶段才退出。6.5 关于测量工具的个人体会MIPI DSI的HS信号差分摆幅只有200mV左右普通探头很难精确测量差分管脚直接量还可能损伤信号。有条件的话用差分探头配合至少1GHz带宽的示波器否则看到的就是被衰减到失真的波形根本定位不了问题。我最初的调试没有差分探头就只能靠屏的现象和ILA内部信号推断效率低很多。后来借到差分探头几个关键问题都很快验证了。如果你的实验室没有这个条件建议把精力更多放在FPGA内部逻辑的排查上内部信号抓准了能解决大部分问题。7. 关于FPGA直驱DSI屏的边界条件与几条实践经验7.1 什么情况下建议收手老老实实用桥接芯片FPGA直驱DSI屏这条路并不是在所有场景下都适用。我的判断依据有几点分辨率超过1080p、刷新率60Hz以上单lane速率需求接近1.5Gbps甚至更高的时候普通SelectIO模拟D-PHY的电压摆幅、上升沿速率、抖动余量都非常勉强这时候就不要再硬撑了专用D-PHY PHY芯片或者桥接芯片是更可靠的选择。需要频繁读屏端寄存器特别是做产测或者需要读tearing signal来同步刷新的场景建议用带D-PHY IP的FPGA比如高云等国产型号它们有完整的D-PHY物理层封装LP和BTA都能原生支持。板卡空间极度受限、差分走线长度超过10cm或者屏和FPGA分布在两块板子上通过FPC连接信号完整性风险会急剧上升。这种情况下要么把DSI PHY芯片放在屏端附近要么就用LVDS转DSI的桥接方案。7.2 PCB设计上能给这条链路带来很大帮助的细节FPGA直驱DSI的成败电气设计占了一半。几个关键细节值得特意叮嘱差分对P/N必须等长走线走线阻抗控制在100欧差分阻抗。MIPI DSI和LVDS的差分阻抗要求一致layout时可以直接参考LVDS规则。走线长度越短越好我实际测试下来5cm以内配合400-500Mbps的速率没有问题超过10cm屏幕偶发闪烁的概率明显上升。如果不得不走长线每lane速率要往低设计或者选择更高的字节时钟来提高每行的发送效率给信号恢复留出更多时间。FPGA IO bank的VCCO必须按屏的IO电平要求设置通常是1.8V。千万别贪方便用3.3V去兼容D-PHY在1.8V下的信号完整性和3.3V完全不同直接驱动会造成电平不匹配。另外所有的DSI lane建议串联33欧姆到47欧姆的电阻放在FPGA端可以抑制反射对波形质量有明显改善。这个电阻不要省特别是走线长度不可控的情况下。7.3 调试和验证阶段的几条经验先用最低成本的小尺寸屏把链路跑通再换目标屏这个顺序能省下很多时间。小屏分辨率低、lane速率低即使电气上有些瑕疵也能正常工作适合验证代码逻辑。等协议层代码稳定了再换到目标屏调电气和参数。调试时建议准备一张纯色和颜色渐变的测试图片按固定顺序切换。我用一张红绿蓝竖条加灰阶的测试图能快速定位很多显示问题。纯色图排查RGB顺序灰阶图排查像素位深度和字节序很直观。ILA的采样深度不要省。初始化命令的发送过程可能长达几百微秒如果采样深度不够抓不到完整的初始化序列等于浪费时间。我的做法是用64K深度采样一次抓够从Sleep Out到Display On的全部命令。7.4 最后分享一点实际的小体会这次项目做下来我最深的感受是MIPI DSI从协议层面看并不复杂真正让人花时间的都是那些“文档上写了但没强调”的细节——ECC校验、延时、blanking时间、位序、LP状态处理任何一环出错屏幕都会用“黑屏”这个非常不友好的方式反馈你。如果你是第一次做FPGA驱动DSI屏我的建议是不要直接对着协议手册写代码先把屏厂驱动IC的datasheet完整看一遍把屏的时序参数表抄下来再对照DSI规范文档理解每一段时序需要什么包。这样两条线一对齐很多问题能在写代码之前就避免掉。另外这套DSI发送链路做好之后可以复用的价值很高。我的做法是把配置参数全部抽成了顶层parameter分辨率、行场时序、lane数、lan速率、初始化命令表地址换屏的时候只需要重新生成初始化命令的coe文件顶层逻辑几乎不用动。如果你也要做多块屏适配建议一开始就按这个思路设计后面会省很多事。