# 引言 在嵌入式 GUI 开发中,STM32F4 配合外部 SDRAM 作为显存是常见做法,尤其当分辨率较高时,内部 SRAM 往往不够用。但很多开发者会遇到一个棘手问题:**显示画面随机花屏、条纹或局部错乱**,且程序逻辑看似完全正确。这类问题往往不是 LCD 驱动或时序问题,而是 **MPU(Memory Protection Unit)配置不当** 导致 Cache 与 SDRAM 数据不一致。 本文将基于 STM32F429 平台,深入分析 MPU 在 SDRAM 显存场景下的关键作用,并给出可落地的解决方案。 # 1. 问题现象与初步定位 ## 1.1 典型现象 - 画面初始化时正常,运行一段时间后出现随机色块或条纹。 - 使用 DMA2D 搬运图像时,偶发数据错位。 - 开启 D-Cache 后问题加剧,关闭 D-Cache 后问题消失。 ## 1.2 初步排查方向 - 检查 SDRAM 时序(刷新率、行列延迟)——通常正常。 - 检查 LTDC 与 DMA2D 配置——通常无误。 - 检查内存访问冲突——发现 SDRAM 区域被多个外设访问,但未配置 MPU。 **关键点**:STM32F4 的 D-Cache 默认是开启的(在 SystemInit 后),而 SDRAM 是外部存储器,默认被配置为 **Write-back** 模式,这会导致 Cache 与 SDRAM 内容不一致。 # 2. 原理剖析:MPU、Cache 与 SDRAM 的关系 ## 2.1 为什么需要 MPU? MPU 是 Cortex-M4 内核提供的存储保护单元,除了访问权限控制外,更重要的是**定义内存区域的 Cache 策略**。对于外部 SDRAM,我们需要明确告诉 CPU: - 该区域是否可缓存(Cacheable)? - 写策略是 Write-back 还是 Write-through? - 是否允许 DMA 访问? ## 2.2 默认配置的陷阱 STM32F4 启动代码(startup_stm32f429xx.s)中,SystemInit 会设置默认的 MPU 配置,但通常**不包含外部 SDRAM 区域**。此时,SDRAM 被当作普通内存,D-Cache 默认以 Write-back 策略工作。 当 CPU 写入显存时,数据先进入 Cache,而 DMA2D 或 LTDC 直接读取 SDRAM 物理地址,就会读到**旧数据**,导致花屏。 ## 2.3 正确策略:Write-through 或 Non-cacheable 对于显存这类需要 CPU 与 DMA 共享的数据区域,推荐使用 **Write-through** 策略,确保 CPU 写入时同步更新 SDRAM。或者干脆配置为 **Non-cacheable**,但会牺牲性能。 # 3. 配置步骤与代码实现 ## 3.1 硬件环境 - MCU:STM32F429ZIT6 - SDRAM:IS42S16400J(1M x 16bit x 4 banks),映射地址 0xC0000000 - LCD:RGB565 接口,分辨率 480x272 ## 3.2 MPU 配置代码 以下代码在 main 函数初始化阶段调用,配置 SDRAM 区域为 Write-through 策略。 ```c #include "stm32f4xx_hal.h" void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct; // 禁用 MPU 进行配置 HAL_MPU_Disable(); // 配置 SDRAM 区域:基地址 0xC0000000,大小 8MB(实际可用 4MB) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; // 根据实际 SDRAM 大小调整 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=000,配合 C=1, B=1 得到 Write-through MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 使能 MPU HAL_MPU_Enable(MPU_CONTROL_PRIVILEGED_DEFAULT); } // 在 main 中调用 int main(void) { HAL_Init(); SystemClock_Config(); MPU_Config(); // 必须在初始化 SDRAM 和 LTDC 之前 // ... 其他初始化 } ``` **关键参数说明**: - `TypeExtField = MPU_TEX_LEVEL0` 且 `IsCacheable=1, IsBufferable=1` 时,内存属性为 **Write-through, no write allocate**,适合显存。 - 若希望完全禁用 Cache,可设置 `IsCacheable = MPU_ACCESS_NOT_CACHEABLE`,但性能会下降。 ## 3.3 验证与测试 配置后,运行图形测试程序(如绘制渐变条、随机色块),观察是否还有花屏。同时可测量性能,确认 Write-through 对帧率影响可接受。 # 4. 完整示例:SDRAM 初始化 + MPU 配置 以下是一个简化的初始化流程,展示 MPU 与 SDRAM 的配合。 ```c void System_Init(void) { // 1. 时钟配置(略) // 2. MPU 配置(必须在 SDRAM 使用前) MPU_Config(); // 3. SDRAM 初始化(使用 HAL 库) SDRAM_Init(); // 内部包含 FMC 配置和 SDRAM 时序 // 4. LTDC 初始化(略) // 5. 测试:向显存写入数据并读取验证 uint16_t *framebuffer = (uint16_t *)0xC0000000; for (uint16_t i = 0; i < 480*272; i++) { framebuffer[i] = 0xF800; // 红色 } // 若配置正确,屏幕应显示纯红色,无花屏 } ``` # 5. 常见误区与注意事项 - **误区1:MPU 配置必须在 SDRAM 初始化之前**。实际上顺序无关,但必须在任何访问 SDRAM 之前完成,否则可能产生总线错误。 - **误区2:所有外部内存都配置为 Write-back**。对于显存,Write-back 会导致 DMA 读取旧数据,必须用 Write-through 或 Non-cacheable。 - **注意事项1:MPU 区域大小必须覆盖整个 SDRAM**,否则未覆盖部分仍使用默认策略。 - **注意事项2:如果使用 RTOS,需确保 MPU 配置在任务调度前完成**。 - **注意事项3:调试时可用硬件断点观察 Cache 操作,或使用 `SCB_CleanDCache()` 手动刷新,但治标不治本。** # 6. 总结 STM32F4 使用 SDRAM 作为显存时,MPU 配置是决定系统稳定性的关键。通过将 SDRAM 区域配置为 Write-through 策略,可以避免 CPU 与 DMA 之间的数据不一致,彻底解决花屏问题。本文提供的配置代码可直接移植到实际项目中,建议开发者根据具体 SDRAM 大小调整区域尺寸,并在项目初期就加入 MPU 配置,避免后期排查的烦恼。 希望这篇文章能帮你少走弯路。如果你有类似问题,欢迎交流!