ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

ZYNQ驱动OV5640实现UDP视频传输:从采集到网络上传的完整方案

ZYNQ驱动OV5640实现UDP视频传输:从采集到网络上传的完整方案 简介面向 ZYNQ 7010 嵌入式开发者与 FPGA 工程师这是一份围绕 OV5640 摄像头视频采集、UDP 网络上传的完整硬件驱动资源可直接用于熟悉 SoC 软硬件协同设计解决从传感器接入到网络输出的链路搭建问题。压缩包共 562 个文件约 37.88MB主要包含 VHDL/Verilog 驱动源码、XDC 约束文件、DCP 网表、RPT 综合报告、Tcl 和 SH 工程脚本、文档及日志类型覆盖逻辑设计、综合实现、仿真与上板调试各环节。目前已有 178 人学习下载。通过工程源码与目录结构读者可以理解 ARM 与 FPGA 的分工配合、OV5640 时序配置、VDMA 数据搬运、UDP 打包发送等关键模块并根据示例脚本快速复现实验、修改分辨率和网络参数用于视频监控、图像采集和远程传输类场景。1. ZYNQ 7010驱动OV5640采集UDP通信上传视频一块板子打通图像采集与网络传输做过图像采集的工程师大多有过这种经历摄像头采集端和网络传输端各有一套逻辑调试时两边分开跑都正常一旦接起来就丢帧、花屏、带宽上不去。ZYNQ 7010驱动OV5640采集UDP通信上传视频这个项目本质上是把“FPGA负责采集与缓存、ARM负责协议栈与网络发送”这条经典分工路径走通。PL端用Verilog把OV5640输出的DVP并行数据流收下来经过帧同步、行缓冲后写入DDR3PS端通过lwIP协议栈建立UDP socket把DDR3里的视频帧按MTU切片发出。这里的关键不是“能不能通”而是“在7010这个资源受限的平台上怎样分配DMA通道和缓存策略才不丢帧”。对于刚接触ZYNQ图像传输的开发者这套方案能帮你理解AXI总线上VDMA的带宽占用模型对于有经验的人参数设计和异常排查也有可借鉴的细节。2. 采集链路总体设计DVP接口、VDMA与DDR3缓存的映射关系2.1 为什么选择DVP接口而非MIPIOV5640同时支持DVP和MIPI两种输出接口在ZYNQ 7010上绝大多数设计选用DVP。原因在于7010的PL端没有原生MIPI CSI-2控制器实现MIPI接收需要额外的Lane管理逻辑和Byte对齐处理复杂度明显上升。DVP接口的信号包括PCLK像素时钟、HREF行有效、VSYNC帧同步、DATA[7:0]8位并行数据时序上更接近传统CMOS sensor的工作方式直接用Verilog状态机就能完成采集。图像格式方面常见的配置是RGB565或YUV422分辨率支持从VGA到1080p。工程里最常用的组合是720p30fps RGB565原因是这个组合下数据率适中1280×720×2字节×30fps ≈ 55MB/sDDR3带宽余量充足UDP也能在千兆网下不丢包地发出去。OV5640寄存器0x3035和0x3036用于设置PLL分频0x3808~0x380B设定输出尺寸这些都需要在上电初始化时通过I2C写入。// OV5640 DVP接口信号连接示例XDC约束节选 set_property PACKAGE_PIN W13 [get_ports cam_pclk] // PCLK set_property IOSTANDARD LVCMOS33 [get_ports cam_pclk] set_property PACKAGE_PIN W12 [get_ports cam_vsync] // VSYNC set_property PACKAGE_PIN U12 [get_ports cam_href] // HREF set_property PACKAGE_PIN V12 [get_ports cam_data[0]] // DATA02.2 VDMA在数据传输中的角色VDMAVideo Direct Memory Access是Xilinx提供的视频专用DMA IP核支持S2MMStream to Memory-Map和MM2SMemory-Map to Stream两个方向。在采集链路中OV5640输出的像素流经过自定义Verilog逻辑转换后接入VDMA的S2MM通道由VDMA负责把数据写入DDR3指定地址。之所以不直接用普通DMA是因为VDMA内置了帧同步逻辑可以根据VSYNC信号自动完成帧指针切换无需PS端软件干预。VDMA参数配置中需要关注三个寄存器S2MM_FRMDLY_STRIDE帧延迟与行距、S2MM_FRMBUF_CNT帧缓冲数量和S2MM_HSIZE行像素数。帧缓冲数量决定能缓存几帧三缓冲最稳妥——一帧在写、一帧在读、一帧在等即使UDP发送稍有抖动也不容易覆盖正在传输的数据。// VDMA S2MM寄存器配置示例PS端通过AXI-Lite写入 #define VDMA_BASE 0x43000000 #define S2MM_HSIZE 0x0100 // 行像素数720p 1280 #define S2MM_STRIDE 0x0104 // stride 行像素 × 2字节 #define S2MM_FRMDLY_STRIDE 0x0108 // bit[13:0]帧延迟, bit[29:16]stride #define S2MM_START_ADDR 0x00AC // 起始地址寄存器 #define S2MM_FRMBUF_CNT 0x0120 // 帧缓冲数量 Xil_Out32(VDMA_BASE S2MM_HSIZE, 1280); Xil_Out32(VDMA_BASE S2MM_STRIDE, 1280 * 2); Xil_Out32(VDMA_BASE S2MM_FRMDLY_STRIDE, (720 16) | 0); Xil_Out32(VDMA_BASE S2MM_FRMBUF_CNT, 3); // 三缓冲 Xil_Out32(VDMA_BASE 0x0000, 0x0003); // S2MM复位后启动2.3 带宽测算与DDR3分配策略ZYNQ 7010的DDR3总带宽约4.2GB/s32位DDR3-1066但这是理论峰值。实际有效带宽受刷新开销、总线仲裁和访问模式影响通常打六折。视频链路占用情况需要分三块看VDMA S2MM写入、VDMA MM2S读取如果PS端直接读取DMA buffer也需要算入、PS CPU访问DDR。分辨率与带宽对应关系如下分辨率格式帧率数据率DDR3占用率估算640×480RGB56530fps18.4MB/s0.4%1280×720RGB56530fps55.3MB/s1.3%1920×1080RGB56530fps124.4MB/s2.9%1920×1080YUV42230fps124.4MB/s2.9%带宽不是瓶颈真正的瓶颈在于VDMA中断频率与UDP发送速率的匹配。720p30fps意味着每帧33msVDMA在帧完成时产生一次中断PS端要在33ms内把这一帧全部发出去。千兆以太网理论速率125MB/s扣掉UDP/IP头开销后实际可用约110MB/s发一帧720p大小的数据约1.8MB需要约16ms。这个时间小于帧间隔所以单看网络也能跟上。但如果中断响应不及时或者lwIP的PBUF分配不当就会出现画面停顿。3. OV5640寄存器配置与Verilog采集逻辑的实现3.1 I2C初始化中的关键寄存器组合OV5640上电后默认输出SXGA1280×96015fps不会自动输出720p 30fps必须通过I2C配置。初始化序列通常包含几十个寄存器写入核心分为三组时钟配置、输出格式配置、时序配置。时钟配置决定PCLK频率输出格式决定字节顺序是RGB565还是YUV422时序配置决定HREF和VSYNC的相对位置直接影响采集逻辑的帧判断。初始化代码一般写成数组表驱动每条记录包含寄存器地址和值。写完后需要做一次软件复位0x3103 0x03然后等待至少50ms让sensor内部PLL锁定。判断初始化是否成功的标准不是I2C写操作无报错而是VSYNC信号是否有稳定脉冲——用ILAIntegrated Logic Analyzer抓一下最直接。// OV5640初始化关键寄存器配置C语言数组节选 // 使用Xilinx IIC控制器IP地址0x3C7位地址 static unsigned char ov5640_init_regs[][2] { {0x3103, 0x11}, // 系统时钟使能 {0x3008, 0x82}, // 软复位需等待50ms {0x3103, 0x03}, // 解除复位 {0x3035, 0x21}, // PLL分频 {0x3036, 0x46}, // PLL倍频决定PCLK {0x3808, 0x05}, // 输出高度高字节 → 720p设置 {0x3809, 0xD0}, // 输出高度低字节 {0x380A, 0x02}, // 输出宽度高字节 {0x380B, 0x80}, // 输出宽度低字节 {0x4300, 0x30}, // RGB565格式RGB顺序 {0x501F, 0x01}, // RGB输出使能 };3.2 像素同步逻辑与行场标志提取Verilog采集逻辑的核心是一个状态机负责把PCLK驱动的串行像素流整理成VDMA期望的AXI4-Stream格式。DVP时序中VSYNC下降沿表示一帧开始HREF高电平期间每个PCLK上升沿对应一个有效像素。常见错误是把VSYNC当作帧有效信号直接用忽略了sensor在VSYNC前后会有若干行无效数据。我做这个逻辑时习惯的做法是先检测VSYNC有效沿然后等待HREF第一次拉高后再开始计数这样能跳过帧消隐区。另一个容易被忽略的细节是数据对齐。RGB565模式下OV5640输出的是一个像素两个字节但DVP是8位接口所以字节顺序是低字节在前还是高字节在前由寄存器0x4300控制而Verilog侧要做对应的字节拼接。推荐的做法是在采集逻辑内部先把两个字节拼成16位再送入FIFO避免在VDMA侧做字节交换。// OV5640数据采集Verilog逻辑核心代码 reg [15:0] pixel_data; reg pixel_valid; reg [1:0] byte_cnt; always (posedge cam_pclk or negedge rst_n) begin if (!rst_n) begin byte_cnt 2d0; pixel_valid 1b0; end else if (cam_href byte_cnt 2d0) begin pixel_data[7:0] cam_data; // 第一个字节存低位 byte_cnt 2d1; pixel_valid 1b0; end else if (cam_href byte_cnt 2d1) begin pixel_data[15:8] cam_data; // 第二个字节存高位 byte_cnt 2d0; pixel_valid 1b1; // 16位像素拼好后有效 end else begin byte_cnt 2d0; pixel_valid 1b0; end end这段逻辑把两个连续的8位数据拼成RGB565像素pixel_valid作为写使能接入FIFO。byte_cnt在HREF为低时强制归零确保每行从对齐位置开始拼接不会出现跨行错位。实际调试中如果图像出现整体偏色或左右错位先检查byte_cnt的复位逻辑是否绑定了HREF——这比检查寄存器配置更常见。3.3 AXI4-Stream数据通路与FIFO深度选择从像素拼接到VDMA之间需要插一个异步FIFO原因有二PCLK来自OV5640VDMA接口时钟来自PL逻辑两侧频率不同像素数据是连续背靠背的而VDMA在空闲时会暂停需要一个缓冲吸收瞬时突发。FIFO深度选择上720p一行1280个像素深度设2048合适不需要更大。深度过深会引入额外延迟而FIFO过半就会置位prog_full反而影响实时性。FIFO输出侧使用Xilinx的axis_data_fifo IP配置为标准AXI4-Stream接口数据宽度16位深度2048不启用寄存器输出。这样VDMA的S2MM通道可以直接挂接不需要额外的AXI协议转换逻辑。注意axis_data_fifo的tlast信号必须正确——VDMA用tlast判断一行结束如果这个信号没有在每行最后一个像素时拉高VDMA会一直在等数据导致帧完成中断永远不产生。4. PS端UDP发送程序设计lwIP协议栈与VDMA中断配合4.1 ZYNQ PS端以太网与lwIP的环境准备PS端的UDP发送建立在双核ARM Cortex-A9上常用的协议栈是lwIP。选择lwIP而不是裸写MAC驱动是因为它在处理IP分片、校验和计算和PBUF内存管理方面已经有成熟实现虽然性能比不上带TCP卸载引擎的方案但在视频传输场景下UDP协议本身够轻瓶颈不在协议处理而在内存拷贝。Xilinx SDK里自带lwIP库模板选择“lwIP Echo Server”模板后在此基础上改造即可。带宽相关参数需要调整lwIP的MEMP_NUM_PBUFPBUF数量、PBUF_POOL_SIZEPBUF池大小和UDP发送buffer。PBUF_POOL_SIZE太小时发送速度一快就会出现“No free pbufs”报错导致丢包。对于720p30fps实测PBUF_POOL_SIZE设为32以上比较稳妥每个PBUF对应一个UDP包1472字节 payload16ms的发帧窗口内需要约1300个包PBUF是复用还是新建会影响性能。// lwIP UDP发送初始化代码基于Xilinx SDK的lwIP库 struct udp_pcb *udp_pcb_inst; struct pbuf *p; err_t err; ip_addr_t remote_ip; IP4_ADDR(remote_ip, 192, 168, 1, 100); // 接收端IP udp_pcb_inst udp_new(); err udp_bind(udp_pcb_inst, IP_ADDR_ANY, 35000); // 本地端口 udp_connect(udp_pcb_inst, remote_ip, 35001); // 远端端口 // 帧发送函数在VDMA中断回调中调用 void send_frame(u32 frame_addr, u32 frame_size) { u32 offset 0; u32 pkt_cnt; u32 i; // 按MTU切片发送1472字节对应1500MTU pkt_cnt (frame_size 1471) / 1472; for (i 0; i pkt_cnt; i) { p pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_POOL); u32 copy_len (frame_size - offset) 1472 ? 1472 : (frame_size - offset); memcpy(p-payload, (void *)(frame_addr offset), copy_len); p-len copy_len; udp_send(udp_pcb_inst, p); pbuf_free(p); offset copy_len; } }这段代码的关键是每一次udp_send调用都对应一个UDP包包内payload的数据是一帧图像连续内存的切片。每次发送前用pbuf_alloc分配新PBUF发送后立即释放保证内存池不被耗尽。如果接收端Wireshark抓包发现大量连续重复的包序号通常是这里的内存复用出了问题。继续看注意udp_send的返回值当返回ERR_MEM时说明PBUF池不足需要降低分辨率和帧率或者增加PBUF池大小。4.2 VDMA中断与帧指针回读PS端程序的工作流程是初始化VDMA → 启动S2MM通道 → 注册帧完成中断处理器 → 在中断回调中读取帧地址并发送。VDMA的帧完成中断会携带帧计数器可以据此判断当前是哪一帧完成。中断回调函数内要做的第一件事是清中断标志S2MM_INT_CLR寄存器写入再通过S2MM_CURDESC寄存器读取当前写指针。帧地址计算有一个容易出错的地方VDMA的三缓冲地址不是简单的“基地址 帧大小×N”而是由S2MM_START_ADDR寄存器依次指定的。如果基地址设为0x10000000、帧大小为1280×720×2字节那么地址安排应为0x10000000、0x10168000、0x102D0000也就是每帧1.4MB对齐。中断回调中需要记录当前完成的帧序号下一次启动采集后应沿用到下一个缓冲否则会读重复帧。// VDMA帧完成中断回调 void vdma_isr_handler(void *callback_ref) { u32 intr_status; u32 cur_frame; intr_status Xil_In32(VDMA_BASE 0x0054); // S2MM_INT_STATUS if (intr_status 0x01) { // 帧完成标志 Xil_Out32(VDMA_BASE 0x0058, 0x01); // 清中断 cur_frame Xil_In32(VDMA_BASE 0x00AC) 28; // 帧指针 // 根据帧指针计算实际发送地址 send_frame(frame_base_addr[cur_frame], frame_size); } }4.3 帧率匹配发送速度和采集速度不同步怎么办采集端固定30fps但UDP发送端因为网络阻塞、PC接收端处理不过来等原因偶发发送减慢是常态。当发送一帧耗时超过33ms就会遇到下一帧采集完成但上一帧还没发完的情况。这时数据竞争不可避免。实际项目中在发送前先查询VDMA的当前写指针位置如果发现“要发送的地址”已经被新一轮写入覆盖就跳帧。这个机制实现简单而且比加锁等待更不容易死锁——视频数据晚一点没关系中断住系统才可怕。为解决网络拥堵带来的持续丢帧可以在发送逻辑中增加一个简单的节流阀记录上一帧的发送耗时如果连续三帧都超过40ms就主动降低发送帧率——隔一帧发一帧。这个降帧是纯PS端行为PL端始终在采集DDR3缓存保证最老帧被覆盖是最安全的。实践中还有一个细节PC接收端收到的帧序不要用UDP序号判断因为UDP本身不保证有序。帧数据包头前4字节写入帧计数接收端按帧计数重组。5. UDP协议栈参数调整MTU、端口缓冲与校验和策略5.1 MTU与UDP包大小如何影响传输效率OV5640的一帧720p RGB565数据量为1280×720×21843200字节在1500字节MTU下需要拆成约1252个UDP包。MTU大小直接影响效率——包越大有效载荷占比越高但超过1500会被IP层分片分片后任一片丢失会导致整包重组失败在无线环境和高丢包网络中建议将UDP payload直接设为1400字节以内。这里给出不同MTU下的对比表MTUUDP payload上限一帧包数量有效载荷占比适用场景15001472125297.4%有线局域网9000巨型帧897220699.4%千兆直连14001372134496.3%跨网段传输巨型帧要确保交换机和接收端网卡都开启支持否则静默丢弃导致黑屏。建议默认使用1500字节MTU这个参数在大多数网络环境都稳定。5.2 校验和增大吞吐还是保数据正确UDP的校验和计算量不大但lwIP默认开启校验和验证会占用CPU时间。对于视频传输且接收端丢包不影响帧因为丢包本身就会出现马赛克部分方案会关闭发送端校验和实测吞吐量约提升8-10%。关闭方法是在lwIP选项CHECKSUM_GEN_UDP中设置编译开关或者在UDP PCB创建后调用udp_set_flags设置UDP_FLAGS_NOCHKSUM。不建议接收端关闭校验和因为接收端校验和能帮你确认是不是网线质量问题。5.3 接收端缓冲区与播放实时性UDP接收端PC的socket接收缓冲区也需要同步调整。Windows下默认15872字节约12个UDP包这点缓冲根本撑不住视频流的突发到达速度会导致大量丢包。网络调试助手一般可以设置接收缓冲大小改到256KB以上。Wireshark抓包时如果发现接收端窗口满了的提示优先怀疑是接收缓冲配置问题。两侧IP和端口保持对应关系接好网线后第一步ping通再接视频。6. 排错维度从无画面到花屏再到高延迟的检查顺序6.1 无画面用ILA先看VSYNC再看HREF无画面是最常见的现象排查顺序比排查手法更重要。先抓VSYNC信号没有脉冲就是OV5640没工作检查I2C配置和sensor供电VSYNC正常但没有HREF多半是输出分辨率配置和实际不一致导致时序状态机没触发。HREF正常但没有数据用ILA看PCLK和数据线的连接查看数据位序是否有误。// ILA触发电平设置在Vivado Hardware Manager中配置 // Mark VSYNC触发条件Rising edge // 在sensor初始化后抓一次验证帧头很多工程师遇到无画面直接去查UDP代码方向错了。采集链路有明确的前后级关系先保证PL侧采集通、DDR3里有图再排查PS侧。6.2 花屏/横纹/偏色分别意味着什么现象可能原因排查手段图像整体偏绿/偏红RGB565字节顺序反了调整寄存器0x4300的值水平方向颜色错位byte_cnt未在行首复位修改Verilog逻辑花屏但颜色基本正常VDMA帧地址计算错误核对帧大小和基地址周期性横条纹DDR3带宽不足或FIFO溢出加大FIFO深度/降分辨率偏色是最好排查的因为寄存器配置就那几个字节顺序选项。花屏如果伴随帧序号跳跃优先查VDMA的三缓冲地址配置是否连续——很多初版代码地址对齐到帧大小但没对齐到DDR的32字节边界导致地址递减时错位。6.3 Wireshark抓包验证UDP上传的实际效果Wireshark在这条链路上不只是抓包工具它可以实时验证链路通断和数据内容。先用“udp.port 35001”过滤出目标流然后检查三个指标包速是否接近理论值720p30fps约为1252×3037560pps、包长是否都在1472字节附近、接收端是否收到连续的帧计数。帧计数在payload的前4字节Wireshark的“Follow UDP Stream”不能直接解析但可以通过“视图→数据包字节”面板定位到自定义帧头。想计算前后两包的时间间隔Wireshark支持列自定义表达式添加frame.time_delta_displayed为列查看两包间隔是否稳定在26μs附近千兆网1472字节的线速发送间隔。帧与帧之间要留出足够的时间间隔。全速发送状态下两帧之间只间隔约16ms发帧耗时接收端如果来不及处理就会在驱动层丢弃表现为花屏但不丢UDP包。解决办法是在PS端发送完一帧后加一个2ms延时再发下一帧不影响帧率观感但能明显降低接收端压力。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表