Search topics...
MCU & System Architecture
intermediate
Weight: 4/10

Boot, vector table, and clock bring-up

Understand the hardware-side boot sequence on a Cortex-M MCU: reset sources, vector table, clock tree bring-up, and peripheral initialization order.

mcu
boot
vector-table
reset
clock-tree
initialization
Loading quiz status...

Quick Cap

When an MCU comes out of reset, the hardware itself does very little — it loads two words from the vector table (initial SP and Reset_Handler address) and starts executing. Everything between that hardware moment and main() is firmware: clock tree bring-up, peripheral init, and the C-runtime preparation. Interviewers test whether you understand the hardware side of boot — what triggers a reset, how the vector table is structured, where VTOR points, and the order in which clocks and peripherals must come up.

Key Facts:

  • Multiple reset sources: power-on (POR), brown-out (BOR), watchdog, software, external pin — each leaves a status flag for diagnostics
  • Vector table layout: SP at offset 0x00, Reset_Handler at 0x04, then NMI, HardFault, configurable faults, SVC, PendSV, SysTick, and IRQs
  • VTOR register: relocates the vector table — used by bootloaders, dual-bank firmware, and RAM-resident vector tables
  • Clock tree bring-up: HSI on at reset, then optionally switch to HSE, configure PLL, raise Flash wait states, switch SYSCLK
  • Peripheral init order: clock the peripheral first, then GPIO mode, then the peripheral itself — wrong order silently fails
  • Status registers (RCC_CSR on STM32) record the reset source — read once at boot and clear

Note: The C-runtime side of boot — .data copy from Flash to RAM, .bss zeroing, VMA vs LMA, and the linker-script mechanics that drive it — lives in Build Systems → Memory Layout & Startup. This page focuses on what the hardware does and the bring-up steps the firmware must perform.

Deep Dive

At a Glance

CharacteristicDetail
Hardware-loaded on resetSP from address 0x00, PC from 0x04 (Cortex-M)
Default reset sourcePower-on reset (POR) on cold boot
Vector table size16 system + N peripheral entries (N = NVIC line count, typically 32-240)
Vector table alignmentPower-of-2, ≥ 128 bytes; required by VTOR
Default clock sourceHSI (internal RC, 8-64 MHz depending on MCU)
Reset status registerRCC_CSR (STM32) / RSTSRC (NXP) / RSR (TI) — one bit per reset source
Time from POR to first instruction100s of microseconds (POR delay + oscillator startup)

Reset Sources

Every reset on a Cortex-M MCU goes through the same code path (Reset_Handler), but the cause matters for diagnostics. A watchdog reset means firmware hung; a brown-out reset means the supply dipped; a power-on reset is a clean cold boot. The MCU records which source fired in a status register that firmware should read once at boot and then clear.

SourceTriggered byTypical handling
POR (Power-on)Vcc rises through thresholdNormal cold boot; no special action
BOR (Brown-out)Vcc dips below configurable levelLog event; possibly switch to safe mode
WDG (Watchdog)IWDG/WWDG timeoutLog + dump last good state; consider safe boot
SW (Software)Firmware writes to AIRCRExpected after firmware update or DFU exit
NRST pinExternal reset assertedOperator/debugger initiated; clean boot
Wakeup from standbyRTC alarm or wakeup pinSkip full re-init; restore minimal state

In production firmware you should always check the reset-cause register early in SystemInit() or right after, copy it to RAM for telemetry, and then clear it (writing the clear bit is mandatory on most STM32 parts — otherwise the flag persists across resets and confuses your diagnostics).

Vector Table Structure

The Cortex-M vector table is an array of 32-bit words placed at the start of code memory. The first two are special — they are loaded by hardware before any instruction executes. The rest are addresses of exception and interrupt handlers, indexed by exception number.

IndexContentUsed When
0Initial SP valueLoaded into SP on reset
1Reset_Handler addressCPU jumps here on reset
2NMI_HandlerNon-maskable interrupt
3HardFault_HandlerUnrecoverable error
4-10MemManage, BusFault, UsageFault, ...Configurable fault handlers
11SVC_HandlerSupervisor call (RTOS context switch entry)
14PendSV_HandlerDeferred context switch (RTOS)
15SysTick_HandlerSystem timer tick
16+IRQ0_Handler, IRQ1_Handler, ...Peripheral interrupts (NVIC lines)

