STM32F4 SDRAM 总线死锁排查实录:MPU 配置不当引发的血案
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动外部 SDRAM 时,MPU(内存保护单元)配置不当可能导致总线死锁,系统卡死且难以调试。本文通过一个真实项目案例,深入剖析 MPU 区域属性(如 Cache、Buffer)与 SDRAM 时序的冲突原理,给出完整的排查流程、MPU 正确配置代码及注意事项,帮助开发者避免此类隐蔽故障。
# 一、问题现象与初步定位
某产品基于 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 稳定运行。