STM32 Flash 写入导致多个 HardFault 错误

Posted

技术标签:

【中文标题】STM32 Flash 写入导致多个 HardFault 错误【英文标题】:STM32 Flash Write causes multiple HardFault Errors 【发布时间】:2021-07-29 08:24:51 【问题描述】:

我正在尝试将几个字节的数据写入 STM32F410CBT3 闪存扇区 4(大小为 64KB),我选择了这个扇区并假设它可以安全使用,因为代码约为 30KB(可能位于扇区中) 1 和 2)。微控制器的时钟速度为 100MHz(通过 PLL)。

这是我的 Flash 写入代码:

/* Programming in 16-bit words, so offset address of 0x04 should be enough */
#define FLASH_SECTOR4_BASEADDRESS                       ((uint32_t)0x8011000)
#define OFFSET                                          ((uint8_t)0x04)

#define ADDR0               FLASH_SECTOR4_BASEADDRESS                                           
#define ADDR1               ((uint32_t)(ADDR0 + OFFSET))
#define ADDR2               ((uint32_t)(ADDR1 + OFFSET))
#define ADDR3               ((uint32_t)(ADDR2 + OFFSET))
/* and so on... */


void FLASH_Init(void)

    /* Only use FLASH Sector 4 for storing configuration/calibration data. s_EraseInit is stored as a static variable. This function called first because s_EraseInit needs to have values before any FLASH functions/routines are called. */
    s_EraseInit.TypeErase = TYPEERASE_SECTORS;
    s_EraseInit.Sector = FLASH_SECTOR_4;
    s_EraseInit.NbSectors = 1;
    s_EraseInit.VoltageRange = VOLTAGE_RANGE_4; /* Input voltage to mcu is around 3.3V */



void FLASH_Write(void)

    /* Stop LPTIM1 interrupts prior to modifying FLASH region */
    HAL_LPTIM_Counter_Stop_IT(&hlptim1);

    /* Assign temp_x values of struct members stored globally. temp_x will be the variable used for the FlashProgram function */
    uint16_t temp0 = GlobalStruct[0].Member1;
    uint16_t temp1 = GlobalStruct[0].Member2;
    uint16_t temp2 = GlobalStruct[0].Member3;
    uint16_t temp3 = GlobalStruct[1].Member1;
    uint16_t temp4 = GlobalStruct[1].Member2;
    uint16_t temp5 = GlobalStruct[1].Member3;
    uint16_t temp6 = GlobalStruct[2].Member1;
    uint16_t temp7 = GlobalStruct[2].Member2;
    uint16_t temp8 = GlobalStruct[2].Member3;
    uint16_t temp9 = GlobalStruct[3].Member1;
    uint16_t temp10 = GlobalStruct[3].Member2;
    uint16_t temp11 = GlobalStruct[3].Member3;

    /* Unlock FLASH peripheral and clear FLASH status register (error) flags */
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP|FLASH_FLAG_OPERR|FLASH_FLAG_WRPERR|FLASH_FLAG_PGAERR|FLASH_FLAG_PGSERR);
    
    /* Mass erase FLASH sector 4 */
    FlashStatus[12] = HAL_FLASHEx_Erase(&s_EraseInit, &s_SectorError);

    /* Write into FLASH sector 4 and lock the FLASH peripheral after using it */
    FlashStatus[0] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR0, temp0);
    FlashStatus[1] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR1, temp1);   
    FlashStatus[2] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR2, temp2);   
    FlashStatus[3] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR3, temp3);
    FlashStatus[4] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR4, temp4);
    FlashStatus[5] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR5, temp5);
    FlashStatus[6] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR6, temp6);
    FlashStatus[7] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR7, temp7);
    FlashStatus[8] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR8, temp8);
    FlashStatus[9] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR9, temp9);
    FlashStatus[10] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR10, temp10);
    FlashStatus[11] = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, ADDR11, temp11);

    HAL_FLASH_Lock();
    
    /* Resume LPTIM1 interrupts after modifying FLASH region */
    HAL_LPTIM_Counter_Start_IT(&hlptim1, LPTIM1_AUTORELOAD_NUMBER);     


