Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Processor Operation Modes
Thread Mode vs Handler Mode — how interrupts force a mode switch, NVIC STIR to generate software interrupts, and what the XPSR register records about the current mode.
1. Overview — What This Example Does
The 003_operation_modes project demonstrates the two execution modes of the ARM Cortex-M4 processor: Thread Mode and Handler Mode. It shows how software can trigger an interrupt (IRQ3) using the NVIC Software Trigger Interrupt Register (STIR), causing the processor to switch from Thread Mode to Handler Mode to execute the ISR.
THREAD MODE
Normal application code
main(), generate_interrupt()
HANDLER MODE
ISR execution
RTC_WKUP_IRQHandler()
The example sequence:
- Print “In thread mode : before interrupt” (Thread Mode)
- Enable IRQ3 in NVIC ISER0
- Write IRQ3 to STIR → CPU immediately jumps to RTC_WKUP_IRQHandler (Handler Mode)
- ISR prints “In handler mode : ISR”
- ISR returns → CPU back to Thread Mode
- Print “In thread mode : after interrupt”
2. Source Code — Full Annotated Walkthrough
#include <stdio.h>
#include <stdint.h>
/* ----------------------------------------------------------------
* generate_interrupt() — executes in THREAD MODE
* Demonstrates how to manually trigger an interrupt via NVIC.
* ---------------------------------------------------------------- */
void generate_interrupt()
{
/* STIR: Software Trigger Interrupt Register
* Address: 0xE000EF00
* Write the IRQ number (0–239) to trigger that interrupt in software.
* Only accessible from privileged mode (Thread Privileged or Handler). */
uint32_t *pSTIR = (uint32_t*)0xE000EF00;
/* ISER0: Interrupt Set-Enable Register 0
* Address: 0xE000E100
* Each bit enables one IRQ: bit N enables IRQ N (N = 0–31).
* Must enable BEFORE writing to STIR, otherwise interrupt is pending
* but not taken (it stays in NVIC pending state). */
uint32_t *pISER0 = (uint32_t*)0xE000E100;
/* Enable IRQ3 (RTC_WKUP on STM32F407) */
*pISER0 |= (1 << 3); /* Set bit 3 in ISER0 = enable IRQ3 */
/* Trigger IRQ3 via software.
* STIR bits [8:0] hold the interrupt ID.
* We mask with 0x1FF to ensure only bits [8:0] are set. */
*pSTIR = (3 & 0x1FF); /* IRQ3 → triggers RTC_WKUP_IRQHandler */
}
/* ----------------------------------------------------------------
* main() — Thread Mode
* ---------------------------------------------------------------- */
int main(void)
{
printf("In thread mode : before interrupt\n");
generate_interrupt(); /* This causes an immediate mode switch */
/* Execution resumes HERE after ISR returns */
printf("In thread mode : after interrupt\n");
for(;;);
}
/* ----------------------------------------------------------------
* RTC_WKUP_IRQHandler — Handler Mode (ISR for IRQ3)
* The processor automatically switches to Handler Mode when entering
* any exception (including IRQs). No software action needed.
* ---------------------------------------------------------------- */
void RTC_WKUP_IRQHandler(void)
{
printf("In handler mode : ISR\n");
/* Implicit return: hardware restores Thread Mode context */
}
Key Observations
- The IRQ3 vector in the vector table maps to
RTC_WKUP_IRQHandler(position 3 in device IRQ table = vector table offset 3+16 = index 19, address 0x08000000 + 19×4 = 0x0800004C) - Writing to STIR triggers the interrupt synchronously — the CPU enters the ISR before the next instruction of
generate_interrupt() - The ISR runs in Handler Mode with MSP (Main Stack Pointer) active
- Return from ISR uses a special EXC_RETURN value loaded into PC
3. ARM Cortex-M4 Processor Modes — Deep Dive
The Two Modes
| Feature | Thread Mode | Handler Mode |
|---|---|---|
| When active | Normal program execution (main, tasks) | Exception/interrupt service routines |
| Access levels | Privileged OR Unprivileged (configurable) | Always Privileged |
| Stack pointer | MSP or PSP (configurable via CONTROL.SPSEL) | Always MSP |
| Entered by | Power-on reset, EXC_RETURN | Any exception (IRQ, SysTick, SVC, Fault) |
| Exited by | Any exception entry | EXC_RETURN value written to PC |
| IPSR value | 0 | Exception number (1-255) |
| Can use SVC? | Yes | No (causes fault) |
XPSR — Program Status Register
The XPSR is a composite register made of three sub-registers:
| Bits | Sub-register | Field | Meaning |
|---|---|---|---|
| [31] | APSR | N | Negative flag (set if result < 0) |
| [30] | APSR | Z | Zero flag (set if result == 0) |
| [29] | APSR | C | Carry flag |
| [28] | APSR | V | Overflow flag |
| [27] | APSR | Q | Saturation flag |
| [26:25] | EPSR | ICI/IT | Interrupt-Continuable Instruction / IT state |
| [24] | EPSR | T | Thumb state bit — must always be 1 on Cortex-M4 |
| [23:16] | EPSR | ICI/IT | IT block state continuation |
| [8:0] | IPSR | Exception Number | 0=Thread Mode, 1=Reset, 2=NMI, 3=HardFault, 4=MemManage, 5=BusFault, 6=UsageFault, 11=SVCall, 14=PendSV, 15=SysTick, 16+=IRQ0+ |
uint32_t xpsr;
__asm volatile ("MRS %0, XPSR" : "=r"(xpsr));
uint8_t exception_num = xpsr & 0x1FF; /* bits [8:0] = IPSR */
if (exception_num == 0)
printf("Thread Mode\n");
else
printf("Handler Mode, exception #%d\n", exception_num);
4. NVIC Registers Used
ISER — Interrupt Set-Enable Register
Base address: 0xE000E100. There are 8 ISER registers (ISER0–ISER7), each covering 32 IRQs.
| Register | Address | IRQ Range |
|---|---|---|
| ISER0 | 0xE000E100 | IRQ0 – IRQ31 |
| ISER1 | 0xE000E104 | IRQ32 – IRQ63 |
| ISER2 | 0xE000E108 | IRQ64 – IRQ95 |
| … | … | … |
To enable IRQ N: Write 1 to bit (N % 32) of ISER(N/32).
/* Generic: enable any IRQ number */
void NVIC_EnableIRQ(uint8_t irq)
{
volatile uint32_t *ISER = (uint32_t*)0xE000E100;
ISER[irq / 32] |= (1UL << (irq % 32));
}
/* Disable IRQ: use ICER (Interrupt Clear-Enable Register) */
void NVIC_DisableIRQ(uint8_t irq)
{
volatile uint32_t *ICER = (uint32_t*)0xE000E180;
ICER[irq / 32] = (1UL << (irq % 32)); /* write-to-clear */
}
STIR — Software Trigger Interrupt Register
Address: 0xE000EF00
| Bits | Field | Access | Description |
|---|---|---|---|
| [8:0] | INTID | Write-only | IRQ number (0–239) to be triggered. Writing triggers the interrupt if it is enabled in NVIC. Read returns 0. |
| [31:9] | — | — | Reserved |
ISPR — Interrupt Set-Pending Register
Address: 0xE000E200. Writing 1 to bit N manually sets IRQ N to pending state. The interrupt will be taken when it is enabled and priority allows.
STM32F407 IRQ Table (First 10)
| IRQ# | Vector Index | Handler Name | Peripheral |
|---|---|---|---|
| 0 | 16 | WWDG_IRQHandler | Window Watchdog |
| 1 | 17 | PVD_IRQHandler | Power Voltage Detector |
| 2 | 18 | TAMP_STAMP_IRQHandler | Tamper / Timestamp |
| 3 | 19 | RTC_WKUP_IRQHandler | RTC Wakeup (used in this example) |
| 4 | 20 | FLASH_IRQHandler | Flash global interrupt |
| 5 | 21 | RCC_IRQHandler | Reset and Clock Control |
| 6 | 22 | EXTI0_IRQHandler | EXTI Line 0 (PA0) |
| 7 | 23 | EXTI1_IRQHandler | EXTI Line 1 (PA1) |
| 8 | 24 | EXTI2_IRQHandler | EXTI Line 2 (PA2) |
| 9 | 25 | EXTI3_IRQHandler | EXTI Line 3 (PA3) |
5. Exception Entry Mechanism
When IRQ3 fires, the processor performs the following in hardware automatically (no software involvement):
/* What the ARM Cortex-M4 hardware does automatically on exception entry */
/* 1. STACKING — saves current context to the active stack (PSP or MSP) */
push to stack:
R0, R1, R2, R3, /* General purpose registers (caller-saved) */
R12, /* Intra-procedure-call scratch register */
LR, /* Link Register (return address from interrupted code) */
PC, /* Program Counter (address of interrupted instruction +4) */
xPSR /* Execution flags + IPSR + T-bit */
/* 2. Vector fetch — reads the ISR address from the vector table */
ISR_addr = *(uint32_t*)(VTOR + exception_number * 4);
/* For IRQ3: VTOR + (3+16)*4 = 0x08000000 + 19*4 = 0x0800004C */
/* 3. Mode switch — IPSR updated, T-bit checked */
IPSR = exception_number; /* XPSR[8:0] = 19 (3+16) */
/* Processor is now in HANDLER MODE */
/* 4. LR gets EXC_RETURN value (not a normal return address!) */
LR = 0xFFFFFFF9; /* if Thread Mode was using MSP before exception */
LR = 0xFFFFFFFD; /* if Thread Mode was using PSP before exception */
/* 5. PC loaded from vector table → ISR begins executing */
EXC_RETURN Values
| EXC_RETURN Value | Return to | Stack used |
|---|---|---|
| 0xFFFFFFF1 | Handler Mode | MSP |
| 0xFFFFFFF9 | Thread Mode | MSP |
| 0xFFFFFFFD | Thread Mode | PSP |
In this example, Thread Mode was using MSP (CONTROL.SPSEL=0, default after reset), so LR = 0xFFFFFFF9. When the ISR executes BX LR, the hardware sees this magic value and:
- Pops R0–R3, R12, LR, PC, xPSR from the stack
- Restores IPSR to 0 (Thread Mode)
- Resumes execution at the saved PC (instruction after the STIR write)
6. Extended Examples
Reading Current Mode in Software
#include <stdint.h>
#include <stdio.h>
typedef enum { THREAD_MODE, HANDLER_MODE } cpu_mode_t;
cpu_mode_t get_current_mode(void)
{
uint32_t ipsr;
__asm volatile ("MRS %0, IPSR" : "=r"(ipsr));
return (ipsr & 0x1FF) ? HANDLER_MODE : THREAD_MODE;
}
void print_mode(void)
{
if (get_current_mode() == THREAD_MODE)
printf("Currently in: THREAD MODE\n");
else
printf("Currently in: HANDLER MODE (exception #%ld)\n",
/* re-read IPSR */ ({uint32_t x; __asm volatile("MRS %0,IPSR":"=r"(x)); x&0x1FF;}));
}
Triggering Multiple IRQs
volatile uint32_t *ISER0 = (uint32_t*)0xE000E100;
volatile uint32_t *STIR = (uint32_t*)0xE000EF00;
void trigger_multiple(void)
{
/* Enable IRQ3 (RTC_WKUP), IRQ6 (EXTI0), IRQ9 (EXTI3) */
*ISER0 |= (1<<3) | (1<<6) | (1<<9);
/* Trigger them in sequence — each returns before next is triggered
* because STIR triggers synchronously when priority allows */
*STIR = 3; /* IRQ3 fires immediately → ISR runs → return */
*STIR = 6; /* IRQ6 fires immediately → ISR runs → return */
*STIR = 9; /* IRQ9 fires immediately → ISR runs → return */
}
7. Interview Questions & Answers
Q1. What are the two operating modes of the ARM Cortex-M4 processor?
Thread Mode is the normal application execution mode. It runs after reset and after returning from exceptions. It can be either privileged or unprivileged (controlled by CONTROL.nPRIV bit).
Handler Mode is entered whenever any exception (IRQ, SysTick, SVC, NMI, HardFault, etc.) is being serviced. It is always privileged. It always uses the MSP (Main Stack Pointer). Multiple handlers can be nested (higher priority exception preempts lower priority handler).
Key rule: Thread Mode can be unprivileged, but Handler Mode is always privileged.
Q2. What is the NVIC STIR register and how is it used?
0xE000EF00. Writing an IRQ number (0–239) to bits [8:0] causes the NVIC to set that interrupt to pending state and take it immediately if the interrupt is enabled and has sufficient priority. It is used for:• Testing ISR code without actual hardware triggering
• Software-initiated inter-processor notifications
• Deferring processing to a lower-priority interrupt context
Important: STIR is only writable from privileged mode by default. The USERSETMPEND bit in CCR (bit 1, address 0xE000ED14) must be set to allow unprivileged access.
Q3. What is IPSR and how does it indicate the current processor mode?
• IPSR = 0: Processor is in Thread Mode (no exception active)
• IPSR = 1: Reset handler (not normally visible at runtime)
• IPSR = 2: NMI handler
• IPSR = 3: HardFault handler
• IPSR = 11: SVC handler
• IPSR = 14: PendSV handler
• IPSR = 15: SysTick handler
• IPSR = 16 and above: External IRQ handlers (IPSR = IRQ# + 16)
Read with:
MRS R0, IPSR in assembly or MRS %0, XPSR and mask bits [8:0].Q4. What registers does the hardware automatically save during exception entry?
• R0, R1, R2, R3 — function argument/return registers
• R12 — intra-procedure scratch register
• LR (R14) — return address from the interrupted function
• PC (R15) — address of next instruction to execute (return point)
• xPSR — program status flags + exception number + T-bit
This is called the exception stack frame or SF1. Registers R4–R11 are not saved by hardware — if an ISR needs them, it must save/restore them manually (or the compiler does it via the ABI callee-save convention).
Q5. What is EXC_RETURN and why does it have those magic values?
• 0xFFFFFFF1: Return to Handler Mode using MSP (nested interrupt returning to outer ISR)
• 0xFFFFFFF9: Return to Thread Mode using MSP
• 0xFFFFFFFD: Return to Thread Mode using PSP
The upper bits (bits 31:5) are all 1s, making these values clearly invalid as code addresses (flash ends well below 0xFFFFFF00). When the processor sees BX with one of these values, instead of branching to that address, it performs the exception exit sequence: pops the stack frame, restores registers, and resumes the appropriate mode and stack.
Q6. Why does an ISR always run in privileged mode?
Q7. What is tail chaining in ARM Cortex-M and how does it relate to operation modes?
Q8. What is the difference between ISER (enable) and ISPR (pending)?
ISER (Interrupt Set-Enable): Controls whether an IRQ is enabled. If bit N=0, the IRQ will never be taken even if it becomes pending. Like the “gate” that allows or blocks the interrupt from reaching the CPU.
ISPR (Interrupt Set-Pending): Controls the pending state of an IRQ. Writing 1 sets it to pending (like writing to STIR). The interrupt is in the “waiting queue” to be taken. If the interrupt is enabled (ISER bit=1) and its priority is high enough, it will be taken immediately. If ISER bit=0, it remains pending but is never taken until enabled.
Think of it as: ISER = the door lock, ISPR = the doorbell. The interrupt is only “answered” when both the bell rings (ISPR=1) and the door is unlocked (ISER=1).
Q9. What is the vector table and where is it located?
Default location:
0x08000000 (start of Flash). Can be relocated by writing to the VTOR (Vector Table Offset Register) at 0xE000ED08. VTOR must be 512-byte aligned. Relocation is used by bootloaders to redirect vectors to the application’s vector table.Q10. Can an interrupt preempt another interrupt?
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments