# 引言 在嵌入式 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 访问的一致性。本文提供的排查步骤和代码示例可帮助开发者快速定位并解决问题。记住,在嵌入式开发中,内存一致性问题往往隐藏很深,理解硬件架构是关键。