Two consequences of this layout matter at the hardware level:

  1. The first stack value is read by hardware before any instruction runs. This means the linker must place the initial SP at the very top of the vector table, and the linker script's _estack symbol must equal the desired SP value (typically the top of RAM).
  2. The table must be aligned to a power-of-2 boundary (≥ 128 bytes on Cortex-M0/M0+, ≥ alignment to next-power-of-2-of-table-size on M3/M4/M7). This is enforced by VTOR's lower bits being read-as-zero.

VTOR — Vector Table Offset Register

By default, the Cortex-M reads its vector table from address 0x00000000 (which is aliased to internal Flash on most MCUs at boot). The VTOR register lets firmware relocate the table to any aligned address. This single feature enables the most common multi-image firmware patterns:

  • Bootloaders. A bootloader at the bottom of Flash has its own vector table at offset 0. Before jumping to the application, it sets VTOR to the application's Flash offset (e.g., 0x08020000) and loads the application's SP from that table's first word, then jumps to the application's Reset_Handler.
  • Dual-bank / OTA. The firmware lives in two slots (A and B). On boot, the bootloader picks the valid slot, sets VTOR accordingly, and jumps. After OTA, it picks the new slot.
  • RAM-resident vector table. Copy the vector table from Flash to RAM, set VTOR to the RAM address, and you can patch ISR addresses at runtime — useful for some RTOS implementations and for live ISR updates.
⚠️VTOR alignment trap

The lower bits of VTOR are read-as-zero. The required alignment is next_power_of_two(table_size). For an STM32 with ~100 IRQs, that is 512 bytes minimum. Place your vector table at a sufficiently aligned address — KEEP(*(.isr_vector)) in the linker script with explicit . = ALIGN(512); is the safe pattern.

Clock Tree Bring-Up

After reset, the MCU is running on the internal RC oscillator (HSI on STM32, e.g., 16 MHz). It's accurate to within a few percent — fine for getting started, terrible for UART baud rates, USB, or anything time-sensitive. Bring-up is a sequence:

text
Reset (HSI ~16 MHz, no PLL)
1. Enable HSE (external crystal) and wait for it to stabilize (HSE_RDY)
2. Configure PLL source (HSE) and multiplier
3. Enable PLL and wait for it to lock (PLL_RDY)
4. Set Flash wait states for the new (higher) SYSCLK frequency
│ ↑ MUST happen BEFORE switching SYSCLK if going faster
5. Configure AHB / APB1 / APB2 prescalers
6. Switch SYSCLK source to PLL (SW = PLL in RCC_CFGR)
7. Wait for SWS to confirm the switch
Running on PLL — peripherals can now be initialized

The order matters at the hardware level:

  • Flash wait states first. At higher SYSCLK frequencies, Flash needs more wait states for reads. If you switch SYSCLK before raising wait states, the CPU misreads instructions and crashes immediately.
  • PLL lock check. Don't switch SYSCLK until PLL_RDY is set. Switching to an unlocked PLL produces an undefined clock.
  • Prescalers before switch. If your APB1 limit is 50 MHz and your new SYSCLK will be 168 MHz, set the APB1 prescaler to /4 before switching SYSCLK, otherwise APB1 momentarily runs above spec.

SystemInit() (from CMSIS) does this sequence on most STM32 ports. On other vendors it may be different — read the reference manual's "Clock Tree Setup" section before assuming.

Peripheral Initialization Order

Peripheral init has a strict ordering at the hardware level. Skipping a step or doing them in the wrong order produces "the peripheral does nothing and there's no error" — a frustrating failure mode.

StepWhy
1. Enable the peripheral clock in RCCWithout a clock, peripheral registers are inaccessible (read returns 0xFFFFFFFF or 0; write is silently dropped)
2. Configure GPIO for the peripheral function (alternate function, speed, pull)Pin must be muxed before peripheral can use it
3. Configure the peripheral (mode, baud, polarity, etc.)Standard register setup — values depend on peripheral
4. Configure NVIC priority for any interrupts the peripheral generatesRequired before enabling, otherwise priority defaults may break preemption
5. Enable the peripheral interrupts in the peripheral's IER registerLocal enable
6. Enable the NVIC lineGlobal enable — only after handler is in place
7. Enable the peripheral itselfNow it starts operating
💡Common bring-up trap

Many candidates remember "configure the peripheral" but forget step 1 (enable its clock in RCC). On STM32, every peripheral has a separate clock-enable bit in RCC_AHBxENR / RCC_APBxENR. Reading or writing to a peripheral whose clock is off is silent, not faulting — debugging this can take hours.

From Reset to main(): The Hardware View

A simplified view of the entire chain:

