STM32H7中的DMA
1. 概述
STM32H7相比其他系列性能有着不小的提升,同时结构也有了较大的变化。这些变化如果不加注意则会影响DMA等功能的使用。本文通过对STM32H7的Bus Matrix及Cache/MPU两方面尝试分析不同点及对应的解决方案。
2.硬件平台
本文中使用的硬件是STM32H750开发板,属于STM32H7 Value Line产品线,代码位于QSPI Flash中,通过XIP方式执行。
3. STM32H7的总线结构和访问规则
STM32H7拥有三个不同的Bus Matrix,分别为D1,D2与D3。
- D1区域为AXI Interconnect Matrix,与CPU通过AXI总线相连,高速外设如Flash,QSPI,FMC等通过此AXI Bus连接到CPU,速度最快。
- D2区域为AHB Multi-layer Bus Matrix,连接了大部分低速外设及另一部分SRAM,通过CPU的AHBP接口连接到CPU,速度其次。
- D3区域为另一个AHB Bus Matrix,与前两个区域不同的是,它不与CPU直接连接,而是通过D2区域的一个AHB Master连接到D3区域。D3区域速度最慢,同时也连接了一部分低功耗外设,速度最慢。
三个区域的时钟及门控可独立控制,以实现低功耗与高性能间的平衡。
在H7的架构中,部分高速外设通过AXI总线连接到Master,相比只有Flash存储器连接到AXI接口的F7系列,外设访问速度也有了提升。
从上图我们可以发现以下访问规律:
- D1区域的Bus Master可以通过D1-to-D2 AHB bus访问D2域的地址范围,也可以通过D2-to-D3 AHB bus访问D3的地址范围。
- D2区域的Bus Master可以通过D2-to-D3 AHB bus访问D3域的地址范围,也可以通过D2-to-D1 AHB bus反向访问D1域的地址范围。
- D3区域没有连接到其他区域的Bus Master,因此只能访问D3区域的地址范围。
总结一下就是:
D1与D2中的Bus Master可以互访对方Slave, D3中的Master不能访问D1与D2 Slave。
4. STM32H7中的DMA与存储器
STM32H7中有三种不同的DMA,分别为MDMA,DMA(DMA1/DMA2)以及BDMA,其特点如下所示:
- MDMA可以通过AXI Interconnect的接口访问D1域,同时能通过专用的AHBS接口访问CPU中的I/DTCM存储器,速度最高。
- DMA1/2位于D2域中,通过AHB接口访问D2域。由于D2域存在着连接到D1域的通路,因此DMA1/2依然可以访问D1域中的存储器及外设。
- BDMA则位于D3域中,仅可以访问D3SRAM及对应的D3域外设,同时功能上也相对有限。
相似的,STM32H7中的存储器也分为了以下几种:
- AXI SRAM(D1_RAM),QUADSPI,FMC,Flash:这些存储器连接到AXI Interconnect,可被能访问D1域的Bus Master访问。
- AHB SRAM1/2 :这些存储器连接到D2域 AHB Bus Matrix,可被能访问D2域的Bus Master访问。
- AHB SRAM3:这些存储器连接到D3域 AHB Bus Matrix,可被能访问D3域的Bus Master访问。
- 特殊的,位于CPU内部的I/DTCM(指令/数据紧耦合存储器)在可被CPU访问的同时也可通过专用接口被MDMA访问。
由此,我们可以得出以下几点规则:
MDMA能访问包含TCM在内的所有存储器(几乎和CPU范围类似),DMA1/2不能访问TCM,BDMA仅可访问AHB SRAM3以及D3域外设。
特别需要注意的是,这些规则不仅应用于DMA控制器,同时也适用于其他Bus Master,如DMA2D,LTDC,Ethernet,SDMMC,USB等等拥有内部DMA的外设,使用时同样要保证用户buffer位于可访问区域内。
5. 关于L1 Cache及MPU
Cortex-M7相比于其他核心,增加了单独的L1 I/D Cache,为了保证DMA访问的内存不出现一致性问题,需要通过配置MPU的方式控制指定区域的缓存策略。
Cortex-M7的MPU通过Region的形式定义访问规则,这里通过STM32CubeMX的配置工具提供简单的缓存配置示例。
MPU的配置最好按照粒度从粗到细的顺序配置,示例中的默认配置是禁用缓存,允许全部访问,在QSPI Flash区域启用读缓存,并允许指令访问及共享。
6. 代码实现
在默认的Linker Script中,数据代码被放置在了DTCM中,如果想要通过DMA访问这部分数据,我们就只能通过MDMA访问,或者修改LDS使得数据放置在AXI SRAM或者AHB SRAM中。以下的修改可以将用户的buffer放置在D2域SRAM中:
_SI_D2SRAM = LOADADDR(.d2_sram);
.d2_sram :
{
. = ALIGN(4);
_DMA_Buffer_Start = .;
*(.d2_sram_buffer)
. = ALIGN(4);
_DMA_Buffer_End = .;
} >RAM_D2 AT> FLASH /* Note: Not actually init, we need to copy manually before use */
然后我们即可初始化这部分数据:
uint32_t bytes_to_copy = &_DMA_Buffer_End - &_DMA_Buffer_Start;
if(bytes_to_copy != 0) {
memcpy((void *)&_DMA_Buffer_Start, (void *)&_SI_D2SRAM, bytes_to_copy);
}
之后通过DMA1或DMA2即可正确实现DMA传输。