第四章:VDMA驱动开发——AXI-Stream与AXI4-MM转换、寄存器配置与中断处理

好,咱们进入VDMA驱动开发的正题。这一章,我打算把VDMA的核心机制掰开揉碎了讲清楚。说白了,VDMA就是视频数据流的“交通枢纽”——一边是AXI-Stream这种流式接口,一边是AXI4-MM这种内存映射接口。你想想看,摄像头采集的数据是源源不断的流,而DDR内存是按地址存取的,这两者怎么对接?VDMA就是干这个活的。

4.1 AXI-Stream与AXI4-MM的转换机制

先说说这两个接口的本质区别。AXI-Stream,没有地址概念,数据像水管里的水一样流过去。AXI4-MM呢,你得告诉它“把数据写到0x1000地址”,它才干活。VDMA内部其实就是一个状态机,负责把流式数据打包成带地址的突发传输。

我个人习惯把VDMA的工作模式分成两种:

  • 写通道(S2MM):从AXI-Stream接收数据,写到DDR里。说白了就是“采集模式”。
  • 读通道(MM2S):从DDR读数据,转换成AXI-Stream发出去。这就是“显示模式”。

我在项目中遇到过一个问题:摄像头输出的数据是24位RGB,但VDMA的AXI-Stream数据位宽是64位。这时候就需要做位宽转换。VDMA内部有FIFO做缓冲,但你要注意数据对齐——我曾经因为没配好STRIDE参数,导致图像显示出来是斜的,排查了半天才发现是行对齐出了问题。

核心要点:VDMA的转换效率取决于突发长度(Burst Length)。建议设置为16或32,太短了带宽上不去,太长了容易占总线太久。

4.2 VDMA寄存器配置详解

VDMA的寄存器说多不多,说少不少。我一般只关注几个关键的,其他的用默认值就行。咱们直接看代码:

// 基础配置
#define VDMA_BASE_ADDR  0x43000000

// 寄存器偏移
#define VDMA_S2MM_CR     0x00  // 控制寄存器
#define VDMA_S2MM_SR     0x04  // 状态寄存器
#define VDMA_S2MM_VSIZE  0x50  // 垂直尺寸
#define VDMA_S2MM_HSIZE  0x54  // 水平尺寸
#define VDMA_S2MM_FRMDLY 0x58  // 帧延迟
#define VDMA_S2MM_START_ADDR1 0xAC  // 帧缓冲地址1
#define VDMA_S2MM_START_ADDR2 0xB0  // 帧缓冲地址2
#define VDMA_S2MM_START_ADDR3 0xB4  // 帧缓冲地址3

// 配置函数
void vdma_config(uint32_t width, uint32_t height, uint32_t stride) {
    // 1. 复位VDMA
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_CR, 0x04);
    usleep(1000);
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_CR, 0x00);

    // 2. 配置帧尺寸
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_VSIZE, height);
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_HSIZE, width * 4); // 32位像素
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_FRMDLY, 0);

    // 3. 设置帧缓冲地址(三缓冲)
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_START_ADDR1, DDR_BASE_ADDR);
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_START_ADDR2, DDR_BASE_ADDR + stride * height);
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_START_ADDR3, DDR_BASE_ADDR + stride * height * 2);

    // 4. 启动VDMA
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_CR, 0x01); // 使能S2MM通道
}

我的经验:配置完寄存器后,一定要读一下状态寄存器(SR)确认VDMA是否进入运行状态。我见过有人配完直接就跑,结果VDMA根本没启动,数据全丢了。

这里有个坑要注意:HSIZE的单位是字节,不是像素。如果你的图像是1920x1080,每个像素32位(4字节),那HSIZE要填1920*4=7680。我曾经在这个地方栽过跟头,图像显示出来全是乱的。

4.3 中断处理机制

VDMA的中断处理,说白了就是让CPU知道“一帧数据已经准备好了”。我建议使用三缓冲机制,配合中断,这样CPU和VDMA可以流水线工作,效率最高。