DiagramPower-on to main()
 Power applied
      │
      ▼  (POR delay, oscillator startup)
 ┌─────────────────────────────────────┐
 │ Hardware                            │
 │  • Loads SP from vector[0]          │
 │  • Loads PC from vector[1]          │
 │  • Begins fetching instructions     │
 └─────────────┬───────────────────────┘
               │ (jumps to Reset_Handler)
               ▼
 ┌─────────────────────────────────────┐
 │ Firmware: Reset_Handler             │
 │  • Read & clear reset-cause flags   │
 │  • Set up C runtime (.data, .bss)   │  ← see Build Systems
 │  • Call SystemInit (clock tree)     │
 │  • Call main()                      │
 └─────────────────────────────────────┘
               │
               ▼
 ┌─────────────────────────────────────┐
 │ Application code                    │
 │  • Bring up peripherals             │
 │  • Start RTOS scheduler / main loop │
 └─────────────────────────────────────┘
Hardware does very little — firmware (Reset_Handler) prepares the C runtime and dispatches main().

Note the hardware portion ends after just a handful of bus cycles. Everything from "executing Reset_Handler" onward is firmware, and that firmware has to be bullet-proof because there is no debugger output, no console, no error reporting available yet — a fault here results in a dead board.

Debugging Story: Boot Failure After Adding HSE Crystal

A team migrated from HSI to a 25 MHz external crystal (HSE) for better USB timing. The firmware compiled cleanly, but the board hung immediately after reset. JTAG showed PC stuck at the line that switched SYSCLK to PLL.

The problem: SystemInit() started HSE, programmed the PLL, and switched SYSCLK — but never waited for the HSE_RDY flag before configuring the PLL source. On most boots HSE happened to be ready in time, but with a slightly slower-starting crystal in the new manufacturing batch, PLL was being clocked from a not-yet-stable source. PLL_RDY then took longer than expected, the SYSCLK switch raced ahead, and the CPU clocked itself from an unlocked PLL.

The fix was a textbook clock-bring-up addition: spin-wait on HSE_RDY before configuring PLL, with a timeout that falls back to HSI on failure. Two lines of code, one painful afternoon of bring-up debugging.

The lesson: Clock-source ready bits are not optional. Always wait for HSE_RDY, PLL_RDY, and the SYSCLK switch confirmation (SWS) — and bound each wait with a timeout so a missing crystal causes a defined fallback rather than a silent hang.

What Interviewers Want to Hear

  • You can name multiple reset sources and explain why diagnostics need to read the cause register early
  • You can describe the vector table layout (SP, Reset, NMI, HardFault, IRQs) and explain VTOR
  • You can sequence clock bring-up correctly: source → PLL → wait states → prescalers → switch
  • You can explain why peripheral clock-enable comes before register access
  • You know peripheral init order: clock, GPIO, peripheral config, NVIC priority, interrupt enable, peripheral enable
  • You know that .data/.bss setup is the build-system side of the same boot — and can cross-reference it

Interview Focus

Classic Interview Questions

Q1: "Walk me through what happens, at the hardware level, between power-on and the first instruction your code executes."

Model Answer Starter: "Power rises through Vcc, the brown-out / power-on detection circuit holds the core in reset, and the internal oscillator starts. After POR delay and oscillator stabilization, reset is released. The Cortex-M hardware then reads two words from the vector table: SP from address 0 and the Reset_Handler address from address 4. It loads SP, sets PC, and begins fetching. From that moment on, everything is firmware. The MCU is running on its internal RC oscillator (HSI on STM32) at the default speed — a few MHz to a few tens of MHz — and no peripheral clocks are enabled yet."

Q2: "What does the vector table contain and why does its alignment matter?"

Model Answer Starter: "It is an array of 32-bit words at the start of code memory. Word 0 is the initial stack pointer — read by hardware on reset before any instruction runs. Word 1 is the Reset_Handler address. Words 2 through 15 are system exception handlers: NMI, HardFault, configurable faults, SVC, PendSV, SysTick. From word 16 onward are the peripheral interrupt handlers in NVIC line order. Alignment matters because the VTOR register's lower bits are read-as-zero — the required alignment is the next power of two greater than or equal to the table size. For a chip with 100+ IRQs you need at least 512-byte alignment, enforced via the linker script."

Q3: "What is VTOR and when do you need to use it?"

Model Answer Starter: "VTOR is the Vector Table Offset Register. By default it is zero, meaning the table is read from the start of internal Flash. You change it any time you want a different table: a bootloader hands off to an application by setting VTOR to the application's Flash offset, then loading the new SP and jumping to its Reset_Handler. Dual-bank firmware uses VTOR to switch between A and B slots. Some RTOS implementations copy the table to RAM and set VTOR there so they can patch ISR pointers at runtime."

