# 引言 在嵌入式图形系统中,SDRAM 常被用作帧缓冲(显存),以支持高分辨率 LCD。STM32F4 系列(如 STM32F429/439)内置 FMC 控制器可外接 SDRAM,但若未正确配置 MPU,系统性能可能骤降 50% 以上。本文通过一个真实案例,剖析 MPU 配置错误如何导致显存访问效率低下,并提供可复用的解决方案。 # MPU 与 SDRAM 访问原理 ## 1. Cortex-M4 MPU 基础 MPU 是 Cortex-M4 内核的可选组件,用于定义内存区域的访问权限和属性。每个区域由基地址、大小和属性寄存器控制。关键属性包括: - **TEX** (Type Extension): 与 C/B 位共同决定缓存策略 - **C** (Cacheable): 是否允许缓存 - **B** (Bufferable): 是否允许写缓冲 对于 SDRAM 区域,常见配置有: | 场景 | TEX | C | B | 效果 | |------|-----|---|---|------| | 普通数据 | 0 | 1 | 1 | 写回缓存,性能高 | | 显存(DMA 访问) | 0 | 0 | 1 | 写缓冲,无缓存,避免不一致 | | 错误配置 | 0 | 1 | 0 | 写直达,但可能引发总线阻塞 | ## 2. 为什么显存需要特殊配置 LCD 控制器(LTDC)通过 DMA 直接读取 SDRAM 中的像素数据。若 MPU 将 SDRAM 配置为 Cacheable,CPU 写入显存的数据可能滞留在 Cache 中,而 LTDC DMA 直接访问物理 SDRAM,导致显示内容陈旧(撕裂)。反之,若配置为 Non-cacheable 但 Bufferable,CPU 写操作会进入写缓冲,虽无缓存一致性问题,但若缓冲策略不当,可能造成总线占用过高。 # 问题现象与排查 ## 1. 症状描述 某项目使用 STM32F429 + 外部 SDRAM(1MB)作为 480x272 RGB 屏显存。初始代码未配置 MPU,系统运行正常。后来为了提升 CPU 访问速度,添加了 MPU 配置,将 SDRAM 区域设为 Cacheable(TEX=0, C=1, B=1),结果帧率从 60fps 骤降至 20fps,且画面出现随机条纹。 ## 2. 根因分析 - **缓存一致性问题**:CPU 写入显存后,LTDC DMA 读取旧数据,导致显示错误。 - **性能骤降**:为解决撕裂,开发者尝试在每次刷新后执行 `SCB_CleanDCache()`,但该操作会强制将整个 Cache 写回 SDRAM,耗时巨大(约 10ms),严重拖慢帧率。 - **总线冲突**:CleanDCache 期间,CPU 和 LTDC 同时访问 SDRAM,产生大量仲裁等待。 # 标准配置步骤 ## 1. 确定 SDRAM 地址范围 在 STM32F4 上,SDRAM 通常映射在 Bank1/SDRAM 区域,地址从 0xC0000000 开始。例如,使用 1MB SDRAM,区域为 0xC0000000 ~ 0xC00FFFFF。 ## 2. 配置 MPU 区域 推荐配置:**Non-cacheable, Bufferable**(TEX=0, C=0, B=1),并启用写缓冲以提升 CPU 写性能,同时避免缓存一致性问题。 ```c void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct; // 禁用 MPU 进行配置 HAL_MPU_Disable(); // 配置 SDRAM 区域为 Non-cacheable, Bufferable MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_1MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_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_DISABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 使能 MPU HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } ``` ## 3. 初始化顺序 在系统启动早期(`SystemInit` 后)调用 `MPU_Config()`,确保在 SDRAM 初始化前生效。 ```c int main(void) { HAL_Init(); SystemClock_Config(); MPU_Config(); // 先配置 MPU SDRAM_Init(); // 再初始化 SDRAM LTDC_Init(); // ... } ``` # 完整代码示例 以下是一个完整的 MPU 配置函数,包含错误检查: ```c void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; // 禁用 MPU HAL_MPU_Disable(); // 配置 SDRAM 区域(0xC0000000, 1MB) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_1MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_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_DISABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 使能 MPU,默认背景区域为特权不可访问 HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); // 验证配置(可选) uint32_t attr = MPU->RBAR & MPU_RBAR_ATTR_MASK; if (attr != 0x03000000) { // 检查 TEX=0, C=0, B=1 Error_Handler(); } } ``` # 注意事项 - **区域大小必须为 2 的幂**:MPU 区域大小支持 32B 到 4GB,但必须与地址对齐。若 SDRAM 大小不是 2 的幂,需拆分为多个区域或使用更大的区域(注意越界风险)。 - **不要使用 Cacheable 配置**:除非你使用双缓冲并手动管理缓存一致性,否则强烈建议 Non-cacheable。 - **写缓冲的影响**:Bufferable 允许 CPU 写操作异步完成,但若后续有读操作,需确保内存屏障(`__DSB()`)保证顺序。 - **LTDC 的 DMA 访问**:LTDC 使用 AHB 总线,不受 MPU 影响,但缓存不一致问题仍会出现,因此 Non-cacheable 是安全选择。 - **性能对比**:实测 Non-cacheable + Bufferable 比无 MPU 配置(默认全缓存)性能略低(约 5%),但远优于因 CleanDCache 导致的骤降。 # 总结 MPU 配置是 STM32F4 使用 SDRAM 作为显存时的关键优化点。错误的缓存策略会导致性能骤降和显示异常。通过将 SDRAM 区域配置为 Non-cacheable 且 Bufferable,可以避免缓存一致性问题,同时保持较好的写入性能。本文提供的配置代码可直接集成到项目中,帮助开发者快速排查类似问题。记住:在嵌入式系统中,性能优化往往需要深入理解硬件架构,而非盲目添加缓存。