引言
在 STM32F4 系列(如 STM32F429/439)中,SDRAM 常被用作外部显存,配合 LTDC 控制器驱动 LCD。由于 SDRAM 带宽有限,CPU 写入帧缓冲后,LTDC 通过 DMA 读取数据。此时,若 CPU 启用了 D-Cache,而 MPU 未正确配置 SDRAM 区域的 Cache 属性,就会导致数据不一致:CPU 写入的数据可能仍停留在 Cache 中,而 DMA 读取的是 SDRAM 中的旧数据,造成画面撕裂或花屏。
MPU 与 Cache 一致性原理
Cache 的工作机制
Cortex-M4 内核的 D-Cache 以 32 字节行为单位缓存内存数据。CPU 写入时,若采用 Write-back 策略,数据先写入 Cache,标记为 dirty,仅在缓存行被替换或显式 clean 时才写回 SDRAM。而 DMA 外设(如 LTDC)直接访问 SDRAM,不经过 Cache,因此可能读到过时数据。
MPU 的配置角色
MPU(Memory Protection Unit)允许将内存区域划分为不同属性,包括 Cache 策略(Write-back / Write-through)、是否可缓存等。对于 SDRAM 区域,常见两种配置:
-
Write-back (WBWA):允许缓存,写回延迟低,但需要软件维护一致性(如使用
SCB_CleanDCache或SCB_CleanDCache_by_Addr)。 - Write-through (WT):每次写操作直接穿透到 SDRAM,保证 DMA 立即可见,但写性能下降。
实测对比:不同 MPU 配置下的性能与正确性
测试环境
- MCU:STM32F429 @ 180MHz
- SDRAM:IS42S16400J,16MB,16-bit 总线
- 显存:SDRAM 地址 0xD0000000,分辨率 480x272,RGB565(约 260KB)
- 测试方法:CPU 循环填充帧缓冲,同时 LTDC 持续刷新,观察画面是否花屏,并测量填充耗时。
测试结果
| MPU 配置 | 填充耗时(ms) | 画面状态 | |---------|-------------|---------| | 无 MPU(默认) | 12.3 | 花屏严重,数据错乱 | | Write-back + 手动 Clean | 8.9 | 正常(需在每次填充后调用 clean) | | Write-through | 10.5 | 正常(无需额外操作) |
分析:
- 无 MPU 时,SDRAM 默认被配置为 Write-back,但未维护一致性,导致 DMA 读取旧数据。
- Write-back 性能最优,但需在 CPU 写入完成后,调用
SCB_CleanDCache()将 dirty 行写回 SDRAM。 - Write-through 性能略低,但无需软件干预,适合简单场景。
推荐配置与代码实现
步骤 1:使能 MPU 和 Cache
在系统初始化时,使能 D-Cache 和 I-Cache,并配置 MPU。
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
// 禁用 MPU 进行配置
HAL_MPU_Disable();
// 配置 SDRAM 区域(0xD0000000,大小 16MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xD0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_16MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; // 使用 TEX=0,C=1,B=1 表示 Write-back
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
步骤 2:初始化 Cache 和 SDRAM
void SystemInit(void)
{
// 使能 D-Cache 和 I-Cache(注意:需在 MPU 配置之后)
SCB_EnableDCache();
SCB_EnableICache();
// 配置 MPU(在 Cache 使能前)
MPU_Config();
// 初始化 SDRAM(略)
SDRAM_Init();
}
步骤 3:写入帧缓冲并维护一致性
若使用 Write-back 模式,在每次 CPU 写入后,必须 clean 相关 Cache 行。
void FillFrameBuffer(uint16_t *fb, uint32_t size)
{
// 填充数据
for (uint32_t i = 0; i < size; i++) {
fb[i] = 0xFFFF; // 白色
}
// 将 dirty 行写回 SDRAM,确保 LTDC 可见
SCB_CleanDCache_by_Addr((uint32_t *)fb, (int32_t)size * 2);
}
若使用 Write-through 模式,则无需手动 clean,但性能略低。
注意事项
- MPU 配置顺序:必须先配置 MPU,再使能 D-Cache,否则 Cache 可能使用默认属性。
-
Clean 粒度:
SCB_CleanDCache_by_Addr的地址需 32 字节对齐,大小需为 32 的倍数,否则可能遗漏部分行。 - 性能权衡:Write-back 性能高,但需谨慎管理;Write-through 简单,适合帧缓冲更新不频繁的场景。
- 其他外设:若 SDRAM 还被其他 DMA(如 DMA2D)访问,同样需要一致性处理。
- 调试技巧:若出现花屏,可先尝试将 MPU 区域改为 Write-through,若问题消失,则确认是 Cache 一致性问题。
总结
通过合理配置 MPU,可以显著提升 SDRAM 作为显存时的性能,同时保证数据一致性。实测表明,Write-back 模式配合手动 Clean 可获得最佳性能,但需注意维护时机;Write-through 模式则更安全但稍慢。开发者应根据应用场景选择合适策略,并遵循正确的初始化顺序。