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 控制信号没对齐

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

相关推荐
熙芯XiChip14 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
程序员老陆14 小时前
从零写一个 FFmpeg 播放器:架构设计远比“解码显示”复杂
ffmpeg·音视频·播放器
伟大的前程14 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工15 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
lilihewo16 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
kuinnebula16 小时前
MP4音频帧定位与提取
音视频
神罚天下ET17 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院18 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰19 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥19 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5