8
\$\begingroup\$

The Context

I have a embedded system running on an STM32L053 processor that needs to process STM32 hardware timer interrupts regularly and cannot be disabled. These top-priority interrupts cannot be prevented because it could cause a failure in the whole system. I also need to process user inputs that can cause writes to NVM (non-volatile memory) at random times to save settings while processing timer interrupts concurrently. I am using IAR compiler.

The problem

During the flash writes that can occur randomly, it blocks CPU execution as well as all interrupts for about 6ms. This is a problem because the processing of hardware timer interrupts cannot be prevented or it can cause a failure of the entire system.

The potential solution

After some research, I found this document on ST's website that explains exactly how to manage this problem. From what I understand in this document, the list of things to do to prevent the blocking of interrupts are the following:

  • Initiating the flash writes from RAM.
  • Interrupt handler code must be placed in RAM
  • Abstain from using constants that are placed in program Flash memory
  • Data EEPROM cannot be read while being written
  • Vector table relocated in RAM
  • Vector table address must be written in the System Control Block register VTOR.
  • Check that the vectors in the table needed during data EEPROM write access period are pointing to RAM

Implementation

Vector table relocation

void SystemInit (void)
{    
  // (...)
  /* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
  /* Vector Table Relocation in Internal SRAM */
  #pragma section = ".intvec_RAM"
  SCB->VTOR = (uint32_t)__section_begin(".intvec_RAM");
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}

Linker Script

/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x08000000;
/*-Memory Regions-*/
define symbol __ICFEDIT_region_ROM_start__ = 0x08000000 ;
define symbol __ICFEDIT_region_ROM_end__   = 0x0800FFFF;
define symbol __ICFEDIT_region_RAM_start__ = 0x200000EC;
define symbol __ICFEDIT_region_RAM_end__   = 0x20001FFF;

/*-Sizes-*/
define symbol __ICFEDIT_size_cstack__ = 0x400;
define symbol __ICFEDIT_size_heap__   = 0x200;
/**** End of ICF editor section. ###ICF###*/

define symbol __RAM_intvec_start = 0x20000000;
define memory mem with size = 4G;
define region ROM_region      = mem:[from __ICFEDIT_region_ROM_start__   to __ICFEDIT_region_ROM_end__];
define region RAM_region      = mem:[from __ICFEDIT_region_RAM_start__   to __ICFEDIT_region_RAM_end__];


define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };

initialize by copy { readwrite };
initialize by copy { section .intvec_RAM };

do not initialize  { section .noinit };

place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place at address mem: __RAM_intvec_start { section .intvec_RAM };

place in ROM_region   { readonly };
place in RAM_region   { readwrite,
                        block CSTACK, block HEAP };

Startup Script

I have two startup scripts added to my project, one with the original vector table (in flash) that has all the handlers except for Reset_Handler replaced with Empty_Handler and the other that has the handlers placed in RAM.

Startup Script

The ISR code

__ramfunc void TIM22_IRQHandler(void)
{
    if(LL_TIM_IsActiveFlag_TRIG(TIM22) == 1)
    {
        LL_TIM_ClearFlag_TRIG(TIM22);
    }
    if(LL_TIM_IsActiveFlag_CC2(TIM22) == 1)
    {
        LL_TIM_ClearFlag_CC2(TIM22);
        LL_TIM_ClearFlag_CC2OVR(TIM22);
    }
}

The Flash write blocking code

This is the piece of code that blocks all interrupts from happening for about about 6ms (the time it takes for a write to flash). It called periodically.

typedef struct 
{
    uint8_t uniqueNumber; // 1 byte
} Eeprom_t;

__ramfunc static void SaveStuffToFlash(void)
{
    static uint8_t _numberToStore = 1;
    _eepromData.uniqueNumber = _numberToStore;
    while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) == 1) 
    {
        // wait for flag to go to zero.
    };
    _numberToStore++;
}

The persistent problem

I don't know why, but the interruptions are still getting blocked when the SaveStuffToFlash function is called. I don't have all the code in RAM, only the code that does the save to the internal flash. I followed all the directions in the application note and it seems to still block interrupts. I have run out of ideas and means to debug this.

\$\endgroup\$
2
  • 4
    \$\begingroup\$ Could it be possible that another interrupt (not your super important timer) fires during the 6ms it takes to write to flash? If the ISR for that interrupt isn't in RAM (or accesses flash in any way), you'll still end up waiting for the flash. You might have to put all ISRs fully in RAM, not just the single important one. \$\endgroup\$ Commented Jan 7, 2022 at 18:14
  • 1
    \$\begingroup\$ OMG that was the issue. Thank you!! It turns out that The systick timer was calling non-RAM functions every millisecond, thus blocking the CPU right after the flash (max 1ms after the beginning of the flash write). \$\endgroup\$
    – Alex C
    Commented Jan 7, 2022 at 18:54

1 Answer 1

6
\$\begingroup\$

It turns out that the Systick interrupt that blocks the CPU at that particular time. The systick interrupt fires at every millisecond and accesses code that is stored in flash. This makes it seem like the CPU was blocked by the flash write, but the real culprit was the STM32 tick timer that blocks everything after at most 1ms after the start of the write flash function.

The answer was to put the systick timer in RAM by add the __ramfunc keyword in the declaration of all the functions that were called from SysTick_Handler():

__ramfunc void SysTick_Handler(void)
{
    HAL_IncTick();
    HAL_SYSTICK_IRQHandler();
}

This is the waveform diagram of the STM32 hardware timer ISR executing (blue waveform), the flash write (green waveform) and the systick interrupt executing (purple waveform). Before the changes, the STM32 hardware timer interrupt would be blocked by the systick. After the changes, the STM32 timer interrupts would no longer be blocked.

Before putting Systick in RAM: before putting Systick in RAM

After putting Systick in RAM after putting Systick in RAM

Thanks a lot Jonathan S for the answer

\$\endgroup\$
1
  • 4
    \$\begingroup\$ If it is this easy to disrupt the program flow, someone will eventually recreate this bug. Careful with that, you may want to be able to detect if it happens so you find out during testing. Perhaps tighten up the watchdog. \$\endgroup\$
    – Jeroen3
    Commented Jan 7, 2022 at 19:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.