4. DMA 传输配置:源地址/目的地址设置、传输宽度与传输计数中断

DMA(Direct Memory Access)传输配置是 RAW 图采集系统中连接采集前端与内存后端的核心桥梁。配置错误将直接导致数据错位、带宽浪费或帧丢失。本节从源/目的地址、传输宽度、传输计数与中断三个维度展开,结合 FPGA 与嵌入式 DSP 的典型寄存器模型进行讲解。

核心原则:DMA 配置必须与 RAW 图的数据格式(Bayer 模式、位宽)、行缓存策略(Line Buffer)以及内存的突发传输特性(Burst Length)严格对齐,否则即使时钟频率满足要求,也无法实现零拷贝、零等待的采集链路。

4.1 源地址与目的地址设置

DMA 传输的源地址通常指向采集前端(如 CMOS 传感器接口、FPGA 内部的 FIFO 或 AXI-Stream 接口)的地址空间;目的地址则指向系统内存(DDR3/DDR4 或 SRAM)中的帧缓冲区。在 RAW 图采集场景中,地址配置需关注以下要点:

  • 源地址固定 vs. 递增:传感器接口通常为固定地址(如 FIFO 的读端口),而内存目的地址必须递增,以顺序存储像素数据。
  • 地址对齐:目的地址必须与传输宽度对齐。例如 32 位传输时,目的地址需 4 字节对齐;若使用 AXI 总线,还需满足 16 字节或 64 字节的突发对齐要求。
  • 行地址跳跃(Stride):RAW 图通常按行存储,每行像素数可能不等于内存中的行跨度(Stride)。DMA 需支持每传输完一行后,目的地址自动加上 Stride 值,跳过行尾填充字节。
实战提示:在 Xilinx VDMA(Video DMA)中,源地址和目的地址分别通过 VSize(垂直尺寸)和 HSize(水平尺寸)配合 Stride 寄存器实现二维地址映射。配置时务必先设置 Stride,再设置帧起始地址,否则可能导致行尾地址计算错误。
DMA 源地址与目的地址映射关系 DMA 源地址与目的地址映射关系 源地址空间 传感器 FIFO / AXI-Stream 固定地址 (如 0x4000_0000) 目的地址空间 DDR3/DDR4 帧缓冲区 递增地址 (行跳跃 Stride) DMA 传输 地址对齐要求: • 8 位传输:目的地址 1 字节对齐(无特殊要求) • 16 位传输:目的地址 2 字节对齐(地址最低位为 0) • 32 位传输:目的地址 4 字节对齐(地址低 2 位为 0)

4.2 传输宽度配置(8/16/32 位)

传输宽度决定了 DMA 每次总线事务搬运的数据位数。在 RAW 图采集系统中,传输宽度的选择直接影响总线利用率和帧率上限。下表对比了三种常见配置的适用场景与性能特征:

传输宽度 适用 RAW 图位深 单次传输像素数 总线利用率 典型场景
8 位 8-bit RAW (如 RAW8) 1 像素 低(总线带宽浪费) 低分辨率、低帧率传感器
16 位 10/12/14-bit RAW (如 RAW10/RAW12/RAW14) 1 像素(高位对齐) 中等 主流工业相机、手机 ISP
32 位 8/10/12-bit RAW 打包 2~4 像素(取决于打包格式) 高 高分辨率、高帧率采集
避坑指南:当 RAW 图位宽不是 8/16/32 的整数倍时(如 RAW10),直接使用 16 位传输会导致每像素浪费 6 位带宽。此时应使用打包模式(如 4 个 RAW10 像素打包为 5 字节,再通过 32 位传输搬运),或使用 FIFO 位宽转换器将数据对齐到 16/32 位边界。
不同传输宽度下的像素打包对比 不同传输宽度下的像素打包对比(以 RAW10 为例) 8 位传输 1 次传输 = 1 个 RAW10 像素(高 2 位丢弃) 16 位传输 1 次传输 = 1 个 RAW10 像素(低 6 位填充 0) 32 位传输(打包) 1 次传输 = 3 个 RAW10 像素(30 位有效) 带宽效率对比(假设总线时钟 100MHz): • 8 位传输:有效带宽 = 100 MB/s,实际像素吞吐 = 100 MPixel/s(浪费 20% 像素信息) • 16 位传输:有效带宽 = 200 MB/s,实际像素吞吐 = 100 MPixel/s(带宽利用率 50%) • 32 位打包传输:有效带宽 = 400 MB/s,实际像素吞吐 = 300 MPixel/s(带宽利用率 75%)

4.3 传输计数与中断配置

传输计数(Transfer Count)决定了 DMA 在一次触发后搬运的数据总量。在 RAW 图采集中,传输计数通常与一帧图像的总字节数或一行像素的总字节数对应。中断则用于通知 CPU 传输完成,触发帧处理或下一帧配置。

4.3.1 传输计数设置策略

  • 帧传输模式:传输计数 = 帧宽度(像素)× 帧高度(行)× 每像素字节数。适用于整帧采集完成后才需要处理数据的场景。
  • 行传输模式:传输计数 = 帧宽度(像素)× 每像素字节数。每传输完一行触发一次中断,适用于需要逐行处理(如去噪、坏点校正)的低延迟场景。
  • 链表传输模式(Scatter-Gather):通过描述符链表将多行或帧的传输计数分散到多个 DMA 描述符中,实现非连续内存地址的连续传输。
