ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA+OV5640+PCIe图像传输实战:从DMA设计到掉卡排查

FPGA+OV5640+PCIe图像传输实战:从DMA设计到掉卡排查 从Xilinx FPGA上跑PCIe前端接OV5640摄像头这活儿听上去不算特别复杂但真做起来从传感器初始化、像素时序对齐到PCIe链路训练、DMA搬运、上位机显示每一层都有隐藏的坑。我最近正好在帮客户做这个方案OV5640这端的720p采集已经稳定出图了PCIe上传链路也调通了这篇就把整个项目从架构选型到具体实现再到掉卡、降速、AER这些头疼问题完整记录一遍给后面接类似项目的朋友做个参考。项目本身的需求很直白OV5640采集视频FPGA做初步处理然后通过PCIe接口把图像数据搬到上位机实时显示。适合正在做FPGA图像采集、PCIe接口开发的朋友阅读尤其是第一次接触Xilinx PCIe IP核和DMA通路设计的这篇文章可以让你少走不少弯路。1. 项目全景这套OV5640 PCIe图像传输系统到底要做什么1.1 需求拆解从OV5640图像采集到PCIe上行的完整链路搭系统之前先把需求拆清楚。客户给到的核心信息就一句话接FPGA开发现已完成OV5640 1280x720分辨率下的图像采集接下来要做PCIe图像传输。这句话背后包含这几个明确的目标第一OV5640传感器要输出稳定的720p视频流。这个稳定包含曝光正确、帧率稳定、无花屏、无丢帧而且数据格式要可控YUV422还是RGB888得早定下来因为后面缓存和DMA的位宽设计都依赖这个选择。第二FPGA内部要做像素数据的整理和缓存。OV5640的DVP接口是PCLK随路时钟加上行场同步信号数据位宽8位或10位这种形态的数据不能直接往PCIe的AXI总线上塞必须转成满足AXI时序的burst传输格式中间还得加缓存来平滑帧率波动和主机读取速度之间的差异。第三PCIe链路要能正确枚举、稳定运行驱动加载后能通过DMA把图像数据从FPGA搬到上位机内存。这一步是项目里工程量最大的部分涉及PCIe IP核配置、BAR空间分配、DMA引擎设计、中断处理以及运行时的稳定性问题。所以整个系统可以分成三个模块来理解图像采集端OV5640 I2C配置 像素时序解析、数据缓存端FIFO/BRAM/DDR 帧同步逻辑、PCIe传输端PCIe IP核 DMA引擎 AXI接口 中断。我的经验是不管项目大小先把这三级流水线在脑海里画清楚再动手写代码能省下后面至少一半的调试时间。1.2 方案选型接口方式与PCIe IP核的第一轮取舍方案选型的核心决策点有两个一个是OV5640用DVP接口还是MIPI接口输出数据另一个是PCIe用Xilinx哪个IP核、DMA用什么方式实现。OV5640这颗传感器比较特殊它同时支持DVP并口和MIPI CSI-2串口两种输出模式。如果FPGA选的是7系列里比较便宜的型号比如Artix-7那用DVP接口是主流选择因为MIPI的物理层D-PHY在普通IO上实现起来很麻烦需要额外的电平转换和阻抗匹配时序约束也比DVP复杂得多。而且DVP输出的8位并行数据和PCLK、HREF、VSYNC时序非常直观用Verilog解析起来简单可靠。这个项目里客户已经做好了OV5640的720p采集大概率就是DVP方案直接沿用就好。如果用的是带有MIPI硬核的Zynq UltraScale或者特定Artix型号那走MIPI CSI-2能省IO资源但RCVRReceiver的字节对齐、通道分配、错误检测这些逻辑会多出来一截。我的看法是只要板卡的IO资源够DVP永远是最省事的选择尤其是项目周期紧张的时候。PCIe IP核的选择更关键。Xilinx目前提供两类方案一类是老牌的7 Series Integrated Block for PCIe这个IP核本质上只是实现了PCIe物理层、数据链路层和事务层相当于给你一个完整的PCIe端口但它不包含DMA引擎。如果你想做大数据量的图像传输必须自己在FPGA逻辑里设计AXI DMA控制器或者外挂Xilinx的AXI DMA IP。好处是灵活坏处是DMA逻辑要自己写调试周期长。另一类是UltraScale系列上主推的XDMA IP核DMA/Bridge for PCIe Gen3它把PCIe链路和DMA引擎集成在了一起提供H2CHost to Card和C2HCard to Host两条DMA通道支持Scatter-Gather和简单DMA两种模式用户只需要通过AXI接口挂上自己的FIFO或者DDR控制器就能跑起来。如果是新项目我强烈建议直接选带XDMA的器件能把DMA这一大块开发量省掉至少两个月。我把这两种方案的关键差异整理在下面这张表里方便对照选型对比项7 Series Integrated Block 自写DMAXDMA IP核DMA引擎不包含需自己实现内置含H2C/C2H通道开发难度高需要精通AXI和DMA协议中低配置IP后主要调驱动支持的器件全部7系列UltraScale/Virtex UltraScale等中断机制需自己搭建MSI中断逻辑内置MSI/MSI-X中断支持适用项目对DMA行为有特殊定制需求标准图像/数据高速搬运2. OV5640采集端从初始化到720p稳定输出的关键细节2.1 SCCB配置与寄存器初始化必须走的流程和不能省的操作OV5640的寄存器配置是通过SCCB总线兼容I2C完成的地址一般是0x788位写地址或者0x3C7位地址。上电之后不能立刻写寄存器必须先满足传感器的上电时序要求。我实际测试下来推荐的流程是先供上AVDD、DOVDD、DVDD三路电源然后给MCLK主时钟24MHz或27MHz推荐用24MHzPLL配置好算等待至少10ms以上再把RESETB引脚从低拉高再等20ms让内部电路稳定。这套时序如果偷懒最常见的故障就是SCCB写入没响应或者输出图像出现奇怪的条纹。初始化寄存器的顺序也很讲究。我的经验是先做软件复位寄存器0x3103写0x03延时后再写0x00然后配置系统时钟分频0x3034、0x3035、0x3037、0x3103再设置分辨率相关的寄存器0x3808、0x3809设置水平分辨率0x380A、0x380B设置垂直分辨率最后配置输出格式和镜像翻转。以1280x72030fps为例YUV422输出格式下关键寄存器配置大致是下面这样// 简化的OV5640初始化序列I2C主机依次写入 // 0x3008 0x82; // 软复位 // 等待至少20ms // 0x3034 0x1A; // PLL配置高字节 // 0x3035 0x11; // PLL配置低字节目标24MHz输入 // 0x3037 0x0F; // PLL根分频 // 0x3103 0x00; // 系统时钟控制 // 0x3808 0x05; // 水平分辨率高字节 - 0x500 // 0x3809 0x00; // 水平分辨率低字节 // 0x380A 0x02; // 垂直分辨率高字节 - 0x2D0 // 0x380B 0xD0; // 垂直分辨率低字节这里要注意一个细节720p模式下输出的PCLK频率是取决于分辨率、帧率以及xI公司的HTS/VTS水平/垂直总尺寸的。按默认的HTS1892、VTS758计算PCLK约等于72MHz。时序解析模块里的时钟域处理比如跨时钟域FIFO的读写端位宽匹配都要按这个实际速率来设计。2.2 行场同步信号与数据格式处理DVP解析不能只看HREFOV5640的DVP时序其实不复杂PCLK上升沿采样数据HREF高电平期间输出有效像素数据VSYNC上升沿表示一帧开始。但实际调试时我踩过几个坑这里重点提一下。第一个坑是数据位宽。OV5640在DVP模式下默认输出10位数据你需要通过寄存器0x4837把输出位宽设为8位。如果不设置直接接8位引脚低两位会被截断图像虽然能显示但颜色细节会出问题。第二个坑是HREF的时序。HREF高电平期间的第一个和最后一个PCLK对应的数据可能并不有效尤其是某些分辨率模式下HREF边沿和数据边沿对不齐。稳妥的做法是在HREF高电平期间加一个两拍的延迟把前两个像素丢弃只采集中间稳定的数据。第三个坑是帧同步。VSYNC的极性可以配置默认是高有效。但某些配置下VSYNC和HREF会出现重叠的情况如果逻辑里只用VSYNC作为帧起始标志而不检查当前是否处于行有效状态就可能在帧中间误触发新一帧的开始导致FIFO里的数据错位。我的做法是在VSYNC有效期间用HREF的第一次上升沿来确认第一行的起点这样无论VSYNC与HREF的相对关系怎么变帧边界都是准确的。3. PCIe传输链路Xilinx IP核选型与DMA通路设计3.1 PCIe IP核选型集成块还是XDMA决定你的开发节奏前面选型章节已经对比过两类IP核这里说实际开发中的选择逻辑。如果老板说三个月内要出demo那你直接选XDMA因为它把最复杂的DMA引擎和中断逻辑封装好了你只需要给XDMA配一个AXI从接口用于寄存器读写和一个AXI主接口用于数据搬运再挂上自己的图像缓存FIFO就行。剩下的事情比如Scatter-Gather描述符表、DMA搬运状态机、MSI中断发送XDMA内部全帮你做好了几乎不用碰。如果项目特别强调DMA行为可控比如你必须按固定的burst长度搬运、必须抢占式地处理多个图像帧的优先级那自写DMA配合7 Series Integrated Block for PCIe是更好的路线。但代价是你要自己处理AXI Read/Write burst的地址对齐、burst长度的边界、last信号与空拍的时序匹配这些逻辑看着不难但调试AXI时序的耗时往往超出预期。我这次项目用的是UltraScale平台上的XDMA IP核配置如下PCIe配置选Gen3 x4AXI数据位宽选256bitDMA模式选Scatter-GatherSG使能了C2H通道和MSI-X中断。之所以选SG模式是因为上位机驱动只需要准备一个描述符环FPGA侧自动完成数据的搬移和描述符的更新比简单DMA模式灵活很多尤其在多帧连续传输的时候SG模式不会出现Simple模式那种必须等上一笔DMA完成才能启动下一笔的限制。3.2 BAR空间、寄存器与DMA缓冲区设计先画好地址地图PCIe枚举时host会给FPGA的PCIe IP核分配BAR空间你需要在IP配置里定义BAR的数量、大小和类型。在XDMA的配置界面里可以定义多个BAR其中建议把BAR0配置成AXI-Lite从接口用来做寄存器读写比如上位机通过这些寄存器读取FPGA的版本号、当前图像帧计数、错误状态。把BAR1配置成AXI MM的主接口用来做DMA搬运的大块数据空间这样驱动可以通过映射BAR1地址直接访问FPGA的帧缓存。地址地图规划清楚之后DMA缓冲区的设计就顺理成章了。给上位机驱动写DMA缓冲区时我会分配一块不小于4MB的内存按帧大小切成多个buffer每个帧一个buffer。以一个720p的YUV422帧为例一帧数据量是1280x720x2字节约1.76MB那么4MB的缓冲区可以放两帧多一点。在SG模式下驱动把这个缓冲区首地址填入描述符FPGA的XDMA就会在完成一张图像的DMA传输后自动跳转到下一张图像的地址实现连续帧传输上位机只需要定期检查当前帧编号即可。带宽这个指标最好在设计阶段就算清楚不要到调试的时候才发现链路带宽不够。计算方式很简单图像数据量除以帧时间。1280x72030fps、YUV422每像素16bit单帧数据量为128072021.84MB每秒数据量为55.3MB/s。PCIe Gen3 x1的原始速率是8GT/s经过128b/130b编码后有效带宽约984.6MB/s实际有效载荷根据传输开销会降到850MB/s左右。就算只用Gen3 x1也远远够用何况我用的是x4。所以瓶颈根本不在PCIe带宽而在图像数据的产生速度和上位机处理的实时性这个结论对后续排查性能问题很重要。4. 图像缓存与帧同步避免花屏和撕裂的关键设计4.1 缓存架构行缓存、帧缓存还是DDR3图像数据在进入PCIe DMA之前必须经过缓存。缓存方案的选择直接决定系统的复杂度和能不能连续传帧。最简单的方案是行缓存。用一行FIFO存一个水平行的像素数据HREF结束就把这一行数据交给DMA引擎发送。这个方案的优点是BRAM消耗小逻辑简单但缺点是它无法处理帧率抖动。如果上位机因为PCIe总线繁忙或者驱动调度不及时导致连续几行的数据没有被DMA及时搬走FIFO就会溢出图像就会出现横向撕裂。好一点的方案是帧缓存。分配两帧BRAM一帧在写入时另一帧被DMA读取采用乒乓操作。这个方案能很好地解决帧率抖动问题因为写入端和读取端分别工作在各自独立的帧节奏里只要读取端能在当前帧写入完成前把上一帧搬完就不会有撕裂问题。代价是BRAM消耗翻倍但我算过账1280x720的YUV422帧需要1.84MB的BRAM用Uram或者大容量BRAM也能装下只是占的资源有点多。如果分辨率更高或者帧率更大那就得上DDR3/DDR4。DDR方案的好处是多帧缓存可以做到帧丢帧控制和延迟播放坏处是要在FPGA里实现DDR控制器还要处理AXI接口到DDR控制器的跨时钟域开发量明显变大。这个项目还在720p阶段我用了双帧BRAM乒乓方案实测完全够用。4.2 帧同步逻辑与时序衔接写满一帧再搬别让DMA追着行跑帧缓存设计里的核心逻辑是帧同步。简单来说写入端和读取端之间必须有一个清晰的分界线写入端写满第N帧读取端才能开始读第N帧否则会因为地址重叠而出现图像错乱。我用的乒乓逻辑长这样两帧BRAM的地址空间记为bufA和bufB。当VSYNC到来时写入端往bufA写入完成后置起bufA_full标志。此时如果DMA引擎空闲就触发DMA从bufA读取同时写入端切换到bufB写入。下一帧VSYNC到来时写入端再切回bufA前提是bufA已经被读完了。如果bufA还没读完就要产生一个帧溢出标志把这个帧直接放弃。这个机制保证DMA永远在读完整的一帧而不会去读正在被写入的半帧数据有效杜绝了花屏和撕裂。跨时钟域也是这个环节容易忽略的问题。OV5640的PCLK是72MHz左右而PCIe XDMA的AXI总线时钟取决于IP配置常见是250MHz或300MHz两边完全不是一个时钟域。所以帧缓存的读端口和写端口之间的地址、数据、控制信号都必须通过异步FIFO或者寄存器链做同步处理。我习惯的做法是写端口在PCLK域读端口在AXI时钟域两个域独立计数通过格雷码或FIFO满空标志交换状态而不是直接同步地址总线否则会出现亚稳态风险导致读到不确定的数据。5. PCIe稳定性问题实录掉卡、降速、AER的排查思路5.1 掉卡与链路训练失败先从硬件查起别急着改代码PCIe项目做到后面你会发现最耗时的问题往往不是逻辑功能不对而是链路不稳定。客户给的信息里有一条特别扎眼掉卡、降speed/lane、AER等问题。这类问题在FPGA PCIe开发里非常典型我先说排查这类问题的整体思路先硬件、再配置、后驱动。掉卡的直接表现是上位机突然看不到设备lspci输出为空。这个现象背后最常见的原因是链路训练失败也就是PCIe物理层的LTSSM状态机没能进入L0状态。硬件层面首先要检查参考时钟REFCLK。PCIe对REFCLK的质量要求很高差分摆幅、抖动、上升时间都有严格规范。如果你用的有源晶振质量不过关或者REFCLK走线离高速数据线太近导致串扰就会间歇性地出现链路训练失败。我踩过最典型的坑是REFCLK的差分对极性接反了结果是某些卡完全枚举不出来某些卡要在反复热复位之后才偶尔成功。还有一个容易被忽略的是板卡金手指的接触质量。PCIe x16插槽的机械公差很大有些转接卡或者延长线质量差金手指氧化或者弹片压力不足就会导致高速差分线接触电阻过大链路协商不稳定。遇到掉卡先用橡皮擦金手指换一个插槽试再考虑软件问题。电源纹波也必须查。FPGA的PCIe硬核和PHY对供电纹波比较敏感尤其是VCCO和MGTAVCC电压如果纹波超过50mV高速SerDes的眼图会明显变差掉卡概率急剧上升。我自己常用的排查工具是示波器测电源探头用短地线弹簧尽量靠近FPGA供电引脚测纹波。5.2 降速、降lane与AER报错从LTSSM状态和驱动日志里找线索降速和降lane是链路训练妥协的结果说明硬件有条件不能满足最高规格的协商目标。比如Gen3 x4的链路因为信号完整性不佳可能退化成Gen2 x4甚至Gen3 x1。排查这个问题首先要看Link Status寄存器通过lspci -vvv查看LnkSta字段确认当前协商到的速度和宽度。如果发现降速但硬件电气指标又没问题那就可能是PCIe IP核里的Max Link Speed、Max Link Width配置不对或者FPGA里的PCIe IP核总线的MPSMax Payload Size和MRRSMax Read Request Size配置成了不兼容的值。AERAdvanced Error Reporting是PCIe的高级错误报告机制能在驱动日志里看到类似PCIe Bus Error: severityUncorrectable的报错。遇到AER报错先看错误源寄存器常见的错误码有URUnsupported Request、CACompletion Abort、ECRC错误、Malformed TLP等。UR错误一般是因为DMA访问了不存在的地址空间比如AXI总线上的从设备地址越界。CA错误多半是因为AXI总线上的从设备没有处理请求直接abort了。还有一种典型的情况是TLP的payload超越了BAR空间映射的范围导致接收端把包当成非法的。热插拔相关的稳定性问题这里也提一句。PCIe热插拔是基于标准卡的机械和电气设计规范的FPGA板卡如果支持热插拔PERST信号必须正确连接并满足时序要求板卡的辅助电源AUX和指示灯引脚也不能省。如果只是要求板卡在带电情况下插拔不掉设备实际做的时候很难完全保证因为FPGA PCIe IP核默认是不支持热插拔而不断链的。我的建议是项目里按规范做好热插拔相关的电源时序和PERST复位逻辑但把运行中拔卡当作一个异常测试项来处理而不是日常使用场景。6. 实测数据与调试心得6.1 上位机验证流程与实测带宽图像连续、帧计数递增才是真正常整体链路调通之后上位机验证这一步非常关键。我的验证工具组合是lspci确认设备枚举dmesg看驱动加载和AER错误然后自己写一个小工具通过BAR0寄存器读取FPGA的帧计数和错误状态再在应用层用DirectShow或者Raw Buffer的方式抓帧显示。实测下来的数据可以给你参考1280x72030fps的YUV422流PCIe Gen3 x4的XDMA连续传输实测吞吐稳定在55.3MB/s附近上位机接收端用多线程轮询加DirectX渲染画面流畅无撕裂帧计数以30Hz的速率稳定递增。如果把分辨率提升到1080p30fps数据量变成124.4MB/s依然在链路能力范围内但上位机的内存拷贝和显示开销会明显变高这时候需要注意接收端的缓冲策略避免因USB或者显示瓶颈造成帧堆积。这里分享一个排查看起来卡顿的技巧先看FPGA侧帧计数是否稳定递增再看上位机接收到的帧计数是否匹配就能快速判断卡顿是发生在采集端、传输链路还是显示端。三个环节的解耦判断能省下大量排查时间。6.2 踩过的坑和能直接用的建议第一个坑是XDMA的SG描述符地址必须按64字节对齐。一开始我直接用了内存分配的默认地址结果DMA传输偶尔出现错误完成。后来在驱动里用了专门的对齐分配接口问题彻底消失。第二个坑是OV5640的SCCB总线上拉电阻。很多开发板为了省事直接把I2C上拉电阻做在板子上但如果你的FPGA IO驱动能力不够或者上拉电阻阻值选得太大SCCB在高速配置时可能出现偶发的ACK失败。我最后是把上拉电阻换成2.2K时钟频率放低到100KHz配置成功率从95%提升到了99.9%以上。第三个坑是AXI总线的burst长度。XDMA在配置界面里可以设置Max Burst Size默认可能是256字节或512字节。实际测试发现如果burst size设置过大上位机内存带宽不够时会拖累整个系统的传输帧率。我后来把Max Burst Size改成了128字节虽然没有到达峰值带宽的极限但传输延迟更稳定综合效果反而更好。第四个坑是关于FPGA复位引脚的使用。PCIe硬核的复位信号和FPGA全局复位必须区分开。如果直接用FPGA的全局复位去复位PCIe硬核会导致PCIe IP核在host枚举之后才完成内部复位链路训练的时间点就乱了轻则枚举缓慢重则就出现前面说的掉卡现象。正确做法是PCIe硬核使用PERST引脚过来的复位应用逻辑使用全局复位两者严格隔离。这个项目做到现在我最大的体会是FPGA图像传输项目里真正的难点往往不在图像采集也不在PCIe协议本身而是在于把采集侧时域约束和传输侧的高速接口无缝地衔接起来。先把每一级的缓存和时钟域边界设计清楚再动手写代码后面会省下大量调试时间。如果你也在做类似的项目希望这篇记录能帮你跳过那些我已经踩过的坑。
返回列表