3. DMA 关键参数:源地址、目的地址、传输宽度、传输计数、循环模式、增量模式

DMA 传输的灵活性来源于其可配置的多个关键参数。正确理解并配置这些参数,是确保 DMA 与 CPU 高效协同、避免数据错乱的基础。本章将逐一剖析 DMA 控制器的六大核心参数,并结合实际场景说明其配置逻辑。

3.1 参数总览与功能矩阵

下表列出了 DMA 传输中最关键的六个参数及其功能描述,后续章节将逐一展开。

参数名称 英文缩写 功能描述 典型配置值
源地址 SRC_ADDR 数据传输的起始地址,可以是外设寄存器或内存地址 &USART1->DR 或 0x20000100
目的地址 DST_ADDR 数据传输的目标地址,可以是内存或外设寄存器 uart_rx_buffer 或 &SPI2->DR
传输宽度 WIDTH 单次传输的数据位宽,决定总线访问粒度 8-bit / 16-bit / 32-bit
传输计数 NDTR / CNDTR 一次 DMA 传输需要完成的数据项总数 100 / 512 / 1024
循环模式 CIRC 传输完成后是否自动重载计数并从头开始 ENABLE / DISABLE
增量模式 PINC / MINC 每次传输后地址是否自动递增 ENABLE / DISABLE
工程师提示: 在 STM32 系列 MCU 中,DMA 的源地址和目的地址分别对应外设端和内存端。配置时需注意地址对齐规则——若传输宽度为 32-bit,则源/目的地址必须按 4 字节对齐,否则可能触发总线错误。

3.2 源地址与目的地址

源地址(Source Address)和目的地址(Destination Address)定义了数据流动的起点和终点。在 MCU 系统中,这两个地址可以指向以下三种区域:

  • 外设数据寄存器:如 USART->DR、SPI->DR、ADC->DR,通常地址固定且不可递增。
  • SRAM 内存区域:如全局数组、动态分配的缓冲区,地址可递增。
  • Flash 存储器:用于从 Flash 向 SRAM 搬运常量数据,地址可递增。

典型的地址配置组合如下:

传输方向 源地址类型 目的地址类型 典型应用
外设 → 内存 外设寄存器(固定) SRAM 数组(递增) UART 接收、ADC 采样
内存 → 外设 SRAM 数组(递增) 外设寄存器(固定) UART 发送、DAC 输出
内存 → 内存 SRAM 区域(递增) SRAM 区域(递增) 数据搬移、图像处理
Flash → SRAM Flash 区域(递增) SRAM 区域(递增) 常量表加载、Bootloader
配置示例: 以下代码展示了如何配置 STM32G4 系列 DMA 的源地址和目的地址,用于将 USART1 接收到的数据搬运到内存缓冲区。
// 定义 DMA 通道配置结构体
DMA_HandleTypeDef hdma_usart1_rx;

// 配置源地址:USART1 数据寄存器(外设端)
hdma_usart1_rx.Instance = DMA1_Channel1;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  // 8-bit 宽度
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     // 8-bit 宽度

// 源地址指向外设寄存器(固定地址)
// 目的地址指向内存缓冲区(递增地址)
// 实际地址在 HAL_DMA_Start() 中传入
HAL_DMA_Start(&hdma_usart1_rx, 
               (uint32_t)&USART1->DR,   // 源地址:外设寄存器
               (uint32_t)uart_rx_buf,   // 目的地址:内存缓冲区
               256);                     // 传输计数:256 字节

3.3 传输宽度

传输宽度(Transfer Width)决定了 DMA 控制器每次从源地址读取、向目的地址写入的数据位宽。常见的宽度选项包括:

  • 8-bit(字节):适用于 UART 字符流、I2C 数据、单字节传感器读数。
  • 16-bit(半字):适用于 16 位 ADC 采样值、音频 PCM 数据。
  • 32-bit(字):适用于 32 位定时器捕获值、图像像素数据、内存批量拷贝。

宽度配置必须遵循 源宽度 = 目的宽度 或 宽度可打包/解包 的规则。部分 DMA 控制器支持源和目的宽度不同,此时会自动进行数据打包(packing)或解包(unpacking)。

宽度不匹配陷阱: 若源宽度为 8-bit,目的宽度为 32-bit,DMA 会将连续的 4 个字节打包成一个 32 位字写入目的地址。如果数据本身不是按 4 字节对齐的,会导致数据错位。务必确认外设和内存的数据宽度一致,或明确使用打包/解包功能。

下图展示了不同传输宽度下,DMA 从源地址读取数据并写入目的地址的粒度差异:

传输宽度对比:8-bit vs 16-bit vs 32-bit 8-bit 传输 B0 B1 B2 B3 B0 B1 B2 B3 16-bit 传输 H0 (B0+B1) H1 (B2+B3) H0 H1 32-bit 传输 W0 (B0+B1+B2+B3) W0

