STM32F4 使用 SDRAM 作为显存时 MPU 配置不当导致花屏的深度排查与修复
👁 4 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动 LCD 时,常将 SDRAM 用作显存以提升性能。然而,若 MPU(内存保护单元)配置不当,会导致随机花屏、数据错乱等诡异问题。本文从硬件原理出发,剖析 MPU 与 Cache 的交互机制,结合实战案例,给出完整的配置步骤、代码示例及排查技巧,帮助开发者彻底规避此类隐患。
# 引言
在嵌入式 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 配置,避免后期排查的烦恼。
希望这篇文章能帮你少走弯路。如果你有类似问题,欢迎交流!