Search code examples
armstm32interrupt-handling

SysTick interrupt causes execution to jump to 0x1fffxxxx on STM32F030


I'm trying to use SysTick_Handler in SW4STM32 for Linux, but whenever the SysTick interrupt is triggered, execution jumps to somewhere in system memory. By my understanding, it should jump into the void SysTick_Handler(void) that I declared, or failing that, into Default_Handler declared in startup_stm32.s where the interrupt vector table is defined. I set a break point in my SysTick_Handler, but it is never reached. In the code below, it gets through init_systick() and stays in the endless for loop if I don't include SysTick_CTRL_TICKINT_Msk, as expected, but when I do include it, the debugger tells me it ends up somewhere around address 0x1fffda7c.

main.c:

#include "stm32f0xx.h"

volatile uint32_t ticks = 0;

void SysTick_Handler(void) {
    ticks++;
}

void init_systick(void) {
    SysTick->LOAD = 43999;
    SCB->SHP[1] |= 0x40000000L;
    SysTick->VAL = 0;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
}

int main(void)
{
    init_systick();
    for(;;);
}

I verified from the .map file that the linker is using the declared SysTick_Handler instead of Default_Handler.

I also tried the following variation to use the standard peripheral library for setup, along with other interrupt priority values, with the same results:

#include "stm32f0xx.h"

volatile uint32_t ticks = 0;

void SysTick_Handler(void) {
    ticks++;
}

void init_systick(void) {
    SysTick_Config(44000);
    NVIC_EnableIRQ(SysTick_IRQn);
    NVIC_SetPriority(SysTick_IRQn, 0);
}

int main(void)
{
    init_systick();
    for(;;);
}

This shouldn't be relevant, but since the target doesn't have a timing crystal, I also modified void SetSysClock(void) in system_stm32f0xx.c to use the HSI clock and PLL, which appears to be working correctly:

static void SetSysClock(void)
{
    RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_HSI;
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI) ;
    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
    RCC->CR &= ~RCC_CR_PLLON;
    while (RCC->CR & RCC_CR_PLLRDY) ;
    RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_PLLMUL & ~RCC_CFGR_PLLSRC) | RCC_CFGR_PLLMUL11;   // PLL takes 8 MHz HSI / 2 as input
    RCC->CR |= RCC_CR_PLLON;
    while (!(RCC->CR & RCC_CR_PLLRDY)) ;
    RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_PLL;
    while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) ;
}

-- EDIT: More info requested in the comments --

It's an M0 core, so it doesn't have vector table relocation. From the reference manual section 2.5 (page 44):

Unlike Cortex ® M3 and M4, the M0 CPU does not support the vector table relocation.

Address 0x00000000 should be mapped either to FLASH memory at 0x08000000, system memory at 0x1fffd800, or to SRAM at 0x20000000. The memory at address 0x00000000 matches system memory at 0x1fffd800, even though SYSCFG_CFGR1 MEM_MODE is set to 00, which should map FLASH memory there. Main FLASH memory at address 0x08000000 contains the correct vector table, but address 0x00000000 is populated with address 0x1fffd99d for the SysTick vector (and all other non-NULL vectors except the reset vector, which is 0x1fffdc41); the vectors shown by the debugger at address 0x00000000 are consistent with the observed behavior. All of this information was collected while paused at a breakpoint at address 0x08000298 (a correct position in FLASH memory where the correct code has been loaded), before executing the interrupt.


Solution

  • As said already, in Cortex-M0 there's no VTOR register and the vector table is at the fixed address of 0x0000'0000. However, in STM32 based on Cortex-M0 (which is only in STM32F0xx), one of three memories (User FLASH, System ROM, RAM) can be mapped onto this address, depending on SYSCFG_CFGR1.MEM_MODE bitfield.

    Content of this bitfield depends on the boot process during reset (see below), and can be observed and changed in program, but only if SYSCFG's clock is enabled in RCC (by setting the somewhat confusingly named RCC_APB2ENR.SYSCFGCOMPEN bit - failing to do this is a common pitfall, resulting in SYSCFG registers reading as zero; and that's probably why OP thought SYSCFG_CFGR1.MEM_MODE is zero even if the System ROM was mapped to 0x0000'0000, thus SYSCFG_CFGR1.MEM_MODE was in fact set to 0b01).

    In most STM32F0xx, content of SYSCFG_CFGR1.MEM_MODE after reset is determined by combination of BOOT0 pin and BOOT_SEL/nBOOTx bits in option bytes. However, in some particular models, there is an additional mechanism, where upon power-on reset the first address in User FLASH is checked against 0xFFFF'FFFF and if this is the case, the System ROM is mapped at 0x0000'0000. This in effect runs the built-in bootloader, when such chip is run the first time, with empty FLASH.

    This "empty check" is performed only upon power-on reset (or using the lesser-known OBL_LAUNCH mechanism), and is retained through non-power-on resets, resulting in System ROM being mapped at 0x0000'0000 upon all subsequent resets until power-on reset, even if the User FLASH has been reprogrammed.

    Some debugging tools may "know" about this feature and may use OBL_LAUNCH to hide this problem from the user. Others may not.

    One of the STM32 models which do have this "empty check" mechanism is STM32F030xC (OP never told us which particular STM32F030 is he using, and there are 3 distinct models of it). See Empty check section of Boot configuration chapter in RM0360.