引言

在嵌入式 GUI 开发中,STM32F4 搭配外部 SDRAM 作为显存是常见方案。然而,很多开发者发现代码逻辑正确,但 LCD 刷新率极低或出现随机花屏。问题往往不在 SDRAM 时序,而在于 MPU 配置。MPU 控制着 CPU 对内存区域的缓存与写回策略,不当配置会让 SDRAM 访问效率大打折扣,甚至导致数据不一致。

MPU 与 SDRAM 的关系

为什么 MPU 影响性能?

STM32F4 的 Cortex-M4 内核内置 MPU,可对内存区域定义访问权限和缓存属性。SDRAM 位于外部存储器映射区(0xC0000000 起),默认情况下 MPU 未启用,此时 CPU 对 SDRAM 的访问是“无缓存”的,即每次读写都直接访问外部总线。LCD 控制器(如 LTDC)通过 DMA 读取显存时,若 CPU 同时写入,无缓存会导致频繁总线竞争,性能下降。

更严重的是,若 MPU 配置为“写回(Write Back)”模式,CPU 写入的数据会暂存在 Cache 中,而 LTDC 直接读取 SDRAM,可能读到旧数据,造成花屏。

关键概念

  • Cache 策略:Write-through(直写)与 Write-back(回写)。直写每次写操作同步到内存,性能略低但数据一致;回写先写 Cache,延迟写回,性能高但需维护一致性。
  • Bufferable:允许写操作缓冲,CPU 不等总线完成,提升写速度。
  • TEX 字段:决定缓存类型,如 0b001 表示 Write-through,0b000 表示 Non-cacheable。

配置步骤

1. 确定 SDRAM 地址范围

假设 SDRAM 挂在 FMC 的 Bank1,起始地址 0xC0000000,大小 8MB(0x800000)。显存区域通常位于 SDRAM 的某段,如 0xC0000000 到 0xC07FFFFF(前 8MB)。

2. 选择缓存策略

对于显存,推荐使用 Write-through + Bufferable(即 TEX=0b001,C=1,B=1)。原因:

  • 写操作直通 SDRAM,保证 LTDC 能立即看到最新数据,避免花屏。
  • Bufferable 允许 CPU 写后不等待,提升写入速度。

若追求极致性能,可尝试 Write-back,但必须定期 Clean Cache 或使用 DMA 双缓冲,否则易出问题。

3. 配置 MPU 寄存器

使用 STM32CubeMX 或直接操作寄存器。下面以寄存器方式给出代码。

完整代码示例

以下代码基于 STM32F429,使用 HAL 库,配置 MPU 区域 0 覆盖 SDRAM 前 8MB。

#include "stm32f4xx_hal.h"

void MPU_Config_SDRAM(void)
{
    MPU_Region_InitTypeDef MPU_InitStruct = {0};

    // 禁用 MPU 进行配置
    HAL_MPU_Disable();

    // 配置区域 0
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress = 0xC0000000;  // SDRAM 起始地址
    MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; // 8MB 区域
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;  // TEX=0b001
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;    // C=1
    MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;  // B=1

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    // 使能 MPU
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

// 在 main 函数中调用
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_FMC_Init();  // 初始化 FMC 以访问 SDRAM

    // 配置 MPU(必须在启用 SDRAM 后、使用显存前)
    MPU_Config_SDRAM();

    // 初始化 LCD 和 LTDC
    // ...

    while (1)
    {
        // 主循环
    }
}

常见问题排查

1. 性能骤降

  • 现象:帧率比预期低 50% 以上。
  • 原因:MPU 未配置,或配置为 Non-cacheable(TEX=0b000)。CPU 每次写显存都直接访问 SDRAM,无缓冲,总线效率低。
  • 解决:确保配置为 Write-through + Bufferable,并检查是否启用了 MPU。

2. 花屏或闪烁

  • 现象:LCD 显示随机条纹或旧画面残留。
  • 原因:MPU 配置为 Write-back,CPU 写入的数据未及时同步到 SDRAM,LTDC 读取到旧数据。
  • 解决:改用 Write-through,或每次绘制后调用 SCB_CleanDCache()(若启用 D-Cache)。

3. 区域重叠

  • 现象:部分内存访问异常。
  • 原因:MPU 区域覆盖了其他外设或内存,导致访问权限冲突。
  • 解决:仔细规划区域,确保 SDRAM 区域不与 SRAM、Flash 等重叠。

注意事项

  • MPU 配置时机:必须在 SDRAM 初始化之后、任何显存访问之前配置。
  • 区域大小:MPU 支持的最小区域为 32 字节,但建议按 1KB 对齐,避免覆盖无关地址。
  • 多区域:若 SDRAM 中同时存放帧缓冲和 GUI 数据,可配置多个区域,分别设置不同策略。
  • DMA 与 Cache 一致性:即使使用 Write-through,DMA 读取也可能因总线缓冲而滞后,必要时使用 __DSB() 指令确保写完成。
  • 测试建议:先用小区域测试,如 1MB,验证性能与显示效果,再逐步扩大。

总结

MPU 配置是 STM32F4 使用 SDRAM 作为显存的关键一环。正确配置 Write-through + Bufferable 可兼顾性能与数据一致性,避免花屏。遇到性能问题时,优先检查 MPU 是否启用及缓存策略;遇到花屏时,检查是否误用了 Write-back。希望本文能帮你快速定位问题,提升开发效率。