2025-11-18 6:01 AM
Hi,
I'm using an STM32H723 and store my configuration data in flash. Here The procedure is: erase the flash once, then write to the erased flash. On read check the flash for a magic word to find where data have been written. Next write happens the same way, it uses the magic word to find an unused part of the flash to write to (in order to save erase cycles). So data are written several times until a new erase is necessary.
Now sometimes I end up in the HardFault handler because of an illegal read:
void *Flash_Storage_Read(const size_t length)
{
uint32_t base_addr = FLASH_STORAGE_START_ADDR;
uint32_t final_addr = base_addr + length;
uint32_t *finalData=(uint32_t*)final_addr;
const uint8_t *flashID=(uint8_t*)final_addr;
while ((final_addr<FLASH_STORAGE_END_ADDR) &&
(*finalData!=0xFFFFFFFF)) -------> this read access causes the HardFault_Handler() to be called
{
base_addr+=length;
final_addr+=length;
finalData=(uint32_t*)final_addr;
}
return (void*)base_addr;
}The problem is, when this happens, it happens at different flash-addresses, so it is not a problem of the address range I'm using (and whenever it happens, it always fits to the range defined in .ld-file). So this lets me assume the problem is caused by my write function.
This is what it looks like (somewhat simplified):
uint8_t Flash_Storage_Write(const void* data,const size_t length)
{
uint32_t base_addr = FLASH_STORAGE_START_ADDR;
uint32_t final_addr = base_addr + length;
uint32_t *finalData=(uint32_t*)base_addr;
const uint8_t *flashID=(uint8_t*)final_addr;
while ((final_addr<FLASH_STORAGE_END_ADDR) &&
(*finalData!=0xFFFFFFFF))
{
base_addr+=length;
final_addr+=length;
finalData=(uint32_t*)base_addr;
}
if (final_addr>=FLASH_STORAGE_END_ADDR)
{
if (!Flash_Storage_Erase()) return false;
base_addr = FLASH_STORAGE_START_ADDR;
final_addr = base_addr + length;
}
if (final_addr>=FLASH_STORAGE_END_ADDR)
return 0;
__disable_irq();
HAL_FLASH_Unlock();
uint32_t write_addr = base_addr & ~(FLASHWORD_SIZE - 1);
uint32_t end_addr = (final_addr + FLASHWORD_SIZE - 1) & ~(FLASHWORD_SIZE - 1);
const uint8_t* src=(const uint8_t*)data;
__attribute__((aligned(32))) uint8_t flashword[FLASHWORD_SIZE];
while (write_addr < end_addr) {
memcpy(flashword, (const void*)write_addr, FLASHWORD_SIZE);
for (uint32_t i = 0; i < FLASHWORD_SIZE; ++i) {
uint32_t abs_index = write_addr + i;
if (abs_index >= base_addr && abs_index < final_addr) {
flashword[i] = src[abs_index - base_addr];
}
}
uint8_t needs_write =0;
for (int i = 0; i < FLASHWORD_SIZE; ++i) {
if (((uint8_t*)write_addr)[i] != flashword[i]) {
needs_write = 1;
break;
}
}
if (needs_write) {
const uint64_t* qw = (const uint64_t*)flashword;
HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, write_addr, (uint32_t)qw);
if (status != HAL_OK) {
HAL_FLASH_Lock();
return false;
}
}
write_addr += FLASHWORD_SIZE;
}
HAL_FLASH_Lock();
__enable_irq();
SCB_InvalidateDCache_by_Addr((uint32_t*)FLASH_STORAGE_START_ADDR,
FLASH_STORAGE_END_ADDR - FLASH_STORAGE_START_ADDR + 1);
return 1;
}Quick note: completely disabling and enabling the IRQs was just a test to find the reason for the troubles, it does not make a difference.
So...any idea what could be wrong here?
Thanks!
Solved! Go to Solution.
2025-11-18 6:39 AM - edited 2025-11-18 6:41 AM
> the magic word I use for finding the start address is 32 bits wide
Writes to flash must be a multiple of the flash page which is 256 bits. Once you write anywhere in a flash page, you can no longer write anywhere else in there.
The code you presented doesn't necessary do this, only if "length" is a multiple of 32. Is it?
> uint32_t write_addr = base_addr & ~(FLASHWORD_SIZE - 1);
> uint32_t end_addr = (final_addr + FLASHWORD_SIZE - 1) & ~(FLASHWORD_SIZE - 1);
You have code which seems to adjust things if the starting address is not at start of a flash page, which makes me think you're not adhering to this.
Speculative accesses can also cause hard faults but unless the address it faults at is at the end of flash, I don't think that's it. MPU settings can prevent out of bounds reads.
2025-11-18 6:17 AM
You're probably writing to an address more than once which triggers a hard fault due to ECC when you read it. Note that writing 0xFFs counts as your one allowed write. Perhaps instrument your write calls with UART logging and ensure no writes are overlapping. "length" will need to be a multiple of flash word size.
2025-11-18 6:26 AM
Hm, I'm quite sure this is not caused by overlapping writes as
- the length is checked properly
- the magic word I use for finding the start address is 32 bits wide
- in the data written there is no other value 0xFFFFFFFF possible so there can be no misdetection of the next write address due to magic word value which is something else
Thus I do not see any possibility that I write to some other area than an erased one which is at 0xFF. Its also strange it happens randomly after an unpredictable number of successful writes and at unpredictable addresses.
Anyway, can I catch this error somehow? Means can I pre-check final_address to avoid the HardFault_handler() is being called when accessing the related address?
2025-11-18 6:39 AM - edited 2025-11-18 6:41 AM
> the magic word I use for finding the start address is 32 bits wide
Writes to flash must be a multiple of the flash page which is 256 bits. Once you write anywhere in a flash page, you can no longer write anywhere else in there.
The code you presented doesn't necessary do this, only if "length" is a multiple of 32. Is it?
> uint32_t write_addr = base_addr & ~(FLASHWORD_SIZE - 1);
> uint32_t end_addr = (final_addr + FLASHWORD_SIZE - 1) & ~(FLASHWORD_SIZE - 1);
You have code which seems to adjust things if the starting address is not at start of a flash page, which makes me think you're not adhering to this.
Speculative accesses can also cause hard faults but unless the address it faults at is at the end of flash, I don't think that's it. MPU settings can prevent out of bounds reads.
2025-11-18 6:57 AM
> > uint32_t write_addr = base_addr & ~(FLASHWORD_SIZE - 1);
> > uint32_t end_addr = (final_addr + FLASHWORD_SIZE - 1) & ~(FLASHWORD_SIZE - 1);
> You have code which seems to adjust things if the starting address is not at start of a flash page, which makes me
> think you're not adhering to this.
This is just some desperate stuff to fix this mysterious problem. The point is, the structure which is written there definitely is a multiple of FLASHWORD_SIZE by following statement:
struct global_config
{
uint8_t id; // checks if flash was ever used or contains ***
...
uint8_t _reserved[0] __attribute__((aligned(FLASHWORD_SIZE)));
};Anyway, when it would be a problem of my alignment, wouldn't the issue be reproducible at the same address always?
2025-11-18 8:49 AM
1) Check your while loops, I had to set some variables used in such a loop to volatile (or was that only do / while ?)
2) Curious I am, because I also wanted to use the H723 flash as an EEPROM, but the fact that only 128 kB sectors can be erased kept me away from that.
3) Here is a function to check addresses to prevent hard faults, stolen and modified from someone (can't remember, sorry for no credits :D ) around here:
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/*
* probe read access to address in the MCU memory space
* return: 0 = OK, 1 = error
* supported: Cortex-M3, M4/F, M7
* NOTE:
* - use this function to prevent hard faults
* - call this with interrupts disabled to avoid side effects
*/
uint8_t ProbeMemAddress(volatile const char *pcAddress)
{
static const uint32_t u32BFARVALID_Msk = ((uint32_t)0x80 << SCB_CFSR_BUSFAULTSR_Pos);
uint8_t u8Result = HAL_OK;
__disable_irq();
/* clear BFARVALID flag by writing 1 to it */
SCB->CFSR |= u32BFARVALID_Msk;
/* ignore BusFault by enabling BFHFNMIGN; disable faults and interrupts */
uint32_t u32FaultMask = __get_FAULTMASK();
__disable_fault_irq();
SCB->CCR |= SCB_CCR_BFHFNMIGN_Msk;
/* probe the address by performing 8-bit read */
__DSB();
*pcAddress;
__DMB();
if( (SCB->CFSR & u32BFARVALID_Msk) != 0 )
{
/* yes, Bus Fault occurred */
u8Result = HAL_ERROR;
}
/* re-enable BusFault by clearing BFHFNMIGN */
SCB->CCR &= ~SCB_CCR_BFHFNMIGN_Msk;
__set_FAULTMASK(u32FaultMask);
__enable_fault_irq();
__DSB();
__enable_irq();
return u8Result;
}
2025-11-18 10:04 AM
You accepted an answer. Is this solved?
> The point is, the structure which is written there definitely is a multiple of FLASHWORD_SIZE by following statement:
Sure, but the starting address may still be off. I can only debug what I can see. Clearly there's an issue somewhere. It's not likely to be a silicon issue.
> (somewhat simplified)
I'm also skeptical when the code shown isn't the exact code. Easy to simplify away bugs unintentionally.