一、问题现象与初步定位

某产品基于 STM32F429,外扩 8MB SDRAM(IS42S16400J),用于 LCD 显存和音频缓冲。系统上电后,SDRAM 初始化正常,但一旦 CPU 频繁读写 SDRAM(如刷屏或 DMA 传输),系统随机死机,JTAG 无法连接,复位后故障复现。

初步排查:

  • 检查 SDRAM 时序(刷新率、CAS 延迟)均符合手册。
  • 单独跑 SDRAM 读写测试(无 MPU)正常,但开启 MPU 后故障出现。
  • 故障点集中在 MPU 配置的 SDRAM 区域。

二、MPU 与 SDRAM 的冲突原理

STM32F4 的 Cortex-M4 内核允许通过 MPU 设置内存区域的 Cache、Buffer 和可执行属性。SDRAM 作为外部存储器,其访问延迟远高于内部 SRAM,且不支持字节写(需要读-改-写操作)。

当 MPU 将 SDRAM 区域配置为 Cacheable + Bufferable(典型默认配置)时,CPU 写操作会先进入写缓冲,然后异步写入 SDRAM。若此时发生以下情况,则可能死锁:

  • 写缓冲未满但 SDRAM 忙:CPU 等待写缓冲清空,而 SDRAM 正在刷新,导致总线阻塞。
  • 非对齐访问:SDRAM 不支持非对齐访问,MPU 的 Buffer 属性允许 CPU 产生非对齐事务,硬件需拆分,但若拆分逻辑与 SDRAM 控制器冲突,则挂死。
  • DMA 与 CPU 竞争:DMA 访问 SDRAM 时,若 MPU 区域属性不一致,可能引发总线仲裁死锁。

三、排查流程与关键证据

  1. 复现并缩小范围:注释掉所有 DMA,仅用 CPU 循环写 SDRAM,故障仍现,排除 DMA。
  2. 检查 MPU 配置:发现使用 MPU_ConfigRegion 时,参数 MPU_REGION_CACHEABLE_BUFFERABLE 被误设为 ENABLE
  3. 对比测试:将 SDRAM 区域改为 MPU_REGION_NO_CACHE(即 Strongly-ordered 或 Device 类型),故障消失。
  4. 逻辑分析仪抓取:在故障瞬间,SDRAM 的 CASRAS 信号持续拉低,总线处于死锁状态。

四、正确配置 MPU 的步骤

以 STM32F4 标准库为例,正确配置 SDRAM 区域(地址 0xC0000000,大小 8MB):

/* 1. 使能 MPU */
MPU_Config(MPU_ENABLE);

/* 2. 配置 SDRAM 区域为 Strongly-ordered(禁止 Cache 和 Buffer) */
MPU_Region_InitTypeDef MPU_RegionInit;
MPU_RegionInit.Enable = MPU_REGION_ENABLE;
MPU_RegionInit.Number = MPU_REGION_NUMBER0;
MPU_RegionInit.BaseAddress = 0xC0000000;
MPU_RegionInit.Size = MPU_REGION_SIZE_8MB;
MPU_RegionInit.SubRegionDisable = 0;
MPU_RegionInit.TypeExtField = MPU_TEX_LEVEL0;  // TEX=000
MPU_RegionInit.AccessPermission = MPU_FULL_ACCESS;
MPU_RegionInit.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
MPU_RegionInit.IsShareable = MPU_ACCESS_SHAREABLE;  // 共享属性
MPU_RegionInit.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 禁止 Cache
MPU_RegionInit.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; // 禁止 Buffer
MPU_RegionInit.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_Init(&MPU_RegionInit);

/* 3. 使能 MPU 故障处理(可选) */
MPU_Config(MPU_ENABLE | MPU_HARDFAULT_NMI);

若需使用 Cache 提升性能,可改为 Write-back 模式,但必须确保 SDRAM 控制器支持,并启用 SCB 的 Cache 维护操作:

SCB_EnableDCache();
// 区域配置改为:TEX=001, IsCacheable=ENABLE, IsBufferable=ENABLE
// 并在 DMA 访问前调用 SCB_CleanDCache_by_Addr()

五、完整代码示例(含初始化)

void MPU_Config_SDRAM(void) {
    // 1. 禁用 MPU
    MPU_Config(MPU_DISABLE);

    // 2. 配置 SDRAM 区域(0xC0000000)
    MPU_Region_InitTypeDef MPU_RegionInit;
    MPU_RegionInit.Enable = MPU_REGION_ENABLE;
    MPU_RegionInit.Number = MPU_REGION_NUMBER0;
    MPU_RegionInit.BaseAddress = 0xC0000000;
    MPU_RegionInit.Size = MPU_REGION_SIZE_8MB;
    MPU_RegionInit.SubRegionDisable = 0;
    MPU_RegionInit.TypeExtField = MPU_TEX_LEVEL0;
    MPU_RegionInit.AccessPermission = MPU_FULL_ACCESS;
    MPU_RegionInit.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
    MPU_RegionInit.IsShareable = MPU_ACCESS_SHAREABLE;
    MPU_RegionInit.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
    MPU_RegionInit.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_Init(&MPU_RegionInit);

    // 3. 使能 MPU
    MPU_Config(MPU_ENABLE);
}

// 主函数中调用
int main(void) {
    // 初始化时钟、SDRAM 控制器等
    SDRAM_Init();
    MPU_Config_SDRAM();
    // 后续正常访问 SDRAM
}

六、注意事项与经验总结

  • 不要盲目开启 Cache:SDRAM 作为显存或 DMA 缓冲时,Cache 可能导致数据不一致,除非使用 Clean/Invalidate 操作。
  • 区分内存类型:普通数据区可用 Write-back,但外设寄存器或共享内存必须用 Device 或 Strongly-ordered。
  • 调试技巧:若死锁,优先检查 MPU 配置,并尝试将区域设为 Strongly-ordered 验证。
  • 使用 Fault 异常:开启 MPU 的 BusFault 和 MemManage 异常,在 HardFault 中打印地址,快速定位。
  • 参考手册:STM32F4 参考手册的 MPU 章节和 ARM Cortex-M4 编程指南,务必细读。

通过本次排查,深刻体会到 MPU 配置并非“开箱即用”,需结合外设特性。希望本文能助你避开此坑,让 SDRAM 稳定运行。