Electronic – What’s the difference between setting SysTick Interrupt in NVIC and using it as an exception

cortex-m4interruptskeilstm32

I'm using an STM32F303 MCU and I've noticed that the SysTick can be set up to cause an exception, which seems to mentioned quite often in various User Guides. This way, it will have a priority level higher than regular Interrupts.

On the other hand, it seems to be possible to set up Systick as an interrupt in NVIC as well. It has its own interrupt request number and it seems it can be set to pending state. Because the NVIC interrupts have programable priority levels, we could also set it up this way as well.

I'm not really sure what's the difference between these two approaches using the SysTick.

Best Answer

A couple of definitions first:

In the Cortex-M programming manual, an Exception is anything that breaks the normal program flow, and invokes a handler from the vector table, and Interrupts are a subset of Exceptions, coming from the peripherals outside the ARM core. Because SysTick is implemented in the Cortex-M core, it is considered an exception, but not an interrupt.

Exceptions have an Exception Number, starting from 0. Interrupts have an IRQ Number, starting from 0. Because all Interrupts are Exceptions, they all get an Exception Number, which is 16 higher than the IRQ Number. The Exceptions that are not Interrupts (including SysTick) have Exception Numbers in the 0-15 range, smaller than the Exception Numbers of the Interrupts. Somewhat confusingly, Exceptions that are not Interrupts have IRQ Numbers too, which by extension fall into the range from -16 to -1. SysTick has Exception Number 15, and IRQ Number -1.

I'm using an STM32F303 MCU and I've noticed that the SysTick can be set up to cause an exception, which seems to mentioned quite often in various User Guides. This way, it will have a priority level higher than regular Interrupts.

All exceptions except Reset, NMI, and Hardfault have configurable priorities. It's just configured differently for interrupts and other exceptions. See the CMSIS implementation as a clear example:

__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if ((int32_t)(IRQn) < 0)
  {
    SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
  else
  {
    NVIC->IP[((uint32_t)(int32_t)IRQn)]               = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
}

You can call NVIC_SetPriority or manipulate SCB->SHP directly to give SysTick a lower priority than any other interrupt.

SysTick has indeed a default priority level that is higher than any interrupt, but that's only because at reset, all priorities are set to 0, and in case of a tie, the lower IRQ number wins, in this case, SysTick with -1.

On the other hand, it seems to be possible to set up Systick as an interrupt in NVIC as well. It has its own interrupt request number and it seems it can be set to pending state. Because the NVIC interrupts have programable priority levels, we could also set it up this way as well.

Only interrupts are configurable through the NVIC registers, and SysTick is not an interrupt. NVIC registers have no place for negative IRQ numbers, in bitmasks or elsewhere.

SysTick can indeed be set to a pending state, but that's done through SCB->ICSR, not in NVIC.

Note that NVIC_SetPriority() and NVIC_GetPriority() are the only NVIC_ functions in CMSIS that deal with negative IRQ numbers properly. Other functions like NVIC_SetPendingIRQ() or NVIC_EnableIRQ() just accept negative IRQ numbers happily, do some bit shuffling on them that results in a HUGE offset from the NVIC registers, and scribble some faraway memory address, possibly causing a hardfault, or other strange behaviour. HAL_NVIC_ functions do some bounds checking when you enable USE_FULL_ASSERT in your headers, but otherwise they just let invalid parameters through to the CMSIS NVIC functions.

EDIT

There is a neat trick to find all places where a function is called with a negative parameter. Put this into a common header:

#define NVIC_EnableIRQ(x) ((void)sizeof(char[x]))

It's a compile-time error whenever this construct is invoked with a negative number. Repeat this for every NVIC function except NVIC_SetPriority() and NVIC_GetPriority().