3. DMA控制器基础:DMA传输原理、通道配置、源/目的地址与传输宽度

DMA(Direct Memory Access,直接存储器访问)控制器是嵌入式系统中负责在无需CPU干预的情况下,在外设与内存之间、或内存与内存之间高速搬运数据的硬件引擎。在UART驱动中引入DMA,可以将串口数据流从逐字节的CPU中断处理中解放出来,实现“CPU只做控制,DMA负责搬运”的协同工作模式。

3.1 DMA传输原理

DMA传输的核心思想是:CPU配置好传输参数后,DMA控制器接管总线控制权,自动完成数据搬运,传输完成后通过中断通知CPU。整个过程CPU可以执行其他任务,从而大幅提升系统吞吐量。

关键概念: DMA传输是“硬件线程”级别的数据搬运,其传输效率远高于CPU逐字节拷贝,尤其适合UART等连续数据流场景。
DMA传输原理流程图 CPU 配置DMA参数 DMA控制器 接管总线 · 搬运数据 源地址(UART RX FIFO) 外设数据寄存器 目的地址(内存缓冲区) 系统RAM 传输完成中断

3.2 DMA通道配置

DMA控制器通常包含多个独立通道,每个通道可以独立配置并服务于不同的外设。以常见的ARM Cortex-M系列MCU为例,DMA通道配置涉及以下关键参数:

配置项 说明 典型值(UART场景)
通道编号 选择DMA控制器中的物理通道 Channel 1 ~ 7
请求源 触发DMA传输的外设请求信号 UART_RX / UART_TX
优先级 多个通道同时请求时的仲裁优先级 高/中/低
传输模式 单次传输/循环传输/内存到内存 循环模式(UART接收)
数据宽度 每次搬运的数据位宽 8位(字节)
突发传输 连续传输的数据单元数量 4/8/16 字节
配置要点: UART接收场景通常使用循环模式,这样DMA可以在缓冲区满后自动回绕到起始地址,实现环形缓冲区的硬件化。发送场景则常用单次模式,每次发送完成后重新配置。

3.3 源地址与目的地址

DMA传输的两个核心地址域:

  • 源地址(Source Address):数据读取的起始位置。对于UART接收,源地址是UART外设的数据寄存器地址(固定不变);对于UART发送,源地址是内存中待发送数据的缓冲区地址(可递增)。
  • 目的地址(Destination Address):数据写入的目标位置。对于UART接收,目的地址是内存缓冲区地址(递增);对于UART发送,目的地址是UART外设的数据寄存器地址(固定不变)。

地址模式分为两种:

地址模式 描述 UART应用场景
固定地址 每次传输后地址不变 外设数据寄存器(UART_DR)
递增地址 每次传输后地址增加(+1/+2/+4) 内存缓冲区(数组/链表)
UART DMA 源/目的地址映射 UART 接收场景(RX) UART_DR 寄存器 源地址(固定) 内存缓冲区 目的地址(递增) buf[0] buf[1] buf[2] ... 地址递增方向 → UART 发送场景(TX) 内存缓冲区 源地址(递增) UART_DR 寄存器 目的地址(固定) tx_buf[0] tx_buf[1] tx_buf[2] ... 地址递增方向 →

3.4 传输宽度

传输宽度(Transfer Width)决定了DMA每次搬运操作的数据位宽。常见选项包括:

  • 字节(8位):每次搬运1字节,适用于UART等字符设备。
  • 半字(16位):每次搬运2字节,适用于16位外设或音频数据。
  • 字(32位):每次搬运4字节,适用于高速外设或内存拷贝。

在UART场景中,传输宽度必须与UART数据寄存器宽度匹配。大多数UART外设的数据寄存器为8位或9位,因此通常配置为8位传输宽度。

宽度不匹配风险: 如果源地址宽度与目的地址宽度不一致,DMA控制器会自动进行数据对齐或截断。例如,从8位外设读取数据写入32位内存时,DMA会按字节填充,但可能导致内存对齐问题。务必确保源和目的的数据宽度一致,或确认DMA控制器支持宽度自动转换。

3.5 DMA传输配置代码示例

以下是一个基于STM32 HAL库的UART DMA接收配置示例,展示了通道、地址、宽度的实际配置过程:

/* UART DMA 接收配置示例(基于STM32 HAL库) */
void UART_DMA_RX_Config(void)
{
    /* 1. 声明DMA配置结构体 */
    DMA_HandleTypeDef hdma_usart1_rx;

    /* 2. 使能DMA时钟 */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* 3. 配置DMA通道参数 */
    hdma_usart1_rx.Instance = DMA1_Channel5;          /* 选择DMA通道5 */
    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位宽度 */
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;     /* 8位宽度 */
    hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;               /* 循环模式 */
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;      /* 高优先级 */

    /* 4. 初始化DMA */
    HAL_DMA_Init(&hdma_usart1_rx);

    /* 5. 将DMA通道与UART外设关联 */
    __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);

    /* 6. 启动DMA接收(源:UART_DR,目的:rx_buffer,长度:BUFFER_SIZE) */
    HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE);
}
配置要点解析:
• PeriphInc = DMA_PINC_DISABLE:外设地址(UART_DR)固定不变。
• MemInc = DMA_MINC_ENABLE:内存地址(rx_buffer)每次传输后递增。
• PeriphDataAlignment = DMA_PDATAALIGN_BYTE:外设数据宽度为8位。
• Mode = DMA_CIRCULAR:循环模式,缓冲区满后自动回绕。

3.6 DMA传输与CPU中断的协同边界

理解DMA与CPU中断的职责划分是本章的核心:

职责 DMA控制器 CPU(中断处理)
数据搬运 负责从外设到内存的逐字节拷贝 不参与,可执行其他任务
传输完成通知 产生DMA传输完成中断 响应中断,处理数据帧
半传输通知 产生DMA半传输中断(可选) 利用双缓冲机制处理数据
错误处理 检测总线错误/传输错误 响应错误中断,恢复或重置
配置管理 无自主决策能力 初始化、启动、停止、重新配置