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) { // 故障处理逻辑待补充 } }
故障截图
- 故障报告截图:

- 故障调用栈截图:

故障根因
该问题由Cortex-M4内核的Flash预取指机制导致:
STM32F3系列的Flash预取缓冲(Prefetch Buffer)会在CPU执行指令时,提前从连续Flash地址读取后续指令填充流水线,不会严格限制在当前运行代码的地址范围内。擦除0x08000000起始的Bootloader页时,对应页会被擦为全0xFF(无效Cortex-M指令),预取单元在擦除过程中、以及擦除完成后函数返回的流程里,会提前读取到这部分无效指令,当无效指令被送入译码执行单元时,就会触发IACCVIOL指令访问违规,进入HardFault。
这个根因完全匹配所有排查到的现象:
- 擦除高地址
0x08003000区域时,预取扫描范围不会覆盖到该段地址,因此无故障 - 单步调试时CPU流水线被调试器暂停,不会提前预取指令,因此故障不触发
- 调整时钟、Flash等待周期不改变预取逻辑,因此无法解决问题
- 擦除完成后直接在擦除函数内进入死循环,不会触发函数返回后的指令流跳转,预取不会读到无效内容,因此无故障
解决方案
两种方案选其一即可:
- 将Flash擦除相关代码重定位到RAM运行:这是最通用稳妥的方案,彻底避免擦除Flash期间内核从Flash取指产生的冲突。给擦除相关函数增加
__attribute__((section(".ramfunc")))修饰,在链接脚本中将对应段分配到RAM区域,确保函数执行前已经被加载到RAM中即可。 - 擦除前临时关闭Flash预取缓冲:在调用擦除函数前调用
__HAL_FLASH_PREFETCH_BUFFER_DISABLE()关闭预取,擦除完成、Flash重新锁定后再调用__HAL_FLASH_PREFETCH_BUFFER_ENABLE()恢复即可。该方法实现简单,短时间关闭预取带来的性能损耗对擦除操作几乎无影响。
额外注意:如果后续需要开启中断,必须先将向量表偏移到应用起始地址0x08001000,在main开头增加SCB->VTOR = FLASH_BASE + 0x1000;配置即可,否则开中断后会因为向量表在已擦除的Bootloader区域再次触发故障。
内容的提问来源于stack exchange,提问作者Leonardo Castilho
相关产品推荐
相关产品推荐

