You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F302擦除0x08000000 Flash页触发IACCVIOL硬故障问题

STM32F302擦除低地址Flash触发IACCVIOL HardFault问题

问题描述

在STM32F302RBT6平台上尝试擦除Flash起始地址为0x08000000的Bootloader区域,该区域共2个2KB大小的Flash页;应用代码起始地址为0x08001000,理论上擦除操作不会影响应用运行。实际测试时,擦除函数返回后立即触发携带*IACCVIOL(指令访问违规)*标志的HardFault,程序无法执行到main函数末尾的while(1)死循环。

核心复现代码

int main (void)
{
  HAL_Init();
  ClockInit();

  APP_MEM_EraseBootloaderSectorInFlash();
  while(1);
}

unsigned char APP_MEM_EraseBootloaderSectorInFlash(void) 
{
  unsigned char result = 0;

  __disable_irq();
  HAL_FLASH_Unlock();
  result = ErasePageinFlash(0x08000000);
  result = ErasePageinFlash(0x08000800);
  HAL_FLASH_Lock();
   __enable_irq();

  return result;
}

static unsigned char ErasePageinFlash(uint32_t page_address)
{
  unsigned char          result = 0;
  uint32_t               page_error;
  FLASH_EraseInitTypeDef erase_init;

  erase_init.TypeErase   = FLASH_TYPEERASE_PAGES;
  erase_init.NbPages     = 1u;
  erase_init.PageAddress = page_address;

  if (HAL_FLASHEx_Erase(&erase_init, &page_error) == HAL_OK)
  {
    result = 1;
  }

  return result;
}

已完成排查项

  • 擦除起始地址为0x08003000的2个未被应用占用的Flash页时,操作正常完成,无故障
  • 调试器单步运行代码时,故障不会复现,程序可正常执行到while(1)
  • 尝试降低系统时钟、增大Flash访问延迟,故障仍存在
  • 将main函数中的while(1)移动到APP_MEM_EraseBootloaderSectorInFlash函数内部时,擦除操作正常完成,无故障

时钟配置代码

static void ClockInit(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
  while (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    // 故障处理逻辑待补充
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  while (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    // 故障处理逻辑待补充
  }
}

故障截图

  • 故障报告截图:故障寄存器报告
  • 故障调用栈截图:HardFault调用栈

故障根因

该问题由Cortex-M4内核的Flash预取指机制导致:
STM32F3系列的Flash预取缓冲(Prefetch Buffer)会在CPU执行指令时,提前从连续Flash地址读取后续指令填充流水线,不会严格限制在当前运行代码的地址范围内。擦除0x08000000起始的Bootloader页时,对应页会被擦为全0xFF(无效Cortex-M指令),预取单元在擦除过程中、以及擦除完成后函数返回的流程里,会提前读取到这部分无效指令,当无效指令被送入译码执行单元时,就会触发IACCVIOL指令访问违规,进入HardFault。

这个根因完全匹配所有排查到的现象:

  • 擦除高地址0x08003000区域时,预取扫描范围不会覆盖到该段地址,因此无故障
  • 单步调试时CPU流水线被调试器暂停,不会提前预取指令,因此故障不触发
  • 调整时钟、Flash等待周期不改变预取逻辑,因此无法解决问题
  • 擦除完成后直接在擦除函数内进入死循环,不会触发函数返回后的指令流跳转,预取不会读到无效内容,因此无故障

解决方案

两种方案选其一即可:

  1. 将Flash擦除相关代码重定位到RAM运行:这是最通用稳妥的方案,彻底避免擦除Flash期间内核从Flash取指产生的冲突。给擦除相关函数增加__attribute__((section(".ramfunc")))修饰,在链接脚本中将对应段分配到RAM区域,确保函数执行前已经被加载到RAM中即可。
  2. 擦除前临时关闭Flash预取缓冲:在调用擦除函数前调用__HAL_FLASH_PREFETCH_BUFFER_DISABLE()关闭预取,擦除完成、Flash重新锁定后再调用__HAL_FLASH_PREFETCH_BUFFER_ENABLE()恢复即可。该方法实现简单,短时间关闭预取带来的性能损耗对擦除操作几乎无影响。

额外注意:如果后续需要开启中断,必须先将向量表偏移到应用起始地址0x08001000,在main开头增加SCB->VTOR = FLASH_BASE + 0x1000;配置即可,否则开中断后会因为向量表在已擦除的Bootloader区域再次触发故障。


内容的提问来源于stack exchange,提问作者Leonardo Castilho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:36:07