3. DMA 传输类型:内存到内存、内存到外设、外设到内存、外设到外设传输模式详解
DMA 控制器作为系统总线上的主设备,其核心价值在于能够在不占用 CPU 的情况下,在源地址与目的地址之间搬运数据。根据源和目的设备的不同,DMA 传输被划分为四种基本模式:内存到内存 (M2M)、内存到外设 (M2P)、外设到内存 (P2M) 以及 外设到外设 (P2P)。理解这四种模式的硬件路径、触发机制和适用场景,是编写高效 DMA 驱动的基础。
3.1 四种传输模式的核心差异
下表从数据流向、触发源、地址增长方式以及典型应用四个维度,对四种模式进行了对比。
| 传输模式 | 数据流向 | 触发源 | 源/目的地址增长 | 典型应用 |
|---|---|---|---|---|
| 内存到内存 (M2M) | 内存 → 内存 | 软件触发 (立即启动) | 源递增 / 目的递增 | 内存拷贝、帧缓冲移动、数据搬移 |
| 内存到外设 (M2P) | 内存 → 外设 FIFO | 外设请求 (DMA_REQ) | 源递增 / 目的固定 | LCD 显示、DAC 输出、SPI 发送 |
| 外设到内存 (P2M) | 外设 FIFO → 内存 | 外设请求 (DMA_REQ) | 源固定 / 目的递增 | ADC 采集、UART 接收、I2S 录音 |
| 外设到外设 (P2P) | 外设 FIFO → 外设 FIFO | 源外设请求 | 源固定 / 目的固定 | SPI 到 UART 桥接、数字信号重映射 |
驱动开发提示: 在配置 DMA 描述符时,
src_addr 和 dst_addr 的增长属性(DMA_ADDR_INCREMENT / DMA_ADDR_NO_CHANGE)必须与传输模式严格匹配。例如,M2P 模式下目的地址通常设置为固定,指向外设的数据寄存器地址。
3.2 内存到内存 (M2M) 传输详解
M2M 模式是最纯粹的 DMA 传输形式,数据在系统内存的两个区域之间移动。该模式通常由软件显式触发,无需等待外设硬件信号。驱动开发者通过写 DMA 控制器的使能寄存器或描述符的 SW 触发位来启动传输。
3.2.1 硬件路径与地址行为
- 源地址:指向内存中的源缓冲区,传输完成后地址自动递增。
- 目的地址:指向内存中的目的缓冲区,传输完成后地址自动递增。
- 传输粒度:支持字节 (8-bit)、半字 (16-bit)、字 (32-bit) 对齐传输。
- 突发传输:支持突发 (Burst) 模式,提高总线利用率。
关键配置: M2M 模式下,DMA 控制器的通道优先级通常设置为最低,因为该操作对实时性要求不高。同时,需要确保源和目的缓冲区在物理地址空间内连续,或使用 SG 描述符链处理非连续内存。
3.3 内存到外设 (M2P) 传输详解
M2P 模式用于将内存中的数据发送到外设。外设(如 SPI 发送器、UART 发送器、LCD 控制器)在准备好接收新数据时,会向 DMA 控制器发送硬件请求信号 (DMA_REQ)。DMA 控制器响应请求,从内存读取一个数据单元,写入外设的 FIFO 或数据寄存器。
3.3.1 流控与地址配置
- 流控:由外设控制传输节奏。外设每发出一个请求,DMA 传输一个数据单元。
- 源地址:内存缓冲区,地址递增。
- 目的地址:外设数据寄存器地址,地址固定不变。
- 数据宽度:必须与外设 FIFO 宽度匹配(如 32-bit 外设不能配置为 8-bit 传输)。
常见陷阱: 在 M2P 模式下,如果外设请求信号是边沿触发而非电平触发,驱动必须在描述符中正确配置请求极性。此外,当使用循环模式(Circular Mode)时,需要确保源缓冲区不会在传输过程中被 CPU 修改,否则会导致数据不一致。
3.4 外设到内存 (P2M) 传输详解
P2M 模式是 M2P 的逆过程,用于将外设接收到的数据存入内存。当外设(如 ADC、UART 接收器)的 FIFO 中有数据就绪时,会向 DMA 发送请求。DMA 读取外设数据寄存器,并将数据写入内存缓冲区。
3.4.1 关键配置参数
| 参数 | 配置值 | 说明 |
|---|---|---|
| 源地址 | 外设数据寄存器地址 | 固定不变 (NO_CHANGE) |
| 目的地址 | 内存缓冲区地址 | 递增 (INCREMENT) |
| 传输完成中断 | 使能 | 通知 CPU 数据已就绪 |
| FIFO 阈值 | 半满 / 全满 | 决定请求触发时机 |
驱动实现要点: 在 P2M 模式下,建议使用描述符链 (Descriptor Chain) 配合乒乓缓冲 (Ping-Pong Buffer) 机制。当 DMA 正在填充一个缓冲区时,CPU 可以安全地处理另一个已满的缓冲区,从而避免数据覆盖。
3.5 外设到外设 (P2P) 传输详解
P2P 模式是最复杂的 DMA 传输类型,数据从一个外设的 FIFO 直接传输到另一个外设的 FIFO,完全绕过内存。这种模式通常用于实时数据流处理,例如将 SPI 接收的数据直接转发到 UART 发送。
3.5.1 硬件约束与挑战
- 数据宽度匹配:源外设和目的外设的数据宽度必须一致,否则需要硬件对齐逻辑。
- 时钟域同步:两个外设可能位于不同的时钟域,DMA 控制器需要处理跨时钟域同步。
- 流控一致性:源外设的请求速率必须与目的外设的消费速率匹配,否则会导致 FIFO 溢出或欠载。
- 地址配置:源地址和目的地址均为固定模式,指向各自外设的数据寄存器。
硬件限制: 并非所有 DMA 控制器都支持 P2P 模式。在实现 P2P 驱动前,必须查阅芯片参考手册,确认 DMA 控制器是否支持“外设到外设”的传输类型,以及是否需要额外的同步逻辑。
3.6 传输模式选择决策树
在实际驱动开发中,选择合适的 DMA 传输模式是设计的第一步。以下决策树可以帮助开发者快速确定模式:
- 数据是否经过内存?
- 是 → 进入步骤 2
- 否 → 选择 外设到外设 (P2P)
- 源设备是内存还是外设?
- 内存 → 选择 内存到外设 (M2P)
- 外设 → 选择 外设到内存 (P2M)
- 源和目的都是内存?
- 是 → 选择 内存到内存 (M2M)
性能考量: M2M 模式通常使用最慢的传输优先级,因为它不涉及实时外设。M2P 和 P2M 模式需要根据外设的实时性要求设置通道优先级(例如,音频 I2S 通常设置为高优先级,而 UART 可以设置为中等优先级)。P2P 模式由于完全绕过内存,具有最低的延迟和最高的实时性,但硬件支持有限。
3.7 描述符配置示例 (伪代码)
以下伪代码展示了在描述符接口中如何根据传输模式配置关键字段:
/* DMA 描述符配置结构体 */
struct dma_descriptor {
uint32_t src_addr; /* 源地址 */
uint32_t dst_addr; /* 目的地址 */
uint32_t control; /* 控制字 */
uint32_t next_desc; /* 下一个描述符地址 */
};
/* 根据传输模式配置描述符 */
void dma_desc_config(struct dma_descriptor *desc,
enum dma_transfer_type type,
uint32_t src, uint32_t dst,
uint32_t size)
{
desc->src_addr = src;
desc->dst_addr = dst;
switch (type) {
case DMA_MEM_TO_MEM:
/* 源递增,目的递增,软件触发 */
desc->control = (size & 0xFFFF) |
DMA_SRC_INC |
DMA_DST_INC |
DMA_SW_TRIGGER;
break;
case DMA_MEM_TO_PERIPH:
/* 源递增,目的固定,外设请求触发 */
desc->control = (size & 0xFFFF) |
DMA_SRC_INC |
DMA_DST_NO_CHANGE |
DMA_HW_TRIGGER;
break;
case DMA_PERIPH_TO_MEM:
/* 源固定,目的递增,外设请求触发 */
desc->control = (size & 0xFFFF) |
DMA_SRC_NO_CHANGE |
DMA_DST_INC |
DMA_HW_TRIGGER;
break;
case DMA_PERIPH_TO_PERIPH:
/* 源固定,目的固定,外设请求触发 */
desc->control = (size & 0xFFFF) |
DMA_SRC_NO_CHANGE |
DMA_DST_NO_CHANGE |
DMA_HW_TRIGGER;
break;
}
desc->next_desc = 0; /* 单次传输,无链 */
}