引言
STM32H7 系列(如 STM32H743/750)搭载 Cortex-M7 内核,主频高达 480MHz,配备 L1-Cache(I-Cache 和 D-Cache)以及可配置的 MPU(Memory Protection Unit)。高性能带来红利的同时,也引入了新的挑战:如果软件未正确管理 Cache 与 MPU,系统会在高负载或随机时刻出现死机、数据异常,且难以复现。本文基于一次实战排查,分享如何通过 MPU 配置解决 Cache 未命中导致的随机死机问题。
问题现象
某项目使用 STM32H743,主频 480MHz,外部 SDRAM 存储图像数据,内部 SRAM 运行关键代码。系统运行数分钟后随机死机,死机时 PC 指针常指向 SDRAM 中的代码段或数据访问指令。通过调试器查看,发现死机位置不固定,且开启 D-Cache 后概率增加,关闭 D-Cache 后问题消失。初步怀疑与 Cache 一致性有关。
原理分析
1. Cortex-M7 的 Cache 与总线架构
Cortex-M7 内核通过 AXI 总线与存储器交互,内部集成 I-Cache 和 D-Cache。Cache 用于缓存主存(如 Flash、SDRAM)中的数据,以加速访问。但 Cache 与主存之间的一致性由硬件维护,仅当访问区域被配置为“可缓存”时才会启用。若某个内存区域被 DMA 外设或另一总线主机(如 LTDC、MDMA)修改,而 CPU 的 D-Cache 中仍保留旧数据,则 CPU 读到的可能是过期数据,反之亦然。
2. MPU 的作用
MPU 允许将内存空间划分为多个区域,并设置每个区域的 Cache 策略(Write-Back/Write-Through)、是否可执行、访问权限等。默认情况下,STM32H7 的 MPU 是关闭的,所有区域按默认属性(可能为 Write-Back)处理,这可能导致非预期的缓存行为。
3. 随机死机的根因
在本案例中,SDRAM 被用作帧缓冲,由 LTDC(LCD 控制器)和 DMA2D 频繁读写。若 SDRAM 区域被配置为 Write-Back 且未在 CPU 访问前执行 Clean/Invalidate 操作,则 CPU 可能从 Cache 读取到旧数据,或写入的数据未及时同步到 SDRAM,导致显示错乱。更严重的是,若代码从 SDRAM 执行(XIP),而 I-Cache 未正确配置,则可能取指错误,产生 HardFault。
解决方案:基于 MPU 的配置
1. 内存区域划分
根据项目需求,将内存划分为以下区域:
- 内部 Flash(0x08000000):代码,可缓存,Write-Through(或 Write-Back,但需保证无 DMA 写)。
- 内部 SRAM(0x20000000):变量,可缓存,Write-Back,但需注意 DMA 访问时的一致性。
- 外部 SDRAM(0xC0000000):帧缓冲,不可缓存或 Write-Through,并关闭 Cache 或使用 MPU 强制非缓存。
- 外设寄存器(0x40000000 等):不可缓存,需配置为 Device 或 Strongly-Ordered。
2. MPU 配置步骤
- 步骤1:使能 MPU 并配置区域。
- 步骤2:设置每个区域的基地址、大小、访问权限和缓存属性。
- 步骤3:使能 MPU 和 Cache。
3. 完整代码示例
以下代码基于 STM32H7 HAL 库,展示如何配置 MPU 使 SDRAM 区域不可缓存:
#include "stm32h7xx_hal.h"
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 禁用 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.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
int main(void)
{
HAL_Init();
SystemClock_Config(); // 配置 480MHz 时钟
MPU_Config();
// 使能 D-Cache 和 I-Cache(注意:需在 MPU 配置后使能)
SCB_EnableDCache();
SCB_EnableICache();
// 初始化 SDRAM、LTDC 等外设...
// 应用代码...
}
4. 关键点说明
- 对于 SDRAM 帧缓冲,建议使用
MPU_ACCESS_NOT_CACHEABLE和MPU_ACCESS_NOT_BUFFERABLE,确保 CPU 直接访问 SDRAM,避免 Cache 一致性问题。 - 对于内部 SRAM 中需要 DMA 访问的缓冲区,可配置为 Write-Through 或使用
SCB_CleanDCache/SCB_InvalidateDCache手动维护。 - 若代码在 SDRAM 中执行,需将对应区域设置为可执行(
DisableExec设为MPU_INSTRUCTION_ACCESS_ENABLE),并建议使用 Write-Through 或非缓存,以避免 I-Cache 问题。
验证与效果
配置 MPU 后,系统连续运行 72 小时未再出现死机。通过逻辑分析仪和调试器确认,SDRAM 访问延迟略有增加(因关闭缓存),但帧率未受影响,且显示正常。若需提升性能,可仅将帧缓冲的写入区域设为 Write-Back,并在每次 CPU 写入后执行 Clean 操作,但需权衡复杂度。
注意事项
- MPU 配置必须在使能 Cache 之前完成,否则 Cache 可能使用默认属性。
- 每个 MPU 区域的大小必须是 2 的幂次方,且基地址需对齐。
- 不要将整个内存空间配置为不可缓存,否则性能会大幅下降。
-
使用 DMA 时,务必检查缓冲区是否与 Cache 冲突,必要时使用
HAL_DMA的缓存维护函数。 - 调试时,可临时关闭 D-Cache 来验证问题是否与 Cache 相关,但最终需正确配置 MPU。
结语
STM32H7 的 480MHz 性能需要精细的内存管理才能发挥。MPU 配置是解决 Cache 一致性问题的关键,也是嵌入式开发者进阶的必修课。希望本文的实战经验能帮助大家少走弯路,让系统稳定运行在高速时钟下。