尧图精选

FPGA实战:从零搭建MIPI DSI发送链路驱动LCD屏

🕒 发布时间:2026/9/28 1:29:02 📁 来源:尧图网络
我接触FPGA驱动屏幕这块也有些年头了从最早的点RGB屏、LVDS屏到后来被各种项目逼着上MIPI DSI接口中间踩过的坑十个手指头数不过来。特别是MIPI DSI这个接口协议说简单也简单但真正要从零开始在Xilinx FPGA上把它跑起来驱动一块实实在在的LCD屏中间隔着好多道坎时钟计算、lane对齐、DPHY电气特性、初始化序列时序、字节序对齐……任何一个环节出错屏幕就是死活不亮或者亮了一半出花屏、闪屏。这篇文章我不打算讲那种“调好一个现成IP核然后点点鼠标”的玩法而是把我实际做过的、从底层开始搭建MIPI DSI发送链路驱动LCD屏的完整思路和关键细节整理出来。无论你是刚接触FPGA图像接口的新手还是被MIPI DSI折腾了好几天的老哥这篇文章里的时钟计算方式、FPGA内部逻辑架构、初始化序列的时序把控、还有那些文档里不会写明的调试坑应该都能帮你省下不少时间。顺便说一句我现在用的还是Vivado 2018.3工程里用的是Artix-7系列芯片屏幕是市面上很常见的5寸720x1280 MIPI DSI屏模组。这套方法是通用的换到Kintex、Zynq或者换成其他分辨率的屏幕思路完全一样只是参数需要重新算一遍。1. 方案选型为什么绕不开MIPI DSI以及FPGA方案的取舍1.1 接口选择背后的逻辑驱动LCD屏常见的接口无非就那几种古老的MCU 8080并口、RGB并口、LVDS、MIPI DSI还有现在手机屏幕上用的D-PHY MIPI DSI。在FPGA项目里选接口通常不是我们想用哪个而是屏幕模组厂商只提供哪个。8080并口和RGB接口的好处是FPGA实现起来极其简单几乎没有高速串行的概念GPIO直接怼上去就行。但它们的缺点同样明显引脚数量太多RGB888加同步信号加时钟轻轻松松几十根线而且传输速率上限很低跑个480x800都勉强。LVDS接口虽然把并行转成了串行速率上去了但LVDS接口的屏幕分辨率一般都是1024x600、1920x1080这类小尺寸屏基本被MIPI DSI垄断了。现在的现实情况就是5寸到7寸这个最火爆的尺寸段大部分屏幕模组都是MIPI DSI接口。尤其是从手机屏幕产业链流出来的那些拆机屏、裸屏清一色DSI接口。我遇到过的几个项目要么是成本敏感选了个便宜的DSI屏要么是客户指定了某个型号的屏幕查了一下资料发现只支持MIPI DSI那就没得选只能硬着头皮上。MIPI DSI接口在嵌入式领域确实有其独特优势引脚少1对时钟lane加1到4对数据lane就能跑1080P、支持命令模式和视频模式、抗干扰能力强差分信号、传输速率高每条lane轻松上到1Gbps。这些特性让它在手机、平板、车载显示领域占据统治地位。1.2 FPGA实现MIPI DSI的两种思路对比既然确定了要用MIPI DSI接口接下来面临一个选择用现成的IP核还是自己写逻辑Xilinx官方其实一直没有提供完全免费的MIPI DSI TX IP核Vivado里带的那个MIPI DSI IP属于收费授权而且用起来约束也很多。市面上第三方IP核比如某厂家的MIPI DSI TX Controller价格不菲对小项目来说不值得。最关键的问题是用现成IP核有个很大的局限性它给你封装好了上层接口一旦你需要在像素数据流中间插入一些特殊的处理逻辑比如OSD叠加、局部刷新、特殊时序控制改起来非常痛苦。所以我的选择是自己写核心逻辑只借助FPGA原生的硬核资源——OSERDESE2、IDELAYE2这些——来完成高速串并转换的部分。MIPI DSI的物理层本质上就是LVDS的一种变种只不过它的电气规范和协议层有自己的一套东西但底层的高速发射器FPGA完全可以通过原语搭出来。这种方案的好处是完全自由想怎么控制就怎么控制想加什么处理逻辑就往数据通道里塞什么逻辑成本为零不需要买授权代码可移植性强理解了原理之后换到别的FPGA平台Intel、Lattice也就是换个原语的问题。坏处也很明显所有细节都要自己抠特别是时序约束这块写不好就是上板子花屏、不显示。2. MIPI DSI协议核心你必须吃透的几个底层概念2.1 DSI物理层lane结构和工作模式MIPI DSI的物理层叫D-PHY采用源同步差分传输方式。一组传输链路包含1对时钟差分线DDR时钟和1至4对数据差分线。每对差分线就是FPGA上的两个相邻引脚一个P一个N。D-PHY的工作状态是分HSHigh-Speed和LPLow-Power两种模式的。HS模式下传输高速串行数据这时候数据线上的电平摆幅很小约200mV靠的是差分信号传输速率从80Mbps到2.5Gbps每lane不等。LP模式下电平摆幅大约1.2V传输速率很慢主要用于传输一些控制信令和进入/退出HS模式时的建立时序。这一点和LVDS接口有本质区别LVDS是纯高速差分没有LP模式这种说法。MIPI DSI的初始化时序、命令发送、BTABus Turnaround等操作都在LP模式下进行而像素数据流主要在HS模式下传输。从FPGA实现的角度看LP模式的控制逻辑不复杂无非就是控制差分引脚的电平状态和时序关系。真正让人头疼的是HS模式的进入和退出时序——从LP状态切换到HS状态中间要经历一个叫“LP-111”到“HS-Zero”再到“HS-Sync”的过程这个过程对时序要求极其严格照片了就是链路训练失败屏幕直接不认。2.2 数据包格式包头的组包与拆包DSI协议在HS模式下传输的数据是以包Packet为单位组织的。一个完整的包包含包头Packet Header、数据载荷Payload Data、包尾Packet Footer。包头是4个字节1字节的数据类型Data Type简称DT、2字节的Word CountWC表示载荷数据的字节数、1字节的ECC校验码。包尾是2字节的CRC16校验码。对于只发不收的场景比如FPGA单向驱动屏幕CRC可以填固定值或者干脆忽略但ECC建议还是算一下有些屏幕控制器会校验包头ECC导致收不到正确命令。视频模式Video Mode下传输像素数据用的是长包类型。比如RGB888最常用的是0x3E这个DT值RGB888 24-bitpacked pixel streamRGB666是0x3E的变种RGB565是0x2E。命令模式Command Mode下传输命令短包类型里常用的有0x05Generic Short Write无参数、0x15Generic Short Write with 1 Parameter、0x39Generic Long Write等。FPGA实现时最关键的是要搞清楚视频模式下你发送的每一个扫描行本质上就是一个超长的数据包。每一行像素数据加上包头、包尾组成一个完整的包。行与行之间的消隐期Horizontal Blanking可以发送短包命令也可以发Blank包。帧与帧之间的垂直消隐期Vertical Blanking类似。2.3 时钟计算从屏幕参数推导DSI速率这是整个项目里最核心、最容易出错的一步。DSI时钟速率直接决定FPGA内部逻辑的工作频率设计算错了屏幕要么黑屏要么严重花屏。以我用的这块5寸720x1280屏幕为例像素时钟计算公式如下Pixel Clock H_Total x V_Total x Frame_Rate屏幕手册或者驱动IC规格书里一般能查到水平总数和垂直总数。我用的这块屏H_Active720H_Blank大约是200左右具体值从IC规格书的时序表里查H_Total就是920左右。V_Active1280V_Blank大约30到40行V_Total按1320算。帧率目标60Hz。Pixel Clock 920 x 1320 x 60 72,864,000 Hz约等于73MHz。得到像素时钟后总数据速率就是Pixel Clock x Bits Per Pixel。RGB888是24bit每像素。Total Data Rate 73MHz x 24 1,752 Mbps如果使用4条数据lane每条lane的速率就是1,752 / 4 438 Mbps。D-PHY规定每条lane的HS速率最低是80Mbps最高1.5Gbps具体看版本438Mbps在合理范围内。需要注意的是DSI协议里真正用来传输像素数据的有效带宽只有总速率的约80%因为每行有包头、包尾、消隐区的开销所以理论计算出来的速率最好留出15%到20%的余量。如果算出来刚好顶着上限就应该加一条lane或者降低帧率。我见过不少新手在这里犯迷糊直接用Active分辨率和色深算速率忽略了消隐开销结果凑出来的时钟在示波器上看单lane速率超过了1Gbps逻辑跑到800MHz不死才怪。3. FPGA内部架构从像素流到MIPI DSI串行信号的完整链路3.1 系统整体框图先给整个系统画个逻辑框架。FPGA内部需要完成以下模块像素数据生成模块可以是简单的测试彩条Color Bar也可以是DDR3读出的图像数据或者摄像头采集后处理的图像数据。DSI协议控制模块负责将并行像素数据和命令封包生成符合DSI协议的数据包字节流。字节转字Byte to Word模块DSI协议的数据通道在物理层是1byte串行发送的但在FPGA内部为了匹配SerDes的工作位宽需要把字节流转换成一定宽度的并行数据。PLL时钟模块产生DSI字节时钟、像素时钟、逻辑时钟。SerDes发射模块基于OSERDESE2实现并串转换这是整个链路的最终出口。这套架构的好处是模块化每一层都是独立的出问题可以逐级定位。3.2 时钟树设计多时钟域的正确处理姿势MIPI DSI发送端涉及三个关键时钟像素时钟Pixel Clock用于驱动像素数据的读取和打包频率等于屏幕像素时钟。字节时钟Byte ClockDSI每个lane串行数据速率除以8。4 lane、438Mbps/lane的场景下字节时钟约等于54.75MHz。串行时钟Serial ClockDDR模式下串行数据速率的一半作为SerDes的并行侧时钟OSERDESE2实际输出数据速率是这个时钟的4倍4:1模式。这三个时钟之间的关系必须严格匹配。像素时钟、字节时钟、串行时钟之间不是简单的整数倍关系设计时需要通过PLL和合理的FIFO来缓冲跨时钟域的数据。我这里用了一个更简单的技巧把像素时钟定为固定的整数然后通过调整消隐期参数来精确匹配DSI字节时钟。比如把H_Total从920调整到960Pixel Clock就变成了960x1320x6076.032MHz总数据率变成1,824.77Mbps每lane速率为456.19Mbps字节时钟57.02MHz。这样在FPGA内部可以产生一个从12MHz参考时钟到456.19MHz串行时钟的PLL配置同时配套产生一个76MHz像素时钟和57MHz字节时钟。这里还需要注意OSERDESE2的输入时钟有一个最高频率限制Artix-7上约800Mbps/2400MHz左右如果串行数据速率太高就得考虑用4条以上lane或者换更快的芯片了。3.3 SerDes发射器OSERDESE2原语的使用要点OSERDESE2是Xilinx 7系列FPGA提供的高速并串转换原语它是实现MIPI DSI物理层的核心。每对差分数据线需要一个OSERDESE2或者用MasterSlave级联结构时钟线则需要一个OSERDESE2并输出一个差分时钟。关键参数配置如下OSERDESE2 #( .DATA_RATE_OQ(DDR), .DATA_RATE_TQ(SDR), .DATA_WIDTH(4), .SERDES_MODE(MASTER), .TRISTATE_WIDTH(1) ) oserdes_data0 ( .OQ(data0_p_out), .OCLK(serial_clock), .CLK(byte_clock), .CLKDIV(byte_clock_div4), .D{byte_data_3, byte_data_2, byte_data_1, byte_data_0}, .TQ(), .T(1b0), .OCE(1b1), .RST(reset_n), .RST_OCLK(1b0), .RST_CLKDIV(1b0), .CLKPERF(1b0), .CLKPERFDELAY(1b0) );OSERDESE2在DDR模式下CLKDIV时钟频率等于串行数据速率的一半再除以并行数据宽度的一半。以4:1 DDR模式为例并行数据的位宽是4bit串行时钟是456.19MHzCLKDIV就是456.19/2228.1MHz。但实际上我们上面的例子用的是字节时钟57MHz作为CLKDIV这怎么对应这里就有个容易混淆的点OSERDESE2支持4:1模式输入4bit输出串行DDR此时CLKDIV频率是OQ输出数据速率DDR下等于每bit速率的1/4。比如OQ输出速率是456.19Mbps实际DDR每个时钟沿出一个bit所以OCLK频率约228.1MHzCLKDIV就得是114.05MHz左右。而我前面说的字节时钟57MHz是什么情况这里需要理清DSI协议的字节时钟定义是每条lane的串行数据速率除以8即每传输8bit对应一个字节时钟周期。但如果用4:1的OSERDESE2一个CLKDIV周期会送出4个串行bitDDR模式下2个时钟沿乘以并行宽度2实际相当于半个字节。要一次送一个字节得用两个OSERDESE2级联或者直接用8:1模式——但Artix-7的OSERDESE2最高支持8:1需要MasterSlave级联DDR模式下并行位宽最高6bit要送8bit必须用两个OSERDESE2级联。实操中我建议这样设计使用8:1模式MasterSlave两个OSERDESE2级联并行数据位宽8bitCLK时钟内部串行时钟频率等于所需串行数据速率的一半CLKDIV频率等于串行数据速率除以8这样正好一个CLKDIV周期处理一个字节逻辑设计最自然。3.4 DPHY电气控制不要把差分引脚直接输出就完事之前说过D-PHY有HS和LP两种模式。FPGA上实现这两种模式切换需要精确控制差分输出引脚的上拉/下拉状态和驱动器使能。先说HS模式。HS模式下D-PHY数据线和时钟线都处于高速差分传输状态。Xilinx 7系列FPGA的HP BankHigh Performance Bank引脚直接支持差分输出用OBUFDS或者OBUFTDS原语就可以输出差分信号。但关键问题在于D-PHY要求HS模式下数据线上的共模电压是200mV差分摆幅也是200mV这跟FPGA普通的LVDS输出共模1.2V摆幅350mV并不完全一致。所以大多数FPGA做MIPI DSI的方案并不追求完完全全满足D-PHY的电平规范而是“近似兼容”——因为屏幕控制器IC的接收端容限比较大只要信号质量不是太差都能正确接收。这也是为什么大家都在FPGA上做MIPI DSI而不担心烧板子的原因但实测下来这种做法对走线长度的要求非常苛刻而且速率上去了就特别容易出问题。再说LP模式。LP模式需要把输出引脚切换成类似单端推挽输出并且要满足LP-TX的时序要求比如LP-111状态正负线全是高电平、HS-Zero、进入HS模式前的建立时间等。FPGA引脚要实现这种可控的三态输出得用OBUFTDS原语通过T端口控制差分输出的使能。我自己实现时是把LP控制逻辑和HS数据输出分开的HS模式下用OSERDESE2送高速数据激活时通过三态控制让OBUFTDS输出通路生效LP模式下把OBUFTDS的T端口置为有效高阻用普通IO控制引脚的逻辑电平来模拟LP状态。这套方案解剖下来其实不复杂麻烦的是两种模式切换的精确时序控制需要用到状态机和精确延时。4. 逻辑实现逐模块拆解FPGA内部代码细节4.1 像素数据源模块先跑通彩条再说不管项目最终要显示什么内容调试阶段一定要先用内建的彩条信号源来验证链路通不通。彩条的好处是每个颜色分量的值是固定的花屏时很容易肉眼定位是颜色通道错位、字节序反了、还是时钟不对。module color_bar_gen( input wire pclk, input wire rst_n, input wire [11:0] h_cnt, input wire [11:0] v_cnt, output reg [23:0] rgb_data ); always (posedge pclk) begin if (!rst_n) begin rgb_data 24h0; end else begin if (h_cnt 360) begin if (v_cnt 640) rgb_data 24hFF0000; // 红色 else rgb_data 24h00FF00; // 绿色 end else begin if (v_cnt 640) rgb_data 24h0000FF; // 蓝色 else rgb_data 24hFFFFFF; // 白色 end end end endmodule这个模块只是示意真正的彩条源比这个会复杂些——需要根据行/场同步计数产生不同的颜色带。调试时建议先用“单色满屏”比如纯红色验证链路通没通再切彩条验证数据对齐。4.2 DSI协议打包模块每一行像素就是一包数据接下来是核心中的核心DSI协议打包模块。这里要处理的逻辑是在每一行有效像素数据到来时把它封装成一个DSI长包。视频模式下一个有效的扫描行对DSI发送端来说就是发送短包0x01Sync Event: V Sync Start或者干脆不发取决于帧时序。发送短包0x11Sync Event: H Sync Start表示行开始。发送包头DT0x3ERGB888WC720*32160字节。发送2160字节的像素数据。发送包尾CRC可以省略或者发0。进入水平消隐发送若干个Blanking包DT0x11的短包变体或者直接拉LP。localparam DT_RGB888 8h3E; localparam DT_H_SYNC 8h11; localparam DT_V_SYNC 8h01; localparam DT_BLANK 8h19; // 状态机IDLE - H_SYNC - PACKET_HEADER - PAYLOAD - CRC - BLANKING always (posedge byte_clk) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: if (line_start) state H_SYNC; H_SYNC: state PACKET_HEADER; PACKET_HEADER: state PAYLOAD; PAYLOAD: if (payload_cnt PIXEL_BYTES - 1) state CRC; CRC: state BLANKING; BLANKING: if (line_end) state IDLE; endcase end end这个模块有几个细节要注意第一个是字节序。DSI协议规定数据发送顺序是从像素的最高位开始还是最低位开始实际上取决于屏幕控制IC的配置。我用的这块屏在RGB888模式下一个像素的3个字节按照R、G、B的顺序发送每个字节内部高位在前。这个顺序不对的话颜色会完全错乱比如红色显示成蓝色。第二个是行同步与帧同步的时序。视频模式下行同步并不是靠单独的同步信号线传给屏幕而是靠发送特定类型的DSI包来标识。H Sync Start包的DT值是0x11V Sync Start包的DT值是0x01。有些屏幕IC还支持通过配置让控制器自动忽略某些包只关心像素数据本身这种情况下时序可以稍微宽松一点。第三个是包与包之间的间隔。连续两个DSI包之间需要留有最小的EoTpEnd of Transmission packet间隔或者LP状态间隔具体值要看屏幕IC的规格书。有些屏要求松有些屏要求严这个只能一项项试。4.3 字节到字宽转换模块对齐的关键步骤OSERDESE2工作在8:1模式下时每次并行输入8bit。但DSI协议层的数据包是按字节组织的如果所有处理都放在字节时钟域那是同步的直接送就行。真正的问题是像素数据从像素时钟域到字节时钟域的转换。这个跨时钟域转换我用的异步FIFO解决FIFO的宽度深度按行缓冲来设计。比如一行是2160字节我就用一个4KB深度的FIFO缓存半行数据。具体做法是像素时钟域把RGB数据打平成字节流写入FIFO字节时钟域按8bit读取FIFO读出的数据直接拼成8bit组送OSERDESE2。这里有个经验之谈异步FIFO的读写指针跨越需要充分打拍同步FIFO的深度最好不要小于一行数据量的1.5倍否则会产生空/满信号毛刺。调试时强烈建议把FIFO的空、满、读写计数信号引出来观察这一步出了bug后面全白搭。4.4 时钟配置模块PLL参数的计算方法使用Xilinx FPGA的MMCM/PLL产生三个时钟像素时钟、字节时钟、串行时钟。以我上面的参数为例串行数据速率456.19MbpsOSERDESE2工作在8:1 DDR模式则串行时钟OSERDESE2的CLK频率是228.1MHz字节时钟CLKDIV是57.02MHz像素时钟是76.03MHz。这三个频率的比值76.03 : 228.1 1 : 3像素时钟和时钟输出之间是整数倍关系。但字节时钟57.02和像素时钟76.03之间就不是整数倍所以必须用独立的PLL输出。我实际用的PLL配置是输入时钟50MHz板载晶振。PLL倍频到912.38MHzVCO频率。然后分频出3个输出分频4得到228.1MHz串行时钟分频16得到57.02MHz字节时钟分频12得到76.03MHz像素时钟。VCO频率912.38MHz对Artix-7的MMCM来说刚好在上限约1200MHz范围内能用但余量不大。如果板子上的晶振是100MHzPLL配置会更好算。还有一个坑要提醒OSERDESE2的CLK和CLKDIV必须来自同一个MMCM/PLL而且必须满足相位关系否则SerDes输出数据会不稳定。Vivado的时序约束里要加set_clock_groups或者用MMCM自带的输出反馈机制来保证相位对齐。5. 初始化序列屏幕点亮最关键的一步5.1 上电时序与初始化命令的来源MIPI DSI屏幕不是上电就能显示的它内部的驱动IC常见的有Ilitek ILI9881C、Fitipower FC8363、Himax HX8399等需要在上电后发送一系列初始化命令配置扫描方向、像素格式、伽马曲线、电源管理等参数然后才能进入显示状态。初始化命令序列哪里来最可靠的方式是找屏幕模组厂商要他们会提供一份初始化代码通常是C语言数组或者寄存器表。如果是买开发板或者现成模组卖家一般也会附送初始化代码。如果你用的是拆机屏或者资料不完整的屏那就只能去驱动IC的规格书里找参考配置再自己一点点调了。我的屏幕用的ILI9881C初始化序列大概有120多条命令像这样// 示例ILI9881C 初始化序列片段 0xE0, 0x00, // 进入扩展命令模式 0xE1, 0x93, 0xE2, 0x65, 0xE3, 0xF8, ... 0x35, 0x00, // Tear Out ON 0x44, 0x03, // Set Tear Scanline 0x51, 0xFF, // 背光亮度 0x53, 0x24, // 背光控制使能 0x55, 0x00, // 图像增强关闭 0x11, // Sleep Out // 延时120ms 0x29, // Display ON发送这些命令走的是DSI的命令模式LP模式或低速HS模式。在FPGA实现里命令模式可以先用LP模式发送短包有些控制器也支持在HS模式下发送短包。5.2 初始化序列的发送时序要求发送初始化命令时时序控制比命令本身还要重要。有几个硬性要求第一Sleep Out0x11命令之后必须延时大于等于120ms让驱动IC内部的DC-DC电源稳定。这个延时不够屏幕就会迟迟不亮或者亮度异常。第二Display On0x29命令通常在Sleep Out之后发送发送完之后屏幕控制器开始等待FPGA发送视频数据流。如果此时FPGA侧数据流没有准备好屏幕上可能显示不了任何内容但背光已经亮了可以看到白屏或者黑屏。第三命令之间需要满足芯片手册规定的tDSI时序主要是包与包之间的间隔。这个间隔因IC而异从几百纳秒到几微秒不等保守一点的话在两个命令包之间插入LP状态并延时1微秒就够了。我建议在FPGA中用一段ROM把整个初始化序列存起来用一个简单的状态机逐条发送。命令发送完后不需要重启FPGA或重新初始化除非你改了分辨率或像素格式。5.3 像素格式配置让FPGA和屏幕IC对齐初始化序列里有一项配置特别重要那就是像素格式。常见的MIPI DSI屏幕支持的像素格式有RGB565、RGB666、RGB888三种。屏幕IC通过命令0x3A设置像素格式比如0x3A, 0x77表示RGB8880x3A, 0x66表示RGB66618bit0x3A, 0x55表示RGB565。FPGA侧发送的数据格式必须和这个设置严格匹配。如果屏幕设置成RGB666但FPGA发的是RGB888数据颜色会偏色、错乱。同样屏幕设置成RGB565但FPGA发RGB888显示出来会严重偏色或者花屏。我的经验是能上RGB888就优先RGB888显示效果最好实现也不复杂每像素3字节对齐打包计算方便。RGB666实际传输时像素数据按18bit但DSI协议规定低位补0凑成24bitRGB666打包格式或者用3byte传2像素的压缩格式复杂度高一些。RGB565适合低带宽场景但颜色还原度打折扣。这里顺带提一个常见问题为什么屏幕上显示的图像颜色不对但画面是正常的大概率就是像素格式不匹配或者RGB字节顺序搞反了。先把屏幕设成纯红色画面测试如果显示出来的不是红色那就是字节序问题如果显示红色但颜色略淡就是格式位宽问题。6. 调试实录那些最容易被忽略的坑6.1 现象一屏幕完全不亮连背光都没有这种问题先别急着查FPGA逻辑。先用万用表量屏幕供电电压VDD逻辑电源一般是1.8V或3.3V、VDDIIO电源、VSP/VSN模拟电源用于LCD偏置。这些电源的电压和时序必须满足屏幕IC的要求很多屏幕在电源时序不对的情况下会进入保护状态不响应任何DSI通信。排除供电问题后检查初始化序列的发送时序。把DSI数据线和时钟线的波形用示波器抓出来如果是差分信号要用差分探头或者至少用单端探头分别抓P和N。确认初始化命令真的发出去了确认命令之间延时正确确认没有发送到一半中断。最后检查FPGA到屏幕之间的物理连接。MIPI DSI走线要求差分阻抗100欧姆100Ω差分阻抗走线长度尽量短P和N的等长要求很高。如果PCB设计走线很差信号质量会导致屏幕不识别任何命令表现为屏幕死活不亮。我曾经遇到过一块板子屏幕偶尔能亮偶尔不亮最后发现是时钟线的P和N间距太小串扰严重。改板后问题解决。6.2 现象二屏幕亮了但显示画面在水平方向错位或者撕裂这个问题几乎可以肯定是视频模式时序参数不对。DSI协议视频模式下的时间参数是通过初始化命令或者包间隔来控制的。水平方向错位一般是H_Total和期望值不匹配画面撕裂tearing要么是屏幕IC的Tearing Effect信号没处理好要么是帧率不匹配。简单排查方法是用逻辑分析仪或者Vivado的ILA抓FPGA内部的行、场计数信号对比屏幕IC规格书里的时序要求表。屏幕IC的规格书会给出一个时序范围如果你的行/场消隐参数落在这个范围内一般都能正常显示。常见的问题是H_BackPorch、H_FrontPorch设置太小或太大导致行同步信号不在扫描行的正确位置。还有一点如果用的是异步FIFO做跨时钟域缓冲FIFO的读空/写满会导致画面在某个位置出现撕裂。这时候要保证FIFO深度够大并且读写时钟频率的匹配关系正确。6.3 现象三画面显示正常但出现细微的噪声点或者水波纹这个现象通常是高速信号质量不好造成的。检查以下项目差分走线的阻抗是否匹配100Ω差分阻抗有没有做阻抗控制。FPGA输出引脚的Slew Rate设置。Xilinx FPGA的IO约束里可以设置SLEW属性MIPI DSI信号建议用FAST模式。数据线和时钟线的等长情况P/N差分对内偏差要小于5到10mil。供电质量。高速翻转时电流变化很大电源纹波过大也会影响信号完整性。7系列FPGA上还有一个IO约束需要注意内部串联电阻内部端接不要开启否则会严重影响高速信号的反射特性。6.4 现象四上电后时序状态机跑飞复现概率高这种问题多半是复位逻辑处理不当。FPGA设计中常见的错误是用异步复位并且复位信号在时钟有效沿附近发生变化导致逻辑进入亚稳态。MIPI DSI这种高速链路对亚稳态特别敏感一旦复位释放时机不对状态机可能进入非法状态屏幕就一直黑屏。解决方法是用同步复位并且复位释放时与字节时钟同步。更稳妥的做法是复位信号经过一个“复位同步器”再进入各个模块reg [1:0] reset_sync; always (posedge byte_clk) begin reset_sync[0] external_reset_n; reset_sync[1] reset_sync[0]; end wire sys_reset_n reset_sync[1];而且PLL的LOCKED信号必须参与复位控制PLL锁定之前所有逻辑保持在复位状态PLL锁定后至少再等若干微秒再释放复位。6.5 调试工具和方法我调试这套MIPI DSI链路时用到的工具按优先级排序万用表查供电、查短路。示波器至少500MHz带宽最好1GHz以上看DSI差分信号有没有起来看初始化序列的脉冲波形。没有差分探头就分别看P和N的单端波形一样能看出很多东西。Vivado ILAIntegrated Logic Analyzer把FPGA内部的状态机状态、FIFO空满、数据计数拉出来看。ILA采样时钟最好用字节时钟别用高速串行时钟。逻辑分析仪可选在DSI速率不高的时候比如LP模式下传输命令可以用逻辑分析仪抓包解析。用ILA调试时有个经验不要一次性set太多探针ILA会占用大量Block RAM和布线资源反而影响时序收敛。先抓控制信号状态机状态、FIFO空满确认链路方向对了再去抓数据。7. 进阶扩展从彩条到真实图像的移植要点彩条跑通只是万里长征第一步。真实项目里FPGA驱动的通常是摄像头采集的图像、DDR3/4里缓存的多帧图像、或者经过算法处理后的视频流。从彩条切换到真实图像有几件事需要额外注意。首先是数据源端的时序。摄像头sensor输出的像素时钟和行/场同步信号与DSI发送端需要的时序不一定一致。常见做法是在中间插入一个“视频时序转换”模块或者用帧缓冲DDR做异步接口让写入端的任意时序和读出端的DSI时序解耦。其次是图像数据的位宽和格式转换。摄像头出来的可能是RAW Bayer、YUV422、RGB565等格式需要转换成RGB888才能喂给DSI打包模块。转换模块的计算量不大但逻辑代码量不小建议先做成独立的IP调试通过了再集成进系统。最后是帧率的匹配。DSI发送端必须按照屏幕要求的帧率发送数据太快了屏幕处理不过来太慢了画面会闪烁。如果系统里图像源帧率不稳定比如USB摄像头建议用帧缓冲配合重复/跳帧策略来稳定输出。8. 最后的经验和几个意外发现这套方案我自己前前后后改了差不多两版才算稳定运行。第一版直接用OSERDESE2配4:1模式逻辑跑通了但在板子上画面总有点轻微雪花点第二版升级成8:1模式把时钟域理顺了再用IMCE内部匹配端接调了一下输出阻抗画面干净了很多。几个值得分享的细节第一个是DSI时钟和数据lane的极性。MIPI DSI规定时钟的上升沿和下降沿都可以采样数据DDR具体哪个沿对应哪个数据位取决于屏幕IC内部的设计。如果初始化之后画面看起来像“每两个像素左右互换”的样子多半是采样沿选反了。我遇到过一次数据lane的顺序配置对了但时钟极性反了显示出来的图像像“锯齿状”当时花了整整半天时间才定位到问题。第二个是在Vivado的IO约束里必须为每个差分对单独设置DIFF_TERM。Artix-7的HP Bank支持内部可编程差分端接但默认是关闭的。MIPI DSI信号速率比较高开启内部端接有时候反而会影响阻抗匹配建议先关闭用外部端接电阻在PCB上放100欧姆差分电阻来处理。如果用了内部端接一定要注意Vccio电压要选对不然会漏电流。第三个是初始化命令里有些字节是带延时的。比如Sleep Out之后要延时Display On之前要延时。很多人的初始化代码是从别处抄来的里面的延时参数是给MCU用的毫秒级延时FPGA实现时要相应调整成时钟计数延时。我建议写一个delay_ms模块参数可配方便调试时调延时长度。第四个发现是有些屏幕IC在接收到错误的初始化命令后并不会直接报错而是默默忽略掉导致你很难判断命令到底有没有被执行。这种情况只能靠逐步缩小范围来排查先初始化最最基本的电源和显示开关命令0x11 Sleep Out、0x29 Display On看屏幕有没有反应亮白屏或黑屏有反应说明DSI链路通了再去调其他参数。如果你正在做类似的项目建议按这个顺序来排坑电源和硬件连接 → DSI物理层信号 → 初始化序列 → 像素数据格式 → 时序参数微调。每一步都确认无误再进下一步别想一口气吃成胖子。特别是物理层信号这一步示波器抓到的波形不漂亮就别急着调上层逻辑否则你在软件层面折腾再久也解决不了信号完整性的问题。这套方法论我后来在多个项目里复制使用过从5寸屏换到7寸屏从ILI9881C换到HX8399从Artix-7换到Kintex-7基本就是改改初始化序列和PLL参数的事。结构没变过调试流程也没变过。希望这些经验能让你少走点弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →