ZYNQ+OV5640+VDMA+HDMI视频链路搭建实录:从摄像头采集到实时显示

文章目录

做基于 FPGA 的机器视觉项目时,很多同学一上来就急着上算法,但实际上,真正决定项目能不能做下去的,往往不是算法本身,而是最基础的视频链路能不能先跑通。对我这次 ZYNQ 项目来说,也是一样。 我这次做的是一个基于 ZYNQ 的视频处理实验平台,前端使用 OV5640 摄像头采集图像,中间通过 AXI4-Stream 视频流在各个模块之间传输,再借助 AXI VDMA 完成 DDR 帧缓存,最后通过 HDMI 输出到显示器。后续我在这个平台上继续叠加了帧间差分运动检测算法,因此,这条视频链路既是显示系统本身,也是后面算法调试的基础。这篇文章先不讲复杂算法,重点记录一下整个系统是怎么从"能采到图"一步步走到"能稳定显示"的。

一、项目目标与整体思路

这个项目的核心目标很明确:

把摄像头采集到的视频数据,经过 ZYNQ PL 端的视频链路处理后,稳定输出到 HDMI 显示器。

从结构上看,整条链路可以分成五部分:

  1. 摄像头数据采集

OV5640 输出像素时钟、行同步、场同步和 8 位数据,通过自定义采集模块整理成视频接口。

  1. 视频流标准化

使用 Video In to AXI4-Stream,把摄像头原始视频信号转换成 AXI4-Stream 格式,便于后续模块统一处理。

  1. 帧缓存

使用 AXI VDMA 把视频流写入 DDR,再从 DDR 中读出。这样做的好处是:

bash 复制代码
可以实现帧级缓存
方便后续多帧算法处理
使输入和输出两端解耦
  1. 显示链路

从 DDR 读出的 AXI4-Stream 视频送入 AXI4-Stream to Video Out,再配合 VTC 输出时序信号,最终驱动 HDMI/LCD 显示。

  1. 格式转换与算法接口预留

由于系统中部分模块使用 RGB565,部分模块使用 RGB888,因此在链路中还加入了颜色格式转换模块。同时,为后续帧差法、目标检测等算法预留了 AXI4-Stream 接口。

这一套结构搭好之后,后续加算法就不是"从零开始",而是在一个已经能稳定显示的平台上逐步叠加功能。

二、Block Design 中各模块的作用

从整个 BD 的结构来看,核心模块并不算特别多,但每个模块都有明确分工。

  1. OV5640_Data 采集模块

这个模块是摄像头数据进入 FPGA 的第一站。它负责接收来自 OV5640 的:

bash 复制代码
PCLK
VSYNC
HREF
Data[7:0]

并在内部整理成适合后续视频处理模块使用的并行视频信号。

简单理解,这个模块做的是"把摄像头吐出来的原始时序数据,变成系统内部能识别的视频像素流"。

  1. v_vid_in_axi4s_0

这是 Xilinx 官方的 Video In to AXI4-Stream IP。

它的作用是把传统视频接口转换成 AXI4-Stream 视频流。

这一步非常关键,因为后面很多视频处理模块,包括 VDMA、算法 IP、Video Out,都是基于 AXI4-Stream 工作的。

在这次项目中,视频流宽度最终采用了 16 位,因为整条处理链后来统一到了 RGB565 格式,这一点在后续调试中非常重要。

  1. rgb888to565 / rgb565to888

由于不同模块对数据格式要求不同,所以颜色格式转换模块必不可少。

rgb888to565:把 24 位 RGB888 压缩成 16 位 RGB565

rgb565to888:把 16 位 RGB565 扩展成 24 位 RGB888

在我后面的算法模块中,也大量用到了 RGB565 转 RGB888 再转灰度 Y 的处理思路。当前 AXI_VIP_Frame_Difference 模块中,输入输出仍然是 16 位 AXI4-Stream,而内部会先把 s0_axis_tdata_dly2 和 fifo_q 扩展为 RGB888,再送入 RGB888_YCbCr444 做灰度计算。

  1. AXI VDMA

这个项目里 VDMA 是核心中的核心。

VDMA 的作用不是简单"搬数据",而是承担了视频系统里的帧缓存任务:

S2MM:把 AXI4-Stream 视频写入 DDR

MM2S:把 DDR 中的视频再读出为 AXI4-Stream

在我的设计里,之所以使用双 VDMA 结构,是为了给后续算法做准备。帧差法本质上需要比较两帧图像,因此必须依赖帧缓存。

5. AXI_VIP_Frame_Difference

这是我自己插入系统里的算法 IP。

从接口上看,它是一个双输入、单输出的 AXI4-Stream 视频处理模块:

bash 复制代码
s0_axis_*:当前视频流
s1_axis_*:另一帧/延迟帧视频流
m_axis_*:处理后的输出视频流

当前这版模块里,已经包含了:

bash 复制代码
双路 16 位 AXI4-Stream 输入
FIFO 缓存
RGB565 到 RGB888 转换
RGB888_YCbCr444 灰度转换
frame_difference_flag 差分判定逻辑

不过这篇文章主要讲显示链路本身,因此算法部分先点到为止,后面会单独开一篇详细写。

  1. VTC 与 AXI4-Stream to Video Out

视频显示除了像素数据,还必须有时序。

VTC(Video Timing Controller)负责生成分辨率对应的同步时序,AXI4-Stream to Video Out 则负责把 AXI 流重新还原成显示接口信号。

这两个模块的关系可以理解为:

bash 复制代码
VTC:告诉系统"现在什么时候是行起始、场起始、有效显示区域"
Video Out:把 AXI 视频流按照这个节奏吐出去