性能优化点:在 FPGA 中实现 DMA 控制器时,传输计数寄存器通常为 16 位或 32 位。对于 4K 分辨率 RAW 图(4096×2160,每像素 2 字节),单帧数据量约 16.6 MB。若使用 16 位计数寄存器(最大 65535),需分多次传输或使用计数扩展模式。建议在系统设计阶段优先选择 32 位计数寄存器。

4.3.2 中断配置与处理

中断配置是 DMA 效率优化的关键环节。过多中断会消耗 CPU 资源,过少中断则可能导致帧处理延迟。常见中断类型包括:

中断类型 触发条件 适用场景 中断频率
传输完成中断 DMA 传输计数归零 帧采集完成、行采集完成 每帧/每行一次
错误中断 总线错误、地址越界、FIFO 溢出 异常检测与恢复 偶发
半传输中断 传输计数达到一半 双缓冲切换(Ping-Pong Buffer) 每帧两次
描述符完成中断 链表中的单个描述符传输完成 Scatter-Gather 模式 每描述符一次
DMA 传输计数与中断流程 DMA 传输计数与中断流程(帧传输模式) 配置传输计数 帧总字节数 启动 DMA 硬件触发/软件启动 传输进行中 计数递减 传输完成 计数归零 中断处理 CPU 读取状态 关键寄存器: • DMA_CNT:传输计数 • DMA_SR:状态寄存器 • DMA_CR:控制寄存器

4.4 典型配置代码示例

以下代码展示了在嵌入式系统中配置 DMA 传输的典型流程,基于 STM32 的 DMA 控制器寄存器模型,但逻辑适用于大多数 FPGA/DSP 平台:

// DMA 传输配置结构体
typedef struct {
    uint32_t src_addr;      // 源地址(传感器 FIFO 地址)
    uint32_t dst_addr;      // 目的地址(帧缓冲区地址)
    uint32_t transfer_size; // 传输计数(字节数)
    uint8_t  width;         // 传输宽度:0=8位, 1=16位, 2=32位
    uint8_t  src_inc;       // 源地址递增模式:0=固定, 1=递增
    uint8_t  dst_inc;       // 目的地址递增模式:0=固定, 1=递增
    uint8_t  irq_enable;    // 中断使能:0=禁用, 1=使能
} DMA_Config_t;

// DMA 初始化函数
void DMA_Init(DMA_Config_t *cfg) {
    // 1. 设置源地址和目的地址
    DMA->SAR = cfg->src_addr;   // 源地址寄存器
    DMA->DAR = cfg->dst_addr;   // 目的地址寄存器

    // 2. 设置传输宽度
    switch (cfg->width) {
        case 0: // 8 位
            DMA->CR &= ~(3 << 8);  // 清除宽度位
            DMA->CR |= (0 << 8);   // 设置 8 位
            break;
        case 1: // 16 位
            DMA->CR &= ~(3 << 8);
            DMA->CR |= (1 << 8);   // 设置 16 位
            break;
        case 2: // 32 位
            DMA->CR &= ~(3 << 8);
            DMA->CR |= (2 << 8);   // 设置 32 位
            break;
    }

    // 3. 设置地址递增模式
    if (cfg->src_inc) {
        DMA->CR |= (1 << 6);   // 源地址递增使能
    } else {
        DMA->CR &= ~(1 << 6);  // 源地址固定
    }

    if (cfg->dst_inc) {
        DMA->CR |= (1 << 7);   // 目的地址递增使能
    } else {
        DMA->CR &= ~(1 << 7);  // 目的地址固定
    }

    // 4. 设置传输计数
    DMA->CNDTR = cfg->transfer_size;  // 传输计数寄存器

    // 5. 配置中断
    if (cfg->irq_enable) {
        DMA->CR |= (1 << 1);    // 传输完成中断使能
        NVIC_EnableIRQ(DMA_IRQn); // 使能 NVIC 中断
    } else {
        DMA->CR &= ~(1 << 1);   // 禁用中断
    }

    // 6. 使能 DMA
    DMA->CR |= (1 << 0);       // DMA 使能位
}

// DMA 中断服务函数
void DMA_IRQHandler(void) {
    if (DMA->ISR & (1 << 1)) {  // 检查传输完成标志
        // 清除中断标志
        DMA->IFCR |= (1 << 1);

        // 帧处理回调
        FrameProcessingCallback();

        // 重新配置下一帧(如果需要连续采集)
        DMA_Config_t next_cfg = {
            .src_addr  = SENSOR_FIFO_ADDR,
            .dst_addr  = NEXT_FRAME_BUFFER,
            .transfer_size = FRAME_SIZE_BYTES,
            .width     = 2,        // 32 位传输
            .src_inc   = 0,        // 源地址固定
            .dst_inc   = 1,        // 目的地址递增
            .irq_enable = 1
        };
        DMA_Init(&next_cfg);
    }
}
调试建议:在首次配置 DMA 时,建议先使用 8 位传输宽度和较小的传输计数(如 64 字节)进行验证。通过逻辑分析仪或芯片内置的 DMA 跟踪功能,检查源地址、目的地址和传输计数是否按预期变化。确认无误后再切换到 32 位传输和全帧计数,以降低调试复杂度。