3. 音频DMA引擎介绍:DMA控制器工作原理、DMA传输模式与音频DMA特点

在音频驱动中,数据搬运是性能瓶颈的核心。CPU 直接参与每个采样点的搬运会严重拖累系统实时性。DMA(Direct Memory Access)控制器正是为解决此问题而设计的硬件引擎,它允许外设与内存之间直接交换数据,无需 CPU 逐字干预。本章深入剖析 DMA 控制器的工作原理、传输模式,并重点阐述音频场景下 DMA 的特殊设计。

3.1 DMA控制器工作原理

DMA 控制器本质上是一个专用的状态机,负责在源地址与目的地址之间搬运数据。其核心工作流程包含三个阶段:请求、传输与终止。

核心工作流程:
  1. 请求阶段:外设(如 I2S 控制器)通过硬件请求线(DMA Request)向 DMA 控制器发出传输请求。
  2. 传输阶段:DMA 控制器获得总线控制权,执行一次或多次总线传输,将数据从源地址读取并写入目的地址。
  3. 终止阶段:传输计数器减至零或收到终止信号,DMA 控制器释放总线,并向 CPU 发出中断通知传输完成。

下图展示了 DMA 控制器在系统中的位置与基本数据流向:

DMA控制器工作原理与系统数据流向 DMA控制器工作原理与系统数据流向 CPU DMA 控制器 内存 (RAM) 外设 (I2S) 配置/中断 数据总线 DMA 请求 说明:CPU 仅负责初始化 DMA 控制器(设置源地址、目的地址、传输长度),传输过程由 DMA 硬件自动完成。

3.2 DMA传输模式

根据源与目的的不同,DMA 传输主要分为三种模式。音频驱动中最常用的是外设到内存(录音)和内存到外设(播放)。

传输模式 源地址 目的地址 典型应用场景 音频相关性
内存到内存 内存区域 A 内存区域 B 数据拷贝、缓冲区搬移、内存清零 低(仅用于音频缓冲区管理)
外设到内存 外设数据寄存器 内存缓冲区 ADC 采样、麦克风录音、传感器数据采集 高(录音路径)
内存到外设 内存缓冲区 外设数据寄存器 DAC 输出、扬声器播放、波形生成 高(播放路径)
关键区别:内存到内存传输通常由软件触发,而外设到内存/内存到外设传输由硬件请求线触发,这决定了音频 DMA 必须使用硬件同步模式。

下图对比了三种传输模式的数据路径:

三种DMA传输模式对比 三种DMA传输模式对比 内存到内存 源内存 目的内存 DMA 外设到内存 外设 (I2S) 内存 DMA 内存到外设 内存 外设 (I2S) DMA 音频驱动中的典型用法 播放:CPU 将音频数据写入内存缓冲区 → DMA 自动搬运到 I2S 发送寄存器 → 扬声器输出 录音:麦克风输入 → I2S 接收寄存器 → DMA 自动搬运到内存缓冲区 → CPU 读取处理

3.3 音频DMA特点

音频流对实时性、连续性和数据完整性有严格要求,因此音频 DMA 相比通用 DMA 有以下显著特点:

  • 循环缓冲(Ring Buffer / Cyclic Buffer):音频数据是连续流,DMA 必须支持地址回绕,在缓冲区末尾自动跳转到起始地址继续传输,避免音频中断。
  • 双缓冲(Double Buffering):通常使用两个缓冲区交替工作,一个用于 DMA 传输,另一个供 CPU 读写,消除竞争条件。
  • 硬件同步(Hardware Synchronization):传输由外设的帧同步信号(如 LRCLK)触发,确保采样率精确稳定。
  • 突发传输(Burst Transfer):音频数据通常以字(16/24/32-bit)为单位,DMA 控制器支持配置突发大小以匹配 I2S 数据宽度。
  • 中断与回调:每完成半缓冲或全缓冲传输时产生中断,驱动在中断服务程序中更新指针或填充新数据。
常见陷阱:如果 DMA 控制器不支持硬件循环模式,驱动必须手动在传输完成中断中重新配置源/目的地址,这会导致短暂的音频毛刺。选择 SoC 时务必确认 DMA 控制器是否支持硬件循环(Hardware Linked List 或 Auto-Reload)。

下图展示了音频 DMA 循环缓冲与双缓冲的典型结构:

音频DMA循环缓冲与双缓冲结构 音频DMA循环缓冲与双缓冲结构 循环缓冲区 (Ring Buffer) 缓冲区 A DMA 正在写入 缓冲区 B CPU 正在读取 地址回绕 I2S 外设 DMA 请求 中断控制器 半/全完成 当 DMA 写满缓冲区 A 后自动回绕到缓冲区 B 起始地址,同时触发中断通知 CPU 处理缓冲区 A 的数据。

3.4 音频DMA关键参数配置

在 Linux 音频驱动中,DMA 控制器通过 dmaengine API 进行配置。以下是与音频强相关的关键参数:

参数 描述 音频典型值 配置接口
传输宽度 单次传输的数据位宽 16-bit / 24-bit / 32-bit dma_set_slave_width()
突发大小 每次总线请求传输的数据单元数 4 / 8 / 16(与 FIFO 深度匹配) dmaengine_slave_config()
源/目的地址 外设数据寄存器物理地址 I2S 的 TX/RX 寄存器地址 dmaengine_slave_config()
循环模式 是否启用地址自动回绕 必须启用 dmaengine_prep_dma_cyclic()
传输方向 DMA 数据流向 DMA_DEV_TO_MEM / DMA_MEM_TO_DEV dmaengine_slave_config()
代码示例:音频 DMA 循环缓冲配置(伪代码)
struct dma_slave_config config = {
    .direction = DMA_MEM_TO_DEV,          // 播放方向
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 4,                    // 每次突发4个字
    .dst_maxburst = 4,
    .slave_id = i2s->dma_tx_id,           // 外设DMA请求ID
};

dmaengine_slave_config(chan, &config);

// 准备循环传输:缓冲区、周期大小、周期数
txdesc = dmaengine_prep_dma_cyclic(chan, buf_phys, buf_size,
                                   period_size, DMA_MEM_TO_DEV,
                                   DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

txdesc->callback = audio_dma_complete;    // 周期完成回调
dmaengine_submit(txdesc);
dma_async_issue_pending(chan);            // 启动传输