3.4 传输计数

传输计数(Number of Data Transfers, NDTR)定义了 DMA 一次传输任务需要完成的数据项总数。每个数据项的大小由传输宽度决定。例如:

  • 传输宽度 = 8-bit,计数 = 256 → 总共传输 256 字节。
  • 传输宽度 = 16-bit,计数 = 256 → 总共传输 512 字节。
  • 传输宽度 = 32-bit,计数 = 256 → 总共传输 1024 字节。

传输计数在每次 DMA 传输完成后自动递减。当计数减至 0 时,DMA 控制器会:

  • 触发传输完成中断(如果使能)。
  • 清除通道使能位,停止传输。
  • 如果使能了循环模式,则自动重载初始计数值。
计数配置要点:
  • 计数最大值受 DMA 控制器硬件限制,STM32G4 系列最大为 65535(16 位计数器)。
  • 对于大数据量传输,可采用 DMA 链表模式 或 双缓冲模式 来突破计数上限。
  • 在传输过程中读取 NDTR 寄存器可获取剩余待传输的数据项数,用于进度监控。

3.5 循环模式

循环模式(Circular Mode)使 DMA 在完成一次传输后自动重载传输计数,并将地址指针重置到初始值,从而开始新一轮传输。该模式特别适用于以下场景:

  • 连续 ADC 采样:DMA 持续将 ADC 转换结果搬运到环形缓冲区。
  • UART 接收流:DMA 不间断接收不定长数据,配合空闲中断判断帧结束。
  • 定时器 PWM 波形生成:DMA 循环更新比较寄存器值,产生任意波形。

循环模式与非循环模式的行为对比如下:

特性 非循环模式(Normal) 循环模式(Circular)
传输完成后行为 停止传输,通道关闭 自动重载计数,继续传输
地址指针 停留在最后一个地址 重置到初始地址
CPU 干预频率 每次传输完成需 CPU 重新启动 无需 CPU 干预,持续运行
典型应用 单次数据搬移、Flash 编程 数据流采集、实时控制
中断触发 传输完成中断一次 每次循环完成均可触发中断

下图展示了循环模式下 DMA 地址指针和计数的自动重置过程:

循环模式工作流程 初始状态 计数 = 256 地址 = 初始 传输中 计数递减 地址递增 完成 → 重载 计数 = 0 地址重置 自动重载

3.6 增量模式

增量模式(Increment Mode)决定每次传输后,DMA 是否自动将源地址和/或目的地址增加一个传输宽度的步长。该参数分为两部分:

  • 外设地址增量(PINC):控制外设端地址是否递增。
  • 内存地址增量(MINC):控制内存端地址是否递增。

四种常见的增量模式组合如下:

PINC MINC 应用场景 地址行为
DISABLE ENABLE 外设 → 内存(最常用) 源地址固定(外设寄存器),目的地址递增(缓冲区)
ENABLE DISABLE 内存 → 外设(最常用) 源地址递增(缓冲区),目的地址固定(外设寄存器)
ENABLE ENABLE 内存 → 内存(数据搬移) 源地址和目的地址均递增
DISABLE DISABLE 外设 → 外设(特殊场景) 源地址和目的地址均固定
增量模式配置陷阱:
  • 当外设地址增量使能时,DMA 会连续读取多个外设寄存器。如果外设寄存器之间没有实际关联(如 USART->DR 和 USART->SR),会导致数据完全错误。
  • 对于外设寄存器,通常应禁用 PINC,因为外设数据寄存器地址是唯一的。
  • 内存地址增量使能时,需确保缓冲区大小足够,避免写入越界。

3.7 参数配置综合示例

以下是一个完整的 DMA 参数配置示例,用于从 USART1 接收 128 字节数据到内存缓冲区,采用循环模式实现连续接收:

// DMA 参数配置结构体
DMA_HandleTypeDef hdma_usart1_rx;

// 使能 DMA 时钟
__HAL_RCC_DMA1_CLK_ENABLE();

// 配置 DMA 通道参数
hdma_usart1_rx.Instance = DMA1_Channel1;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;      // 外设到内存
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;          // 外设地址不递增
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;              // 内存地址递增
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  // 8-bit 宽度
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     // 8-bit 宽度
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;                   // 循环模式
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;          // 高优先级

// 初始化 DMA 通道
HAL_DMA_Init(&hdma_usart1_rx);

// 启动 DMA 传输:源地址 = USART1 数据寄存器,目的地址 = 缓冲区,计数 = 128
HAL_DMA_Start(&hdma_usart1_rx, 
               (uint32_t)&USART1->DR, 
               (uint32_t)uart_rx_buffer, 
               128);
调试建议: 在调试 DMA 传输时,建议先使用非循环模式 + 单次传输验证地址和宽度配置是否正确。确认无误后,再启用循环模式。使用调试器观察 NDTR 寄存器的递减过程,可以快速定位计数配置问题。