3. DMA 传输类型:单次传输、块传输、循环传输、链式传输(Scatter/Gather)
S32K3 的 eDMA 引擎支持四种核心传输类型,理解它们的差异是进行高效数据搬运和中断协同设计的基础。每种类型对应不同的应用场景,在中断触发时机、地址更新规则和链路管理上各有特点。
3.1 传输类型概览与对比
| 传输类型 | 单次传输量 | 自动重复 | 中断触发时机 | 典型应用 |
|---|---|---|---|---|
| 单次传输 | 1 次 Minor Loop | 否 | Major Loop 完成 | 单次 ADC 结果读取 |
| 块传输 | N 次 Minor Loop | 否 | Major Loop 完成 | SPI 数据块收发 |
| 循环传输 | N 次 Minor Loop | 是(自动重载) | 每次 Major Loop 完成 | 定时器捕获、PWM 更新 |
| 链式传输 | 可变(跨通道) | 是(通道链接) | 每个链接点触发 | Scatter/Gather 描述符表 |
关键概念:Minor Loop 是 DMA 传输的最小原子单位,通常等于源/目的数据宽度(如 32 位)。Major Loop 由多个 Minor Loop 组成,代表一次完整的传输请求完成。
3.2 单次传输(Single Transfer)
单次传输是最基本的模式:DMA 响应一次请求,完成一个 Minor Loop 的数据搬运后即停止,等待下一次软件触发或外设请求。该模式下,TCD.CITER(当前迭代计数)初始值为 1,传输完成后自动清零。
// 单次传输 TCD 配置示例(S32K3 RTD 驱动风格)
tcd.SADDR = (uint32_t)&src_buffer;
tcd.DADDR = (uint32_t)&dst_register;
tcd.NBYTES = 4; // Minor Loop 大小:4 字节
tcd.CITER = 1; // Major Loop 迭代次数 = 1
tcd.BITER = 1;
tcd.CSR = DMA_CSR_START_MASK; // 使能硬件请求或软件触发
中断协同要点:单次传输完成后,DMA 会置位对应通道的完成标志位(TCD.CSR.DONE),并可触发 INTC 中断。由于传输量小,中断延迟敏感度较低,适合分配给中等优先级。
3.3 块传输(Block Transfer)
块传输将多个 Minor Loop 组合为一个 Major Loop。例如,从 SPI 接收 FIFO 连续读取 16 个 32 位数据:每个 Minor Loop 搬运 4 字节,16 个 Minor Loop 构成一个 Major Loop。传输完成后才产生中断,中间不打断 CPU。
// 块传输 TCD 配置(16 次 Minor Loop)
tcd.NBYTES = 4; // 每次搬运 4 字节
tcd.CITER = 16; // 共 16 次 Minor Loop
tcd.BITER = 16;
tcd.SLAST = 0; // 源地址不调整(FIFO 固定地址)
tcd.DLAST_SGA = -64; // 目的地址回绕(环形缓冲)
项目移植陷阱:块传输中,如果外设请求频率高于 DMA 处理速度,会导致溢出。务必计算外设峰值速率与 DMA 带宽的匹配关系。S32K3 eDMA 在 160 MHz 下,单次 Minor Loop 约需 4~6 个总线时钟周期。
3.4 循环传输(Circular Transfer)
循环传输在块传输基础上增加了自动重载机制:Major Loop 完成后,TCD 的 CITER 和地址指针自动恢复为初始值,无需 CPU 重新配置。适用于周期性数据采集或波形生成。
- 地址回绕:通过
SLAST或DLAST_SGA设置负偏移量,使地址指针回到缓冲区起点。 - 中断频率:每次 Major Loop 完成都会触发中断,可用于数据同步或标志更新。
- 缓冲区大小:必须为 2 的幂次方,且与
NBYTES × CITER对齐。
中断优先级建议:循环传输的中断频率固定且可预测,建议分配中等优先级(如 INTC 优先级 8~12),避免阻塞高优先级实时任务,同时确保数据不丢失。
3.5 链式传输(Scatter/Gather)
链式传输是 S32K3 eDMA 最强大的特性,允许通过硬件自动加载多个 TCD 描述符,实现复杂的数据搬运序列。每个 TCD 完成后,DMA 自动从内存中加载下一个 TCD,无需 CPU 干预。
3.5.1 链式传输工作原理
- 描述符表:在系统内存中预定义一组 TCD 结构体数组,每个 TCD 描述一次传输的源、目的、计数和链接信息。
- 链接字段:TCD 的
DLAST_SGA字段在链式模式下存储下一个 TCD 的内存地址。 - 自动加载:当前 TCD 的 Major Loop 完成后,eDMA 引擎自动从
DLAST_SGA指向的地址加载新 TCD,并开始下一次传输。
3.5.2 链式传输配置步骤
- 定义描述符数组:在非缓存内存区域(如 SRAM)中分配
tcd_t chain_tcd[3]。 - 填充 TCD 字段:为每个 TCD 设置源地址、目的地址、传输计数、数据宽度。
- 设置链接指针:将
tcd[0].DLAST_SGA指向&tcd[1],tcd[1].DLAST_SGA指向&tcd[2],最后一个 TCD 的DLAST_SGA置 0。 - 使能通道:将第一个 TCD 加载到 DMA 通道寄存器,设置
CSR.START位。
// 链式传输描述符配置示例
#define CHAIN_LEN 3
tcd_t chain_tcd[CHAIN_LEN] __attribute__((section(".sram_data")));
// 初始化 TCD[0]
chain_tcd[0].SADDR = (uint32_t)&adc_result[0];
chain_tcd[0].DADDR = (uint32_t)&ram_pool_a;
chain_tcd[0].NBYTES = 4;
chain_tcd[0].CITER = 8;
chain_tcd[0].BITER = 8;
chain_tcd[0].DLAST_SGA = (uint32_t)&chain_tcd[1]; // 链接到下一个 TCD
// 初始化 TCD[1]
chain_tcd[1].SADDR = (uint32_t)&spi_rx_fifo;
chain_tcd[1].DADDR = (uint32_t)&ram_pool_b;
chain_tcd[1].NBYTES = 4;
chain_tcd[1].CITER = 16;
chain_tcd[1].BITER = 16;
chain_tcd[1].DLAST_SGA = (uint32_t)&chain_tcd[2];
// 初始化 TCD[2](最后一个)
chain_tcd[2].SADDR = (uint32_t)&timer_capture;
chain_tcd[2].DADDR = (uint32_t)&ram_pool_c;
chain_tcd[2].NBYTES = 2;
chain_tcd[2].CITER = 4;
chain_tcd[2].BITER = 4;
chain_tcd[2].DLAST_SGA = 0; // 终止链
中断协同设计:链式传输通常只在最后一个 TCD 完成后触发中断,前序 TCD 不使能中断(
CSR.INTMAJOR = 0)。这样可以减少中断次数,CPU 只需在整条链完成后一次性处理所有数据。中断优先级建议设置为中高(如优先级 4~6),确保链式传输的实时性。
3.6 传输类型选择决策树
在实际项目移植中,可根据以下条件快速选择传输类型:
3.7 项目移植中的常见问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 循环传输地址不回绕 | DLAST_SGA 偏移量计算错误 |
确保偏移量 = - (NBYTES × CITER) |
| 链式传输卡在第一个 TCD | DLAST_SGA 指向无效地址或对齐错误 |
检查描述符地址 32 位对齐,且位于可访问内存区域 |
| 中断触发频率异常 | 循环传输中 CSR.INTMAJOR 误配置为每个 Minor Loop 触发 |
确认 CSR.INTHALF 和 CSR.INTMAJOR 位设置正确 |
| 块传输数据错位 | 源/目的地址递增方向与缓冲区布局不匹配 | 检查 SOFF、DOFF、SLAST、DLAST_SGA 的符号和数值 |
关键提醒:在 S32K3 项目移植中,如果从 S32K1 或 Kinetis 系列迁移,注意 eDMA 的 TCD 结构体字段布局与旧版 DMA 不完全兼容。特别是
DLAST_SGA 在链式模式下的语义变化,务必参考 S32K3 RM(参考手册)第 18 章 eDMA 描述符表定义。