中断配置分三步走:

  1. 使能VDMA中断:在控制寄存器里设置相应的中断使能位
  2. 配置中断控制器:在Zynq的GIC里注册VDMA中断
  3. 编写中断服务函数:处理帧完成事件
// 中断初始化
void vdma_intr_init() {
    // 使能VDMA中断
    uint32_t cr = Xil_In32(VDMA_BASE_ADDR + VDMA_S2MM_CR);
    cr |= 0x1000;  // 使能S2MM帧完成中断
    Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_CR, cr);

    // 注册中断(以Xilinx SDK为例)
    XScuGic_Config *gic_cfg = XScuGic_LookupConfig(XPAR_SCUGIC_0_DEVICE_ID);
    XScuGic_CfgInitialize(&gic, gic_cfg, gic_cfg->CpuBaseAddress);
    XScuGic_Connect(&gic, XPAR_FABRIC_VDMA_0_S2MM_INTROUT_VEC_ID,
                    (Xil_InterruptHandler)vdma_isr, NULL);
    XScuGic_Enable(&gic, XPAR_FABRIC_VDMA_0_S2MM_INTROUT_VEC_ID);
}

// 中断服务函数
void vdma_isr(void *arg) {
    uint32_t sr = Xil_In32(VDMA_BASE_ADDR + VDMA_S2MM_SR);

    if (sr & 0x1000) {  // 帧完成中断
        // 清除中断标志
        Xil_Out32(VDMA_BASE_ADDR + VDMA_S2MM_SR, 0x1000);

        // 切换当前帧缓冲
        current_frame = (current_frame + 1) % 3;

        // 通知应用层处理图像
        frame_ready = 1;
    }
}

注意:中断服务函数里不要做耗时操作!我见过有人直接在ISR里做图像处理,结果导致中断嵌套,系统直接崩溃。正确的做法是:ISR里只设置标志位,主循环里再处理数据。

4.4 实战中的避坑指南

嗯,这里我要多说几句。VDMA驱动开发,坑真的不少。我总结几个常见的:

  • 帧同步问题:如果VDMA和摄像头时钟不同步,会出现帧错位。解决办法是使用VDMA的帧同步信号(F Sync)来对齐。
  • 缓存一致性问题:Zynq的CPU有L1/L2 Cache,VDMA写DDR的数据可能还在Cache里没刷出去。记得用Xil_DCacheFlushRange()刷缓存。
  • 中断丢失:如果中断处理太慢,VDMA可能已经产生了下一帧中断,导致上一帧中断被覆盖。我建议用环形缓冲区来缓存中断事件。

我曾经在一个项目中,VDMA采集的图像每隔几帧就会出现撕裂。排查了三天,最后发现是帧缓冲地址没对齐到64字节边界。嗯,从那以后我配置地址时都会用memalign(64, size)来分配内存。

4.5 VDMA工作流程图

下面这张图是我自己画的VDMA工作流程,你看一眼就能明白整个数据流向:

VDMA数据流与中断处理流程 摄像头传感器 AXI-Stream VDMA核心 S2MM写通道 MM2S读通道 寄存器配置 中断控制 AXI4-MM DDR内存 中断控制器 (GIC) CPU (PS) 视频流 流数据 突发写 存储 中断信号 IRQ 寄存器配置 图例: 数据源 VDMA核心 总线接口 存储 中断路径

从这张图你能看到,VDMA处于整个数据流的中心位置。左边是摄像头过来的流式数据,右边是DDR的内存空间,上面是CPU通过AXI-Lite配置寄存器,下面是中断通知CPU“数据准备好了”。整个流程环环相扣,任何一个环节出问题,图像就出不来。

调试小技巧:如果VDMA不工作,先检查状态寄存器的VDMA_S2MM_SR的bit 12(帧完成标志)。如果这个位一直没置1,说明VDMA根本没收到数据。这时候回头查摄像头那边的时钟和数据使能信号。

好了,VDMA驱动开发的核心内容就这些。说白了就是三件事:配寄存器、处理中断、管好帧缓冲。你把这些搞明白了,图像采集这块基本就通了。

可可精品资料库