/*********************** Relevant code from the HAL Library *******************************/

/**
  * @brief  Program byte, halfword, word or double word at a specified address
  * @param  TypeProgram  Indicate the way to program at a specified address.
  *                           This parameter can be a value of @ref FLASH_Type_Program
  * @param  Address  specifies the address to be programmed.
  * @param  Data specifies the data to be programmed
  * 
  * @retval HAL_StatusTypeDef HAL Status
  */
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)

  HAL_StatusTypeDef status = HAL_ERROR;
  
  /* Process Locked */
  __HAL_LOCK(&pFlash);
  
  /* Check the parameters */
  assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram));
  
  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE);
  
  if(status == HAL_OK)
  
    if(TypeProgram == FLASH_TYPEPROGRAM_BYTE)
    
      /*Program byte (8-bit) at a specified address.*/
      FLASH_Program_Byte(Address, (uint8_t) Data);
    
    else if(TypeProgram == FLASH_TYPEPROGRAM_HALFWORD)
    
      /*Program halfword (16-bit) at a specified address.*/
      FLASH_Program_HalfWord(Address, (uint16_t) Data);
    
    else if(TypeProgram == FLASH_TYPEPROGRAM_WORD)
    
      /*Program word (32-bit) at a specified address.*/
      FLASH_Program_Word(Address, (uint32_t) Data);
    
    else
    
      /*Program double word (64-bit) at a specified address.*/
      FLASH_Program_DoubleWord(Address, Data);
    
    
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE);
    
    /* If the program operation is completed, disable the PG Bit */
    FLASH->CR &= (~FLASH_CR_PG);  
  
  
  /* Process Unlocked */
  __HAL_UNLOCK(&pFlash);
  
  return status;

关于上面的代码,全局结构的成员要么是 8 位的,要么是 16 位的。我在进入闪存写入序列之前停止了 LPTIM1 中断,因为我认为如果 LPTIM1 中断恰好在微控制器覆盖其闪存时发生,它可能会导致问题。

问题

即使一个非常相似的代码(不同之处在于写入的数据)在不同的 STM32F410CBT3 微控制器上工作,我也一直在此代码上遇到 HardFault 错误。我还匹配了 MDK-ARM IROM1: Start 0x8000000, Size 0x1C000 上的链接器设置,之前它位于 IROM1: Start 0x8000000, Size 0x20000。我观察到的常见模式是,基于 MDK-ARM 的内存查看器,闪存被擦除,其中?? 出现在所有内存块上。之后,它再次转到0xFF,表明没有写入任何块/节。此时,代码已经在其 HardFault Handler 中。如果代码正常工作,扇区将从?? 变为写入闪存的任何值。

关于 HardFault 错误,它有时显示为 内存管理错误,其中引发了 IACCVIOL(指令访问冲突标志),而 SCB->MMFAR 不包含任何地址。在大多数情况下,它显示为 Bus Fault,其中 SCB->BFAR = 0xFFFFFFFFBFARVALID 标志高,PRECISERR 标志也被提高。我查看了 STM32F410 内存映射,地址 0xFFFFFFFF 指向 512 MB 块 7 Cortex-M4 的内部外设。在极少数情况下,它显示为 Usage Fault,其中UNDEFINSTR 位为高。有时,Flash 写入序列确实有效,但前提是使用断点。

我尝试了什么

