尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA实战:UDP以太网视频传输完整链路解析

FPGA实战:UDP以太网视频传输完整链路解析 简介面向FPGA视频传输应用开发者本工程基于OV5640摄像头采集视频在FPGA内部完成像素缓存与逻辑处理后通过千兆以太网发送实现接近实时的视频传输链路是从图像采集到网络输出的完整参考设计。资源共包含276个文件压缩包仅约3.98兆字节文件类型涉及硬件描述语言源码、仿真测试脚本、约束与工程配置、时序与面积报告以及可直接烧写的比特流文件便于用户从代码到上板的完整学习。当前已有746人学习下载。通过研读该工程可以掌握摄像头接口时序、跨时钟域FIFO设计、以太网帧封装与发送控制以及处理高带宽视频流时的缓冲与流量控制方法同时可利用附带脚本复现仿真与综合流程尤其针对OV5640的SCCB配置、帧同步信号处理等细节能帮助规避常见工程陷阱适合FPGA视频处理和网络通信方向的开发者深入实践。 做FPGA的十有八九都绕不开两座山图像处理、以太网通信。这个“fpga实例-以太网传送视频程序”项目就是把这两座山叠起来做的一套完整链路——FPGA端把视频源数据按UDP包拆分通过千兆以太网送到电脑电脑端用上位机接收、解析并显示最终实现“摄像头在板端画面在屏幕上”的实时视频预览。它解决的是高速图像数据的远距离、低成本回传问题适合正在啃FPGA图像处理和网络协议栈、想找一个能真正跑通的综合项目的工程师参考。我会按项目实战的顺序拆解整条链路先梳理整体架构和方案选型再把带宽计算、包格式设计、PHY寄存器配置一个个讲透最后给出关键代码框架和实测排查记录。全程按我实际调板子的经验来讲数据和代码都是可直接落地的不是那种只讲概念的入门科普。1. 项目架构从像素到以太网帧的完整链路1.1 数据流设计与模块划分整个项目的本质是一台“视频发送机”厂编采集视频像素暂存到缓存里接着按以太网帧格式封装并发送PC端再负责收包、解包、拼帧。具体数据流可以描述为视频源摄像头或测试图生成进入采集模块送入DDR3或片上FIFO做缓冲随后由打包模块按设定的包长切块并加上自定义包头送到以太网MAC层封装成UDP/IP报文再经RGMII接口交给PHY芯片转成差分信号从网线发送出去。在FPGA内部模块划分我习惯做成这样视频采集模块驱动OV5640这类摄像头或者直接生成彩条、棋盘格等测试图像方便在没有相机时先调通链路。帧缓存与读写仲裁用DDR3 MIG IP实现大容量缓冲或对低分辨率直接用Block RAM双口FIFO。缓存的作用是消除以太网发送的突发和视频帧率之间的速率不匹配。打包控制模块按行或按块把图像数据切成大小固定的数据段每段加帧号、块号、长度字段组包后交给协议栈。以太网MACUDP/IP封装如果用Xilinx可以直接用Ethernet IP核自带MAC和RGMII也可以自己写精简的MAC收发逻辑负责生成前导码、以太网头、IP头、UDP头以及CRC校验。PHY驱动与MDIO管理通过MDIO总线配置PHY芯片的寄存器读取link状态、协商速率控制复位和休眠。这套模块划分的好处是每一级都有明确的接口调试时可以逐级自环先发固定数据包验证PHY和MAC再发图像数据验证DDR读写最后跑全流程。如果你用的是国产板卡比如黑金、高云之类带PHY的开发板思路完全一样只需要换对应厂商的IP或自己补MAC层。1.2 为什么选UDP而不是TCP这是刚开始做视频传输的人最容易纠结的问题。我的答案是视频实时传输优先选UDP除非你有很强的理由必须用TCP。原因有三点。第一TCP为了可靠性付出了巨大代价握手建立连接需要时间重传机制会引入延迟和乱序窗口管理和拥塞控制逻辑复杂用纯RTL实现全硬件TCP协议栈工作量非常大调试周期长而且传统TCP在丢包时的行为对实时视频并不友好——你宁愿丢一帧也不愿等重传后的旧数据。第二UDP协议栈在FPGA里只有几十行状态机的量级固定好源IP、目的IP、源端口、目的端口填充长度字段和校验和就可以发出。UDP允许组播同一路视频流可以被多台电脑同时接收这在调试和演示时太方便了。第三可靠性的补偿可以由应用层来做发送端给每个包编序号接收端根据序号判断丢包并做容错显示偶尔丢一两个包对画面影响不大最多在解码帧的对应位置出现花屏下一帧就恢复了。这套策略的实际效果远比TCP重传好。2. 关键指标与协议细节先把带宽和包格式算明白2.1 带宽预算是怎么算的项目动手之前第一件事不是写代码而是拿计算器算带宽。以最常见的1080p30fps、RGB565格式为例一帧像素数据量是1920×1080×2字节≈4.15MB每秒30帧就是约124.4MB/s。注意这里是没有加任何协议头的纯像素数据。千兆以太网的物理线速是125MB/s但这不是有效负载速率。每个以太网帧都有前导码8字节、帧间隔12字节、以太网头14字节、CRC 4字节加上IP头20字节和UDP头8字节实际有效载荷利用率大概在94%左右也就是说千兆网能承载的视频裸流上限大约在117MB/s附近。1080p30 RGB565需要124.4MB/s已经超了所以裸传必然丢包。实际项目里我通常这样取舍如果坚持1080p30就得换成MJPEG压缩或H.264硬编码把码率压到二三十兆如果不想动压缩就退到720p30 RGB565一帧约1.84MB每秒55.3MB千兆网还有明显余量跑起来非常稳。这就是带宽预算的意义——它直接决定了你的分辨率和像素格式选型。2.2 应用层包格式设计UDP的单个数据报文长度受MTU限制。标准以太网MTU是1500字节扣掉IP头20字节和UDP头8字节UDP载荷最大是1472字节。如果超过这个值IP层会做分片而FPGA实现对分片包的发送接收、重组非常麻烦所以我强烈建议应用层包长设一个更保守的值比如1400字节。分包时要让上位机能重建完整图像我设计的自定义包头结构如下字段长度说明帧序号2字节当前图像帧的编号从0递增用于上位机判断丢帧块序号2字节当前帧内的分包序号从0递增用于按序拼接数据长度2字节本包携带的像素数据字节数保留字段2字节备用可存分辨率、格式等标志一帧720p30 RGB565的数据量是1843200字节按1400字节一包计算需要1317个UDP包。上位机收到第一个包的块号为0就可以开始申请缓冲根据块号直接写入对应偏移直到收满一帧再显示。这个设计在带宽上留了余量在工程上又足够简单是业内很常见的做法。2.3 为什么说MTU和IP分片是坑新手最容易踩的坑就是包长一不小心超过1472字节导致IP分片。FPGA端如果自己写IP层封装分片逻辑意味着要维护多个分片的ID、偏移和标志位状态机和存储都变得复杂而像W5500这类以太网模块虽然内部有自己的协议栈但分片处理能力依然有限。实际工作中很多厂商的GigE Vision相机默认使用的包长是1500以内为的就是规避分片。所以我的原则是包长宁小勿大。1400字节已经能满足绝大多数视频流数据量上多出的几十字节开销完全可以忽略。结构简单换来的是稳定性这在FPGA项目里永远是最值钱的事。3. PHY寄存器配置与RGMII时序真相3.1 MDIO读写怎么才能稳PHY芯片是FPGA和网线的桥FPGA通过MDIO接口一根时钟MDC、一根数据MDIO读取和配置它的寄存器。最常见的PHY有Realtek RTL8211系列、Marvell 88E1512、国产裕太微等寄存器基本兼容IEEE 802.3定义只是扩展寄存器有厂商差异。初始化时最重要的两个寄存器是寄存器0BMCR基本控制寄存器bit15是软件复位bit12是自协商使能bit13、8、6、5组合控制速度和双工模式。寄存器1BMSR基本状态寄存器bit5是自协商完成标志bit2是link状态bit6、7表示10M/100M能力。典型的上电配置流程是先对寄存器0写0x8000触发软复位等待复位完成确认bit12为1使能自协商轮询寄存器1的bit5和bit2等待自协商完成且link起来之后再读取寄存器0确认协商后的速度是千兆还是百兆。RTL8211的MDIO地址通常是0x00但也有0x01的版本上板前先看原理图确认地址引脚。如果PHY配置正确还能通过寄存器值判断物理链路问题。比如以太网线只接了4根线、或者对端是百兆交换机协商结果会降到100M带宽瞬间少了十倍画面必然卡顿。3.2 RGMII接收端时序为什么要调delayRGMII接口在千兆模式下时钟频率是125MHz但数据是在时钟上下沿都采样的上升沿送低4位下降沿送高4位等效数据率250Mbps×4位1000Mbps。问题在于PHY和FPGA之间PCB走线会导致时钟和数据存在相位偏移如果直接按沿采样可能采到毛刺或不稳定的跳变沿。解决方案有两个一是在PCB设计时保证RGMII的TX时钟和TX控制、TX数据严格等长二是在FPGA内部用IDELAY原语对接收时钟或数据做相位调整。Xilinx的Ethernet IP核内部已经集成了这些延迟校准但如果你是自己写RGMII收发的必须在时序约束里明确生成时钟和数据的相对关系否则综合后的结果跑着跑着就随机出错。实际调试时我习惯在PHY寄存器里找有没有RGMII TX/RX delay的配置位比如RTL8211F的0xa43之类的扩展寄存器先通过PHY侧调整delay不够再用FPGA的IDELAY微调。这是最快能稳定跑满千兆的办法。3.3 PHY寄存器分析怎么用说一个很典型的现场板卡上电后UDP发送端一直发数据但PC端Wireshark什么都抓不到。这时候不要盯着代码发呆先读PHY寄存器。如果读到寄存器1的bit2为0说明物理link根本没起来问题在网线、对端设备或者PHY芯片供电如果link是好的但协商在100M说明网线或对端不支持千兆带宽预算会被打破。如果MDIO总线上读写一直超时或返回全1优先检查MDIO地址是否正确、MDC时钟频率是否过高最好低于2.5MHz、MDIO引脚是否加上拉电阻。寄存器分析是网络调试的基本功。与其乱猜不如把PHY所有状态位的含义整理成一张表逐个对照排查效率会高很多。4. 代码层面的关键实现4.1 视频发送状态机的骨架下面是一个简化版的UDP发送状态机核心是把“从FIFO读数据→组UDP包→交给MAC发送”串起来。这里只列关键状态实际工程里还要加超时保护、FIFO空满判断和数据计数器。localparam IDLE 4d0; localparam SEND_HEAD 4d1; // 发送以太网头 IP头 UDP头 localparam SEND_DATA 4d2; // 发送1400字节有效载荷 localparam SEND_CRC 4d3; // 交给MAC层补FCS localparam WAIT_END 4d4; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else case (state) IDLE: begin if (fifo_empty 1b0 eth_tx_ready) state SEND_HEAD; end SEND_HEAD: begin if (head_done) state SEND_DATA; end SEND_DATA: begin if (data_cnt PKG_SIZE) state SEND_CRC; end SEND_CRC: begin if (crc_done) state WAIT_END; end WAIT_END: state IDLE; default: state IDLE; endcase end有个细节要提醒发送UDP头中的长度字段需要实时计算为“IP总长度208data_len”如果组帧是逐字节流式发送就要提前用一个计数器把当前包的data_len算出来在发送头的时候填进去。很多人忘了这一步导致Wireshark里能看到包但校验长度不对接收端直接丢包。4.2 RGMII输出数据的位拼装如果不用Xilinx IP核自己拼RGMII数据时8位并行数据要拆分到上下沿。核心代码如下// 8bit数据转RGMII 4bit DDR输出 always (posedge eth_tx_clk) begin rgmii_txd[3:0] tx_data[3:0]; // 上升沿发低4位 end always (negedge eth_tx_clk) begin rgmii_txd[3:0] tx_data[7:4]; // 下降沿发高4位 end assign rgmii_tx_ctl (posedge_tx_en) ? 1b1 : tdata_en; // 控制信号同理由上下沿分别赋值这段代码看起来简单但要注意eth_tx_clk必须是由时钟管理器生成的125MHz时钟并且和tx_data保持确定的相位关系。官方IP的做法是把时钟和数据一起约束自己写RTL的话必须用ODDR原语而不是像我上面那样单纯用两个always在不同沿赋值——综合工具不一定能正确映射。所以实际开放中我通常建议直接调用原语ODDR既可靠又省事。工程上不要为了“省一个原语”去赌综合器的行为。4.3 上位机怎么收和显示PC端接收建议先用Python快速验证再移植到C#或C做最终版本。Python的socket接收很简单import socket import struct import cv2 import numpy as np sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((192.168.1.100, 5000)) # 绑到PC网卡IP sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024) frame bytearray(1920 * 1080 * 2) while True: data, addr sock.recvfrom(1500) frame_id, block_id, length struct.unpack(HHH, data[:6]) offset block_id * 1400 frame[offset:offsetlength] data[6:6length] # 当 block_id 某帧最大块号时一帧凑齐转成图像显示这段程序里最容易忽视的是接收缓冲区大小。Linux下默认socket缓冲区可能只有几百KB千兆网速率下瞬间就会丢包所以要用setsockopt调大。另外绑定的IP必须和FPGA发送的目的IP在同一个网段最简单的方式是PC网卡设静态IP比如192.168.1.100FPGA端发往192.168.1.100两端无需DHCP。5. 实测记录与避坑指南5.1 实测数据结果我在Artix-7板卡上跑720p30 RGB565实测上位机显示稳定在28~30fps网络吞吐约55MB/s帧延迟在80~120ms之间画面基本无撕裂。换到1080p30未压缩RGB565时网络吞吐逼近118MB/s开始出现偶发丢包表现为画面横向撕裂或条状花屏重传是不可能的只能靠下一帧覆盖。这个结果完全印证了带宽预算的结论1080p裸流跑千兆已到极限没有余量。如果要稳要么压缩要么降分辨率。很多工业相机选择在相机内部做MJPEG压缩再用GigE Vision传出来原因就在这里。5.2 常见问题速查表故障现象可能原因解决办法PC抓不到任何UDP包PHY link未建立、MDIO配置错误先读PHY寄存器1确认link检查网线和对端设备确认MDC时钟频率能抓到包但封包长度字段不对UDP长度字段计算错误用Wireshark对比实际载荷和Length字段在发送状态机里补算长度画面撕裂、花屏带宽超限、socket接收缓冲区过小、分包顺序错乱降低分辨率或换压缩格式调大接收缓冲区检查帧号和块号解析图像颜色错乱像素格式字节序不一致核对RGB565发送和接收端的高低字节顺序必要时上位机做字节交换高速跑几分钟后死机PHY复位时序违规、时钟约束缺失检查上电复位时序加约束后重新综合布局布线网速只有100Mbps网线只接入4芯、对端设备不支持千兆换完整8芯网线确认PHY协商结果按需强制千兆模式5.3 几个我踩过的坑第一个坑是MDC时钟频率。最开始我看PHY的datasheet说MDC最高支持25MHz就直接在系统里分频出12.5MHz用结果MDIO读写不稳定时好时坏。问题是MDIO时序需要足够的建立保持时间尤其在上拉电阻阻值偏大的情况下时钟边沿和数据翻转会错位。后来我把MDC降到2.5MHz以下读写才完全稳定。MDIO只是配置通道不追求高速慢一点反而更稳。第二个坑是上电后立刻读PHY寄存器返回全F。有些PHY需要几十毫秒完成内部校准如果FPGA复位结束马上发MDIO读命令PHY还没准备好返回数据自然是无效的。正确做法是在初始化状态机里加一个至少100ms的延时等PHY稳定后再操作。第三个坑是RGMII时钟约束缺失导致时序混乱。只要在Vivado里看到“RGMII接口时钟和数据路径未约束”之类的Critical Warning就要停下来要么用官方IP要么自己加约束不要指望跑仿真能发现——仿真是理想波形上板才见真章。第四个坑是上位机接收缓冲区不足时表现出的现象很像FPGA丢包画面随机撕裂但无线速统计时又发现PC实际收到的包远少于发出的包。这个排查花了我一晚上后来用Wireshark抓包统计才发现丢包发生在操作系统内核缓冲区和FPGA一点关系都没有。先确认接收端再查发送端排查顺序很重要。6. 从项目延伸到工程级的改进思路这个项目做通之后很多人会问下一步怎么走。如果让我建议优先级第一个加的模块是图像压缩。用FPGA实现MJPEG编码并不算困难JPEG的核心DCT和霍夫曼编码都有成熟的IP或开源代码可参考压缩后1080p码率能控制在20Mbps以内千兆网余量巨大。第二个值得加的是丢包重传或前向纠错。虽然在UDP上做重传会引入延迟但如果只在关键帧或关键行上做选择重传对画面质量提升非常明显。FEC则可以通过冗余包恢复少数丢失的包带宽多花5%~10%换来几乎无损的传输适合数据链要求高的场合。第三个方向是组播。FPGA发送端用UDP组播地址多台PC同时接收同一路视频对产线测试或教学演示都非常实用。RTL8211这类PHY对组播不需要额外配置只需在IP层把目的IP设为组播地址并确保交换机开启IGMP Snooping否则会按广播泛洪处理。最后分享一个我个人的工程习惯每个模块都预留一个环回测试点。视频数据在进入打包模块之前可以一键切到固定递增数以太网发送端能向PC发固定模式的测试包上位机收包软件里也加一个帧率、吞吐量统计窗口。这套“自测”机制在项目联调时能节省大量时间因为你永远要先定位是发送端的问题还是接收端的问题而不是对着黑盒子瞎猜。本文还有配套的精品资源点击获取
返回列表