3. DMA控制器架构
理解 STM32 的 DMA,不能只停留在“配置一个外设、搬一段数据”的层面。真正决定 DMA 性能、冲突行为和调试难度的,是它背后的控制器架构:谁发出请求、请求进入哪条通道或流、多个请求同时到来时谁先被服务、仲裁结果如何转化为 AHB 总线上的实际传输。把这五个环节串起来,才能解释“为什么两个 DMA 同时工作时采集会丢数据”这类典型问题。
一、核心原理:从请求源到总线矩阵的五级链路
STM32 的 DMA 控制器本质上是一个“请求驱动的搬运引擎”。它的工作链路可以拆成五级:请求源 → 通道/流 → 优先级 → 仲裁器 → AHB 总线矩阵。请求源是外设或存储器发出的“我需要传输”的信号,例如 ADC 转换完成、USART 接收寄存器非空、定时器更新事件、SPI 收发就绪等。这些信号并不会直接占用总线,而是先被映射到某个固定的通道或流上。
在 F1/F4 等经典型号中,DMA1/DMA2 采用“通道”结构,每个通道绑定若干固定外设请求,通道号与外设的对应关系由芯片手册的请求映射表决定,不能随意更改。在 F7/H7/G4 等较新型号中,DMA 采用“流(Stream)”结构,每个流有 8 个可选请求(通过 CSELR 或类似寄存器选择),灵活性更高,但同一时刻一个流只能服务一个请求。无论通道还是流,它们都是“请求进入控制器的入口”。
请求进入后,控制器会依据每个通道/流的优先级设置进行排序。优先级通常分为“软件优先级”和“硬件优先级”两层:软件优先级由用户配置为 Very High / High / Medium / Low;当多个通道软件优先级相同时,再由硬件通道号决定,通道号越小优先级越高。仲裁器的作用就是在这两层规则下,选出当前应当获得总线使用权的那一个请求。
二、AHB 总线矩阵:DMA 与 CPU 的共享通道
仲裁胜出的请求最终要通过 AHB 总线矩阵访问存储器或外设。AHB 总线矩阵是连接 CPU、DMA、Flash、SRAM、外设总线的一条或多条交叉开关。DMA 作为 AHB 上的主设备(Master),与 CPU 竞争同一片存储资源。当 DMA 访问 SRAM 时,CPU 若同时访问同一块 SRAM,就会产生总线等待,表现为 CPU 执行变慢或 DMA 吞吐下降。
因此,高性能采集系统的设计重点之一,就是让 DMA 的数据流尽量避开 CPU 的高频访问区域,或者利用多主控、多总线(如 AHB1/AHB2、D2 域 SRAM)来分流。例如在 H7 上,把 ADC 采集缓冲放在 D2 域 SRAM,DMA 走 D2 到 D1 的路径,可以显著降低与 CPU 取指的冲突。
三、方法流程:一次 DMA 传输的完整决策过程
把架构落到操作上,一次 DMA 传输的决策流程可以概括为以下步骤:
- 请求产生:外设事件置位对应通道/流的请求标志,例如 ADC 的 EOC 置位。
- 请求映射:该请求被路由到预先配置好的通道或流;若该通道已被占用,请求保持挂起。
- 优先级排序:仲裁器读取所有挂起请求的软件优先级,选出最高者。
- 硬件裁决:若软件优先级相同,比较通道号或流号,编号小者优先。
- 总线申请:胜出通道向 AHB 总线矩阵发起主设备访问请求。
- 数据传输:按配置的源地址、目标地址、数据宽度和传输数量完成搬运。
- 状态更新:传输计数递减,达到设定值后置位传输完成标志并触发中断。
这个流程说明:DMA 的“快”并不是无条件的。如果多个高优先级请求持续到来,低优先级通道可能长时间得不到服务;如果 DMA 与 CPU 频繁争抢同一块 SRAM,实际带宽会被总线仲裁切分。
四、案例场景:ADC 连续采集与串口回传的冲突
假设一个采集系统同时使用 ADC1 和 USART1:ADC1 以 1 MHz 采样率连续转换,通过 DMA1 Stream0 搬运到缓冲区;USART1 以 115200 波特率接收上位机命令,通过 DMA1 Stream5 搬运到命令缓冲。若两者都配置为 Medium 优先级,仲裁器将按通道号裁决,Stream0 优先于 Stream5。此时如果 ADC 请求持续不断,串口接收请求可能被延迟,导致 USART 接收寄存器溢出(ORE)。
// 典型配置片段(以 HAL 为例)
hdma_adc.Instance = DMA1_Stream0;
hdma_adc.Init.Channel = DMA_CHANNEL_0;
hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; // ADC 采集设为高
hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式持续采集
hdma_usart.Instance = DMA1_Stream5;
hdma_usart.Init.Channel = DMA_CHANNEL_4;
hdma_usart.Init.Priority = DMA_PRIORITY_MEDIUM; // 串口命令设为中
hdma_usart.Init.Mode = DMA_NORMAL;
复盘结论是:不能只看“谁更重要”,还要看“谁的请求更密集”。对于持续高速的 ADC 流,应使用循环模式并配合足够大的双缓冲;对于偶发但要求及时响应的串口命令,应适当提高优先级,或改用中断方式处理短命令,避免与高速流争抢仲裁资源。
五、常见误区与排查清单
| 误区 | 现象 | 排查与修正 |
|---|---|---|
| 认为优先级可以随意分配 | 低优先级通道长期不响应 | 检查请求密度,必要时改用中断或提高优先级 |
| 忽略通道/流与外设的固定映射 | DMA 完全不触发 | 对照参考手册请求映射表,确认 CSELR 或通道号正确 |
| 多个 DMA 访问同一 SRAM 区 | CPU 变慢、采集吞吐下降 | 分散缓冲区到不同 SRAM 域,或使用双缓冲 |
| 循环模式与普通模式混用不当 | 传输一次后停止 | 连续采集用 CIRCULAR,单次命令用 NORMAL |
| 未处理传输完成与半完成中断 | 缓冲区覆盖或数据错位 | 启用 TC/HT 中断,及时切换缓冲指针 |
掌握请求源、通道/流、优先级、仲裁器和 AHB 总线矩阵这五个环节,就掌握了 DMA 从“事件发生”到“数据落地”的完整路径。后续配置任何 DMA 通道时,都可以沿着这条链路逐级检查:请求是否产生、映射是否正确、优先级是否合理、仲裁是否公平、总线是否拥塞。这比单纯记忆寄存器位更能解决实际问题。