STM32F4 使用 SDRAM 作为显存时 MPU 配置不当导致花屏的排查与修复
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动 LCD 时,常将 SDRAM 用作显存以提升性能。然而,若 MPU(内存保护单元)配置不当,会出现随机花屏、颜色错乱等诡异现象。本文从 MPU 工作原理出发,分析 SDRAM 显存场景下的典型配置错误,给出排查步骤与完整修复代码,帮助开发者彻底解决此类问题。
# 一、问题现象与背景
某项目基于 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 属性是嵌入式开发进阶的必备技能,希望本文能帮助你少走弯路。