引言

在嵌入式 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-AllocateWrite-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 非共享)

注意:TypeExtFieldIsCacheable/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 初始化及显存刷新流程。

// 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 显示如丝般顺滑。