一、问题背景与现象

某工业控制项目中,STM32F429 通过 FMC 接口外挂 8MB SDRAM(IS42S16400J),用于存储图像帧缓冲和日志数据。系统运行一段时间后,出现以下随机故障:

  • 图像显示出现花屏或错位,重启后恢复。
  • DMA 从 SDRAM 搬运数据到外设时,偶发数据错位。
  • 调试时读取 SDRAM 变量,发现值在 CPU 写入后未立即更新。

这些现象均指向 D-Cache 与 SDRAM 的数据一致性问题。

二、原理剖析:为什么 D-Cache 会引发问题?

2.1 D-Cache 的工作机制

STM32F4 系列(如 F429、F407)内置 4KB 数据缓存(D-Cache),采用写回(Write-back)策略。当 CPU 写入 SDRAM 地址时,数据先被写入 Cache 行(通常为 32 字节),并标记为脏(Dirty)。只有在 Cache 行被替换或显式清理时,数据才会真正写回 SDRAM。

2.2 一致性问题场景

  • CPU 写,DMA 读:CPU 将数据写入 SDRAM,但数据仍停留在 D-Cache 中,DMA 直接访问 SDRAM 物理地址,读到的是旧数据。
  • DMA 写,CPU 读:DMA 将外部数据写入 SDRAM,但 D-Cache 中可能保留了旧副本,CPU 读取时命中 Cache,得到过期数据。
  • 多核/外设共享:如 LTDC 控制器直接读取 SDRAM 作为显存,同样会遇到一致性问题。

2.3 关键硬件特性

  • Cache 行大小:STM32F4 的 D-Cache 行大小为 32 字节。
  • 地址映射:SDRAM 区域默认是 Cacheable 的,除非显式配置为 Non-cacheable。
  • 操作指令:SCB_CleanDCache()SCB_InvalidateDCache()SCB_CleanInvalidateDCache()

三、问题定位方法

3.1 复现与隔离

  • 在调试器中观察 SDRAM 变量,发现 CPU 写入后立即读取,值正确;但 DMA 读取时错误。
  • 关闭 D-Cache(SCB_DisableDCache())后,问题消失,确认是 Cache 导致。

3.2 使用 Cache 操作函数验证

在 DMA 传输前,手动调用 SCB_CleanDCache(),在 DMA 完成后调用 SCB_InvalidateDCache(),问题暂时解决,但性能下降明显。

3.3 进一步分析

通过查看 Cortex-M4 编程手册,确认 FMC 控制器不参与 Cache 一致性维护,必须由软件保证。

四、修复方案与配置步骤

4.1 方案选择

根据应用场景,有三种主流方案:

  1. 禁用 D-Cache:最简单,但失去缓存加速,性能损失大。
  2. 维护 Cache 一致性:在关键操作前后手动 Clean/Invalidate,适合低频访问。
  3. 配置 MPU 将 SDRAM 区域设为 Non-cacheable:彻底避免一致性问题,适合高频访问且对一致性要求高的场景。

本案例采用方案 3,因为图像数据频繁读写,且 DMA 交互多。

4.2 配置 MPU 步骤

  1. 使能 MPU:在系统初始化时调用 MPU_Config()
  2. 设置区域:将 SDRAM 基地址(如 0xC0000000)配置为 Normal memory,属性为 Non-cacheable。
  3. 设置权限:允许特权和非特权访问。
  4. 使能区域:确保区域号有效。

4.3 代码实现

#include "stm32f4xx.h"

void MPU_Config(void)
{
    MPU_Region_InitTypeDef MPU_InitStruct;

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

    // 配置 SDRAM 区域(基地址 0xC0000000,大小 8MB)
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress = 0xC0000000;
    MPU_InitStruct.Size = MPU_REGION_SIZE_8MB;
    MPU_InitStruct.SubRegionDisable = 0;
    MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;  // Normal memory
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;  // 关键:Non-cacheable
    MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    // 使能 MPU
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

// 在 main 函数中调用
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MPU_Config();  // 必须在启用 D-Cache 之前配置
    SCB_EnableDCache();
    // ... 其他初始化
}

4.4 注意事项

  • 配置顺序:MPU 配置必须在使能 D-Cache 之前完成,否则无效。
  • 区域重叠:确保 MPU 区域不与其他内存区域(如 Flash、SRAM)重叠。
  • 对齐要求:MPU 区域基地址必须按区域大小对齐,如 8MB 区域要求基地址低 23 位为 0。
  • 性能影响:Non-cacheable 区域访问速度会降低,但相比一致性错误,这是可接受的。
  • 调试技巧:若问题仍存在,检查 MPU 配置是否正确,可通过读取 MPU->RBARMPU->RASR 验证。

五、验证与总结

修复后,运行压力测试 72 小时,未再出现数据错乱。DMA 传输稳定,图像显示正常。

5.1 经验总结

  • 在 STM32F4 系列中使用 SDRAM 时,务必考虑 D-Cache 一致性。
  • 优先使用 MPU 将 SDRAM 配置为 Non-cacheable,简单可靠。
  • 若需保留 Cache 性能,则必须严格管理 Clean/Invalidate 操作,并注意 Cache 行对齐。
  • 建议在项目初期就规划好内存属性和 Cache 策略,避免后期返工。

六、扩展思考

对于更复杂的场景,如双核或使用 LTDC 时,可考虑使用 Cache 维护操作或硬件缓冲。但最稳妥的方案仍是合理配置 MPU,从根源上消除一致性问题。希望本文能帮助开发者少走弯路,构建更稳定的嵌入式系统。