最初,我立即将结构成员放入HAL_FLASH_Program API。基于 UsageFault 错误,我认为结构成员访问速度太慢,所以我声明了那些无符号 16 位 tempX 变量来保存结构成员的值。错误仍然存​​在,也许这不是原因,因为编译器可能会优化它。我也尝试过使用延迟,但错误仍然存​​在。 我将 16 位 tempX 变量声明更改为 uint32_t,因为我在某处读到输入需要是 32 位或 64 位。我将其更改为uint32_t 声明,但错误仍然存​​在。 我也尝试使用FLASH_TYPEPROGRAM_WORD 代替FLASH_TYPEPROGRAM_HALFWORD,但错误仍然存​​在。在不同的微控制器上,我注意到这不会产生太大影响,因为如果我要在 Flash 部分本身写入一个 16 位值(例如0xAAAA),它会显示为0x0000AAAA,如果使用了FLASH_TYPEPROGRAM_WORD,如果使用了FLASH_TYPEPROGRAM_HALFWORD,则使用0xFFFFAAAA,因为左侧16 位被清除为0xFFFF,但没有重写为0x0000,因为只有最少的16 位被覆盖。 最初,我认为写0xFFFF 会导致问题,但在我确保所有结构成员都是非零或非FFFF 后,错误仍然存​​在。在不同的微控制器上,我仍然可以将0xFFFF 写入闪存而不会导致任何错误。 我也尝试使用其他位置(尽管仍位于第 4 区),但错误仍然存​​在。 我验证了每当发生 HardFault 错误时,FlashStatus[x], x = 0...12 仅包含 HAL_OKFLASH->SR 寄存器通常也不显示任何内容(无错误状态)。 变量s_SectorError通常有0xFFFFFFFF,表示成功擦除所有扇区(Sector 4)

问题 我在这里想念什么?任何帮助将不胜感激。我也不知道如何更深入地调试这个问题,任何调试这个问题的提示也将不胜感激。 This is the Keil uVision's page on Fault Reporting Guide,我将其用作识别 HardFault 错误的参考。谢谢!

【问题讨论】:

您是否通过单步调试会话确定了导致故障的代码行? 是的 - 如果我确实在调用 HAL_FLASH_Program() 的每一行设置断点,它似乎工作(虽然有时它确实工作,但将错误的值写入某些地址) .如果我只在HAL_FLASH_Lock()HAL_FLASH_Unlock() 设置断点,它有时会工作,有时不会,然后会引发 HardFault 错误之一。在我最近的尝试中,我只收到了 Bus Fault 错误,没有遇到 Usage Fault 错误或 Memory Manage Fault 错误。 @Tagli 我的意思是说不,我还没有找到导致它的确切行.. 你可以使用 GPIO 代替断点来找到你得到硬故障的确切位置吗? 我不知道这部分,但我可以放弃一些编写闪存驱动程序的一般提示和技巧。禁用所有中断。不要从您正在编程的银行执行闪存驱动程序。确保闪存预分频时钟正确。禁用看门狗或将其设置得足够长,以免在擦除周期内发出声音。弄错任何一个,你都会得到非常微妙的错误。 【参考方案1】:

我在查看 HAL 文件时错误地使用了 VOLTAGE_RANGE_4FLASH_VOLTAGE_RANGE_4 而不是 FLASH_VOLTAGE_RANGE_3。 HAL 文件显示这两个选项都适用于由 2.7V - 3.6V 供电的微控制器,但两个选项之间的区别在于电压范围 4 的外部 Vpp。使用FLASH_VOLTAGE_RANGE_3 实际上解决了我的问题 ,意思是我原来的问题是错误擦除闪存引起的,而不是因为我对闪存进行了错误的编程。

我最初选择VOLTAGE_RANGE_4是因为我在阅读参考手册中的这个页面时误解了电压范围:

虽然下面的这一部分实际上告诉我应该使用哪个电压范围,因为 HAL 库中提到的电压范围对应于 FLASH_CR->PSIZE 位:

为什么每次尝试都会引发不同的 HardFault 错误,以及为什么需要以某种方式(64 位擦除/32 位擦除/16-位擦除/ 8位擦除)取决于电源电压。有时它如何工作也很奇怪,但在配置错误的情况下大部分时间都无法工作。

【讨论】:

以上是关于STM32 Flash 写入导致多个 HardFault 错误的主要内容,如果未能解决你的问题,请参考以下文章

stm32 flash半页写入

了解STM32参考手册中的写入flash流程

基于STM32的Flash读写详解

基于STM32的Flash读写详解

将全局声明的缓冲区写入 FLASH 时出现 STM32 Hardfault 异常

如何在 STM32F4、Cortex M4 上写入/读取 FLASH