• 正文
  • 相关推荐
申请入驻 产业图谱

STM32H5 FLASH擦写HardFault解决:RO区域不可缓存,ICACHE需关或MPU配置

23小时前
125
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

STM32H5(如 H503RB)擦写 FLASH 时触发 HardFault,核心原因是开启 ICACHE(指令缓存)后读取了 FLASH 的 RO(只读)区域(如 FLASHSIZE_BASE)——RO 区域默认不可缓存,缓存访问会引发总线错误。解决方案有两种:擦写前后关闭 / 重启 ICACHE,或通过 MPU 将 RO 区域设为 Non-cacheable,两种方案均可彻底规避异常。

资料获取:开发经验 | LAT1579 STM32H5擦写FLASH时意外触发hardfault

1. 问题背景:HardFault 的诡异触发场景

客户基于 STM32H503RB 开发,遇到关键异常:

  1. 异常现象:使能 ICACHE 以提升代码效率,在 FLASH 擦写前调用GetSector函数(通过地址查找扇区)时,意外触发 HardFault;
  2. 正常情况:关闭 ICACHE 后,FLASH 擦写、扇区查找均正常;
  3. 核心环境:STM32H503RB(双 bank FLASH,支持 read-while-write)、STM32CubeH5 例程 “FLASH_EraseProgram”、ICACHE 使能。

2. 问题复现:3 步必现异常

2.1 基础配置

  • 基于官方 “FLASH_EraseProgram” 例程,保留 FLASH 擦写核心逻辑;
  • main函数开头使能 ICACHE:
    if (HAL_ICACHE_Enable() != HAL_OK) {
      Error_Handler();
    }
    

2.2 关键修改(触发异常)

  • 注释例程中 “擦写前关闭 ICACHE、擦写后重启” 的代码;
  • 调用GetSector函数查找目标地址对应的扇区:
    uint32_t FirstSector = GetSector(FLASH_USER_START_ADDR); // 触发HardFault
    

2.3 定位关键触发点

  • 单步调试发现:HardFault 触发在读取FLASHSIZE_BASE(0x08FFF80CUL)时;
  • 该地址属于 FLASH 的 RO 区域,ICACHE 开启时缓存访问导致总线错误。

3. 根源拆解:RO 区域的缓存访问禁忌

3.1 RO 区域的定义与特性

根据 STM32H5 参考手册,FLASH 的 RO 区域范围为0x08FFF800~0x08FFFFFF,包含多个关键寄存器地址:

地址 定义 用途
0x08FFF800UL UID_BASE 唯一设备 ID 寄存器
0x08FFF80CUL FLASHSIZE_BASE FLASH 容量数据寄存器
0x08FFF80EUL PACKAGE_BASE 封装信息寄存器
  • RO 区域通过 AHB 主接口访问,默认不可缓存
  • 手册明确要求:RO/OTP 区域需禁用缓存,否则需通过 MPU 配置 Non-cacheable 属性。

3.2 异常触发逻辑

  • ICACHE 开启后,CPU 会缓存访问过的指令 / 数据,当读取 RO 区域时,缓存访问与 RO 区域的 “不可缓存” 属性冲突;
  • 冲突引发总线错误,触发 Cortex-M33 内核的 HardFault 中断,且异常并非直接发生在 FLASH 擦写函数,而是前置的扇区查找(读取 FLASHSIZE_BASE)。

4. 解决方案:两种落地方法,按需选择

方法 1:擦写 FLASH 前后关闭 / 重启 ICACHE(简单直接)

核心逻辑:暂时关闭 ICACHE 规避 RO 区域访问冲突,擦写完成后重新开启以恢复性能,适配大多数场景。

核心代码示例

