cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F405: flash write causes processor to halt

JW.4
Associate II

I am working with an STM32F405 and am attempting to use a page of flash to store ~1K of persistent data that will be seldom written and often read (device serial number and calibration data).

I have been trying to reference sample code and a few other questions in this forum, but am still having some trouble. What I have so far:

In my .ld, I have created a new section for my user data in sector 11 of flash (addr 0x080E 0000 - 0x080F FFFF):

/* Memories definition */
MEMORY
{
  CCMRAM    (xrw)    : ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 896K
  DATA   (rwx)     : ORIGIN = 0x80E0000,   LENGTH = 128K
}
 
/* Sections */
SECTIONS
{  
  .user_data :
  {
    . = ALIGN(4);
    KEEP(*(.user_data))
    . = ALIGN(4);
  } > DATA
...

In my application, I have a variable mapped to this region that I plan on using for reading my data that I'm making sure is initialized to all 0xFF on initial program write:

__attribute__((__section__(".user_data"))) const uint8_t userConfig[1024*128] = {[0 ... (1024*128-1)] = 0xFF};

In my application, I have tried erasing sector 11 with HAL_FLASHEx_Erase:

static void Write_Flash(uint32_t address, uint8_t toWrite)
{
    uint32_t SectorError = 0;
    FLASH_EraseInitTypeDef EraseInitStruct;
    HAL_StatusTypeDef halStat;
 
    halStat = HAL_FLASH_Unlock();
    if (halStat != HAL_OK){
        while(1);
    }
    __disable_irq();
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
    EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    EraseInitStruct.Sector = FLASH_SECTOR_11;
    EraseInitStruct.NbSectors = 1;
    halStat = HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError);
    if (halStat != HAL_OK) {
        while(1);
    }
 
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, (uint32_t)address, toWrite);
    HAL_FLASH_Lock();
    __enable_irq();
 
    return;
}

And when that did not work, I tried instead using FLASH_Erase_Sector

void Erase_Flash(void)
{
    __disable_irq();
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR );
    FLASH_Erase_Sector(FLASH_SECTOR_11, VOLTAGE_RANGE_3);
    FLASH->CR |= FLASH_CR_LOCK;
    __enable_irq();
}

For both of these functions, I am seeing the same results: when running with a debugger, my code stops executing as soon as __enable_irq() is invoked. What I mean by this is if I set a break point for the instruction after __enable_irq() is invoked, the debugger never reaches that break point. However, if I single step my code with the debugger, that code still executes. If I pause my code via debugger while waiting for the break point to be reached, my code will be inside an interrupt vector (for example, my systick interrupt or one of my UART handlers). When this happens, I can set a break point later on in the ISR, and it will be reached via free run. I can single step through the remaining ISR code and validate that upon exit, it returns to the line of code immediately after __enable_irq() was invoked, and again I'm able to single step through code. However, if I free run the code, it again never reaches the next immediate instruction, and pausing via debugger will show the code in the same state described above (it will be in an ISR, but the call stack of the debugger shows the code never got past the instruction it was at when I hit free run).

I thought this might be an issue related to the address bus stalling when a flash erase occurs, which would cause the next instruction of my application not to be executed, but my understanding is that as soon as the BSY bit of the FLASH_SR is clear, the erase should be complete and my processor should be able to fetch its next instruction out of sector 0. I validated with the debugger that the BSY bit is indeed clear, and that my program is executing from flash sector 0 (as opposed to trying to run from the same sector I'm erasing).

To test this a bit more, I modified my .ld to have a section in RAM for code to execute from:

  /* Initialized data sections into "RAM" Ram type memory */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.code_for_ferase) /* RAM section for running flash erase fxns */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */
 
    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
 
  } >RAM AT> FLASH

And then changed my flash erase function to use this:

#define KEY1 0x45670123
#define KEY2 0xCDEF89AB
 
__attribute__ ((long_call, section (".code_for_ferase"))) static  void Erase_Flash(void)
{
    // make sure nothing's doing a write right now
    while (FLASH->SR & FLASH_FLAG_BSY);
 
    //unlock the control register
    WRITE_REG(FLASH->KEYR, KEY1);
    WRITE_REG(FLASH->KEYR, KEY2);
 
    // set the erase size, 32 bit based on running @ 3.3V
    CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);
    FLASH->CR |= FLASH_PSIZE_WORD;
 
    // set the sector to erase, sector 11
    CLEAR_BIT(FLASH->CR, FLASH_CR_SNB);
    FLASH->CR |= FLASH_CR_SER | (11 << FLASH_CR_SNB_Pos);
 
    // start the operation
    FLASH->CR |= FLASH_CR_STRT;
 
    // wait for completion
	while (FLASH->SR & FLASH_FLAG_BSY);
}

