What are Processor Operation Modes-Embedded C Training for Freshers

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Example 03 — Source Code Deep-Dive

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.

Diagram: 1. Overview — What This Example Does

THREAD MODE

Normal application code
main(), generate_interrupt()

IRQ3 triggered → ⇄ ← EXC_RETURN

HANDLER MODE

ISR execution
RTC_WKUP_IRQHandler()

The example sequence:

  1. Print “In thread mode : before interrupt” (Thread Mode)
  2. Enable IRQ3 in NVIC ISER0
  3. Write IRQ3 to STIR → CPU immediately jumps to RTC_WKUP_IRQHandler (Handler Mode)
  4. ISR prints “In handler mode : ISR”
  5. ISR returns → CPU back to Thread Mode
  6. 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

FeatureThread ModeHandler Mode
When activeNormal program execution (main, tasks)Exception/interrupt service routines
Access levelsPrivileged OR Unprivileged (configurable)Always Privileged
Stack pointerMSP or PSP (configurable via CONTROL.SPSEL)Always MSP
Entered byPower-on reset, EXC_RETURNAny exception (IRQ, SysTick, SVC, Fault)
Exited byAny exception entryEXC_RETURN value written to PC
IPSR value0Exception number (1-255)
Can use SVC?YesNo (causes fault)

XPSR — Program Status Register

The XPSR is a composite register made of three sub-registers:

BitsSub-registerFieldMeaning
[31]APSRNNegative flag (set if result < 0)
[30]APSRZZero flag (set if result == 0)
[29]APSRCCarry flag
[28]APSRVOverflow flag
[27]APSRQSaturation flag
[26:25]EPSRICI/ITInterrupt-Continuable Instruction / IT state
[24]EPSRTThumb state bit — must always be 1 on Cortex-M4
[23:16]EPSRICI/ITIT block state continuation
[8:0]IPSRException Number0=Thread Mode, 1=Reset, 2=NMI, 3=HardFault, 4=MemManage, 5=BusFault, 6=UsageFault, 11=SVCall, 14=PendSV, 15=SysTick, 16+=IRQ0+
How to read XPSR in software:
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.

RegisterAddressIRQ Range
ISER00xE000E100IRQ0 – IRQ31
ISER10xE000E104IRQ32 – IRQ63
ISER20xE000E108IRQ64 – 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

BitsFieldAccessDescription
[8:0]INTIDWrite-onlyIRQ number (0–239) to be triggered. Writing triggers the interrupt if it is enabled in NVIC. Read returns 0.
[31:9]——Reserved
STIR access control: By default, only privileged software can write to STIR. Bit 1 of CCR (Configuration and Control Register, 0xE000ED14) — the USERSETMPEND bit — allows unprivileged Thread Mode code to write to STIR when set to 1.

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.

STIR vs ISPR: STIR is a single write-only register for software trigger. ISPR is a set of 8 registers (like ISER) that directly control pending bits. Writing to STIR internally sets the corresponding ISPR bit. Both result in the same outcome: the interrupt becomes pending.

STM32F407 IRQ Table (First 10)

IRQ#Vector IndexHandler NamePeripheral
016WWDG_IRQHandlerWindow Watchdog
117PVD_IRQHandlerPower Voltage Detector
218TAMP_STAMP_IRQHandlerTamper / Timestamp
319RTC_WKUP_IRQHandlerRTC Wakeup (used in this example)
420FLASH_IRQHandlerFlash global interrupt
521RCC_IRQHandlerReset and Clock Control
622EXTI0_IRQHandlerEXTI Line 0 (PA0)
723EXTI1_IRQHandlerEXTI Line 1 (PA1)
824EXTI2_IRQHandlerEXTI Line 2 (PA2)
925EXTI3_IRQHandlerEXTI 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 ValueReturn toStack used
0xFFFFFFF1Handler ModeMSP
0xFFFFFFF9Thread ModeMSP
0xFFFFFFFDThread ModePSP

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 and Handler Mode.

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?
STIR (Software Trigger Interrupt Register) is at address 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 (Interrupt Program Status Register) occupies bits [8:0] of the composite XPSR register. It holds the current exception number:
• 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?
The ARM Cortex-M4 hardware automatically pushes 8 registers onto the active stack (PSP if Thread Mode was using PSP, otherwise MSP):
• 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?
EXC_RETURN is a special value loaded into LR by the processor during exception entry. It signals that this is an exception return, not a normal function return. The values are:
• 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?
Handler Mode is always privileged by design in the ARM Cortex-M architecture. The reasoning: interrupt handlers frequently need to access NVIC registers, SCB registers, peripheral registers, and other system-level resources. Requiring special permission grants or mode switches inside ISRs would add latency and complexity. By always being privileged, ISRs can freely access all system resources. The application programmer is responsible for ensuring ISR code doesn’t misuse this privilege. This is a hardware architecture decision documented in the ARM Cortex-M4 Technical Reference Manual.
Q7. What is tail chaining in ARM Cortex-M and how does it relate to operation modes?
Tail chaining is an exception optimisation where, if a higher-priority or same-priority interrupt becomes pending while an ISR is executing, instead of fully popping and then re-pushing the stack frame (16 cycles), the processor reuses the existing stack frame and vectors directly to the next ISR (6 cycles). It stays in Handler Mode throughout — no Thread Mode re-entry. The processor effectively “chains” the tail of one ISR to the entry of the next. This reduces interrupt latency for back-to-back interrupts and is completely transparent to the ISR code.
Q8. What is the difference between ISER (enable) and ISPR (pending)?
Both are NVIC register sets but they control different interrupt states:

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?
The vector table is an array of 32-bit function pointers stored at the beginning of memory. Each entry (vector) contains the address of an exception/interrupt handler. The first entry (offset 0) is the initial Main Stack Pointer value. The second entry (offset 4) is the Reset_Handler address. Subsequent entries are for NMI (offset 8), HardFault (offset 12), …, SysTick (offset 60), IRQ0 (offset 64), IRQ1 (offset 68), etc.

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?
Yes — this is called interrupt nesting or preemption, and it requires the preempting interrupt to have a numerically lower priority value (higher urgency) than the current one. On ARM Cortex-M4 with NVIC, lower priority number = higher priority. If IRQ3 (priority 0x50) is running and IRQ5 (priority 0x20) fires, IRQ5 preempts IRQ3. A new exception stack frame is pushed (this time onto MSP, since we’re in Handler Mode), the nested ISR runs, then returns and resumes IRQ3. This is why nested ISRs require sufficient stack space — each level of nesting uses 8×4=32 bytes of stack minimum (+ any local variables).

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *