IoT
How ESP32 Starts When Power Is Applied
November 22, 2023
The ESP32 is a RISC-based microcontroller widely used among electronics enthusiasts, largely through the Arduino platform. Arduino makes development fast and easy, which also means the boot process underneath it isn’t widely understood. This article goes through what actually happens when power is applied to an ESP32.
Reset causes
On every power-on, the ESP32 first checks its reset cause, and the boot flow depends on which one it finds. There are three broad cases:
- Reset from deep sleep
- Software CPU reset, watchdog (WDT) CPU reset
- Power-on reset, software SoC reset, WDT SoC reset
This article covers the third case in depth. The first two are special scenarios worth their own write-up, coming soon.
Reset vector and the first-stage bootloader
Every microcontroller is hardwired to load a predefined program memory address into the program counter (PC) the moment it starts. That address is the reset vector. On the ESP32, the reset vector points into an internal ROM, programmed by the manufacturer and read-only. It’s still firmware, just a very specific kind:
- Much smaller than the application it hands off to
- Only initializes the peripherals strictly needed to get the application started
- Lives in read-only memory
This firmware is known as the ROM code, ROM bootloader, or 1st stage bootloader (terminology can get confusing, since in some contexts “1st stage bootloader” refers to the next firmware the ROM bootloader hands off to instead; here we’re following ESP32’s own documentation, where the ROM firmware itself is the 1st stage bootloader).
Depending on the exact chip, the ROM firmware’s responsibilities differ slightly, but broadly: on power-on, all initial processing runs on the PRO core, while the APP core stays in low-power sleep. Execution starts from the reset vector, which on the ESP32 is 0x40000400.
Bootstrapping pins
The ROM firmware reads a set of bootstrapping (strapping) GPIO pins, which let you pass information into it at boot. The ESP32 supports two boot modes:
- SPI Boot
- Download Boot
The mode is selected by the state of GPIO0 and GPIO2 at reset. This is why most ESP32 dev boards have a “Boot” button next to the reset button: holding Boot while resetting puts the chip into download mode.
A few other things are configurable through strapping pins at the same time:
| Function | Notes |
|---|---|
| Boot mode select | GPIO0 / GPIO2, as above |
| UART0 TX debug logging | Enable if you need boot-time debug logs |
| Internal voltage regulator output | Must match the SPI flash voltage; already handled for you on ESP32 modules |
| SDIO slave timing | Flash interface clock and clock edge (FE = falling edge, RE = rising edge); already handled for you on ESP32 modules |
Address space and memory layout
The rest of the boot process is tightly coupled to the ESP32’s address space and memory layout, so it’s worth having that picture in mind before going further: during boot stage 1, the PC points into ROM while the stack pointer (SP) points into DRAM.
Boot stage 2: the second-stage bootloader
Since the 1st stage (ROM) bootloader is fixed at manufacturing time, it expects the 2nd stage bootloader at SPI flash address 0x00001000 (or, if secure boot is enabled, after the first 4 KB reserved for the secure boot header; this article assumes secure boot is off, so that first 4 KB is unused).
The 1st stage bootloader verifies the 2nd stage bootloader using details encoded in its image header, against a memory map of the SPI flash (typically 4 MB, with OTA partitions). Once verified, control jumps to the 2nd stage bootloader.
The 2nd stage bootloader is open-source and user-customizable. The default one shipped with ESP-IDF is a capable bootloader with OTA support built in, and it’s the one this article covers (customizing it is a topic for its own future write-up). Its responsibilities:
Reading the partition table. Once it has control, it reads the partition table and checks whether an OTA section is present.
Identifying the OTA boot section. ESP32 uses an A/B partition scheme for OTA: two same-sized partitions hold the firmware, and a new update always writes to whichever partition isn’t currently active (if A is running, B gets the update, and vice versa).
Memory loading and configuration. The identified binary images update the memory layout, the MMU gets configured for both the PRO and APP cores, the application image is validated, and control passes to it.
Boot stage 3: application startup
The 2nd stage bootloader hands off by calling a non-returnable startup function pointer into the application. From there, initialization is architecture- and application-specific, though ESP-IDF follows a consistent structure (worth its own write-up soon). Once setup completes, any additional CPU cores are started.
Notes
If RTC slow or fast memory has been used to optimize boot time (via the STAT_VECTOR_SEL registers), the device can start execution from RTC memory instead of ROM, changing the flow described above.