2. DMA 基础回顾:工作原理、传输模式与请求仲裁
在深入串口 DMA 乒乓操作之前,必须牢固掌握 DMA 的核心机制。DMA(Direct Memory Access,直接存储器访问)允许外设或存储器之间直接交换数据,无需 CPU 逐字节干预。本章将系统梳理 DMA 的工作原理、三种传输模式以及请求与仲裁逻辑,为后续双缓冲设计打下硬件基础。
2.1 DMA 工作原理:从 CPU 卸载到专用通道
传统无 DMA 的串口收发中,CPU 需在每个字节到达或发送完成后进入中断服务函数,将数据从 DR 寄存器搬运到内存缓冲区。这种方式在高速通信(如 1 Mbps 以上)时会严重占用 CPU 时间,导致系统无法处理其他实时任务。
DMA 控制器本质上是一个独立的总线主设备,它拥有自己的地址寄存器、计数器和控制逻辑。一次典型的 DMA 传输包含以下四个阶段:
- 请求阶段:外设(如 USART)产生 DMA 请求信号(TXE / RXNE),通知 DMA 控制器需要传输一个数据单元。
- 仲裁阶段:当多个 DMA 通道同时请求时,仲裁器根据优先级规则选择当前最高优先级的通道。
- 传输阶段:DMA 控制器从源地址读取数据,写入目标地址,同时更新传输计数器和地址指针。
- 完成阶段:当传输计数器减至 0 时,DMA 通道产生传输完成中断(TCIF),通知 CPU 数据块已就绪。
关键概念:DMA 传输的最小单位是“数据单元”(字节、半字或字),由外设数据宽度决定。串口通常使用字节(8 位)宽度。
2.2 DMA 传输模式:单次、循环与内存到内存
STM32 DMA 控制器支持三种核心传输模式,理解它们的差异是正确配置乒乓操作的前提。
| 传输模式 | 工作方式 | 传输完成后动作 | 典型应用场景 |
|---|---|---|---|
| 单次模式 (Normal) | 传输指定数量的数据单元后停止 | 产生 TCIF 中断,通道关闭 | 单帧接收、一次性数据搬移 |
| 循环模式 (Circular) | 传输完成后自动重载初始地址和计数,循环执行 | 每次循环结束产生 TCIF 中断 | 连续数据流、乒乓缓冲区、音频播放 |
| 内存到内存 (Memory-to-Memory) | 由软件触发,无需外设请求 | 传输完成后产生 TCIF 中断 | 内存拷贝、数据整理、协议栈缓冲区移动 |
乒乓操作核心依赖:循环模式是双缓冲机制的基础。通过循环模式,DMA 可以在两个缓冲区之间自动切换,无需 CPU 在每次传输完成后重新配置地址和计数。
2.3 DMA 请求与仲裁:多通道协同的规则
STM32 系列(如 F4/H7)通常拥有两个 DMA 控制器(DMA1 和 DMA2),每个控制器包含 8 个独立通道。每个通道可以响应多个外设的请求,但同一时刻只能服务于一个外设。
2.3.1 DMA 请求映射
每个外设的 DMA 请求被硬连线到特定的 DMA 通道。以 STM32F4 为例,USART1 的接收请求映射到 DMA2 通道 2,发送请求映射到 DMA2 通道 7。这种映射关系在参考手册的 DMA 请求映射表中定义,配置时必须严格对应。
| 外设 | 请求方向 | DMA 控制器 | 通道 | 数据流 |
|---|---|---|---|---|
| USART1_RX | 外设 → 内存 | DMA2 | Channel 2 | Stream 2 |
| USART1_TX | 内存 → 外设 | DMA2 | Channel 7 | Stream 7 |
| USART2_RX | 外设 → 内存 | DMA1 | Channel 5 | Stream 5 |
| USART2_TX | 内存 → 外设 | DMA1 | Channel 6 | Stream 6 |
常见错误:初学者容易将 USART1_RX 配置到 DMA1 的某个通道,导致 DMA 请求永远无法触发。务必查阅芯片参考手册的 DMA 请求映射表,确认外设与通道的对应关系。
2.3.2 仲裁机制
当多个 DMA 通道同时请求总线访问时,仲裁器根据以下规则决定优先级:
- 软件优先级:每个通道可配置为低、中、高、最高四个等级。
- 硬件优先级:当软件优先级相同时,通道编号小的优先级更高(如 Channel 1 > Channel 2)。
- 轮询策略:在循环模式下,DMA 控制器采用轮询方式确保所有通道都能获得总线访问机会,避免低优先级通道被饿死。
2.4 DMA 配置关键参数速查
在 HAL 库中配置 DMA 时,以下参数直接影响传输行为:
// HAL 库 DMA 初始化结构体关键成员
DMA_HandleTypeDef hdma_usart1_rx;
hdma_usart1_rx.Instance = DMA2_Stream2; // 数据流选择
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; // 通道选择
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; // 外设数据宽度
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 内存数据宽度
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // 循环模式(乒乓操作关键)
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH; // 优先级
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 直通模式
hdma_usart1_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_usart1_rx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_usart1_rx.Init.PeriphBurst = DMA_PBURST_SINGLE;
乒乓操作配置要点:Mode 必须设置为
DMA_CIRCULAR,这样 DMA 在传输完一个缓冲区后会自动回到起始地址继续传输,配合双缓冲切换逻辑实现无缝数据流。