# 一、问题现象与初步定位 某产品基于 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 的 `CAS` 和 `RAS` 信号持续拉低,总线处于死锁状态。 # 四、正确配置 MPU 的步骤 以 STM32F4 标准库为例,正确配置 SDRAM 区域(地址 0xC0000000,大小 8MB): ```c /* 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 维护操作: ```c SCB_EnableDCache(); // 区域配置改为:TEX=001, IsCacheable=ENABLE, IsBufferable=ENABLE // 并在 DMA 访问前调用 SCB_CleanDCache_by_Addr() ``` # 五、完整代码示例(含初始化) ```c 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 稳定运行。