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)