如果这两个模块配置不对,最常见的现象就是:

bash 复制代码
黑屏
花屏
只显示部分区域
显示器有同步但图像不对

三、为什么一开始必须先统一位宽和格式

我这次调试过程中,一个特别深的体会就是:

bash 复制代码
视频系统最怕"链路中每段都差一点"。

比如前面是 RGB888,后面是 RGB565;某个模块按 24 位理解,另一个模块按 16 位读;SDK 里 hsize 还是按 3 字节算,而硬件已经改成 2 字节,这种问题单独看都不大,但串起来就是黑屏、竖条纹、花屏。

所以后来我做了一个非常重要的决定: 把中间处理链统一为 RGB565,也就是 16 位。

这样做有几个直接好处:

  1. VDMA 配置统一

AXI VDMA 的 Stream Data Width 统一设为 16 位

  1. SDK 配置统一

read_hsize 和 write_hsize 都按 hsize * 2 计算

  1. 算法 IP 接口统一

AXI_VIP_Frame_Difference 的输入输出也全部改成 16 位 AXI4-Stream

  1. 调试思路更清晰

只要显示出问题,优先怀疑:

bash 复制代码
位宽不匹配
stride 不对
格式转换前后不一致

这一步虽然看起来只是"改成 16 位",但实际上是后面所有调试能够推进下去的前提。

四、显示链路调通后,为什么还要保留 VDMA

有同学会问,既然只是显示图像,为什么不直接:

摄像头 → Video In → Video Out → HDMI

中间为什么还要绕一圈 DDR?

答案很简单:因为后面我要做算法。

如果只是摄像头直显,数据只存在于当前这一时刻,你无法方便地拿"前一帧"和"当前帧"做比较。

而引入 VDMA + DDR 之后:

bash 复制代码
当前帧可以写到 DDR
上一帧可以从另一块缓存中读出
算法 IP 可以在双路视频之间做比较

这其实就是把一个"显示系统"扩展成了一个"可做视频算法实验的平台"。

五、从稳定显示到叠加帧差算法的过渡

在系统能稳定显示之后,我开始往中间插入 AXI_VIP_Frame_Difference 模块。

一开始最容易犯的错误,就是"觉得算法模块代码没问题,直接全功能接进去"。

但实际调试下来发现,这样非常容易把问题搞复杂。

所以后面我采用了一个更靠谱的方法:

  1. 先做纯透传

先让算法模块什么都不做,只把 s0_axis 原样输出到 m_axis。

只要这一步能显示,说明:

c 复制代码
模块接口没问题
上下游连接没问题
AXI4-Stream 协议基本没问题
  1. 再加 FIFO

先把第二路输入和 FIFO 加回来,但输出仍然保持透传。

  1. 再加灰度和差分标志

在模块内部计算 frame_difference_flag,但输出仍然不改动。

  1. 最后再尝试运动区域标红和框选

这样一层层恢复功能,才能真正定位问题到底出在哪一层。

这个思路后来证明非常有效。因为如果一开始就把 FIFO、灰度、差分、框选、画框全部塞进去,一旦出问题,根本不知道是哪里错了。

六、当前项目已经走到哪一步

到目前为止,这个项目已经完成了下面这些内容:

bash 复制代码
OV5640 摄像头采集
Video In to AXI4-Stream
RGB565 / RGB888 格式转换
双 VDMA 帧缓存
VTC + Video Out 显示
AXI4-Stream 自定义算法模块插入
帧差法的基本运动检测与调试

从代码上也能看到,当前算法模块已经具有完整的 AXI4-Stream 输入输出结构,并且内部完成了 FIFO 缓存、RGB 扩展、灰度转换和差分标志计算。

也就是说,这个系统已经不是一个简单的"摄像头显示实验",而是一个可以继续做视频算法验证的平台。

七、这次搭建视频链路最大的经验

如果让我总结一句最重要的话,那就是:先把视频链路跑通,再谈算法。

因为对于 FPGA 视频项目来说:

bash 复制代码
黑屏未必是算法问题
花屏未必是摄像头问题
框不出来也未必是检测逻辑本身的问题

很多时候,真正的问题可能只是:

bash 复制代码
位宽没统一
stride 算错了
VDMA 读写没有对上
AXI4-Stream 控制信号没对齐

所以把底层显示平台先搭稳,实际上是在给后面算法调试"铺路"。

相关推荐
Topplyz2 小时前
PCB开尔文走线
嵌入式硬件·pcb·layout
Harvy_没救了2 小时前
Vim 快捷键手册
linux·编辑器·vim
晚安Jellyfish2 小时前
驱动---ARM系统移植
arm开发
C^h2 小时前
RT thread使用u8g2点亮oled显示屏
linux·单片机·嵌入式硬件·嵌入式
senijusene3 小时前
IMX6ULL ADC 驱动开发解析:
驱动开发·嵌入式硬件
航Hang*3 小时前
第2章:进阶Linux系统——第10节:Linux 系统编程与 Shell 脚本全解笔记(GCC+Make+Vim+Shell Script)
linux·运维·服务器·学习·vim·apache·vmware
UTP协同自动化测试3 小时前
智能家居中控屏测试:触摸屏操作 + I2C 读取传感器 + UART 与子设备通信 + GPIO 控制
功能测试·单片机·嵌入式硬件·测试工具·智能家居
Black蜡笔小新3 小时前
视频融合平台EasyCVR核心技术架构解析与多场景应用实践
架构·音视频
Flamingˢ3 小时前
基于 FPGA 的帧间差分运动检测
人工智能·目标跟踪·fpga开发