4. DMA 基础:直接存储器访问原理与核心配置
DMA(Direct Memory Access,直接存储器访问)是嵌入式系统中一项关键的硬件加速技术。它允许外设与存储器之间直接传输数据,而无需 CPU 的逐字节干预。在串口不定长接收场景中,DMA 能够将接收到的数据自动搬运到内存缓冲区,CPU 仅在空闲中断触发时才处理数据,从而大幅降低系统负载。
核心价值: 使用 DMA 后,串口接收的 CPU 占用率可从 100%(轮询模式)降至接近 0%,仅在数据帧结束时产生一次中断。
4.1 DMA 传输原理
DMA 控制器本质上是一个独立的总线主设备,它拥有自己的总线仲裁逻辑和地址生成单元。一次典型的 DMA 传输包含以下步骤:
- 请求阶段: 外设(如 USART)产生 DMA 请求信号(例如 RXNE 寄存器非空)。
- 仲裁阶段: DMA 控制器向总线矩阵发起总线访问请求,等待 CPU 释放总线控制权。
- 传输阶段: DMA 从源地址读取数据,写入目标地址,同时更新传输计数器。
- 完成阶段: 当传输计数器减至 0 时,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 时,自动将计数器重载为初始值,同时存储器地址指针回到起始位置,形成一个环形缓冲区。
注意: 循环模式下,如果 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 |