ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA图像处理入门:AXI VDMA原理、配置与实战避坑指南

FPGA图像处理入门:AXI VDMA原理、配置与实战避坑指南 1. 从一次图像采集丢帧说起为什么需要VDMA很多刚接触FPGA图像处理的朋友第一次做摄像头采集项目时脑子里想的都是把摄像头数据存进DDR再读出来送到HDMI显示这么一条链路。听起来简单但真动手写代码的时候问题就来了摄像头出来的数据是连续不断的像素流DDR的读写接口却是突发式的中间怎么衔接如果用一个简单的FIFO做缓冲深度根本不够几行图像就能把FIFO撑爆。更麻烦的是显示端读取的速度和采集端写入的速度往往不一致一个快一个慢没有缓冲机制的话画面就会撕裂、丢帧。这时候就需要一个专门的搬运工——AXI VDMAVideo Direct Memory Access视频直接内存访问。它本质上是Xilinx现AMD提供的一个IP核专门解决视频流和内存之间的高速数据搬运问题。你可以把它理解成一个智能的、带地址管理功能的DMA控制器只不过它搬运的不是普通数据而是有严格时序要求的视频帧。这篇文章主要面向刚入门FPGA图像处理、准备用VDMA做项目的朋友。我会从VDMA到底解决什么问题讲起把它的内部工作机制拆开揉碎再结合实际的配置参数和设计思路把那些文档里不会明说、但实际项目中一定会踩的坑都讲清楚。看完之后你应该能独立完成一个基于VDMA的图像采集显示链路的搭建并且知道每个参数为什么这么设。2. VDMA到底在搬什么核心机制拆解2.1 三个接口各司其职VDMA这个IP核最直观的理解方式就是看它有几个接口。打开Vivado的IP配置界面你会看到它主要有三组AXI接口AXI4-Lite这是控制接口用来配置寄存器。比如你要告诉VDMA从哪个地址开始读、一帧图像多宽多高、用几个缓冲区都是通过这个接口写寄存器完成的。它不搬数据只传命令。AXI4 Memory Map这是面向DDR的接口负责实际的数据读写。VDMA通过这个接口访问DDR中的帧缓冲区把数据写进去或者读出来。AXI4-Stream这是面向视频流的接口分为S2MMStream to Memory-Mapped写方向和MM2SMemory-Mapped to Stream读方向。摄像头的数据通过S2MM接口进来显示端的数据通过MM2S接口出去。这三个接口的分工非常明确控制接口管怎么搬内存接口管搬到哪流接口管搬什么。理解了这个分工后面配置的时候就不会迷糊。2.2 S2MM与MM2S两个方向独立工作VDMA内部其实包含两个独立的通道S2MM通道负责把视频流写入DDRMM2S通道负责把DDR中的数据读出来变成视频流。这两个通道可以同时工作互不干扰这就是为什么VDMA能实现一边采集一边显示的全双工操作。每个通道内部又有一套自己的地址生成逻辑。以S2MM为例当你配置好帧缓冲区起始地址和帧大小之后VDMA会自动计算每一行数据应该写到DDR的哪个位置。它内部有一个行计数器和一个列计数器每接收完一行像素行计数器加一地址就跳到下一行的起始位置。这个过程完全由硬件完成不需要CPU干预所以速度非常快。MM2S通道的逻辑类似只不过方向反过来它从DDR中按行读取数据然后按照视频时序的要求把像素一个一个推送到AXI4-Stream接口上。这里有个细节需要注意MM2S读出来的数据速率必须和显示端的像素时钟匹配否则要么显示端等数据要么数据来了显示端还没准备好两种情况都会导致画面异常。2.3 帧缓冲与乒乓操作VDMA最核心的设计思想之一就是多帧缓冲。你可以配置VDMA使用1到32个帧缓冲区实际项目中最常用的是3个。为什么是3个而不是2个这就涉及到乒乓操作和流水线的问题。假设只用2个缓冲区S2MM写缓冲区A的时候MM2S读缓冲区B。当S2MM写完A、MM2S读完B之后两者需要交换。但交换的瞬间如果S2MM已经开始写B了而MM2S还没完全读完B就会发生冲突。用3个缓冲区的话S2MM写AMM2S读BC作为备用。当S2MM写完A它可以立即去写C而MM2S继续读B。等MM2S读完B它可以去读A此时S2MM正在写C。这样三个缓冲区轮转读写之间始终有足够的间隔不会冲突。这个机制在VDMA的寄存器里体现为Frame Store Number和Circular Mode。Circular Mode开启后VDMA会自动循环使用所有配置的帧缓冲区不需要CPU每次手动切换地址。对于实时视频流来说这个模式几乎是必选的。2.4 帧同步信号的关键作用VDMA怎么知道一帧图像什么时候开始、什么时候结束靠的是帧同步信号。在S2MM方向当VDMA检测到帧起始信号通常来自摄像头的VSync或DE信号的上升沿时它会开始把接下来的像素写入当前帧缓冲区的起始地址。一帧写完后它会自动切换到下一个缓冲区并产生一个中断通知CPU。这里有个容易忽略的点VDMA的帧同步信号极性、宽度都是可以配置的。如果你的摄像头输出的同步信号和VDMA默认配置不匹配就会出现VDMA一直在写但写进去的数据全是错位的情况。我在实际项目中就遇到过因为VSync极性设反导致图像整体偏移了半帧的问题排查了很久才发现是同步信号配置的问题。3. 配置VDMA时那些不起眼但致命的参数3.1 数据位宽与内存映射的匹配VDMA的流接口数据位宽可以配置为8、16、32、64、128、256、512、1024位。这个位宽必须和你的视频流数据位宽一致。比如OV5640摄像头输出的是RGB565格式每个像素16位那流接口位宽就设16。但这里有个陷阱VDMA的内存映射接口位宽是固定的32位、64位或128位取决于你选的AXI总线宽度流接口的位宽和内存接口的位宽之间会有一个自动转换。这个转换过程对性能有影响。如果流接口是16位内存接口是64位那么VDMA需要攒够4个像素才能组成一次64位的内存写入。这意味着VDMA内部会有一个小FIFO来做位宽转换。如果流接口的数据速率很高而这个FIFO深度不够就可能出现溢出。所以在高分辨率、高帧率的场景下建议尽量让流接口位宽和内存接口位宽保持一致减少转换开销。3.2 帧缓冲区地址的对齐要求VDMA对帧缓冲区的起始地址有对齐要求。具体来说地址必须按照内存接口位宽对齐。如果内存接口是64位那么地址必须是8字节对齐的。这个要求看起来简单但实际配置的时候很容易出错尤其是当你手动分配DDR地址空间的时候。我一般建议把帧缓冲区的起始地址设在DDR的一个大边界上比如0x10000000、0x20000000这种。这样不仅满足对齐要求而且方便计算和调试。另外每个帧缓冲区的大小也要仔细算一帧图像的字节数 宽度 × 高度 × 每像素字节数。比如1920×1080的RGB888图像一帧就是1920×1080×3 6220800字节约6MB。三个缓冲区就是18MB这个空间在分配DDR的时候必须预留出来。3.3 寄存器配置的先后顺序VDMA的寄存器配置是有顺序要求的顺序错了IP核可能不工作。正确的流程大致是这样的先复位VDMA确保所有寄存器处于初始状态。配置S2MM和MM2S的帧缓冲区起始地址分别写入对应的寄存器。配置帧大小水平方向的有效像素数和垂直方向的行数。配置帧延迟和帧存储数量。使能Circular Mode如果需要循环缓冲。最后启动S2MM和MM2S通道。这个顺序不能乱尤其是启动通道必须在所有配置完成之后。我见过有人先启动通道再配置地址结果VDMA从默认地址0开始读写直接把DDR里的其他数据覆盖了整个系统跑飞。3.4 中断与轮询的取舍VDMA每完成一帧的搬运可以产生一个中断。你可以选择用中断方式处理也可以用轮询方式查询状态寄存器。对于实时性要求高的系统中断方式更合适因为CPU不用一直盯着寄存器。但中断方式也有代价中断服务程序的响应时间会影响帧切换的及时性。在实际项目中我通常的做法是如果帧率不高比如30fps以下用中断完全没问题如果帧率很高60fps以上可以考虑用轮询或者DMA状态机的方式减少中断开销。另外VDMA的中断可以配置为帧完成中断、帧延迟中断等多种类型根据实际需求选择不要一股脑全开否则中断太频繁反而影响性能。4. 从零搭建一条VDMA图像链路实操步骤4.1 硬件平台与IP核选型假设我们要做一个典型的图像采集显示项目OV5640摄像头采集1280×72030fps的RGB565图像通过VDMA存入DDR再读出来送到HDMI显示。硬件平台选的是Zynq-7000系列比如ZC702或类似开发板因为Zynq有PS端可以方便地配置VDMA寄存器PL端负责视频流处理。IP核方面除了VDMA本身还需要Video In to AXI4-Stream把摄像头的并行视频信号转换成AXI4-Stream格式。AXI4-Stream to Video Out把VDMA读出的AXI4-Stream数据转换成HDMI需要的视频时序。Video Timing Controller生成显示端的时序信号。AXI Interconnect连接VDMA和PS端的AXI总线。这些IP核在Vivado中都有现成的直接拖进来配置就行。关键是它们之间的连接关系和数据流向要理清楚。4.2 Vivado中的Block Design搭建在Vivado中新建Block Design按以下顺序添加和连接IP添加Zynq Processing System配置DDR控制器和时钟。确保DDR的地址空间足够大至少能放下3帧1280×720×2字节 约5.5MB的图像数据再加上其他程序运行的空间建议DDR容量不低于512MB。添加VDMA IP配置为读写双通道模式。流接口位宽设为16匹配RGB565内存接口位宽设为64匹配Zynq的HP端口。添加Video In to AXI4-Stream配置输入视频格式为RGB565输出为AXI4-Stream。添加AXI4-Stream to Video Out和Video Timing Controller配置输出分辨率为1280×720。用AXI Interconnect把VDMA的控制接口和Zynq的M_AXI_GP端口连起来把VDMA的内存接口和Zynq的S_AXI_HP端口连起来。连接完成后Vivado会自动生成地址分配。这里要特别注意VDMA的寄存器地址范围后面写驱动的时候要用到。4.3 SDK中的寄存器配置代码在Xilinx SDK或Vitis中VDMA的配置可以通过直接写寄存器完成也可以调用Xilinx提供的驱动库。直接写寄存器更灵活但需要查手册用驱动库更方便但有些高级功能可能不支持。我一般先用驱动库快速跑通再根据需求改成直接寄存器操作。以下是一个典型的VDMA初始化代码片段基于直接寄存器操作#define VDMA_BASEADDR 0x43000000 // 复位VDMA Xil_Out32(VDMA_BASEADDR 0x00, 0x00000004); usleep(1000); Xil_Out32(VDMA_BASEADDR 0x00, 0x00000000); // 配置S2MM帧缓冲区地址3个缓冲区 Xil_Out32(VDMA_BASEADDR 0x5C, FRAME_BUF0_ADDR); Xil_Out32(VDMA_BASEADDR 0x60, FRAME_BUF1_ADDR); Xil_Out32(VDMA_BASEADDR 0x64, FRAME_BUF2_ADDR); // 配置MM2S帧缓冲区地址 Xil_Out32(VDMA_BASEADDR 0xAC, FRAME_BUF0_ADDR); Xil_Out32(VDMA_BASEADDR 0xB0, FRAME_BUF1_ADDR); Xil_Out32(VDMA_BASEADDR 0xB4, FRAME_BUF2_ADDR); // 配置帧大小1280像素 × 720行 Xil_Out32(VDMA_BASEADDR 0x58, 1280 * 2); // S2MM水平方向字节数 Xil_Out32(VDMA_BASEADDR 0x54, 720); // S2MM垂直方向行数 Xil_Out32(VDMA_BASEADDR 0xA8, 1280 * 2); // MM2S水平方向字节数 Xil_Out32(VDMA_BASEADDR 0xA4, 720); // MM2S垂直方向行数 // 使能Circular Mode Xil_Out32(VDMA_BASEADDR 0x04, 0x00000003); // 启动S2MM和MM2S通道 Xil_Out32(VDMA_BASEADDR 0x30, 0x00000003);这段代码看起来简单但有几个地方容易出错。首先是帧大小的计算水平方向写的是字节数而不是像素数所以1280像素×2字节 2560字节。如果这里写成1280VDMA会认为一行只有1280字节导致图像错位。其次是Circular Mode的使能位S2MM和MM2S是分开控制的0x03表示两个通道都使能。4.4 中断服务程序的处理逻辑如果使用中断方式需要在中断服务程序里做帧切换的处理。典型的逻辑是void VDMA_ISR(void *InstancePtr) { u32 status Xil_In32(VDMA_BASEADDR 0x34); // 读取S2MM状态 if (status 0x1000) { // 帧完成中断 // 清除中断标志 Xil_Out32(VDMA_BASEADDR 0x34, 0x1000); // 处理帧数据比如通知上层应用 frame_ready_flag 1; } }这里要注意中断标志的清除方式有些寄存器是写1清除有些是写0清除VDMA的中断状态寄存器是写1清除。如果写错了中断会一直触发CPU就卡在中断里出不来了。5. 那些文档不会告诉你的踩坑实录5.1 图像偏移与错位同步信号的锅前面提到过VDMA依赖帧同步信号来判断一帧的开始和结束。如果同步信号的极性、宽度或者时序和VDMA的配置不匹配就会出现图像偏移。我遇到过一次典型的情况摄像头输出的VSync是低电平有效但VDMA默认配置是高电平有效结果VDMA把VSync的低电平期间当成了有效帧导致每帧图像都偏移了半帧。排查这个问题的思路是先用ILA集成逻辑分析仪抓取VDMA的流接口信号看帧起始信号和实际数据的关系。如果发现帧起始信号出现的位置和预期不符就去检查摄像头的同步信号配置和VDMA的同步信号配置是否一致。这个问题的解决方法很简单改一下VDMA的寄存器配置就行但找到问题所在可能需要花不少时间。5.2 DDR带宽不足导致的画面撕裂VDMA读写DDR需要占用AXI总线的带宽。如果系统里还有其他高带宽的外设比如另一个VDMA、或者CPU频繁访问DDR就可能出现带宽竞争导致VDMA读写不及时画面出现撕裂或者丢帧。解决这个问题的思路有几个一是提高VDMA的AXI接口优先级在AXI Interconnect中配置QoS服务质量参数二是优化DDR的访问模式尽量使用突发传输减少小数据量的随机访问三是降低分辨率或帧率减少带宽需求。在实际项目中我一般会先用Xilinx的Performance Analysis工具测一下DDR的实际带宽占用再决定怎么优化。5.3 帧缓冲区地址冲突如果帧缓冲区的地址范围和其他数据比如程序代码、堆栈重叠就会出现数据被覆盖的问题。这种问题的表现往往是图像偶尔正常偶尔花屏很难排查。避免这个问题的方法是在DDR的地址分配阶段就做好规划给VDMA的帧缓冲区预留独立的地址空间并且在链接脚本中把其他数据排除在这个区域之外。我通常会在DDR的最前面留出1MB给程序运行然后从0x00100000开始分配帧缓冲区。这样既避免了地址冲突又方便计算和调试。5.4 复位不彻底导致的IP核不工作VDMA的复位信号需要保持足够长的时间否则IP核可能没有完全复位导致配置寄存器写不进去。Xilinx的文档建议复位信号至少保持16个时钟周期但实际项目中我一般会保持1ms以上确保稳定。另外复位之后要等待一段时间再配置寄存器给IP核内部状态机足够的时间回到初始状态。这个等待时间不用太长几百个时钟周期就够了但绝对不能省略。6. 进阶思路让VDMA跑得更稳更快6.1 多通道VDMA的应用场景如果项目需要同时处理多路视频流比如双目摄像头、多路监控可以用多个VDMA实例每个实例负责一路视频。Zynq-7000系列通常有2到4个HP端口可以连接多个VDMA的内存接口。但要注意多个VDMA同时访问DDR会加剧带宽竞争需要合理分配优先级和缓冲区地址。另一种方案是用一个VDMA的多个帧缓冲区来交替处理多路视频但这种方式对时序控制要求更高适合对成本敏感、对性能要求不极致的场景。6.2 与Frame Buffer IP的配合使用Xilinx还提供了一个Frame Buffer IP可以在VDMA的基础上增加一些图像处理功能比如缩放、色彩空间转换等。如果你的项目需要这些功能可以考虑用Frame Buffer IP替代裸VDMA减少自己写逻辑的工作量。但Frame Buffer IP的资源占用比VDMA高不少在资源紧张的FPGA上要慎重选择。6.3 性能优化的几个实用技巧增大突发长度VDMA的AXI接口支持的最大突发长度是256实际配置时尽量设大一些减少总线握手开销。使用HP端口的高性能模式Zynq的HP端口有普通模式和高性能模式高性能模式下带宽更高但功耗也更大。根据实际需求选择。避免跨时钟域VDMA的流接口和内存接口可能工作在不同的时钟域跨时钟域会引入额外的延迟。如果可能尽量让它们工作在同一个时钟域或者使用异步FIFO来缓冲。7. 我个人在实际项目中的几点体会做了几个基于VDMA的图像项目之后我最大的感受是VDMA本身不难难的是整个链路的时序匹配和资源分配。VDMA只是一个搬运工它能不能好好工作取决于你给它的环境是否合适。帧缓冲区地址对不对、同步信号配没配对、DDR带宽够不够这些才是决定项目成败的关键。另外调试VDMA问题的时候ILA是必不可少的工具。不要靠猜直接抓信号看时序大部分问题都能一目了然。我习惯在VDMA的流接口和内存接口上都挂ILA这样既能看数据流又能看地址变化排查问题的效率高很多。最后分享一个小技巧在项目初期可以先用一个简单的测试图案比如彩条代替摄像头输入验证VDMA的读写链路是否正常。等链路跑通了再接入真实的摄像头。这样可以排除摄像头本身的问题把调试范围缩小到VDMA和DDR这一块省去很多来回折腾的时间。
返回列表