恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Zynq+OV5640+UDP:FPGA图像采集与实时网络传输系统设计

  • 首页
  • 资讯中心
  • /
  • Zynq+OV5640+UDP:FPGA图像采集与实时网络传输系统设计

相关资讯

Robocup救援仿真多智能体系统:代码实现与算法实战解析 2026/9/1 3:40:06
人形机器人不卷参数卷表情?爱湫用3D超短焦投影打造情感交互新路径 2026/9/1 3:40:06
指绘入门到进阶:雾湖妖精接力绘画完整流程指南 2026/9/1 3:40:06

最新资讯

携程春招第三批笔试攻略:题型、考点与编程题复盘
C语言结构体详解:从数据打包到内存对齐的完整指南
摩拜数据工程师校招笔试题解析:算法SQL与业务场景
AH8669非隔离AC-DC降压方案:从选型到PCB布局的完整设计实战
普元获评低/零代码市场典型供应商,融合智能化能力推进复杂场景
Unity iOS 桥接层 .mm 文件:Objective-C++ 三语合一解析

今日推荐

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Zynq+OV5640+UDP:FPGA图像采集与实时网络传输系统设计

发布时间:2026/9/1 3:45:07
Zynq+OV5640+UDP:FPGA图像采集与实时网络传输系统设计 简介本资源是一套基于Xilinx Zynq-7000 SoCXC7Z020CLG484-1的嵌入式图像采集与网络传输完整工程面向FPGA开发工程师、嵌入式视觉系统学习者及高校相关课程实践者解决OV5640摄像头高帧率图像采集与实时UDP以太网传输的技术落地问题适用于智能监控、远程视频调试、边缘视觉原型开发等场景。压缩包含1507个文件总大小113MB涵盖Verilog183个实现图像采集与DMA控制逻辑、C语言120个完成ARM端UDP协议栈配置与数据包封装、XDC约束70个保障时序收敛以及Vivado工程配置、HDF硬件描述、Bit流与SDK工程等关键交付物。内容预览中高频出现的__synthesis_is_complete__标记表明该工程已通过完整综合与实现流程可直接导入Vivado 2018.3加载运行。已有351人学习下载提供从硬件驱动到网络传输的端到端参考实现包括分辨率/帧率可调机制、软硬协同调试要点及典型UDP丢包应对提示显著降低Zynq平台图像网络传输类项目的开发门槛。 做图像采集传输的工程师应该没少见过 Zynq OV5640 UDP 这个组合。去年我在做一套工业检测装置的上位机预处理方案时就是靠这条链路把传感器画面实时丢到网口另一端的 PC 上没用 HDMI也没加视频编码芯片。整套系统从 OV5640 的 DVP 引脚采数据开始在 PL 侧打包成 AXI4-Stream再用 VDMA 缓存到 DDR最后由 PS 端的 LwIP 协议栈把帧数据拆成 UDP 报文发出去。这个工程最典型的价值是不需要额外买图像采集卡也没有复杂的视频编码芯片就用 Zynq 本身把“传感器采集”和“网络传输”这两件事一起干了。对正在学 FPGA/Zynq、或者想快速搭一套图像无线传输原型的人来说这套链路里的每一个环节——OV5640 寄存器初始化、DVP 时序、VDMA 帧缓存、LwIP UDP 组包——都是可以拿出来反复咀嚼的硬知识。下面我把从架构选型到驱动实现再到现场调试踩过的坑完整梳理一遍。1. 项目背景与整体方案选型分析1.1 这套系统到底在做什么简单说数据流是这样走的OV5640 摄像头通过并行的 DVP 接口输出 8 位像素数据和 HREF、VSYNC、PCLK 等同步信号。这些信号进入 Zynq 的 PL 侧后先由一个采集模块把像素数据和行、帧有效信号打包成 AXI4-Stream 总线。接着 AXIVDMA 的 S2MM 通道会把这股流式数据搬到 DDR3 的帧缓冲区域。当一帧完整写入 DDR 后PS 端读取这帧数据通过以太网控制器和 LwIP 协议栈将整帧图像按 MTU 限制切分成多个 UDP 报文从网口发送给上位机。这里的核心思路是PL 负责高速并行信号的时序对齐和数据搬运PS 负责协议栈和上层调度。两个世界通过 AXI 总线连接Zynq 的优势恰恰在于把这两种处理器架构放在同一颗芯片里省去了 FPGA 与外部 ARM 通信的种种麻烦。从工程管理角度看这样做出来的系统只有一个设备节点代码和硬件也能在同一套工具链里维护迭代成本低很多。1.2 为什么选择 Zynq 而不是纯 FPGA 或单片方案如果换成纯 FPGA理论上也可以用 Verilog 直接写以太网 MAC 和 UDP/IP 协议栈但实际工作量会非常可观。UDP 虽然简单你仍然需要处理 ARP 请求、IP 校验、MAC 地址过滤、CRC 校验、以及 PHY 芯片的寄存器配置。哪怕只做一个只发不收的裸 UDP 发送器调试 PHY 芯片的时序也能消耗掉不少精力。Zynq 的 PS 侧自带千兆以太网 MACGEM再搭配 LwIP 协议栈这些底层问题基本被封装好了我们可以把更多时间花在图像数据和传输策略上。如果换成普通 ARM MCU问题则出现在图像采集侧。OV5640 在 720p 下的像素时钟可以达到 70MHz 左右RGB565 每像素两个字节瞬时数据率在 140MB/s 以上MCU 的 DVP 接口和内部总线带宽很难吃住这个速度。就算用一些带 DVP 的高性能 MCU帧率和分辨率也受限而且难以扩展更复杂的图像预处理。用树莓派这类 Linux 板卡虽然录像是强项但很多时候我们需要在采集端做实时控制、触发同步、自定义协议甚至直接把图像先做 ROI 裁剪或者像素级预处理再发出去。Zynq 的 PL 侧可以方便地扩展这类逻辑PS 侧又保留了软件灵活性。综合下来这个组合是当前做嵌入式图像传输最成熟的路线之一。1.3 UDP 协议的选择与带宽预估为什么这里选 UDP 而不是 TCP图像数据量很大实时性要求又高TCP 的拥塞控制和确认重传机制在带宽吃紧时会产生严重的延迟抖动也可能因为重传把旧数据混进新帧里导致画面解码混乱。UDP 无连接、无状态丢包之后我们只需要根据帧号丢弃不完整帧或者重发关键帧实时性是可以保证的。对于视频监控、图像预览、远程检测这类场景这是非常合理的选择。当然选择 UDP 前必须算清楚带宽账。以 640x480 RGB565 为例一帧大小是 614400 字节30fps 的话就是 18.4MB/s换算成 bit 是 147Mbps这已经超出百兆网口的实际能力。如果是 1280x72030帧率达到 442Mbps百兆网口根本不可能。所以很多教学工程里默认的百兆网口方案实际只能跑 320x24030 或者 640x48015 这种规格要跑更大的分辨率就得换千兆网口或者对图像做压缩。这点在调试阶段特别重要不然你会花大量时间怀疑协议栈和网卡实际上纯粹是带宽不够。2. OV5640 传感器与采集时序细节2.1 OV5640 的寄存器配置和输出格式OV5640 是一颗 500 万像素传感器支持 DVP 和 MIPI 两种输出方式。在这个工程里我们用的是 DVP 模式引脚包括 8 位数据线 D[7:0]、像素时钟 PCLK、行有效 HREF、帧有效 VSYNC以及 SCCB 接口的 SIO_C 和 SIO_D。SCCB 其实和 I2C 很接近Zynq PS 侧自带的 I2C 控制器可以直接拿来用。OV5640 的 I2C 写地址是 0x3C读地址是 0x3D。芯片上电以后并不是默认输出可用的视频流必须通过 SCCB 写入大量寄存器完成内部 PLL 配置、输出时序配置、数据格式选择等初始化。常见的有效初始化序列往往有几百个寄存器一般直接从厂商或开发板例程里拿来使用。但有几个关键寄存器建议自己掌握寄存器地址功能说明0x3035 / 0x3036PLL 分频和倍频配置直接决定 XCLK 到内部时钟的倍频关系0x3808 / 0x3809图像输出宽度的高 8 位和低 8 位0x380A / 0x380B图像输出高度的高 8 位和低 8 位0x380C / 0x380DHTS即水平方向总周期数0x380E / 0x380FVTS即垂直方向总行数0x4300输出数据格式控制例如 RGB565、YUV422、JPEG 等0x300A / 0x300B芯片 ID分别读出 0x56 和 0x40用于验证 SCCB 通信正常实际调试时我习惯在 SDK 里先读一下 0x300A 和 0x300B确认 SCCB 通路没问题再去烧寄存器序列。如果这两个寄存器读出来不对后面所有图像问题都不用看先从硬件连接和 I2C 时序查起。2.2 DVP 时序与 PL 侧采集的对应关系OV5640 在 DVP 模式下VSYNC 高电平表示进入一帧HREF 高电平表示当前 PCLK 正在输出有效像素数据。RGB565 输出时一个像素通过两个 PCLK 周期输出先高字节后低字节。采集模块只需要在 HREF 拉高期间每个 PCLK 上升沿采样一次 8 位数据然后把两个字节拼成一个 16 位像素再送进后续的 AXI4-Stream 通路。这里的对应关系是PCLK 作为采集时钟HREF 作为行有效信号VSYNC 作为帧有效信号。用 Xilinx 标准 Video In to AXI4-Stream IP 时需要把 HREF 接到 vid_io_in_deVSYNC 接到 vid_io_in_field数据接到 vid_data。如果不想依赖这个 IP也可以写一个很小的 Verilog 采集模块用 HREF 拉高作为行内数据的使能用 VSYNC 产生帧开始标志最后把数据拼成 AXI4-Stream 的 tdata、tlast 和 tuser。我自己的工程里就采用了后一种方式逻辑非常直白也更好排查。有一点必须注意OV5640 的 VSYNC 和 HREF 极性并不是固定的可以在寄存器里配置为高有效或低有效。所以一旦出现完全无图或者图像上下颠倒除了看寄存器还要排查采集模块里的极性判断是否和寄存器设置一致。这类问题往往隐藏得比较深因为只靠仿真看不出结果。2.3 杜邦线连接 OV5640 的坑很多同学初期图方便用杜邦线把 OV5640 模块直接连到 FPGA 开发板的扩展口。在 QVGA 分辨率下这么做可能还能正常出图一旦把分辨率提到 720p 或者更高PCLK 频率上去了就会出现横纹、颜色错乱、甚至完全花屏。我自己用 20cm 杜邦线连接时640x48030 下色条测试都过不了图像里会莫名其妙多出一些空行和噪点。原因其实不复杂DVP 并行接口有 8 根数据线加 3 根同步信号杜邦线之间没有等长设计和屏蔽高速翻转时互相串扰PCLK 边缘抖动一大采样位置就飘了。解决办法有三个方向第一尽量用开发板自带的 FPC 摄像头接口或者直接在 PCB 上排线连接第二如果非要用杜邦线长度控制在 10cm 以内并且把线束捆在一起减小环路面积第三在 FPGA 侧用 IDELAY 等原语调整 PCLK 的采样沿或者通过降低分辨率和帧率来容忍信号劣化。想稳定跑高分辨率布线问题是绕不开的。3. PL 端图像通路与 VDMA 核心配置3.1 Block Design 搭建与关键 IP 选型在 Vivado 的 Block Design 里我会按下面这套清单搭工程ZYNQ7 Processing System开启 DDR3、UART1、ENET0、I2C0、SD0并把 FCLK_CLK0 设为 100MHz 作为 PL 侧主要时钟FCLK_CLK1 设为 24MHz 作为 OV5640 的 XCLK。AXIVDMA使能 S2MM 通道数据宽度配成 32 位或 64 位Stream Data Width 配成 8 位或 16 位帧缓冲数量设为 3Burst Length 配 16。自定义采集模块负责把 OV5640 的 DVP 信号转成 AXI4-Stream。AXI BRAM Controller 或简单的 Debug 寄存器用于软件读取状态例如帧计数、VDMA 状态等。这里的选择逻辑是Zynq-7000 系列 PL 侧的 AXIVDMA 支持 S2MM 写内存和 MM2S 读内存两个方向。图像采集工程至少要用 S2MM。如果后续还需要把 DDR 中的图像数据通过 PL 侧 HDMI 或 LCD 显示那就要把 MM2S 也打开。这个工程只做 UDP 传输PS 本身就能读 DDR所以 MM2S 可以不使能减少逻辑资源占用。时钟方案上OV5640 的 XCLK 从 PS 的 FCLK_CLK1 引出一般给 24MHz 即可。PL 侧的采集和 VDMA 逻辑跑 100MHz。OV5640 输出的 PCLK 和 100MHz 不在一个时钟域VDMA 内部有异步 FIFO 可以处理跨时钟域不用担心。3.2 AXIVDMA 的工作原理与启动方式AXIVDMA 是 Xilinx 专门为视频流 DMA 场景设计的 IP核心能力是把 AXI4-Stream 的数据搬到内存或者把内存里的数据变成流。它与普通 DMA 的最大区别是内置了帧缓冲地址表可以自动在多个帧缓冲地址之间轮转实现类似 Ping-Pong 缓存的效果。工程中我建议使用 3 个帧缓冲。假设图像是 640x480x2 字节每帧大小 614400 字节可以按 1MB 对齐在 DDR 里开辟三块地址例如 0x10000000、0x10100000、0x10200000。VDMA 采集时循环写入这三块区域当前帧写到第 N 块时PS 端可以安全地读取第 N-1 块的数据不会读到正在被写入的半帧数据。关键在软件侧务必用帧完成中断来标志“该帧已经完整写入”不要用定时器猜测或者直接读固定地址。SDK 里使用 AXIVDMA 驱动时核心 API 如下#include xaxivdma.h XAxiVdma Vdma; XAxiVdma_Config *VdmaConfig; VdmaConfig XAxiVdma_LookupConfig(XPAR_AXIVDMA_0_DEVICE_ID); XAxiVdma_CfgInitialize(Vdma, VdmaConfig, VdmaConfig-BaseAddress); // 配置 S2MM 帧缓冲地址循环设置 for (int i 0; i 3; i) { XAxiVdma_WriteReg(Vdma.BaseAddress, XAXIVDMA_S2MM_FRAMEBUF_ADDR_OFFSET(i), FrameBufAddr[i]); } // 设置垂直和水平尺寸 XAxiVdma_WriteReg(Vdma.BaseAddress, XAXIVDMA_S2MM_VSIZE_OFFSET, height); XAxiVdma_WriteReg(Vdma.BaseAddress, XAXIVDMA_S2MM_HSIZE_OFFSET, stride); // 启动写传输 XAxiVdma_StartWriteTransfer(Vdma, 0);stride是每行字节数必须等于图像宽度乘每像素字节数例如 640 宽 RGB565 就是 1280。如果设置的 stride 和实际数据长度不一致图像会斜着裂开或者出现奇怪的空行这个问题我们在调试部分还会再提。3.3 帧缓冲管理与帧完成中断三缓冲并不等于自动解决所有问题。如果 PS 端发送逻辑不管帧号依然可能重复发送同一帧或者跳过一帧。我在工程里用的是这种方式在 VDMA 的 S2MM 完成中断服务函数中读取当前中断对应的帧缓冲索引把frame_ready[slot]置 1。主循环里检测到frame_ready[slot]为 1 时从对应的 FrameBufAddr 读数据并发送发送结束后清零。这里有个细节值得注意Zynq 的 VDMA 中断状态寄存器在读取后必须软件清零否则中断会一直触发。很多初学朋友在板子上发现现象是图像能出但系统卡死或者网络非常慢排查到最后往往是中断标志没清干净CPU 一直陷在中断里。AXIVDMA 驱动里提供XAxiVdma_IntrGetStatus和XAxiVdma_IntrClear接口建议用这两个接口做清状态操作不要在中断服务函数里直接对寄存器做算术运算。至于 DDR 地址分配尽量放在一个不会和堆栈、代码段冲突的地址。SDK 的链接脚本默认代码段在 0x00100000 左右DDR 的高地址空间比较安全。如果板载 DDR 是 512MB0x18000000 之后的空间可以放心使用。我习惯把三块帧缓冲区定义成全局数组并加上__attribute__((aligned(32)))确保地址满足 VDMA 的对齐要求避免出现总线错误或者性能下降。4. PS 端 UDP 发送与上位机接收实现4.1 LwIP 在裸机上的初始化流程在 Zynq 上跑 LwIPBSP 默认会生成xemacpsif驱动它封装了 GEM 控制器的初始化和数据收发回调。使用 socket API 时工程的xil_lwip外部库会在后台处理协议栈的定时器我们只需要像普通嵌入式 socket 一样调用接口。初始化部分大致是#include lwip/init.h #include lwip/netif.h #include netif/xadapter.h #include lwip/sockets.h #include lwip/dhcp.h struct netif server_netif; ip4_addr_t ipaddr, netmask, gw; IP4_ADDR(ipaddr, 192, 168, 1, 10); IP4_ADDR(netmask, 255, 255, 255, 0); IP4_ADDR(gw, 192, 168, 1, 1); lwip_init(); netif_add(server_netif, ipaddr, netmask, gw, NULL, ethernetif_init, netif_input); netif_set_default(server_netif); netif_set_up(server_netif);这里netif_input在裸机模式下比较常用。如果你的 SDK 版本生成的是线程化 LwIP则需要改成tcpip_input或者开启sys_thread_new具体看 BSP 的 lwip 配置。初始化完成后socket 的用法就和标准 POSIX 基本一致int sock socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in dest {0}; dest.sin_family AF_INET; dest.sin_port htons(9000); dest.sin_addr.s_addr inet_addr(192.168.1.20);在裸机上用 socket API 时要注意不能在一个死循环里频繁调用sleep或usleep打断协议栈的 packet 处理。我的做法是主循环直接处理帧发送每发送完一帧短暂延时不长于几毫秒并且让 LwIP 的sys_check_timeouts或平台相关处理有机会运行。4.2 大帧拆分与自定义包格式UDP 的单个数据报长度受 MTU 限制。标准以太网 MTU 是 1500 字节减去 IP 头 20 字节和 UDP 头 8 字节应用层 payload 最多 1472 字节。如果加上自定义包头整个包长度不能超过 1472。为了留有余量我一般把每包的有效图像数据设为 1400 字节自定义包头 32 字节合计 1432 字节不会触碰 MTU 上限。自定义包头建议这样定义typedef struct __attribute__((packed)) { uint32_t magic; // 帧头标识例如 0xA55A5AA5 uint32_t frame_id; // 帧序号 uint32_t pkt_count; // 当前帧总共拆成多少包 uint32_t pkt_index; // 当前包在帧内的序号从 0 开始 uint32_t data_len; // 当前包内图像数据的字节数 uint32_t width; // 图像宽度 uint32_t height; // 图像高度 uint16_t format; // 图像格式0 表示 RGB565 uint8_t reserved[2]; // 对齐保留 } img_pkt_header_t;发送一帧的核心函数可以写成#define PAYLOAD_SIZE 1400 #define BUF_SIZE (PAYLOAD_SIZE sizeof(img_pkt_header_t)) int send_frame(int sock, struct sockaddr_in *dest, uint8_t *frame, uint32_t frame_size, uint32_t frame_id, uint16_t w, uint16_t h) { uint8_t buffer[BUF_SIZE]; uint32_t pkt_count (frame_size PAYLOAD_SIZE - 1) / PAYLOAD_SIZE; for (uint32_t i 0; i pkt_count; i) { img_pkt_header_t *hdr (img_pkt_header_t *)buffer; uint32_t n frame_size - i * PAYLOAD_SIZE; if (n PAYLOAD_SIZE) { n PAYLOAD_SIZE; } hdr-magic 0xA55A5AA5; hdr-frame_id frame_id; hdr-pkt_count pkt_count; hdr-pkt_index i; hdr-data_len n; hdr-width w; hdr-height h; hdr-format 0; memcpy(buffer sizeof(img_pkt_header_t), frame i * PAYLOAD_SIZE, n); int len ((int)sizeof(img_pkt_header_t)) (int)n; int ret sendto(sock, buffer, len, 0, (struct sockaddr *)dest, sizeof(*dest)); if (ret ! len) { // 这里通常不是 socket 失败而是发送缓冲区满 // 可以考虑丢弃当前帧剩余包等待下一帧 return -1; } } return (int)pkt_count; }frame_id必须是单调递增的上位机靠它判断是否收到新帧。因为uint32_t会溢出回绕接收端比较帧号时不要直接判断“等于”而是使用差值判断例如(uint32_t)(new_id - last_id) 0x80000000表示新帧号比旧帧号新。4.3 上位机接收组包逻辑上位机用 Python 做一个简单的 UDP 接收器很方便。核心逻辑是收到包后检查 magic然后按照frame_id和pkt_index写入一块预分配的缓冲区当某个帧的包收齐后回调处理。如果中间丢了包就在收到下一帧第一个包时清空不完整帧保证不上送残帧。import socket BUFSIZE 2048 IMAGE_W 640 IMAGE_H 480 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((0.0.0.0, 9000)) sock.settimeout(1) frame_buf {} current_frame None while True: try: data, addr sock.recvfrom(BUFSIZE) except socket.timeout: continue magic, frame_id, pkt_count, pkt_index, data_len, width, height, fmt \ struct.unpack_from(IIIIIIH2x, data, 0) if magic ! 0xA55A5AA5: continue if current_frame is None: current_frame frame_id frame_buf[frame_id] bytearray(width * height * 2) if current_frame ! frame_id: # 说明上一帧没凑齐丢弃并切到新帧 frame_buf.pop(current_frame, None) current_frame frame_id frame_buf[frame_id] bytearray(width * height * 2) offset pkt_index * PAYLOAD_SIZE if offset data_len len(frame_buf[frame_id]): frame_buf[frame_id][offset:offsetdata_len] data[32:32data_len] if len(frame_buf[frame_id]) width * height * 2: # 完整帧交给上层处理 process_frame(bytes(frame_buf[frame_id]), width, height) frame_buf.pop(frame_id, None) current_frame None真实的工程还会把process_frame换成 OpenCV 的imshow、保存文件、或者做图像质量判断。这个接收逻辑关键在“只保留当前帧”和“不完整帧直接丢弃”否则长时间运行内存会越积越多。5. 调试实录花屏、ping 不通、丢包排查5.1 图像花屏和颜色错误的排查清单花屏可能是很多人遇到的第一个大问题。我的排查顺序是固定的可以大幅度缩短定位时间。第一步先用 SDK 读 OV5640 芯片 ID确认 SCCB 通信正常。如果 ID 读不到先查模块供电、复位引脚、SIO_C/SIO_D 是否接反以及 XCLK本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号