The puzzle
A busy-wait delay written for 10 ms lasts 104 ms the first time it runs, when it is called early in startup, and 10 ms ever after. And on another board, a counter stored in a RAM variable “survives” a reset button press but comes back as random junk after the battery is swapped. None of this is a bug in the delay or the counter. It is the clock and the reset, the two systems that run before your first line of code and decide the conditions it runs under. What do they do, and what can firmware count on?
STEP 1
The clock sets the pace
Every register, counter and state machine in the chip changes state on clock edges. A clock of frequency has period : 125 MHz is 8 ns per cycle, 12 MHz is 83.3 ns. Lesson 2’s timing formula, , is only as good as your knowledge of , and is set by firmware.
Microcontrollers offer several clock sources:
- Internal RC oscillator. On-chip, starts almost instantly, needs no external parts, but its frequency typically varies noticeably with temperature, supply voltage and from chip to chip.
- Crystal oscillator. Uses an external quartz crystal; accurate to within a small fraction of a percent, but takes longer to start (often milliseconds).
- PLL (phase-locked loop). Multiplies a reference up to a high frequency, then divides it down: .
The RP2040 SDK’s default plan is a worked instance of all three ideas: a 12 MHz crystal feeds the system PLL, which multiplies by 125 to a 1500 MHz internal oscillator and divides by 6 and then 2:
The SDK sets this up in its runtime initialisation, after reset and before main(). Until then the chip runs from whatever clock it resets to, often an internal oscillator slower than the final clock. That explains the delay in the opening: a loop calibrated for 125 MHz that runs before the clocks are configured, on a start-up clock of, say, 12 MHz, takes times longer. Any busy-wait simply scales with the frequency it actually runs at. Unit 8 builds the full clock tree; the lesson here is that the clock frequency is part of the program’s state, not a constant.
STEP 2
Memory has its own speed
A clock edge every 8 ns does not make flash faster. A flash read takes a fixed access time set by the memory technology; if that is longer than one clock period, the flash controller holds the core with wait states (lesson 3’s wait signal) until the data is valid. In the simplest model,
wait states per access, so each fetch takes cycles.
↑ This step uses the figure at the top of the page.
Real parts publish a table of required wait states against clock frequency and supply voltage, and firmware must program the flash controller for the right number before raising the clock; raising the clock first means reading flash too early and executing garbage. The penalty is hidden, most of the time, by prefetch buffers that read ahead along straight-line code and by caches that keep recently used instructions. The RP2040 reads its external flash through such a cache, which is why its address map has separate cached and uncached flash windows.
STEP 3
Reset puts everything in a known state
Logic that has just powered up is in an arbitrary state. Reset puts the blocks it covers into their documented reset state and starts the core at a fixed place. Several things can trigger it:
- Power-on reset (POR): the supply has just risen past a threshold.
- Brown-out reset (BOR): the supply dipped below a safe level while running. The RP2040’s brown-out detector, for example, is enabled at reset with its threshold set to 0.860 V (reset value 0x91 of its
BODregister); its selectable thresholds, 0.473 V to 1.118 V, show that it watches the chip’s low-voltage core supply, not the 3.3 V I/O supply. - External reset pin: a button or supervisor chip pulls it low (the RP2040 calls it RUN).
- Watchdog: firmware did not refresh the watchdog timer in time (unit 14).
- Software request: firmware asks for a reset, for example after installing an update. On Cortex-M, CMSIS’s
NVIC_SystemReset()writes the key 0x5FA and the SYSRESETREQ bit to theAIRCRregister; what that reset covers is decided by the chip. (The RP2040 SDK’s own reboot function,watchdog_reboot(), goes through the watchdog instead.) - Debugger: a probe restarts the target.
Each reset returns the blocks it covers to their documented reset values and restarts execution at the reset vector; which blocks each source covers is listed in the chip’s reset chapter. The causes differ in what they leave behind and in how firmware can tell them apart. The reset-cause registers named are the RP2040’s (from its SDK register headers); other chips have an equivalent reset-status register with different names.
What a reset guarantees is the documented reset value of each register it covers, which reference manuals and generated headers list field by field (the SDK’s headers carry a _RESET value for every field). Not everything is covered: the core’s general-purpose registers r0–r12 are architecturally unknown after reset, reset-cause registers deliberately keep their record, and different sources reset different sets of blocks (the RP2040 lets firmware choose which blocks a watchdog reset touches). The chip’s reset chapter lists what each source covers. What no reset guarantees is RAM contents. After a power-on reset, SRAM is indeterminate: it cannot be relied on for anything. After a reset with power maintained, SRAM usually still holds what it held, which is why the counter on the third board seemed to survive the button, but that is not a promise unless the chip documents it. Where retention matters, chips provide explicit mechanisms: the RP2040’s watchdog has scratch registers documented to persist through a soft reset, and the SDK uses them to pass information across a reboot.
STEP 4
From reset to main()
On a Cortex-M processor the first thing the core does after reset is read the vector table, a table of 32-bit words at a known address (address 0 on reset for Armv6-M):
- Word 0 is loaded into SP: the initial stack pointer.
- Word 1 is loaded into PC: the address of the reset handler. Its lowest bit must be 1, marking Thumb code; the core clears it when it sets the PC and records Thumb state in the xPSR’s T bit.
- The reset handler, part of the startup code, sets up the C environment: copy
.datafrom flash to RAM, zero.bss, initialise clocks and other hardware, and finally callmain().
The RP2040 SDK’s crt0.S shows exactly this shape: its vector table begins .word __StackTop, .word _reset_handler, and the handler copies data, clears .bss, calls runtime_init and then main. (On the RP2040 the core first runs the boot ROM mapped at address 0, which finds the program in flash and hands over to this vector table; unit 5 follows that path.) No C code that relies on initialised or zeroed static variables may run until .data and .bss are set up, which is why the first part of startup code is written in assembly or in very careful C. (Some startup files call a C function such as CMSIS’s SystemInit() to configure clocks even before that; such functions must not use static data.)
STEP 5
Worked example: why a 5× faster clock was only 4 % faster
A loop executes 4 instructions per iteration for 1 000 000 iterations directly from flash with a 40 ns access time. Assume one cycle per instruction plus flash wait states, and, to expose the effect, no cache or prefetch.
At 24 MHz: ns, longer than 40 ns, so . Each instruction takes 1 cycle: cycles ms.
At 125 MHz: ns, , so each fetch takes 5 cycles: cycles ms.
The clock went up 5.2 times and the loop got 4 % faster. With the loop held in a cache (or copied to SRAM, which the RP2040 SDK supports through its “not in flash” function attributes), every fetch takes one cycle and the same loop takes ms. The model is deliberately crude; real cores fetch 32 or more bits at a time and prefetch ahead. But it is why datasheets give wait-state tables, why chips have caches, and why the only trustworthy timing is a measurement (unit 6) or a hardware timer (unit 8).
MYTHS AND FACTS
Common misconceptions
The processor starts at main()
It starts at the reset handler named in the vector table; startup code runs first and calls main().
Reset clears RAM
Reset sets the registers it covers to their reset values. RAM is indeterminate after power-on and merely unchanged, without guarantee, after other resets; startup code zeroes only .bss.
Twice the clock means twice the speed
Only if memory keeps up. Wait states, peripheral bus speeds and cache misses do not scale with the core clock.
Reset values are zero
Each register field has its own documented reset value; the RP2040’s brown-out control register, for example, resets to 0x91.
A watchdog reset is the same as power-on
The cause registers distinguish them, and good firmware treats them differently: a watchdog reset means something went wrong.
The internal oscillator frequency is exact
It is specified with a tolerance; anything timing-critical (serial baud rates, timekeeping) needs that tolerance checked or a crystal.
Check yourself
Answer in your head, then open the card.
A PLL has a 16 MHz reference, a feedback multiplier of 96, and post-dividers of 4 and 3 (reference divider 1). What is the output frequency?
MHz.
Flash access time is 30 ns. How many wait states are needed at 48 MHz and at 100 MHz, in the simple model?
48 MHz: . 100 MHz: , with zero margin: the access fits exactly in three cycles. Real wait-state tables build in margin for temperature, voltage and process, so a datasheet might well demand 3 here.
The first two words of a Cortex-M vector table are 0x2004_2000 and 0x1000_01F7. What are SP and PC after reset?
SP = 0x2004_2000. PC = 0x1000_01F6: bit 0 of the vector marks Thumb state and is not part of the address.
Why must firmware set the flash wait states before switching to a faster clock, not after?
Between the two steps the core would run at the high clock with too few wait states and read flash before the data is valid, executing corrupted instructions.
Sources (5)
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2_common/hardware_clocks/include/hardware/clocks.h and src/rp2040/hardware_regs/include/hardware/platform_defs.h — the default clock plan: a 12 MHz crystal (XOSC_KHZ 12000) feeds the system PLL, “12 / 1 = 12MHz * 125 = 1500MHz / 6 / 2 = 125MHz” (SYS_CLK_KHZ 125000)
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2_common/pico_standard_link/crt0.S — the application’s vector table begins “.word __StackTop” then “.word _reset_handler”; the reset handler copies .data, clears .bss, calls runtime_init and then main
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2040/hardware_regs/include/hardware/regs/vreg_and_chip_reset.h and watchdog.h — CHIP_RESET at offset 0x08: HAD_POR (“power-on reset or brown-out detection”), HAD_RUN (“the RUN pin”), HAD_PSM_RESTART (“the debug port”); BOD reset value 0x91 (enabled, threshold 0.860 V); WATCHDOG_REASON at offset 0x08 is zero after a hardware reset and has TIMER and FORCE bits; the watchdog SCRATCH registers persist through a soft reset
- Arm, CMSIS 6, CMSIS/Core/Include/core_cm0plus.h, __NVIC_SystemReset() and SCB_AIRCR definitions — a software reset request writes the key 0x5FA to AIRCR.VECTKEY (bits 31:16) together with SYSRESETREQ (bit 2)
- Arm, Armv6-M Architecture Reference Manual (DDI0419), §B1.5.5 “Reset behavior” and §B1.5.3 “The vector table” — on reset the processor loads SPmain from vector table entry 0 and the PC from entry 1 (the reset vector, whose bit 0 must be 1 for Thumb state)