# 一、问题现象与背景 某项目基于 STM32F429,使用外部 SDRAM(如 W9825G6KH)作为 LTDC 的显存,分辨率 800x480,RGB565。系统运行正常,但偶尔出现花屏:表现为屏幕局部颜色错乱、闪烁,或整屏随机噪点,尤其在 DMA2D 搬运图像或 CPU 写入显存时更频繁。 初步排查:SDRAM 初始化时序正确,读写测试通过;LTDC 配置无误。问题指向 MPU 对 SDRAM 区域的缓存策略配置。 # 二、MPU 与 SDRAM 显存的关系 STM32F4 的 Cortex-M4 内核带有 MPU,可对内存区域设置访问权限和属性,其中最关键的是 **Cache 策略**(Write-Back / Write-Through / Non-cacheable)。 SDRAM 作为显存时,存在两个访问方: - **CPU 或 DMA2D**:写入图像数据到显存; - **LTDC 控制器**:持续从显存读取像素刷新屏幕。 若 MPU 将 SDRAM 配置为 **Write-Back 模式**,CPU 写入的数据会暂存在 Cache 中,不会立即写回 SDRAM。而 LTDC 直接读取 SDRAM,导致它看到的是旧数据,从而产生花屏。 # 三、典型错误配置 常见错误代码(如使用 STM32CubeMX 默认配置或手动设置): ```c MPU_Region_InitTypeDef MPU_InitStruct; HAL_MPU_Disable(); MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; // SDRAM 地址 MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_REGION_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_REGION_CACHEABLE; // 错误:设为 Cacheable MPU_InitStruct.IsShareable = MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // 可能组合出 Write-Back MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC; HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); ``` 上述配置中,`IsCacheable = MPU_REGION_CACHEABLE` 且 `TypeExtField = MPU_TEX_LEVEL1` 会得到 Write-Back 策略,导致数据滞留 Cache。 # 四、排查步骤 1. **确认 SDRAM 读写正确**:先禁用 MPU,用简单的内存写读测试(如写入 0xAA55 再读回)验证 SDRAM 硬件正常。 2. **检查 LTDC 配置**:确认显存地址、像素格式、行偏移等无误。 3. **观察花屏触发条件**:若仅在 CPU 写显存后出现,且过一会儿恢复(Cache 回写),则高度怀疑 Cache 一致性问题。 4. **临时禁用 MPU 测试**:将 MPU 配置为 Non-cacheable 或直接关闭 MPU,若花屏消失,则确认是 MPU 配置问题。 # 五、修复方案 正确做法:将 SDRAM 显存区域配置为 **Write-Through 或 Non-cacheable**,保证 CPU 写入立即生效。推荐使用 Write-Through,兼顾性能与一致性。 修复后的 MPU 配置代码: ```c void MPU_Config_SDRAM_As_Framebuffer(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; HAL_MPU_Disable(); // 配置 SDRAM 区域(0xC0000000,大小 8MB) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_REGION_NOT_BUFFERABLE; // 关键 MPU_InitStruct.IsCacheable = MPU_REGION_CACHEABLE; // 允许 Cache,但配合 TEX 设为 Write-Through MPU_InitStruct.IsShareable = MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; // 与 C=1, B=0 组合为 Write-Through MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 可选:配置其他区域(如内部 SRAM)为 Write-Back 以提升性能 HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } ``` 关键点:`TypeExtField = MPU_TEX_LEVEL0` 且 `IsCacheable=1`、`IsBufferable=0` 时,内存属性为 **Write-Through**,CPU 写入会同时更新 Cache 和 SDRAM,保证 LTDC 读取到最新数据。 # 六、完整示例:初始化 SDRAM 并配置 MPU ```c // 初始化 SDRAM(使用 HAL 库,假设已配置 FMC) extern void SDRAM_Init(void); void System_Init(void) { HAL_Init(); SystemClock_Config(); // 1. 初始化 SDRAM SDRAM_Init(); // 配置 FMC 和 SDRAM 控制器 // 2. 配置 MPU 针对 SDRAM 区域 MPU_Config_SDRAM_As_Framebuffer(); // 3. 初始化 LTDC 和 DMA2D // LTDC_Init(); // DMA2D_Init(); } // 测试:向显存写入颜色数据 void FillScreen(uint16_t color) { uint32_t fb_addr = 0xC0000000; for (uint32_t i = 0; i < 800*480; i++) { *((uint16_t*)fb_addr + i) = color; } } ``` # 七、注意事项 - **不要随意将显存设为 Write-Back**:除非你每次写完后手动 `SCB_CleanDCache()`,但会降低效率且易遗漏。 - **SDRAM 刷新与 Cache 无关**:MPU 配置不影响 SDRAM 自刷新,但影响 CPU 访问路径。 - **多区域配置**:如果 SDRAM 同时用于其他数据(如帧缓冲、堆),建议划分不同 MPU 区域,显存区域用 Write-Through,其他区域可用 Write-Back 提升性能。 - **调试技巧**:使用硬件调试器观察 SDRAM 内存值,对比 CPU 写入值与实际内存值,可快速判断 Cache 是否回写。 - **CubeMX 配置**:在 CubeMX 中,MPU 设置里选择“Write-Through”或“Non-cacheable”即可,避免手动错误。 # 八、总结 MPU 配置在 SDRAM 显存场景中至关重要。通过将显存区域设为 Write-Through 策略,保证了 CPU 写入与 LTDC 读取的一致性,彻底解决花屏问题。理解 MPU 的 Cache 属性是嵌入式开发进阶的必备技能,希望本文能帮助你少走弯路。