一、问题背景与现象
某工业控制项目中,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 方案选择
根据应用场景,有三种主流方案:
- 禁用 D-Cache:最简单,但失去缓存加速,性能损失大。
- 维护 Cache 一致性:在关键操作前后手动 Clean/Invalidate,适合低频访问。
- 配置 MPU 将 SDRAM 区域设为 Non-cacheable:彻底避免一致性问题,适合高频访问且对一致性要求高的场景。
本案例采用方案 3,因为图像数据频繁读写,且 DMA 交互多。
4.2 配置 MPU 步骤
-
使能 MPU:在系统初始化时调用
MPU_Config()。 - 设置区域:将 SDRAM 基地址(如 0xC0000000)配置为 Normal memory,属性为 Non-cacheable。
- 设置权限:允许特权和非特权访问。
- 使能区域:确保区域号有效。
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->RBAR和MPU->RASR验证。
五、验证与总结
修复后,运行压力测试 72 小时,未再出现数据错乱。DMA 传输稳定,图像显示正常。
5.1 经验总结
- 在 STM32F4 系列中使用 SDRAM 时,务必考虑 D-Cache 一致性。
- 优先使用 MPU 将 SDRAM 配置为 Non-cacheable,简单可靠。
- 若需保留 Cache 性能,则必须严格管理 Clean/Invalidate 操作,并注意 Cache 行对齐。
- 建议在项目初期就规划好内存属性和 Cache 策略,避免后期返工。
六、扩展思考
对于更复杂的场景,如双核或使用 LTDC 时,可考虑使用 Cache 维护操作或硬件缓冲。但最稳妥的方案仍是合理配置 MPU,从根源上消除一致性问题。希望本文能帮助开发者少走弯路,构建更稳定的嵌入式系统。