引言
在嵌入式 GUI 开发中,STM32F4 配合外部 SDRAM 作为显存是常见方案,尤其当分辨率较高时。然而,不少开发者会遇到图像撕裂现象:屏幕上半部分显示新帧,下半部分仍为旧帧。多数人会先怀疑 LTDC 时序或 DMA 传输,但忽略了一个关键环节——MPU 配置。Cortex-M4 内核的 MPU 若未正确设置,会导致 SDRAM 区域被默认属性(如不可缓存、不可缓冲)访问,进而引发读写不一致,最终表现为撕裂。
原理:MPU 如何影响 SDRAM 显存访问
MPU 是 Cortex-M4 内核提供的内存保护机制,它除了权限控制外,还定义了每个内存区域的缓存(Cacheable)、缓冲(Bufferable)和可共享(Shareable)属性。对于 SDRAM 这类外部存储器,默认情况下(未配置 MPU)会被视为不可缓存、不可缓冲,这意味着每次 CPU 读写都直接访问 SDRAM,而 LTDC 控制器(DMA 方式)也在访问同一块内存。
问题在于:当 CPU 写入显存(例如绘制像素)时,由于没有写缓冲,写入操作可能被拆分为多个总线周期,而 LTDC 读取显存是连续突发传输。若 CPU 写入尚未完成,LTDC 已读取到旧数据,就会产生撕裂。更严重的是,若启用了数据缓存(D-Cache)但未配置 MPU 为 Write-Back,则缓存与 SDRAM 内容不一致,导致 LTDC 读到过期数据。
因此,正确配置 MPU 区域,将 SDRAM 显存区域设置为 Write-Back、Write-Allocate 或 Write-Through 属性,并确保缓存一致性,是解决撕裂的关键。
配置步骤
1. 确定 SDRAM 地址范围
STM32F4 的 FMC 控制器将 SDRAM 映射到 Bank1 或 Bank2,典型地址如 0xC0000000(Bank1)或 0xD0000000(Bank2)。以 4MB SDRAM 为例,范围是 0xC0000000 ~ 0xC03FFFFF。
2. 初始化 MPU 区域
使用 CMSIS 提供的 MPU_Region_InitTypeDef 结构体配置。关键参数:
-
BaseAddress:SDRAM 基地址 -
Size:区域大小(需为 2 的幂,且不小于 32 字节) -
Enable:使能该区域 -
AccessPermission:设为全权限(MPU_REGION_FULL_ACCESS) -
TypeExtField:设为MPU_TEX_LEVEL1(表示 TEX=1) -
IsCacheable:设为MPU_ACCESS_CACHEABLE -
IsBufferable:设为MPU_ACCESS_BUFFERABLE -
IsShareable:设为MPU_ACCESS_NOT_SHAREABLE(SDRAM 非共享)
注意:TypeExtField 与 IsCacheable/IsBufferable 组合决定实际缓存策略。推荐组合:TEX=1, C=1, B=1 表示 Write-Back, Write-Allocate,适合显存场景。
3. 使能 MPU 和 D-Cache
配置后需调用 MPU_Enable() 和 SCB_EnableDCache()。注意顺序:先配置 MPU,再使能 D-Cache,否则缓存策略不生效。
4. 维护缓存一致性
即使配置了 Write-Back,CPU 写入后数据可能仍在缓存中,LTDC 访问 SDRAM 时需确保缓存已回写。因此,在每帧绘制完成后,需调用 SCB_CleanDCache() 或 SCB_CleanDCache_by_Addr() 将脏缓存行写回 SDRAM。
完整代码示例
以下基于 STM32F429 和 HAL 库,展示 MPU 初始化及显存刷新流程。
// MPU 配置结构体
MPU_Region_InitTypeDef MPU_InitStruct;
void MPU_Config(void)
{
// 禁用 MPU 进行配置
HAL_MPU_Disable();
// 配置 SDRAM 区域(假设基地址 0xC0000000,大小 4MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_4MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
// 在 main 函数中调用
int main(void)
{
HAL_Init();
SystemClock_Config();
// 先配置 MPU,再使能 D-Cache
MPU_Config();
SCB_EnableDCache();
// 初始化 SDRAM 和 LTDC(略)
// ...
while (1)
{
// 绘制一帧到显存(例如使用 GUI 库)
DrawFrame((uint32_t*)0xC0000000);
// 确保缓存数据回写到 SDRAM
SCB_CleanDCache();
// 等待 LTDC 垂直消隐,然后切换显示(可选)
// ...
}
}
排查方法
若已按上述配置仍出现撕裂,可按以下步骤排查:
- 检查 MPU 是否生效:在调试器中查看 MPU 寄存器(如 MPU_RBAR、MPU_RASR),确认区域使能且属性正确。
-
验证缓存回写:在绘制后、切换帧前,调用
SCB_CleanDCache(),若撕裂消失,说明是缓存一致性问题。 -
尝试不同缓存策略:将 TypeExtField 改为
MPU_TEX_LEVEL0,并设置 C=1, B=0(Write-Through),可避免回写延迟,但性能略降。 - 检查 LTDC 配置:确保 LTDC 的 DMA 突发长度与 SDRAM 总线宽度匹配(如 32 位),并启用 FIFO 阈值。
- 使用双缓冲:在 SDRAM 中分配两个帧缓冲区,绘制一个,显示另一个,利用垂直消隐切换,彻底避免撕裂。
注意事项
- MPU 区域大小必须为 2 的幂,且最小 32 字节,否则配置无效。
- 使能 D-Cache 前必须先配置 MPU,否则默认属性可能导致不可预知行为。
- SDRAM 初始化必须在 MPU 配置之后,因为 MPU 区域属性会影响 FMC 访问时序。
- 若使用 DMA2D 进行图形加速,其访问不经过 MPU,但 CPU 写入后仍需缓存回写,否则 DMA2D 可能读到旧数据。
- 在 RTOS 环境下,多任务访问显存时需加锁,并确保缓存操作在临界区执行。
总结
图像撕裂问题往往不是单一原因,但 MPU 配置不当是 STM32F4 使用 SDRAM 显存时的常见陷阱。通过正确设置 MPU 区域为 Write-Back 策略,并在帧绘制后主动回写缓存,可以显著提升显示稳定性。本文提供的配置代码和排查思路,希望能帮助开发者快速定位问题,让 GUI 显示如丝般顺滑。