ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zynq-7000上USB摄像头图像采集:V4L2+VDMA链路详解

Zynq-7000上USB摄像头图像采集:V4L2+VDMA链路详解 1. 项目需求与方案选型这篇学习笔记我拖了两周才写主要是样例工程做完以后一直在纠结要不要把采集程序里那些调试代码也贴出来。先说说这次做了什么在Zynq-7000平台上把一颗普通的USB摄像头插到开发板的USB OTG口上在PS侧Linux系统里通过V4L2读回图像再通过VDMA把帧数据从DDR搬进PL侧做显示和简单处理。整个过程跑通之后给我的感觉是——USB摄像头在嵌入式图像采集里真的被很多人低估了。做Zynq图像采集很多人上来就想到MIPI CSI或者DVP并口摄像头觉得只有这样才够“嵌入式”。但如果只是做原型验证、低成本方案或者想快速拿到一帧图像做算法测试USB摄像头反而更省事。它不像MIPI那样要对着PHY、时钟、上电时序折腾半天插上就能识别Linux内核有现成的UVC驱动应用层用V4L2接口就能读数据。这篇笔记适合刚接触Zynq图像采集的同学也适合那些已经写过MIPI接口、想换个数据通路扩展一下视野的工程师。先说清楚这次任务的边界平台是Xilinx Zynq-7000系列PS跑Linux摄像头是一颗典型的USB 2.0 UVC免驱摄像头采集到的YUV原始帧先落到DDR然后通过VDMA的MM2S通道送到PLPL侧接一个简单的AXI-Stream图像处理模块。没有做复杂的ISP也没有做目标检测就是先把“从USB摄像头到PL侧拿到连续帧”这条路打通。如果你后续要接视频编码、AI加速器、或者HDMI显示这条数据通路基本不用改。1.1 为什么这次选USB摄像头而不是MIPI我在之前的笔记里调过MIPI接口的摄像头那次光是上电时序和时钟就折腾了小半天最后还要对着寄存器手册一页一页查。USB摄像头在这一点上优势很明显UVCUSB Video Class是一个成熟的行业标准摄像头内部已经把感光芯片、ISP、图像格式转换做好了主机侧只需要通过标准接口枚举、协商格式、拉流。对开发来说等于把“暖启动”和“底层图像格式解析”这两件脏活外包给了设备端。当然USB摄像头也不是没有代价。USB 2.0 High-Speed的理论带宽是480Mbps实际可用带宽往往只有40MB/s左右所以高分辨率高帧率的原始视频流根本跑不动。我这次用的是640x48030fps的YUYV格式单帧640x480x2≈614KB30fps就是18.4MB/s还在USB带宽的安全范围内。如果需要1080P30fpsYUYV原始流要124MB/sUSB 2.0完全撑不住只能走MJPEG压缩流然后在CPU里解码或者干脆换USB 3.0控制器——但Zynq-7000的PS自带USB控制器就是2.0这点在设计方案时就要有预期。1.2 技术路线V4L2 VDMA AXI-Stream数据从哪里来、到哪里去是整个方案的骨干。我的选择是PS侧Linux通过V4L2从USB摄像头取帧帧缓存放DDRPL侧用Xilinx的AXI VDMA IP核通过MM2S通道把DDR里的帧读出来转成AXI-Stream流推给PL里的图像处理或显示IP。这条路线的核心优势是解耦PS只管采集和上层应用PL只管流式处理数据搬运交给DMA不占用CPU算力。为什么用VDMA而不是普通的AXI DMA因为这个场景是“连续帧搬运”VDMA内部有帧缓冲管理逻辑可以自动完成帧同步、行同步还支持帧计数和中断比用AXI DMA逐段搬运要省心得多。具体来说VDMA的MM2S通道从DDR的固定地址连续读数据每次读到一帧就产生一个帧完成信号应用程序只需要在DDR里维护一个环形缓冲把摄像头采集到的帧轮转写入这几个地址PL侧就能持续拿到最新帧。这套方案避免了一个很常见的坑如果用CPU直接搬运帧数据640x48030fps看起来不算高但每帧614KB乘30fps每秒要搬18MBCPU会持续处于高占用状态后续再做点图像处理就直接卡死。VDMA搬运不占CPU资源这是它在这里不可替代的原因。1.3 硬件和软件准备这次实验用的开发板是Zynq-7000系列中比较常见的型号PS DDR3容量1GBPL侧带了HDMI输出接口方便验证图像通路。USB摄像头选的是罗技C270老熟人了UVC标准兼容性好Linux下基本零配置就能识别。软件方面Linux内核基于Xilinx的2019.2 BSPVivado版本也是2019.2设备树和PL侧IP版本配套使用能减少很多版本带来的奇怪问题。需要特别提醒的是Zynq-7000的PS侧USB控制器有两个通常usb0接到OTG口usb1可以接到host口。我这次用的是usb0设备树里要确认驱动正常、VBUS供电使能。如果你发现插上摄像头没有反应第一件事不是查驱动而是用万用表量一下OTG口的VBUS有没有输出5V这个我在后面问题排查部分会细讲。2. 系统原理与关键概念在正式动手之前有几个概念必须弄清楚否则后面调程序会一直处在“试一下看看”的状态。Zynq-7000是异构SoCPS侧是双核ARM Cortex-A9PL侧是FPGA逻辑两边靠AXI总线互联。要理解USB摄像头图像采集的完整链路需要把USB控制器、Linux V4L2驱动栈、以及PL侧的AXI-Stream数据通路串起来看。2.1 Zynq的USB控制器与Linux驱动Zynq-7000的PS集成了两个USB 2.0控制器硬件IP来自Chipidea现在属于NXP在Linux里由chipidea和ehci驱动共同配合工作。这里有个容易混淆的点芯片内部USB控制器是OTG模式的既可以做host也可以做device而USB摄像头需要host模式。所以设备树里一般配置为dr_mode host或者留成otg并在运行时通过角色切换协议搞定。Linux下USB摄像头的驱动路径是USB核心枚举设备 → 发现接口类是Video Class → 加载uvcvideo驱动 → 注册V4L2设备节点/dev/video0。这个过程你在dmesg里能看得很清楚最后一行通常是uvcvideo: Found UVC 1.00 device。如果走到这一步说明硬件枚举和驱动加载都成功了接下来的问题基本都在应用层。2.2 V4L2与UVC协议的关系V4L2Video for Linux 2是Linux内核的视频采集框架它定义了一套标准ioctl接口应用层用open、ioctl、mmap这些系统调用就能控制摄像头。UVC则是USB设备侧的通信协议规定了摄像头如何上报能力、如何传输图像。两者的关系是UVC驱动把USB摄像头包装成标准的V4L2设备所以应用层根本不关心你用的是UVC摄像头还是MIPI桥接芯片转出来的V4L2设备接口都一样。这就带来一个很大的好处你为USB摄像头写的V4L2采集代码以后接其它V4L2设备比如HDMI采集卡、虚拟摄像头也基本能用。我自己的代码里open之后第一件事是VIDIOC_QUERYCAP查看设备能力第二件事是枚举格式确认摄像头支持什么像素格式。UVC摄像头一般支持YUYV和MJPEG两种格式有些还支持H.264这些都是通过标准接口查询出来的不需要猜。2.3 图像数据从摄像头到PL侧的完整路径把整条链路画出来你会发现它其实是三段式第一段摄像头内部把感光阵列读出的原始Bayer数据经过自带ISP处理转成YUYV或MJPEG格式通过USB总线传给PS侧的USB控制器。这段数据在USB控制器内部会被DMA引擎直接搬到DDR里的环形缓冲区不需要CPU参与。第二段Linux应用通过V4L2的mmap机制把这几个DDR缓冲区映射到用户态拿到的是帧数据的虚拟地址。应用可以在这里做保存、格式转换、算法处理如果需要送到PL侧就直接把对应的物理地址告诉VDMA让VDMA去DDR读数据。第三段VDMA的MM2S通道从DDR读出整帧数据按AXI-Stream协议逐像素输出到PL侧。AXI-Stream没有地址概念只有源源不断的数据和有效的握手信号非常适合流式图像处理。PL侧的模块可以是简单的亮度变换、边缘检测也可以是HDMI显示的时序生成器只需要按行同步和帧同步来消费数据流就行。这三段中最容易被忽略的是第二段和第三段之间的地址交接。Linux有MMU用户态拿到的是虚拟地址VDMA访问的是物理地址所以必须先通过DMA API或者查页表得到物理地址再传给VDMA。初始化和地址转换做不对VDMA读到的就是一段残缺数据表现出来就是花屏。2.4 带宽账先算清楚再动手做图像采集系统一定要养成先算带宽的习惯不然做完才发现性能不够返工成本极高。我这次直接在纸上算了一遍摄像头输出YUYV格式每个像素2字节640x480分辨率30fps数据量640 × 480 × 2 614400字节/帧 ≈ 600KB每秒数据量600KB × 30 18MB/sUSB 2.0实际可用带宽约30~40MB/sDDR3带宽Zynq常见配置是32bit、DDR3-1066峰值约4.2GB/s一算就明白瓶颈在USB总线不在DDR。所以640x48030fps是稳妥的720p30fps的YUYV约55MB/s已经逼近USB极限不建议尝试。如果确实要跑高分辨率格式要换成MJPEG摄像头输出的是一帧JPEG压缩数据可能只有几十到几百KBUSB带宽压力小很多但代价是CPU要负责解压。Zynq的PS侧是双核A9软解1080P MJPEG会很吃力这种时候就得考虑把解码放到PL侧或者接受低帧率。3. 内核与设备树配置说实话如果你用的是官方BSPUSB摄像头这一路的驱动默认大概率已经编译成模块或直接编进内核了。但为了不被BSP的“默认能用”麻痹最好还是手动确认一遍配置项顺便也能搞清楚每个配置的作用。这节内容是纯实操照着敲就行。3.1 Linux内核配置项在内核源码目录下执行make menuconfig按路径找到下面几个选项确保它们的状态是[*]或[M]Device Drivers → Multimedia support → Video for LinuxV4L2框架基础必须打开。Device Drivers → Multimedia support → Media USB Adapters → USB Video Class (UVC)这是核心选项编译成[*]可以直接减少模块加载的麻烦。Device Drivers → USB support → Chipidea high speed dual role controllerZynq的USB控制器驱动如果系统能识别U盘说明这项已经是开的。Device Drivers → USB support → USB Mass Storage support虽然不是摄像头必须但调试U盘时会用到。我习惯把UVC驱动编进内核而不是模块原因很朴素省掉modprobe那一步启动后摄像头直接就出现在/dev/video0了。如果你的BSP里已经有了跳过这节也没问题。3.2 设备树节点调整Zynq设备树里USB节点一般在zynq-7000.dtsi中定义板级文件里需要确认status okay。以usb0为例usb0 { status okay; dr_mode host; usb-phy usb_phy0; };这里重点说dr_mode。dr_mode otg也不是不能用但需要额外的角色切换处理对摄像头这种固定host设备来说纯属增加变量。直接设成hostUSB控制器就会以host模式初始化VBUS供电也会按host时序来打开。还有一个容易坑到人的地方Zynq的USB PHY需要独立的电源节点和复位控制。如果板级设备树里PHY的电源GPIO没有配置正确会出现一个很奇怪的现象——dmesg里USB控制器初始化正常但只要一插设备供电就拉不下来。这种问题查硬件比查软件快。3.3 启动后验证编译内核和设备树烧到SD卡里启动登录系统后依次执行这几条命令可以快速判断链路是否正常lsusb dmesg | grep -i usb dmesg | grep -i uvc v4l2-ctl --list-devices v4l2-ctl --list-formats-ext -d /dev/video0lsusb能看到类似Bus 001 Device 002: ID 046d:0825 Logitech, Inc. Webcam C270的输出说明USB枚举成功。dmesg | grep uvc能看到uvcvideo: Found UVC 1.00 device。v4l2-ctl --list-formats-ext会列出设备支持的所有格式和分辨率这个信息在写采集程序时非常有用。如果lsusb里根本没有设备大概率是硬件链路或VBUS供电问题如果lsusb有设备但/dev/video0不存在大概率是UVC驱动没加载或者被系统禁用了。先按这个顺序排查基本能定位九成问题。4. 应用层采集程序与数据流向PL内核配置好只是第一步真正干活的是应用层采集程序。这里我提供一个最小可用的V4L2采集框架不依赖OpenCV不依赖GStreamer只用标准系统调用。这样你能看清每一帧数据的来龙去脉也为后面接VDMA打下基础。4.1 最小V4L2采集程序核心流程是打开设备 → 设置格式 → 请求缓冲区 → mmap映射 → 入队所有缓冲区 → 开启采集 → 循环出队/入队。下面是精简后的代码骨架#include stdio.h #include stdlib.h #include string.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include sys/mman.h #include linux/videodev2.h #define WIDTH 640 #define HEIGHT 480 #define BUFFERS 4 int main(int argc, char *argv[]) { int fd open(/dev/video0, O_RDWR); if (fd 0) { perror(open); return -1; } struct v4l2_format fmt {0}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width WIDTH; fmt.fmt.pix.height HEIGHT; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field V4L2_FIELD_NONE; if (ioctl(fd, VIDIOC_S_FMT, fmt) 0) { perror(S_FMT); return -1; } struct v4l2_requestbuffers req {0}; req.count BUFFERS; req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, req) 0) { perror(REQBUFS); return -1; } void *buffers[BUFFERS]; size_t lengths[BUFFERS]; for (int i 0; i BUFFERS; i) { struct v4l2_buffer buf {0}; buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; buf.index i; if (ioctl(fd, VIDIOC_QUERYBUF, buf) 0) { perror(QUERYBUF); return -1; } lengths[i] buf.length; buffers[i] mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[i] MAP_FAILED) { perror(mmap); return -1; } if (ioctl(fd, VIDIOC_QBUF, buf) 0) { perror(QBUF); return -1; } } enum v4l2_buf_type type V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, type) 0) { perror(STREAMON); return -1; } for (int frame 0; frame 100; frame) { struct v4l2_buffer buf {0}; buf.type V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_DQBUF, buf) 0) { perror(DQBUF); break; } // 在这里处理 buffers[buf.index] 中的数据 // printf(frame %d, size %d\n, frame, buf.bytesused); if (ioctl(fd, VIDIOC_QBUF, buf) 0) { perror(QBUF); break; } } enum v4l2_buf_type stop V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_STREAMOFF, stop); for (int i 0; i BUFFERS; i) munmap(buffers[i], lengths[i]); close(fd); return 0; }这段代码的关键点是缓冲区循环。摄像头驱动维护一个DMA缓冲区队列应用必须保证缓冲区始终在队列里否则驱动没有地方写数据采集就会断流。入队QBUF和出队DQBUF要成对出现一帧处理完立刻入队这是保证帧率稳定的基础。4.2 把帧数据交给VDMA应用层拿到帧之后下一步是把帧数据送到PL侧。我的做法是预留4个物理地址连续的DMA缓冲区让V4L2直接采集到这块区域或者采集后memcpy过去然后把这4个地址轮流配给VDMA的MM2S通道。更高效的做法是让V4L2的缓冲区本身就位于物理地址连续的DMA内存里这样省掉一次拷贝但实现起来要碰驱动内部buffer管理一般不建议新手一开始就做。VDMA的寄存器配置不复杂核心是写帧地址和帧大小#define VDMA_BASE 0x43000000 #define VDMA_MM2S_START_ADDRESS(n) (VDMA_BASE 0x100 (n) * 0x10) #define VDMA_MM2S_HSIZE (VDMA_BASE 0x128) #define VDMA_MM2S_VSIZE (VDMA_BASE 0x12C) #define VDMA_MM2S_FRMDLY_STRIDE (VDMA_BASE 0x120) volatile uint32_t *vreg (uint32_t *)mmap(...); vreg[VDMA_MM2S_HSIZE / 4] WIDTH * 2; // 行跨度bytes vreg[VDMA_MM2S_VSIZE / 4] HEIGHT; // 行数 vreg[VDMA_MM2S_FRMDLY_STRIDE / 4] WIDTH * 2; // 帧内行步长 for (int i 0; i 4; i) { vreg[VDMA_MM2S_START_ADDRESS(i) / 4] phys_addr[i]; }这里有个容易被忽略的细节VDMA要求帧起始地址按32字节对齐行跨度和帧跨度也要一致。如果你的采集缓冲区地址是malloc出来的虚拟地址肯定不满足这个要求。所以我在工程里是直接通过dma_alloc_coherent或分配连续物理内存的驱动程序来申请缓冲区拿到物理地址后传给VDMA。如果你用普通的用户态程序没有权限直接访问物理地址推荐用/dev/mem映射或写一个小的内核模块做buffer管理。我自己的经验是不要试图在用户态搞定所有事。Zynq这类SoC上做图像采集底层buffer管理总是绕不开物理地址问题要么用现成的库比如Xilinx的video driver框架要么自己写个简单的内核驱动。4.3 采集后的扩展方向数据到了PL侧之后能做的事情就多了。最简单的是接一个AXI-Stream to Video Out IP把YUV数据按VGA时序送到HDMI接口直接在显示器上看实时画面。稍微复杂一点的做法是在PL侧加一个帧差检测或边缘检测模块对每一帧做实时处理然后只把处理结果传给PS侧。这些扩展的公共基础都是VDMA这条搬运通路所以这次把USB采集链路打通后面做什么都有底了。网络方向也是热门需求。最近有看到大家讨论用RK3588实现USB摄像头转RTSP流这个思路在Zynq-7000上也能做只是侧重点不同。RK3588有硬件视频编码器转RTSP时可以直接用H.264硬编CPU占用很低。Zynq-7000没有视频硬编单元要么用软件编码器x264/FFmpeg跑低分辨率要么在PL侧用HLS或FPGA逻辑实现简单的JPEG编码再封装成流。我的建议是如果你只是做原型验证直接用FFmpeg把V4L2数据源转成RTSPffmpeg -f v4l2 -input_format yuyv422 -video_size 640x480 -framerate 30 \ -i /dev/video0 -c:v libx264 -preset ultrafast -tune zerolatency \ -f rtsp rtsp://192.168.1.10:8554/live在Zynq双核A9上640x48030fps的x264编码勉强能跑CPU会接近满载但作为功能验证完全够用。4.4 与RK3588方案的对比思考我把Zynq-7000和RK3588放在一起对比不是要比谁强谁弱而是想说明不同平台对同一需求的解构方式完全不一样。RK3588走的是“主控SoC全包”路线USB摄像头采集、硬件编码、RTSP推流全部在CPU侧完成开发周期短代码量小适合产品迭代。Zynq-7000走的是“PSPL协同”路线虽然USB采集和网络推流都依赖PS的Linux环境但PL侧可以灵活地插入自定义图像处理模块比如在推流之前先做背景减除、ROI裁剪、像素格式转换这些操作几乎不占用CPU。选型没有绝对的对错关键是你手头有什么资源和瓶颈在哪里。如果你的项目目标是快速交付一个USB摄像头转RTSP的功能RK3588这类带硬件编解码器的SoC显然更合适如果你的项目核心是图像预处理算法想在数据进来的第一时间做自定义计算Zynq的PL侧自由度更高。我之前在项目里用过Zynq做实时缺陷检测USB摄像头把图像采进来PL侧直接跑一个模板匹配的简单逻辑只有检测到疑似缺陷才把整帧传给PS做二次确认这样网络和CPU的压力都小很多。5. 常见问题与调试经验这一节是实战中积累的排查记录我把最典型的几个问题整理成表格后面再展开细说。症状可能原因核查方法解决思路lsusb无设备VBUS供电异常 / PHY配置不对量OTG口VBUS电压、查设备树PHY节点检查电源GPIO、改设备树lsusb有设备无/dev/video0uvcvideo未加载 / 驱动被禁用dmesggrep uvc/dev/video0存在打开失败设备被占用fuser /dev/video0杀掉占用进程图像花屏或错位V4L2格式与VDMA格式不一致 / stride不对对比数据和寄存器统一像素格式正确设置行跨度采集几秒后断流缓冲区处理不及时驱动队列空转看应用是否QBUF及时增加缓冲区数量或加快处理帧率明显偏低带宽瓶颈 / CPU占用过高用perf top查看改MJPEG或降低分辨率5.1 USB摄像头枚举失败插上摄像头没有lsusb输出这个问题我遇到过两次一次是VBUS供电不够一次是PHY时钟配置有问题。USB摄像头的工作电流通常在200~500mA之间如果开发板OTG口的供电能力设计不足或者全靠一个电流受限的LDO供电摄像头初始化到一半就会掉电重启。排查方法很简单拿万用表测VBUS引脚没有5V就是供电问题有5V但一插设备就被拉低说明带载能力不够得外接带电源的USB Hub。PHY配置问题隐蔽一些。Zynq的USB PHY可以通过ULPI接口连接外部PHY芯片也可以通过内部集成PHY直接用。设备树里如果没有正确配置PHY的时钟和复位引脚内核可能枚举失败或者枚举后立刻断开。这类问题的特征是dmesg里反复出现usb 1-1: device descriptor read/64, error -71之类的错误这时候优先检查PHY的VCC和时钟频率而不是折腾驱动代码。5.2 V4L2打开失败与格式协商即使/dev/video0存在open成功也不代表后面一切顺利。最常见的问题是摄像头实际支持的格式和应用层请求的格式不匹配。比如有些低成本USB摄像头只支持MJPEG不支持YUYV而你代码里写死了V4L2_PIX_FMT_YUYVVIDIOC_S_FMT会返回失败或者自动切换成摄像头支持的格式。调试时我习惯先跑一遍v4l2-ctl --list-formats-ext -d /dev/video0把摄像头支持的格式全部打出来再根据这个列表去设置pixelformat。另外要注意很多摄像头支持的最大分辨率是插值上去的实际光学分辨率只有640x480强行设成1280x720会得到一幅放大模糊的画面。选分辨率时优先看摄像头标称的原生物理分辨率效果会好很多。5.3 花屏、卡顿与缓冲区策略花屏的本质是数据被错误地拼接或截断。常见原因有两个一是VDMA的行跨度设置不对比如图像是640像素宽、每像素2字节行跨度应该设为1280字节如果你设成了640字节VDMA会每行只读一半数据画面会严重错位二是V4L2采集到的缓冲区和VDMA读的缓冲区不是同一个应用在写入新帧之前VDMA已经开始读这帧数据了读到一半数据被覆盖画面就会闪。解决缓冲区竞争问题需要在应用层实现一个简单的乒乓机制——至少准备两套缓冲区一帧用于VDMA读取一帧用于V4L2写入交替使用。这样虽然会引入一帧延迟但能换来回放稳定。卡顿的原因则更多是缓冲区入队不及时。如果应用在处理一帧时耗时过长超过了摄像头产生一帧的间隔驱动里的缓冲区队列就会耗尽摄像头只能丢帧。加大req.count比如从4改成8能缓解这个问题但根治还是要提高单帧处理速度。5.4 调试工具与系统级性能优化强烈建议安装v4l-utils包里面的v4l2-ctl和v4l2-compliance在调试时非常好用。v4l2-compliance可以跑一遍完整的V4L2兼容性测试很多驱动层面的问题一测就知道。同时用busybox top或perf top观察CPU占用率可以迅速判断帧率瓶颈是在采集侧还是处理侧。如果发现CPU占用过高优化顺序是先看内存拷贝次数能零拷贝就零拷贝再看像素格式能用MJPEG小数据量采集就尽量别用YUYV硬扛最后看算法语言能用NEON指令优化的计算就换成NEON。我在Zynq上做过一次YUYV转RGB的格式转换纯C实现640x48030fps大概占掉一个核的60%改成NEON优化后降到15%左右效果立竿见影。因此不要一上来就抱怨平台性能不够先把自己代码里的浪费清一遍再说。调试图像通路时我还习惯在VDMA读地址处设置一个固定颜色的测试帧比如一帧纯红色YUV数据。如果显示端能看到纯红色画面说明从DDR到显示的链路是通的问题就缩小到V4L2采集或缓冲区搬运这一步。这种“分段隔离”的方法在复杂数据链路调试里能帮你省掉大量瞎猜的时间。最后再分享一个小经验Zynq的PS和PL之间数据搬运最容易忽视的是DDR带宽竞争。当USB控制器、VDMA、CPU同时访问DDR3时虽然带宽足够但延迟抖动会导致帧间隔不均。如果你在显示端看到画面有规律的周期性撕裂可以尝试把VDMA的优先级调高一点或者在PL侧加一个小FIFO做帧缓冲平滑。图像采集这件事很多时候不是某一个点不行而是多个点之间的协作没做好。把这些协作细节一个个理顺系统自然就稳定了。
返回列表