The puzzle
Plug in a board and, a few milliseconds later, an LED blinks. In between, the supply rose from 0 V through a region where no transistor behaves predictably, something decided the chip was ready, and the processor fetched its first instruction without any code telling it where to look. Your firmware’s first line runs in an environment that someone else set up: which parts of it can you count on, and which parts are still random?
STEP 1
Waiting for a good supply
Logic is only defined at a valid supply voltage. While the supply ramps up from zero, flip-flops can settle in any state, and code that ran would be unpredictable. Microcontrollers therefore contain a power-on-reset (POR) circuit (a few rely on an external supervisor chip instead) that holds the whole chip in reset until the supply has passed a threshold, and then keeps holding for a short additional delay so that the supply and internal regulators can settle. Only then is reset released.
If the supply rises in from 0 to roughly linearly, it crosses a threshold at , and the core can start fetching at about
where is the POR hold delay and the start-up time of the clock the chip boots on. This simple model adds the clock start-up after the hold; in many chips the internal oscillator already runs during the hold, and some use it to time the hold. Every number in that expression is chip-specific; datasheets also specify a maximum supply ramp time, because a very slow ramp can confuse the POR circuit.
↑ This step uses the figure at the top of the page.
A brown-out detector does the same job while running: if the supply sags below a safe level it forces a reset rather than let the logic misbehave (unit 14). The RP2040’s chip-level reset block (VREG_AND_CHIP_RESET) records which of these caused the last reset: its CHIP_RESET register has one flag for power-on or brown-out, one for the RUN pin and one for the debug port (unit 3 lesson 6).
STEP 2
What the core does by itself
When reset is released, a Cortex-M core does two things before executing any instruction (Armv6-M Architecture Reference Manual, “Reset behavior”):
- It reads word 0 of the vector table into the main stack pointer, SP.
- It reads word 1, the reset vector, into the PC, using bit 0 as the Thumb state bit.
On an Armv6-M core the table is at address 0 at this point. The core starts in Thread mode, privileged, using the main stack. That short list is all the hardware sets up. Everything else is either a documented reset value or explicitly undefined:
Architectural facts (Armv6-M, such as the Cortex-M0+) apply to every vendor’s chip; the clock and peripheral rows are the RP2040’s, from its boot ROM source and SDK register headers, as one example. Your reference manual lists the equivalent reset values for your part.
Three consequences are worth spelling out.
The stack exists before any code runs. Because SP comes from the table, the reset handler can be an ordinary C function with local variables. That is a deliberate Cortex-M design choice; older architectures needed an assembly stub just to set up a stack.
Registers r0–r12 are unknown. Code that reads a register before writing it, for example a hand-written assembly startup that assumes r0 = 0, works on one chip or one debugger and fails on another.
Interrupts are not masked, but none can fire. PRIMASK is 0, so interrupts are not globally disabled, but every interrupt-enable bit in the NVIC is 0 after reset, so no peripheral interrupt can be taken until firmware enables it. Of the exceptions that can arrive on their own, only NMI and HardFault are live from the first instruction; SVCall and PendSV happen only when software raises them.
STEP 3
What the rest of the chip looks like
SRAM is indeterminate after power-on. On most microcontrollers, including the RP2040, nothing clears it; startup code must initialise whatever the program relies on (lesson 4).
Flash is unchanged: it holds the program and the vector table, which is the whole point.
Peripherals are at their reset values, and many chips go further and hold them in reset or with their clocks gated. The RP2040 is an extreme example: its RESETS register resets to 0x01FFFFFF, holding every peripheral in reset. Its boot ROM releases only what it needs: the GPIO pads (to switch off the input buffers of the ADC pins), then the QSPI pins and pads it needs to read flash, and the timer. The SDK’s runtime_init() later resets every peripheral except the flash interface it is running from and a few clock, USB and system-control blocks, releases those that need only the system and reference clocks, configures the clocks, and only then releases the UARTs, SPIs, ADC, RTC and USB. A driver that writes a UART’s registers before its reset is released writes to a block that is not listening.
The clock is whatever the chip boots on, usually an internal oscillator: fast to start, but neither fast nor accurate. The RP2040 boots from a ring oscillator; the comments in its boot ROM source tabulate the resulting system clock as 1.8 MHz minimum, 6.5 MHz typical and 11.3 MHz maximum. Code that runs before the clock is configured, which on the RP2040 includes the SDK’s start-up code (lesson 5), runs at an uncertain speed several times slower than the final clock.
STEP 4
Worked example: when does the first instruction run?
A board’s 3.3 V regulator takes 2 ms to ramp up. Suppose (illustratively) the POR threshold is 2.8 V, the hold delay 1 ms, and the chip boots on an internal RC oscillator that is running within 50 µs.
The first instruction runs about 2.75 ms after power is applied. If the same chip had to start a crystal oscillator first, typically much slower to start than an RC oscillator, the figure would grow by the crystal’s start-up time; that is one reason chips boot on an internal oscillator and let firmware start the crystal later.
Now the reset handler starts. At the RP2040’s typical 6.5 MHz boot clock, each cycle is about 154 ns. A startup routine of 20 000 cycles, mostly copying and zeroing memory (lesson 4), takes about 3 ms at that clock, but anywhere from 1.8 ms at 11.3 MHz to 11 ms at 1.8 MHz. Start-up time budgets must use the slow end of the range.
MYTHS AND FACTS
Common misconceptions
Reset sets all registers to zero
Each register has its own documented reset value, and the core’s general-purpose registers r0–r12 are unknown.
Interrupts are disabled at reset
They are not globally masked (PRIMASK = 0), but no NVIC interrupt is enabled, so none can fire until firmware enables one. NMI and HardFault are live from the start.
The processor starts executing at address 0
A Cortex-M core reads the stack pointer and reset vector from the table at address 0, then starts at the address the reset vector gives.
RAM is cleared at power-on
On most chips SRAM is indeterminate; startup code clears what the program needs.
The chip starts at full speed
It starts on a boot clock, often an internal oscillator running several times slower than the final clock and with a wide tolerance.
Peripherals are ready after reset
Many chips hold peripherals in reset or with clocks gated until firmware releases them.
Check yourself
Answer in your head, then open the card.
A supply rises from 0 to 3.3 V in 5 ms; the POR threshold is 2.64 V and the hold delay is 0.5 ms. Ignoring clock start-up, when is reset released?
ms to reach the threshold, plus 0.5 ms: 4.5 ms.
A hand-written startup routine begins adds r0, r0, #1 and uses r0 as a counter. What is wrong?
r0 is unknown at reset. The counter starts from whatever value the register happens to hold. Every register must be written before it is read.
Right after reset, a peripheral raises its interrupt request. PRIMASK is 0. Is the handler called?
No. The interrupt is not enabled in the NVIC (all enable bits reset to 0), so it stays pending; nothing is taken until firmware enables it.
A startup routine takes 30 000 cycles. How long does it take on the RP2040’s boot clock, at the typical and the slowest rates from the boot ROM’s table?
Typical 6.5 MHz: ms. Slowest 1.8 MHz: ms.
Sources (5)
- Arm, Armv6-M Architecture Reference Manual (DDI0419), §B1.5.5 “Reset behavior” — on reset the processor enters Thread mode, privileged, using the main stack; SPmain is loaded from vector-table entry 0 and the PC from entry 1 with bit 0 giving the Thumb state; LR is set to 0xFFFFFFFF; general-purpose registers r0–r12 are UNKNOWN
- Arm, CMSIS 6, CMSIS/Core/Include/core_cm0plus.h — SCB->VTOR (TBLOFF from bit 8) where the core implements it, the NVIC enable registers (ISER/ICER) and SysTick control register
- Raspberry Pi Ltd, pico-bootrom-rp2040, bootrom/bootrom_main.c and bootrom/bootrom_rt0.S — the boot clock table in the source comments (CLK_SYS on startup: min 1.8, typ 6.5, max 11.3 MHz); main() releases only IOQSPI, PADS_QSPI and TIMER from reset before reading flash (bootrom_rt0.S has already released PADS_BANK0 to disable the ADC pins’ input buffers); rt0: “On a cold boot, the clocks will already be enabled, because the power-on state machine will have reset the clock controls”
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2040/hardware_regs/include/hardware/regs/resets.h and vreg_and_chip_reset.h — RESETS_RESET_RESET 0x01ffffff: every peripheral is held in reset after a chip reset; CHIP_RESET records whether the last reset came from power-on/brown-out, the RUN pin or the debug port
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2_common/pico_runtime/runtime.c — runtime_init() resets every peripheral except the QSPI interface, the PLLs, USB and SYSCFG, releases at once those clocked only by clk_sys and clk_ref, calls clocks_init(), then releases the rest (UARTs, SPIs, ADC, RTC, USB)