STM32F4 使用 SDRAM 作显存时 MPU 配置不当导致图像撕裂的排查与修复
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列上驱动 LCD 并采用 SDRAM 作为显存时,图像撕裂(tearing)常被误认为是时序或 DMA 问题,实则根因往往是 MPU(内存保护单元)配置不当。本文深入剖析 MPU 在显存访问中的关键作用,结合 Cortex-M4 内核特性,给出完整的配置步骤、代码示例及排查方法,帮助开发者彻底解决撕裂问题,提升显示稳定性。
# 引言
在嵌入式 GUI 开发中,STM32F4 配合外部 SDRAM 作为显存是常见方案,尤其当分辨率较高时。然而,不少开发者会遇到图像撕裂现象:屏幕上半部分显示新帧,下半部分仍为旧帧。多数人会先怀疑 LTDC 时序或 DMA 传输,但忽略了一个关键环节——MPU 配置。Cortex-M4 内核的 MPU 若未正确设置,会导致 SDRAM 区域被默认属性(如不可缓存、不可缓冲)访问,进而引发读写不一致,最终表现为撕裂。
# 原理:MPU 如何影响 SDRAM 显存访问
MPU 是 Cortex-M4 内核提供的内存保护机制,它除了权限控制外,还定义了每个内存区域的**缓存(Cacheable)**、**缓冲(Bufferable)**和**可共享(Shareable)**属性。对于 SDRAM 这类外部存储器,默认情况下(未配置 MPU)会被视为**不可缓存、不可缓冲**,这意味着每次 CPU 读写都直接访问 SDRAM,而 LTDC 控制器(DMA 方式)也在访问同一块内存。
问题在于:当 CPU 写入显存(例如绘制像素)时,由于没有写缓冲,写入操作可能被拆分为多个总线周期,而 LTDC 读取显存是连续突发传输。若 CPU 写入尚未完成,LTDC 已读取到旧数据,就会产生撕裂。更严重的是,若启用了数据缓存(D-Cache)但未配置 MPU 为 Write-Back,则缓存与 SDRAM 内容不一致,导致 LTDC 读到过期数据。
因此,正确配置 MPU 区域,将 SDRAM 显存区域设置为 **Write-Back、Write-Allocate** 或 **Write-Through** 属性,并确保缓存一致性,是解决撕裂的关键。
# 配置步骤
## 1. 确定 SDRAM 地址范围
STM32F4 的 FMC 控制器将 SDRAM 映射到 Bank1 或 Bank2,典型地址如 0xC0000000(Bank1)或 0xD0000000(Bank2)。以 4MB SDRAM 为例,范围是 0xC0000000 ~ 0xC03FFFFF。
## 2. 初始化 MPU 区域
使用 CMSIS 提供的 `MPU_Region_InitTypeDef` 结构体配置。关键参数:
- `BaseAddress`:SDRAM 基地址
- `Size`:区域大小(需为 2 的幂,且不小于 32 字节)
- `Enable`:使能该区域
- `AccessPermission`:设为全权限(`MPU_REGION_FULL_ACCESS`)
- `TypeExtField`:设为 `MPU_TEX_LEVEL1`(表示 TEX=1)
- `IsCacheable`:设为 `MPU_ACCESS_CACHEABLE`
- `IsBufferable`:设为 `MPU_ACCESS_BUFFERABLE`
- `IsShareable`:设为 `MPU_ACCESS_NOT_SHAREABLE`(SDRAM 非共享)
注意:`TypeExtField` 与 `IsCacheable`/`IsBufferable` 组合决定实际缓存策略。推荐组合:TEX=1, C=1, B=1 表示 Write-Back, Write-Allocate,适合显存场景。
## 3. 使能 MPU 和 D-Cache
配置后需调用 `MPU_Enable()` 和 `SCB_EnableDCache()`。注意顺序:先配置 MPU,再使能 D-Cache,否则缓存策略不生效。
## 4. 维护缓存一致性
即使配置了 Write-Back,CPU 写入后数据可能仍在缓存中,LTDC 访问 SDRAM 时需确保缓存已回写。因此,在每帧绘制完成后,需调用 `SCB_CleanDCache()` 或 `SCB_CleanDCache_by_Addr()` 将脏缓存行写回 SDRAM。
# 完整代码示例
以下基于 STM32F429 和 HAL 库,展示 MPU 初始化及显存刷新流程。
```c
// MPU 配置结构体
MPU_Region_InitTypeDef MPU_InitStruct;
void MPU_Config(void)
{
// 禁用 MPU 进行配置
HAL_MPU_Disable();
// 配置 SDRAM 区域(假设基地址 0xC0000000,大小 4MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_4MB;
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_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
// 在 main 函数中调用
int main(void)
{
HAL_Init();
SystemClock_Config();
// 先配置 MPU,再使能 D-Cache
MPU_Config();
SCB_EnableDCache();
// 初始化 SDRAM 和 LTDC(略)
// ...
while (1)
{
// 绘制一帧到显存(例如使用 GUI 库)
DrawFrame((uint32_t*)0xC0000000);
// 确保缓存数据回写到 SDRAM
SCB_CleanDCache();
// 等待 LTDC 垂直消隐,然后切换显示(可选)
// ...
}
}
```
# 排查方法
若已按上述配置仍出现撕裂,可按以下步骤排查:
- **检查 MPU 是否生效**:在调试器中查看 MPU 寄存器(如 MPU_RBAR、MPU_RASR),确认区域使能且属性正确。
- **验证缓存回写**:在绘制后、切换帧前,调用 `SCB_CleanDCache()`,若撕裂消失,说明是缓存一致性问题。
- **尝试不同缓存策略**:将 TypeExtField 改为 `MPU_TEX_LEVEL0`,并设置 C=1, B=0(Write-Through),可避免回写延迟,但性能略降。
- **检查 LTDC 配置**:确保 LTDC 的 DMA 突发长度与 SDRAM 总线宽度匹配(如 32 位),并启用 FIFO 阈值。
- **使用双缓冲**:在 SDRAM 中分配两个帧缓冲区,绘制一个,显示另一个,利用垂直消隐切换,彻底避免撕裂。
# 注意事项
- **MPU 区域大小必须为 2 的幂**,且最小 32 字节,否则配置无效。
- **使能 D-Cache 前必须先配置 MPU**,否则默认属性可能导致不可预知行为。
- **SDRAM 初始化必须在 MPU 配置之后**,因为 MPU 区域属性会影响 FMC 访问时序。
- **若使用 DMA2D 进行图形加速**,其访问不经过 MPU,但 CPU 写入后仍需缓存回写,否则 DMA2D 可能读到旧数据。
- **在 RTOS 环境下**,多任务访问显存时需加锁,并确保缓存操作在临界区执行。
# 总结
图像撕裂问题往往不是单一原因,但 MPU 配置不当是 STM32F4 使用 SDRAM 显存时的常见陷阱。通过正确设置 MPU 区域为 Write-Back 策略,并在帧绘制后主动回写缓存,可以显著提升显示稳定性。本文提供的配置代码和排查思路,希望能帮助开发者快速定位问题,让 GUI 显示如丝般顺滑。