I also tried modifying my above flash_erase functions (the ones that use FLASH_Erase_Sector and HAL_FLASHEx_Erase) to similarly run from RAM (including all the functions those in turn call). In all of those cases, I saw better results- the BSY bit cleared, and I was able to set break points and free run as expected, but as soon as I exited a function that was running in RAM and returned to running out of FLASH, I saw the same bad behavior of my next code fetch not occurring as expected when free running.

At this point, I'm at a loss for what to do- I'm sure I've just missed something small somewhere, but I don't know what. Any help on this would be greatly appreciated.

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

> If I pause my code via debugger while waiting for the break point to be reached, my code will be inside an interrupt vector (for example, my systick interrupt or one of my UART handlers

It sounds like your MCU is stuck inside an ISR due to a flag not getting cleared. I would delve more into that.

You said the erase didn't work, but you didn't say if you checked the actual memory to see if it was cleared, just the the code after enabled interrupt didn't work. Use STM32CubeProgrammer to verify that the erase was successful and the byte you write is written.

If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post

6 REPLIES 6
TDK
Guru

> If I pause my code via debugger while waiting for the break point to be reached, my code will be inside an interrupt vector (for example, my systick interrupt or one of my UART handlers

It sounds like your MCU is stuck inside an ISR due to a flag not getting cleared. I would delve more into that.

You said the erase didn't work, but you didn't say if you checked the actual memory to see if it was cleared, just the the code after enabled interrupt didn't work. Use STM32CubeProgrammer to verify that the erase was successful and the byte you write is written.

If you feel a post has answered your question, please click "Accept as Solution".
JW.4
Associate II

Thank you for the quick reply!

> It sounds like your MCU is stuck inside an ISR due to a flag not getting cleared. I would delve more into that.

I'll explore a bit here- my ISRs have been working without any problems up until the point where I added in the flash erase feature to my project, so I had been working under the assumption that performing a flash erase wouldn't cause any code that cleared ISR bits to no longer clear them, but that's definitely something I can investigate. I was able to verify that simply invoking disable_irq and enable_irq (without performing a flash erase) did not cause my system to get into the same bad state.

> You said the erase didn't work, but you didn't say if you checked the actual memory to see if it was cleared, just the the code after enabled interrupt didn't work. Use STM32CubeProgrammer to verify that the erase was successful and the byte you write is written.

Thank you for asking about this. With my current code (manually setting and clearing bits while executing out of RAM with __disable_irq and __enable_irq invoked before and after my erase function is called), I see the memory as not erased when I break at my unlock operation, and erased if I set a breakpoint after my final while (BSY is not set) loop. I have not yet added my function back in to perform the flash write, as I would like to root cause why performing an erase is causing everything to go bonkers before adding in more complexity.

JW.4
Associate II

Here's an interesting bit of discovery based off that first question. I tried performing a "long" operation between disable_irq and enable_irq, and have gotten into the same state:

    uint64_t times = 0;
    __disable_irq();
    //Erase_Flash();
 
    while (times < 3000000) {
    	times++;
    }
    __enable_irq();

When running without the irq calls, this code takes ~5 seconds to complete. With the en/disable_irq calls, I see the same behavior as when I perform my flash operation. This leads me to believe that something bad is occurring in my system when interrupts are disabled for an unexpectedly long time. I will continue investigating this thread- thank you for mentioning it!

For 3M loops to take 5 seconds on a processor clocking at 168 MHz, suggest some very heavy interrupt loading.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
Pavel A.
Evangelist III

This looks like some of your interrupt sources do not tolerate delay in handling and freak out.

JW.4
Associate II

Yep- this ended up being the issue. I had an interrupt handler that was not clearing the proper error bit when an overflow error occurred (which has never occurred up until now, but is guaranteed to happen if the ISR stops processing input for 5 seconds!)

After properly clearing error bits, everything is working exactly as it should. Thank you again @TDK​  for pointing me in the right direction!