4. DMA 基础:直接存储器访问原理与核心配置

DMA(Direct Memory Access,直接存储器访问)是嵌入式系统中一项关键的硬件加速技术。它允许外设与存储器之间直接传输数据,而无需 CPU 的逐字节干预。在串口不定长接收场景中,DMA 能够将接收到的数据自动搬运到内存缓冲区,CPU 仅在空闲中断触发时才处理数据,从而大幅降低系统负载。

核心价值: 使用 DMA 后,串口接收的 CPU 占用率可从 100%(轮询模式)降至接近 0%,仅在数据帧结束时产生一次中断。

4.1 DMA 传输原理

DMA 控制器本质上是一个独立的总线主设备,它拥有自己的总线仲裁逻辑和地址生成单元。一次典型的 DMA 传输包含以下步骤:

  1. 请求阶段: 外设(如 USART)产生 DMA 请求信号(例如 RXNE 寄存器非空)。
  2. 仲裁阶段: DMA 控制器向总线矩阵发起总线访问请求,等待 CPU 释放总线控制权。
  3. 传输阶段: DMA 从源地址读取数据,写入目标地址,同时更新传输计数器。
  4. 完成阶段: 当传输计数器减至 0 时,DMA 产生传输完成中断(可选)。
DMA 单次传输完整流程 外设请求 总线仲裁 数据搬运 传输完成 关键说明: • 外设请求:USART 数据寄存器非空时自动触发 DMA 请求。 • 总线仲裁:DMA 与 CPU 共享总线,DMA 优先级高于 CPU 时才能获得总线控制权。 • 数据搬运:DMA 内部源地址、目标地址和传输长度寄存器协同工作。 • 传输完成:传输计数器归零时,可触发中断或自动重载(循环模式)。

4.2 传输方向

DMA 传输方向由 DIR 位控制,决定了数据流的源和目标。STM32 支持三种传输方向:

DIR 配置 源地址 目标地址 典型应用场景
外设 → 存储器 外设数据寄存器(如 USART->DR) 内存缓冲区(如 uint8_t rx_buf[256]) 串口接收、ADC 采集
存储器 → 外设 内存缓冲区(如 uint8_t tx_buf[]) 外设数据寄存器(如 USART->DR) 串口发送、DAC 输出
存储器 → 存储器 内存区域 A 内存区域 B 内存拷贝、数据搬移
串口接收场景: 必须配置为 外设 → 存储器 方向。外设地址固定为 USART->DR 寄存器,存储器地址为递增的数组指针。

4.3 循环模式

循环模式(Circular Mode)是 DMA 的一项重要特性。当 DMA 传输计数器减至 0 时,自动将计数器重载为初始值,同时存储器地址指针回到起始位置,形成一个环形缓冲区。

循环模式 vs 普通模式 普通模式(Normal) 传输 N 字节后停止,DMA 通道关闭 需重新配置传输长度才能再次启动 循环模式(Circular) 传输 N 字节后自动重载计数器 存储器地址自动回绕到起始位置 串口接收中的循环模式: • 设置 DMA 缓冲区大小为 256 字节,循环模式开启。 • 数据持续写入缓冲区,CPU 通过空闲中断判断帧尾,从缓冲区读取完整数据帧。
注意: 循环模式下,如果 CPU 读取速度跟不上 DMA 写入速度,新数据会覆盖旧数据。建议缓冲区大小至少为最大数据帧长度的 2 倍,并配合空闲中断及时处理。

4.4 增量模式

增量模式控制 DMA 传输过程中地址指针是否自动递增。STM32 的 DMA 控制器为源地址和目标地址分别提供了独立的增量使能位:

  • PINC(外设增量模式): 外设地址通常固定(如 USART->DR 寄存器),一般关闭。
  • MINC(存储器增量模式): 存储器地址需要递增以存储连续数据,必须开启。
配置项 推荐值 原因
PINC(外设地址增量) Disabled(0) 外设数据寄存器地址固定,无需递增
MINC(存储器地址增量) Enabled(1) 数据需连续存储到数组的不同位置
PSIZE(外设数据宽度) 8-bit 串口数据寄存器为 8 位
MSIZE(存储器数据宽度) 8-bit 与 PSIZE 保持一致,避免数据错位

4.5 DMA 配置代码示例

以下代码展示了 STM32 HAL 库中串口 DMA 接收的初始化配置:

// 定义 DMA 接收缓冲区
#define RX_BUF_SIZE  256
uint8_t rx_buffer[RX_BUF_SIZE];

// DMA 初始化句柄
DMA_HandleTypeDef hdma_usart1_rx;

void MX_DMA_Init(void)
{
    __HAL_RCC_DMA1_CLK_ENABLE();

    hdma_usart1_rx.Instance = DMA1_Channel5;          // USART1_RX 使用 DMA1 通道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;

    HAL_DMA_Init(&hdma_usart1_rx);

    // 将 DMA 句柄与 USART 关联
    __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
}

// 启动 DMA 接收(在 main 函数中调用)
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUF_SIZE);
配置要点:
  • DMA_CIRCULAR 使能循环模式,确保 DMA 持续接收。
  • DMA_PINC_DISABLE 固定外设地址,DMA_MINC_ENABLE 递增内存地址。
  • 数据宽度统一为 BYTE,与串口 8 位数据格式匹配。

4.6 三种模式对比总结

特性 普通模式 循环模式 增量模式
传输完成后行为 停止,通道关闭 自动重载,继续传输 地址递增(存储器)
适用场景 单次固定长度传输 连续数据流接收 所有需要连续存储的场景
CPU 干预频率 每次传输完成需重新配置 无需干预,持续运行 由 DMA 硬件自动完成
串口接收推荐 不推荐(需频繁重启) 强烈推荐 必须开启 MINC