Skip to main content

Interrupts

NVIC registers

The Nested Vectored Interrupt Controller exposes each control as a pair of write-1-to-act register banks rather than as read-modify-write bit fields. That avoids a race: setting a bit never disturbs the others, so no interrupt has to be disabled while updating them. Writing 0 does nothing at all.

RegisterFull nameEffect of writing 1
ISERInterrupt Set-Enable RegisterEnable the interrupt
ICERInterrupt Clear-Enable RegisterDisable the interrupt
ISPRInterrupt Set-Pending RegisterForce the interrupt pending
ICPRInterrupt Clear-Pending RegisterClear the pending state
IABRInterrupt Active Bit RegisterRead-only; interrupt is being serviced
IPRInterrupt Priority RegisterByte per interrupt, priority value

Reading ISER or ICER returns the same information — the current enable state — so a bit that reads as 1 in ICER means the interrupt is enabled, not disabled.

ISER

Enables one or more interrupts. Until the corresponding ISER bit is set, the peripheral can flag its event and set the pending bit, but the core never dispatches the handler.

Write 1 to the bit position for the interrupt you want enabled. In CMSIS:

NVIC_EnableIRQ(TIM6_DAC_IRQn);

ICER

Disables one or more interrupts. Use it when a source must stop reaching the core — during a critical reconfiguration, or once the peripheral is no longer needed.

NVIC_DisableIRQ(TIM6_DAC_IRQn);

Disabling does not discard a pending request. If the event occurs while the interrupt is disabled, the pending bit is still set and the handler runs the moment it is re-enabled. Clear the pending bit explicitly if that is not what you want.

ISER and ICER compared

  • Enable versus disable — ISER switches an interrupt on so the core accepts and services it; ICER switches it off.
  • Same write convention — both are activated by writing 1 to the relevant bit. There is no "write 0 to disable"; that is exactly what the second register exists for.

ICPR

Clears the pending state. A pending bit records that the event happened and has not yet been serviced. It can survive longer than expected: the peripheral flag was cleared but the NVIC bit was not, or the interrupt fired while disabled. The result is a handler that runs one extra time for an event already dealt with.

Write 1 to the corresponding bit to clear it:

NVIC_ClearPendingIRQ(TIM6_DAC_IRQn);

The usual sequence when enabling an interrupt during initialisation is to clear any stale pending state first, then enable:

NVIC_ClearPendingIRQ(USART1_IRQn);
NVIC_SetPriority(USART1_IRQn, 5);
NVIC_EnableIRQ(USART1_IRQn);

Handler runs repeatedly

Almost always the peripheral's own flag was never cleared. The NVIC pending bit is re-asserted as long as the peripheral keeps its interrupt line high, so clearing the NVIC side alone changes nothing — the handler simply re-enters.

Clear the source flag inside the handler, and clear it before returning:

void USART1_IRQHandler(void) {
HAL_UART_IRQHandler(&huart1); // the HAL clears the peripheral flags
}

A write to a peripheral register can take several cycles to reach the bus. If the handler returns immediately after clearing the flag, the core may re-enter before the write lands. __DSB() at the end of the handler forces the write to complete first. This is rare in practice but real, and the failure looks like a double-fired interrupt.

Priorities

Cortex-M priorities are inverted: a lower numerical value means a higher priority. STM32 parts implement four priority bits, giving values 0–15 in the upper nibble of each IPR byte.

With NVIC_PRIORITYGROUP_4 — the HAL default, set in HAL_Init — all four bits are preemption priority and none are subpriority. Any interrupt can preempt one of numerically higher value.

Under FreeRTOS, interrupts that call FromISR APIs must have a numerical priority equal to or greater than configMAX_SYSCALL_INTERRUPT_PRIORITY — that is, a lower logical priority. An ISR at priority 0 that calls a kernel function corrupts the scheduler, and the resulting failures are intermittent and far from the cause. configASSERT catches this if it is enabled; enable it during bring-up.