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 = 0xFFFFFFFF
的 BFARVALID
标志高,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_OK
。 FLASH->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_4
或 FLASH_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 错误的主要内容,如果未能解决你的问题,请参考以下文章