Q4: "Walk me through clock-tree bring-up to switch from HSI to PLL on an external crystal."

Model Answer Starter: "Six steps in this order: enable HSE and spin-wait for HSE_RDY with a timeout. Configure the PLL source as HSE and set the multiplier and dividers for the desired SYSCLK. Enable PLL and spin-wait for PLL_RDY. Set the Flash wait-state count for the new higher frequency — this must come before the SYSCLK switch, otherwise the CPU misreads instructions and crashes. Set AHB/APB prescalers so the bus clocks stay within their per-bus maximums after the switch. Finally, write the SYSCLK source select bits to PLL and confirm via the SWS bits that the switch took. Skipping the wait flags or doing the wait states out of order is a classic boot-time hang."

Q5: "A junior engineer wrote 'I configured the UART but no bytes are being sent.' What's the first thing you check?"

Model Answer Starter: "The peripheral clock enable in RCC. On most MCUs every peripheral has a separate clock-gate bit in an RCC enable register; if that bit is zero, all reads from the UART return zero (or 0xFFFFFFFF) and all writes are silently dropped. The UART configuration registers will look correct on the debugger but the peripheral itself is not actually clocked. After clock enable, the next things to check are GPIO alternate-function configuration, NVIC priority, and whether the global UART enable bit was actually set — in that order."

Trap Alerts

  • Don't say: "Boot is just reset, then main()" — this skips the entire vector-table / clock / peripheral-init chain that interviewers want you to explain.
  • Don't forget: Peripheral clock enable in RCC — if the peripheral isn't clocked, all access is silent, never faulting.
  • Don't ignore: Flash wait states when raising SYSCLK — wrong order causes immediate, undebuggable boot crashes.

Follow-up Questions

  • "What does SystemInit() typically do, and where does it get called from?"
  • "How do you read and clear the reset-source register on STM32?"
  • "How do you implement a dual-bank bootloader using VTOR?"
  • "What goes wrong if you forget to wait for PLL_RDY before switching SYSCLK?"
  • "How do you debug a boot hang when no debugger output is available?"
  • "Why is the vector table's first entry the initial SP and not the Reset_Handler?"
💡Cross-link: the C-runtime side of boot

This page covered the hardware side. The C-runtime side — Reset_Handler walkthrough, .data copy from Flash to RAM, .bss zeroing, VMA vs LMA, linker script mechanics — lives in Memory Layout & Startup under Build Systems. Interviewers commonly ask both halves; a complete answer connects them.

💡Practice Boot & Startup Interview Questions

Ready to test yourself? Head over to the Boot & Startup Interview Questions page for a full set of Q&A with collapsible answers — perfect for self-study and mock interview practice.

Practice

What are the first two values the Cortex-M hardware reads from the vector table on reset?

During clock-tree bring-up, when must you set the Flash wait states?

Why is VTOR alignment a power of two greater than or equal to the table size?

A peripheral configuration register reads back as zero after you write to it. What's the most likely cause?

Why should firmware read and clear the reset-cause register early in boot?

Real-World Tie-In

OTA-Capable IoT Device — A LoRa sensor node uses a dual-bank Flash layout: bank A holds the running firmware, bank B receives OTA updates. The bootloader at the start of Flash validates the CRC of each bank, picks the valid one, sets VTOR to that bank's vector table offset, loads the application's SP from the new table's first word, and jumps to its Reset_Handler. If the new firmware crashes (watchdog reset is recorded), the bootloader sees the watchdog flag in RCC_CSR on next boot and falls back to the previous bank.

Field-Returned Board with Random Hangs — A field team reported intermittent boot hangs on a single batch of boards. JTAG attach showed the CPU stuck during clock bring-up. Investigation revealed that the new HSE crystal vendor had slightly slower startup; the firmware was setting up PLL before HSE_RDY was asserted, occasionally clocking PLL from an unstable source. Fix: spin-wait on HSE_RDY with a 100 ms timeout that falls back to HSI on expiry. Zero failures since.

Smart Watch with USB-Critical Timing — Migrated from internal RC to a 25 MHz crystal to meet USB clock-accuracy requirements (USB-FS needs ≤ 500 ppm). Clock bring-up changed: HSE at 25 MHz feeds PLL multiplied to 168 MHz SYSCLK, with a separate USB PLL output dividing down to the required 48 MHz. Flash wait states bumped from 0 to 5. Boot time increased by ~1 ms (HSE startup), accepted in exchange for stable USB enumeration.

Was this helpful?