STM32F4 使用 SDRAM 作为显存时 MPU 配置不当导致的性能骤降排查与修复
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列上使用 SDRAM 作为 LCD 显存时,MPU(内存保护单元)配置不当会引发严重的性能下降,表现为帧率骤降、画面撕裂甚至系统卡顿。本文从 Cortex-M4 的 MPU 原理出发,深入分析 SDRAM 区域缓存属性(Cacheable/Bufferable)对 DMA 和 CPU 访问的影响,给出标准配置步骤、完整代码示例及常见陷阱,帮助开发者快速定位并解决此类嵌入式性能问题。
# 引言
在嵌入式图形系统中,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,可以避免缓存一致性问题,同时保持较好的写入性能。本文提供的配置代码可直接集成到项目中,帮助开发者快速排查类似问题。记住:在嵌入式系统中,性能优化往往需要深入理解硬件架构,而非盲目添加缓存。