一、问题现象与初步定位
某产品基于 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 区域属性不一致,可能引发总线仲裁死锁。
三、排查流程与关键证据
- 复现并缩小范围:注释掉所有 DMA,仅用 CPU 循环写 SDRAM,故障仍现,排除 DMA。
-
检查 MPU 配置:发现使用
MPU_ConfigRegion时,参数MPU_REGION_CACHEABLE_BUFFERABLE被误设为ENABLE。 -
对比测试:将 SDRAM 区域改为
MPU_REGION_NO_CACHE(即 Strongly-ordered 或 Device 类型),故障消失。 -
逻辑分析仪抓取:在故障瞬间,SDRAM 的
CAS和RAS信号持续拉低,总线处于死锁状态。
四、正确配置 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 稳定运行。