// FLASH擦写函数(优化后)
void FLASH_Erase_Program_With_ICACHE_Control(void) {
  uint32_t FirstSector = 0, NbOfSectors = 0;
  FLASH_EraseInitTypeDef EraseInitStruct = {0};
  uint32_t SectorError = 0;

  // 步骤1:擦写前关闭ICACHE
  if (HAL_ICACHE_Disable() != HAL_OK) {
    Error_Handler();
  }

  // 步骤2:查找扇区(此时读取RO区域无缓存冲突)
  FirstSector = GetSector(FLASH_USER_START_ADDR);
  NbOfSectors = GetSector(FLASH_USER_END_ADDR) - FirstSector + 1;
  EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
  EraseInitStruct.Banks = FLASH_BANK_1;
  EraseInitStruct.Sector = FirstSector;
  EraseInitStruct.NbSectors = NbOfSectors;
  EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;

  // 步骤3:执行FLASH擦写、编程
  if (HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) {
    Error_Handler();
  }
  // (编程逻辑省略,按实际需求添加)

  // 步骤4:擦写完成后重新开启ICACHE
  if (HAL_ICACHE_Enable() != HAL_OK) {
    Error_Handler();
  }
}

方法 2:MPU 配置 RO 区域为 Non-cacheable(无需关 ICACHE)

核心逻辑:通过 MPU 将 RO 区域(0x08FFF800~0x08FFFFFF)标记为 Non-cacheable,ICACHE 可持续开启,兼顾性能与兼容性,适合需要全程启用缓存的场景。

核心代码示例(MPU 配置)

void MPU_RO_Region_Config(void) {
  MPU_Region_InitTypeDef mpu_init = {0};

  // 步骤1:配置前禁用MPU
  HAL_MPU_Disable();

  // 步骤2:配置RO区域(0x08FFF800~0x08FFFFFF)
  mpu_init.Enable = MPU_REGION_ENABLE;
  mpu_init.BaseAddress = 0x08FFF800; // RO区域起始地址
  mpu_init.Size = MPU_REGION_SIZE_256B; // 区域大小(覆盖RO全范围)
  mpu_init.AccessPermission = MPU_REGION_FULL_ACCESS; // 读写权限
  mpu_init.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; // 禁用缓冲
  mpu_init.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 关键:禁用缓存
  mpu_init.IsShareable = MPU_ACCESS_NOT_SHAREABLE; // 非共享
  mpu_init.TypeExtField = MPU_TEX_LEVEL0; // 匹配RO区域属性
  mpu_init.SubRegionDisable = 0x00; // 启用所有子区域
  mpu_init.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; // 禁止执行指令

  // 步骤3:应用MPU配置并重新启用
  HAL_MPU_ConfigRegion(&mpu_init);
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

使用说明

  • main函数开头先配置 MPU,再启用 ICACHE:
    MPU_RO_Region_Config(); // 先配置MPU
    if (HAL_ICACHE_Enable() != HAL_OK) { // 再启用ICACHE
      Error_Handler();
    }
    
  • 后续 FLASH 擦写、扇区查找无需关闭 ICACHE,直接执行即可。

5. 关键注意:RO 区域全量地址表

除 FLASHSIZE_BASE 外,以下地址均属于 RO 区域,访问时需禁用缓存:

地址 宏定义 功能描述
0x08FFF800UL UID_BASE 唯一设备标识符(96 位)
0x08FFF80CUL FLASHSIZE_BASE FLASH 容量寄存器(16 位)
0x08FFF80EUL PACKAGE_BASE 封装类型寄存器(16 位)
0x08FFF810UL RESERVED 保留 RO 区域
0x08FFF800~0x08FFFFFF - 完整 RO 区域范围

6. 开发经验小结

  1. RO/OTP 区域禁忌:STM32H5 的 RO、OTP 区域默认不可缓存,开启 ICACHE 后直接访问必触发 HardFault;
  2. 排查技巧:带 Cache 的 STM32 型号(H5/H7/G4 等)遇到不明 HardFault,可先关闭 ICACHE 排查 —— 缓存访问冲突是高频诱因;
  3. 方案选择:简单场景用 “关闭 / 重启 ICACHE”,性能敏感场景用 “MPU 配置”,后者无需中断缓存功能,更适合持续高性能需求;
  4. 双 bank 特性:STM32H5 的双 bank FLASH 支持 read-while-write,但不改变 RO 区域的缓存访问限制,不可混淆。

相关推荐