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 |
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 |
// 定义 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)。
下图展示了不同传输宽度下,DMA 从源地址读取数据并写入目的地址的粒度差异:
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 地址指针和计数的自动重置过程:
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);