# 引言 在嵌入式 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 显示如丝般顺滑。