STM32H7 的 D-Cache 与 DMA 一致性:用 MPU 配置 Non-Cacheable 区域的完整流程
1. 问题根源:D-Cache 与 DMA 的冲突
STM32H7 搭载 Cortex-M7 内核,主频高达 480MHz,为了弥补 CPU 与存储器之间的速度差距,引入了 D-Cache(数据缓存)。当 CPU 访问数据时,会先将数据从 RAM 加载到 Cache,后续访问直接命中 Cache,从而提升性能。
然而,DMA 控制器直接访问物理内存,不经过 Cache。这就导致了两种典型的数据不一致场景:
- DMA 读取:CPU 刚修改了位于 Cache 中的数据,但尚未写回 RAM,DMA 从 RAM 读取到的是旧数据。
- DMA 写入:DMA 将新数据写入 RAM,但 CPU 访问时命中了 Cache 中的旧数据,导致 CPU 读到旧值。
传统的解决方法是手动调用 SCB_CleanDCache_by_Addr() 和 SCB_InvalidateDCache_by_Addr() 来维护一致性,但这增加了代码复杂度,且容易遗漏。
2. 更优方案:MPU 配置 Non-Cacheable 区域
Cortex-M7 的 MPU(内存保护单元)允许我们将特定内存区域配置为 Non-Cacheable(非缓存)。这样,CPU 对该区域的访问将直接读写物理内存,与 DMA 天然保持一致,无需手动维护 Cache。
适用场景:DMA 缓冲区、共享内存、外设寄存器等。
3. 完整配置流程(以 STM32CubeIDE 为例)
3.1 确定内存区域
假设我们使用 D2 域中的 SRAM1(地址 0x30000000,大小 128KB)作为 DMA 缓冲区。我们将其中一部分(例如 0x30000000 ~ 0x3000FFFF,64KB)配置为 Non-Cacheable。
3.2 在 CubeMX 中配置 MPU
- 打开 System Core > MPU。
- 点击 Add 添加一个区域。
- 设置参数:
- Region: 0
- Base Address: 0x30000000
- Size: 64KB
- TEX: Level 1 (0b000)
- C: 0 (Non-cacheable)
- B: 0
- S: 0
- AP: Full access
- XN: Disable (允许执行)
- 勾选 Enable MPU 和 Enable MPU during HardFault and NMI handlers(可选)。
- 生成代码。
3.3 代码实现(手动配置 MPU)
如果未使用 CubeMX,可手动配置:
#include "stm32h7xx.h"
void MPU_Config(void)
{
HAL_MPU_Disable();
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 配置 SRAM1 的 0x30000000 ~ 0x3000FFFF 为 Non-Cacheable
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
在 main() 中调用 MPU_Config(),并确保在使能 D-Cache 之前完成。
3.4 使用 Non-Cacheable 缓冲区
定义 DMA 缓冲区时,将其放在配置的 Non-Cacheable 区域:
// 使用链接脚本或属性将数组定位到 0x30000000
__attribute__((section(".non_cacheable"))) uint8_t dma_buffer[1024];
或在链接脚本中定义段:
MEMORY
{
NON_CACHEABLE (rw) : ORIGIN = 0x30000000, LENGTH = 64K
}
SECTIONS
{
.non_cacheable :
{
*(.non_cacheable)
} > NON_CACHEABLE
}
4. 注意事项
- MPU 配置时机:必须在使能 D-Cache 之前配置 MPU,否则可能引发不可预知行为。
- 区域对齐:MPU 区域基地址和大小必须按 2 的幂次对齐。
- 重叠区域:避免 MPU 区域重叠,否则优先级高的区域生效。
- 性能影响:Non-Cacheable 区域访问速度较慢,仅用于 DMA 缓冲区,不要滥用。
- 多核场景:STM32H7 为单核,若使用双核(如 H745),需确保两个核的 MPU 配置一致。
- 调试:若出现 HardFault,检查 MPU 配置是否与访问权限冲突。
5. 总结
通过 MPU 将 DMA 缓冲区配置为 Non-Cacheable,可以一劳永逸地解决 D-Cache 与 DMA 的一致性问题,避免繁琐的 Cache 维护操作。此方法适用于 STM32H7 全系列,是高性能嵌入式开发的必备技能。