在把 DMA 功能从 STM32F7 移植到 STM32H7 MCU 上时,发现 DMA 无法传输数据,具体表现是无法进入回调函数,通过搜索发现 ST 论坛上也有人遇到此问题(STM32H7,STM32H743 ADC with DMA),参考帖子的解决方法,并对比 STM32F7 和 STM32H7 的用户手册,发现问题出在 STM32H7 内部 SRAM 地址映射上,造成 DMA 无法访问目标地址。
参考资料:RM0410STM32F76xxx and STM32F77xxx advanced Arm®-based 32-bit MCUsV4.0
一、STM32F7 和 STM32H7 的内部 SRAM 地址映射的区别
STM32F7 内部 SRAM 地址映射如图 1 所示。

重点看图中红框圈出来的部分,也就是 0x2000 0000 ~ 0x3FFF FFFF 这一段,包含了 DTCM(128K)、SRAM1(368K)、SRAM2(16K)和保留的部分。其中,0x2000 0000 为 DTCM 的起始地址,长度为 0x20000;0x2002 0000 为 SRAM1 的起始地址,长度为 0x5C000;0x2007 C000 为 SRAM2 的起始地址,长度为 0x4000;所以总共可用的内部 SRAM 长度为 0x80000。
所以,MDK 默认程序运行起始地址为 0x2000 0000,长度为 0x20000,如图 2 所示。

这刚好对应的是 DTCM 的起始地址和长度,这部分的数据总线为 64 位,运行起来较快。
对于 STM32F7 而言,DTCM 除了被 M7 内核直接访问之外,还可以被 GP-DMAs and peripherals DMAs 访问。
The DTCM and ITCM RAMs (tightly coupled memories) are not part of the bus matrix.
The Data TCM RAM is accessible by the GP-DMAs and peripherals DMAs through specific
AHB slave bus of the CPU.
The instruction TCM RAM is reserved only for CPU. it is accessed at CPU clock speed with
0 wait states. The architecture is shown in Figure 1.
RM0410.Rev4,page72
This bus connects the AHB Slave bus of the Cortex®-M7 to the BusMatrix. This bus is used
by DMAs and Peripherals DMAs for Data transfer on DTCM RAM only.
The ITCM bus is not accessible on AHBS. So the DMA data transfer to/from ITCM RAM is
not supported. For DMA transfer to/from Flash on ITCM interface, all the transfers are
forced through AHB bus.
RM0410.Rev4,page73
再来看一下 STM32F7 的总线架构(上部分为主设备,右侧的部分为从设备),如图 3 所示。

浅橙色框部分就是 GP-DMAs and peripherals DMAs,访问路径见红色的箭头线条(DMA2 访问路径示例)。
GP-DMAs 容易理解,就是 DMA1 和 DMA2,而 peripherals DMAs 则是带有专用 DMA 的外设控制器,如 ETH、USB OTG HS 和 LCD-TFT 等等。所以,浅橙色框的部分都可以访问到 DTCM。GP-DMAs and peripherals DMAs 能访问 DTCM 的意思就是其可以在 DTCM 内开辟内存,并具有读写权限。
因此在使用 STM32F7 做 ETH 或者 USB OTG HS 开发的时候,使用 MDK 默认配置的 SRAM 起始地址就可以成功。但是将 STM32F7 的 DMA 代码移植到 STM32H7 之后,就不工作了,这是因为 STM32H7 的总线架构和内部 SRAM 更加复杂。参见 STM32H7 的总线架构和内部 SRM 的文章:
根据文章里的描述,DTCM 起始地址仍是 0x2000 0000,长度为 0x20000,但是 DTCM 只能被 M7 内核访问,其他主设备,如 GP-DMAs and peripherals DMAs 均不具备访问权限,所以如果仍旧使用 MDK 默认配置的 SRAM 起始地址,DMA 肯定无法传输数据。也就是说通用 SRAM 的地址变了,但是编译器还是把 DTCM 的地址作为通用 RAM 的起始地址,所以造成 DMA 无法传输数据。
二、解决办法
下面给出 3 种解决办法,这 3 种解决办法不仅限于解决 DMA 的数据传输问题,同时也可以作为 STM32H7 系列 MCU 的内存管理方法。
2.1 修改项目配置
选择支持 DMA 访问的 AXI SRAM 区域作为通用 RAM 起始地址,如图 4 所示。

使用 DMA 传输时把内存起始地址修改为 D1 域。也就是把勾打在 RAM2 上。这是最简单的解决方法。
2.2 修改通用 RAM 地址
使用 __attribute__((section( )))指定 DMA 访问的目标地址。
在定义数组时,通过 __attribute__((section(“.ARM.__at_address”)))指令把数组地址分配到 AXI SRAM 区域内即可(此时不需要修改项目配置)。不过,这种方法指定的地址只能在 bss 段里,bss 段所在的内存区域在 ld 文件里看,看最后指向哪个 RAM 区域。
下面以 SAI 外设为例:
// 指定数组地址 uint8_t SAI_Buffer_A[2 * 4] __attribute__((section(".ARM.__at_0x24000000"))); ... // 开始 DMA 传输 HAL_SAI_Receive_DMA(&hsai_BlockA1, SAI_Buffer_A, 2) ... // SAI 接收完成回调 void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { ... }
2.3 分散加载多块内存
这种方法对于 STM32H7 系列 MCU 的开发尤其适用。一般情况下,开发环境都会生成一个内存加载文件,不同开发环境生成的内存加载文件的扩展名和位置不一样,例如 STM32CubeIDE 生成的内存加载文件扩展名为.ld,Keil 生成的内存加载文件扩展名为.sct。以.ld 扩展名为例简单介绍一下。
在 ld 文件中添加:
.lwip_sec (NOLOAD) : { . = ABSOLUTE(0x30040000); *(.RxDecripSection) . = ABSOLUTE(0x30040060); *(.TxDecripSection) . = ABSOLUTE(0x30040200); *(.RxArraySection) } >RAM3_D2 AT> FLASH
使用该内存:
__attribute__((section(".RxDecripSection"))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */ __attribute__((section(".TxDecripSection"))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */ __attribute__((section(".RxArraySection"))) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUFFER_SIZE]; /* Ethernet Receive Buffer */
通过以上三种方案,就可以解决 STM32H7 系列 MCU 内存管理问题。
扫码关注尚为网微信公众号

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/932.html