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;当多个通道软件优先级相同时,再由硬件通道号决定,通道号越小优先级越高。仲裁器的作用就是在这两层规则下,选出当前应当获得总线使用权的那一个请求。

关键点:仲裁器只决定“谁先上总线”,并不改变单次传输的数据量。一次仲裁胜出后,DMA 会按配置的传输宽度和数量完成一轮突发或单次传输,然后重新参与下一轮仲裁。

二、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 取指的冲突。

STM32 DMA 控制器架构:请求源 → 通道/流 → 优先级 → 仲裁器 → AHB 总线矩阵 请求源 ADC 转换完成 USART RXNE TIM 更新事件 SPI 收发就绪 通道 / 流 Stream0 / Channel1 Stream1 / Channel2 Stream2 / Channel3 Stream3 / Channel4 优先级 Very High High Medium Low 仲裁器 软件优先级比较 Very High > High > Medium > Low 硬件通道号比较 通道号越小,优先级越高 胜出者获得总线使用权 其余请求保持挂起 AHB 总线矩阵(多主多从交叉开关) CPU 主设备 DMA 主设备 Flash 从设备 SRAM 从设备 外设总线 胜出请求 目标存储区 ADC 缓冲区 / 串口接收缓冲 源存储区 SRAM / Flash 数据区 DMA 与 CPU 共享 AHB 总线矩阵,访问同一存储体时会产生总线竞争

三、方法流程:一次 DMA 传输的完整决策过程

把架构落到操作上,一次 DMA 传输的决策流程可以概括为以下步骤:

  1. 请求产生:外设事件置位对应通道/流的请求标志,例如 ADC 的 EOC 置位。
  2. 请求映射:该请求被路由到预先配置好的通道或流;若该通道已被占用,请求保持挂起。
  3. 优先级排序:仲裁器读取所有挂起请求的软件优先级,选出最高者。
  4. 硬件裁决:若软件优先级相同,比较通道号或流号,编号小者优先。
  5. 总线申请:胜出通道向 AHB 总线矩阵发起主设备访问请求。
  6. 数据传输:按配置的源地址、目标地址、数据宽度和传输数量完成搬运。
  7. 状态更新:传输计数递减,达到设定值后置位传输完成标志并触发中断。

这个流程说明: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 中断,及时切换缓冲指针
调试建议:先用示波器或 GPIO 翻转测量 DMA 传输完成中断的间隔,确认实际吞吐;再逐步增加并发请求,观察哪个通道最先出现延迟,从而定位仲裁瓶颈。
注意:仲裁器只保证“按规则选择”,不保证“实时性”。在硬实时采集场景中,应把最关键的请求放在最高优先级,并预留足够的总线带宽余量,而不是依赖仲裁器“总能及时响应”。

掌握请求源、通道/流、优先级、仲裁器和 AHB 总线矩阵这五个环节,就掌握了 DMA 从“事件发生”到“数据落地”的完整路径。后续配置任何 DMA 通道时,都可以沿着这条链路逐级检查:请求是否产生、映射是否正确、优先级是否合理、仲裁是否公平、总线是否拥塞。这比单纯记忆寄存器位更能解决实际问题。