STM32H7 的 DMA 能访问 DTCM 吗?

Direct Mode 下不能:DMA2 Stream5 的 M0AR 指向 DTCM 0x200000E0 时 DMEIF5 置位、EN 被硬件清零;改成 AXI SRAM 0x24000000 后立即恢复正常。另附 Stream 偏移与 M0AR 写保护两个连带坑。

Direct Mode 下不能。把 DMA2 Stream5 的 M0AR 指向 DTCM 地址 0x200000E0 时,DMA_HISR 的 DMEIF5 会置位,EN 位被硬件自动清零;把同一路 DMA 的源地址改成 AXI SRAM 的 0x24000000 后,传输立即正常。原因不在配置,而在内存域:DTCM 挂在 Cortex-M7 私有总线上,而 DMA 是 AHB 主设备。

现象

条件:STM32H723ZG,用 DMA2 Stream5 把内存里的一个 4 字节影子值搬到 GPIOE_ODR,想做成「定时器触发、CPU 不参与」的输出。

第一次配置后出现两个互不相干的异常,很容易误判成同一件事:

异常 实测值 直觉误判
写 S5M0AR = 0x200000E0,读回是 0x000000A0 0xA0 是 FCR 的复位值 「寄存器被写保护」
传输使能后 EN 在 100 μs 内自己变 0 HISR 的 DMEIF5 置位 「DMA 时钟没开」

两个现象叠加,看起来像「DMA 完全不能用」,实际是三个独立问题:地址算错、写保护、内存域不可达。

原因

1. DTCM 不在 AHB/AXI 总线矩阵上。 Cortex-M7 的 DTCM 通过私有总线直连内核,零等待是它的优势来源;而 DMA 控制器是 AHB 主设备,只能访问总线矩阵上的内存与外设。Direct Mode 下访问 DTCM 属于非法目标,硬件报 Direct Mode Error 并关闭通道。

2. Stream 偏移算错了 0x10。 STM32H7 的 DMA 寄存器布局与 F4 不同:头部 0x00–0x0C 是 LISR/HISR/LIFCR/HIFCR 四个中断状态寄存器,Stream0 从 0x10 开始,每个 Stream 间隔 0x18:

Stream N 基地址 = DMAx_BASE + 0x10 + N × 0x18
S5: CR=+0x88  NDTR=+0x8C  PAR=+0x90  M0AR=+0x94  M1AR=+0x98  FCR=+0x9C

若按「Stream0 从 0 开始」去算,S5M0AR 会被写到 +0x84,那实际是 S4 的 FCR。写入不报错,读回一个复位默认值——这就是 0xA0 的来源。

3. EN=1 时 M0AR 写不进去。 H7 的 DMA 在通道使能状态下对 M0AR 有写保护,必须先关通道,并轮询等 EN 真正变 0,才能改地址。

怎么做

第一步:把缓冲区放到 DMA 能访问的内存。 各内存域对 CPU 与 DMA 的可达性不同:

内存域 起始地址 CPU DMA1/DMA2 用途建议
ITCM 0x00000000 零等待取指 不可访问 ISR 代码
DTCM 0x20000000 零等待读写 Direct Mode 不可访问 CPU 独享数据(路由表、WIRE)
AXI SRAM 0x24000000 经总线矩阵 可访问 需要 DMA 搬运的缓冲区

代价是 AXI SRAM 比 DTCM 多 1–2 个总线周期,在 100 μs 周期下约 4 ns,可忽略。

第二步:按正确顺序配置通道。

/* STM32H723ZG · DMA2 Stream5:AXI SRAM 影子值 → GPIOE_ODR
 * 偏移取自 RM0468:CR=0x88 NDTR=0x8C PAR=0x90 M0AR=0x94 FCR=0x9C
 */
#define DMA2_BASE     0x40020400UL
#define S5CR    (*(volatile uint32_t *)(DMA2_BASE + 0x88))
#define S5NDTR  (*(volatile uint32_t *)(DMA2_BASE + 0x8C))
#define S5PAR   (*(volatile uint32_t *)(DMA2_BASE + 0x90))
#define S5M0AR  (*(volatile uint32_t *)(DMA2_BASE + 0x94))
#define S5FCR   (*(volatile uint32_t *)(DMA2_BASE + 0x9C))

/* 1) 关通道 + 轮询等 EN=0(这一步不能省) */
S5CR = 0;
while (S5CR & 1U) { }

/* 2) 地址:源在 AXI SRAM,不要填 DTCM 地址 */
S5M0AR = (uint32_t)&shadow_gpio;                 /* 0x2400xxxx,4 字节对齐 */
S5PAR  = (uint32_t)0x58021014UL;                 /* GPIOE_ODR */
S5NDTR = 1;

/* 3) DMDIS=0:直通模式;置 1 等于启用 FIFO,需再配阈值 */
S5FCR  = 0;

/* 4) 32 位字宽、存储器→外设、MINC、TCIE,最后使能 */
S5CR = (2U << 11) | (2U << 13) | (1U << 10) | (1U << 6) | (1U << 4) | (1U << 0);

第三步:按需要选替代方案。 如果数据必须留在 DTCM(例如 CPU 侧要零等待读),有三种做法:

方案 做法 代价
镜像 缓冲区只放 AXI SRAM,ISR 直接写那里 每次写多 1–2 周期(约 4 ns)
双写 ISR 同时写 DTCM 与 AXI SRAM,DMA 读 AXI 多一次写操作
走 AHBS 依赖 M7 的 AHBS 从端口让 DMA 到达 DTCM 需额外配置,行为不保证,未做实测确认

验证

三个寄存器都要读回确认,缺一不可:

检查项 期望值 不通过的含义
S5CR bit0 (EN) 传输后仍为 1 被 DMEIF 或 FIFO error 清零
S5M0AR 等于写入的地址 偏移算错或写保护未解除
HISR 的 DMEIF5 / FEIF5 均为 0 目标不可达 / FIFO 配置错

改前改后的对照(同一块板、同一段代码,只改源地址):

源地址 EN 状态 结果
改前:0x200000E0(DTCM) 100 μs 内变 0 DMEIF5 置位,GPIO 不更新
改后:0x24000000(AXI SRAM) 保持 1 传输完成,GPIO 正常更新

边界

  • 本结论只在 DMA Direct Mode 下实测确认。H7 的 DTCM 存在经 AHBS 从端口被访问的可能性,但需要额外配置且行为未保证,本方案未采用。
  • 偏移量结论仅适用 STM32H7 系列 DMA(0x10 + N×0x18)。F4 系列布局不同,H743 与 H723 的部分外设寄存器偏移也不同,不能跨型号照搬。
  • AXI SRAM 方案会引入 1–2 个总线周期延迟,若控制周期压到 1 μs 以下需重新评估。
  • 双写方案在「ISR 写完 DTCM、尚未写 AXI SRAM」的瞬间存在 1 个周期的不一致窗口,对同步性要求极高的场景应改为单缓冲区 + 显式换页。

最后验证于 2026-10-28 · STM32H723ZG(RM0468)· pyOCD(-t stm32h723xx)

STM32H7 的 DMA 能访问 DTCM 吗?