STM32F4 使用 SDRAM 作为显存时 MPU 配置不当导致图像撕裂的排查与解决
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动 LCD 并采用 SDRAM 作为显存时,MPU(内存保护单元)配置不当会引发图像撕裂、花屏等诡异问题。本文从 MPU 在 Cortex-M4 中的作用出发,深入分析 SDRAM 区域缓存策略对 DMA 与 CPU 访问一致性的影响,结合完整代码示例,手把手教你排查和修复 MPU 配置,确保图像输出稳定流畅。适合具备一定嵌入式基础的开发者快速定位问题。
# 引言
在嵌入式 GUI 开发中,STM32F4 系列(如 STM32F429、STM32F407)常通过 FSMC/FMC 接口外挂 SDRAM 作为显存,以支持高分辨率 LCD 显示。然而,很多开发者会遇到一个棘手问题:图像在刷新时出现撕裂(tearing)或随机花屏,而代码逻辑看似无误。这往往不是 LCD 驱动或 DMA 配置的锅,而是 MPU(Memory Protection Unit)对 SDRAM 区域的缓存策略配置不当所致。
本文将带你深入理解 MPU 在 Cortex-M4 中的作用,剖析其与 SDRAM 显存访问的关联,并给出系统的排查方法和修复方案。
# MPU 与 SDRAM 显存访问的关联
## Cortex-M4 的 MPU 简介
MPU 是 Cortex-M4 内核提供的内存保护单元,它允许将物理内存划分为多个区域,并为每个区域设置访问权限和缓存属性(如 Cacheable、Bufferable、Write-back 等)。对于 STM32F4 系列,MPU 常用于:
- 保护关键代码区(如 Bootloader)
- 配置外部内存(如 SDRAM)的缓存策略
- 隔离外设寄存器区域
## 为什么 MPU 影响 SDRAM 显存?
STM32F4 的 CPU 内核和 DMA 控制器都可以访问 SDRAM。当 CPU 写入显存数据时,如果 MPU 将 SDRAM 区域配置为 **Cacheable(可缓存)**,数据会先被写入 CPU 的缓存(Cache),而不会立即同步到 SDRAM。此时,如果 DMA 控制器(如 LTDC 或 DMA2D)直接读取 SDRAM 中的显存数据,就会读到旧数据或未更新的数据,导致图像撕裂、残影或花屏。
此外,如果 MPU 配置为 **Write-back(回写)** 模式,CPU 写入的数据会暂存在缓存中,直到缓存行被替换或显式刷新时才写入 SDRAM,这进一步加剧了不一致性。
## 常见错误配置
- 将 SDRAM 区域配置为 Cacheable + Write-back
- 未启用 MPU,但默认情况下 Cortex-M4 的缓存行为可能由系统控制寄存器决定,导致意外缓存
- 配置区域大小或基地址错误,覆盖了其他外设
# 排查步骤
## 1. 确认 SDRAM 地址映射
首先,确认 SDRAM 在 STM32F4 的地址空间中的位置。通常,FMC 的 Bank1 映射到 0x60000000 开始,Bank2 为 0x64000000,Bank3 为 0x68000000,Bank4 为 0x6C000000。例如,使用 Bank1 的 SDRAM 时,基地址为 0x60000000。
## 2. 检查 MPU 配置代码
查看初始化代码中是否配置了 MPU 区域。常见错误如下:
```c
// 错误示例:将 SDRAM 配置为 Cacheable + Write-back
MPU_Region_InitTypeDef MPU_InitStruct;
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; // 根据实际 SDRAM 大小
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // 可能导致缓存
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; // 错误!
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
```
## 3. 观察现象
- 如果图像仅在 CPU 写入后立即刷新时出现撕裂,而 DMA 读取时正常,则高度怀疑缓存问题。
- 使用调试器查看 SDRAM 内存内容,发现 CPU 写入的数据未及时出现在 SDRAM 中。
# 解决方案:正确配置 MPU
## 原理:禁用缓存或使用 Write-through
对于显存区域,推荐配置为 **Non-cacheable** 或 **Write-through** 模式。Write-through 模式下,CPU 写入会同时更新缓存和 SDRAM,保证一致性;而 Non-cacheable 则完全绕过缓存,但性能稍低。对于显存,通常选择 Non-cacheable 以简化设计,或者 Write-through 以平衡性能。
## 配置步骤
1. 在系统初始化时(如 `SystemInit` 或 `main` 函数开头),使能 MPU 并配置 SDRAM 区域。
2. 设置 `TypeExtField` 为 `MPU_TEX_LEVEL0`,并设置 `IsCacheable = MPU_ACCESS_NOT_CACHEABLE`,`IsBufferable = MPU_ACCESS_NOT_BUFFERABLE`。
3. 确保区域大小覆盖整个 SDRAM(如 8MB)。
4. 使能 MPU 并启用 Fault 异常(可选)。
## 完整代码示例
以下为基于 STM32CubeF4 的 MPU 配置示例:
```c
#include "stm32f4xx_hal.h"
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
// 禁用 MPU 进行配置
HAL_MPU_Disable();
// 配置 SDRAM 区域(假设基地址 0x60000000,大小 8MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x60000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8MB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; // 不使用 TEX 扩展
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 关键:禁止缓存
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; // 禁止缓冲
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
// 在初始化 SDRAM 和 LCD 之前配置 MPU
MPU_Config();
// 初始化 SDRAM、LCD 等...
// SDRAM_Init();
// LCD_Init();
while (1)
{
// 主循环
}
}
```
## 注意事项
- **配置顺序**:MPU 配置必须在 SDRAM 初始化之前完成,否则可能产生总线错误。
- **区域重叠**:确保 MPU 区域不与其他外设区域重叠,否则可能导致意外行为。
- **性能影响**:禁用缓存会降低 CPU 写入显存的性能,但通常 LCD 刷新由 DMA 完成,CPU 写入频率不高,影响可忽略。
- **调试建议**:如果仍出现撕裂,可尝试使用 `SCB_CleanDCache()` 或 `SCB_InvalidateDCache()` 手动维护缓存(如果启用了缓存),但最佳实践是直接禁用缓存。
# 总结
MPU 配置不当是 STM32F4 使用 SDRAM 作为显存时图像撕裂的常见原因。通过将 SDRAM 区域配置为 Non-cacheable 或 Write-through,可以确保 CPU 与 DMA 访问的一致性。本文提供的排查步骤和代码示例可帮助开发者快速定位并解决问题。记住,在嵌入式开发中,内存一致性问题往往隐藏很深,理解硬件架构是关键。