prerequisite
Flash, RAM, and partitions
Where code and data live on an ESP32: flash that survives power-off, RAM that does not, PSRAM, and the partition table that divides flash into regions.
Before this
This page assumes you are comfortable with:
Why you need this
Every flashing step names an offset, every out-of-memory error names a kind of RAM, and every lost setting after a power cut is a question of which memory it lived in. Before you can follow flashing and the ROM bootloader, you need a picture of the memories on the board and how flash is carved up.
The idea
An ESP32 board has three kinds of memory. This page uses 1 KB = 1024 bytes and 1 MB = 1024 KB, the way memory and flash sizes are counted.
| Memory | Where | Survives power-off? | Typical size | Speed |
|---|---|---|---|---|
| SRAM (static RAM) | Inside the chip | No | Hundreds of KB | Fastest |
| Flash | A separate chip on the module or board, wired over SPI | Yes | 4 MB to 32 MB | Slow to write, read through a cache |
| PSRAM (pseudo-static RAM) | A second chip, or inside the chip's package | No | 2 MB to 32 MB | Slower than SRAM |
RAM (random-access memory) is where a running program keeps its variables, stack, and buffers. It forgets everything when power goes away. Flash is the program's home: the bootloader, your app, settings, and files stay there with the power off. PSRAM is extra RAM on a separate memory die, for big buffers such as a display frame or downloaded files.
The boards on the workbench, from the author's board notes and the Inspector's catalog:
| Board | Chip | Internal SRAM | Flash | PSRAM |
|---|---|---|---|---|
| CYD, CYDE, WROOM-32 DevKit | ESP32 | 520 KB | 4 MB | none |
| Super Mini ESP32-S3 | ESP32-S3 | 512 KB | 4 MB | 2 MB |
| YD-ESP32-S3 (workbench unit, S3-N16R8 module) | ESP32-S3 | 512 KB | 16 MB | 8 MB |
| ESP32-C6-DevKitM clone | ESP32-C6 | 512 KB (0x40800000 to 0x4087FFFF) | 4 MB | none |
| Waveshare ESP32-P4 Core DevKit | ESP32-P4 | 768 KB (0x4FF00000 to 0x4FFBFFFF) | 32 MB | 32 MB |
The classic ESP32's 520 KB comes from ESP-IDF's memory-types guide. The S3's 512 KB is from the author's Super Mini notes; the other S3 board uses the same chip. Not all of it is free for your program: the boot ROM, the radio stack, and the operating system take their share first.
Flash is erased in sectors
Flash can be read one byte at a time, but writing is lopsided. A write can only clear bits from 1 to 0. Setting them back to 1 needs an erase, and erasing works only on a whole sector. On the flash chips ESP32 boards use, a sector is 4096 bytes (0x1000); esptool's documentation calls it "the smallest erasable unit." That is why every flash offset on these pages is a multiple of 0x1000.
Running code straight from flash
Copying a 1 MB program into a few hundred KB of RAM is impossible, so ESP32 chips run most code in place from flash. A hardware unit called the MMU (memory management unit) maps a window of flash into the chip's address space, and a cache keeps recently used parts in SRAM. ESP-IDF's guide says accessing cached flash "is as fast as accessing other types of internal memory" once it is in the cache. Code that must run when the cache is off, such as an interrupt handler that can fire during a flash write, goes in instruction RAM instead.
You can see both styles in a C6 boot log the Inspector annotates: a segment marked map at 0x42000020 is code read in place from flash, and a segment marked load at 0x40800000 is data copied into SRAM.
The partition table
Flash is one long array of bytes, so ESP-IDF divides it into named regions listed in a partition table. ESP-IDF writes the table at offset 0x8000 by default. Its documentation gives the rules:
- The table is 0xC00 bytes long, allowing up to 95 entries plus a checksum, and fills one 4 KB sector.
- Partitions of type
appstart on a 64 KB boundary (a multiple of 0x10000). - Data partitions start on a 4 KB boundary.
- A blank offset means "right after the previous partition", rounded up to the required boundary.
Common entries:
| Name | Type | Holds |
|---|---|---|
nvs |
data | Non-volatile storage: small key-value settings that survive power-off |
phy_init |
data | Radio calibration data |
otadata |
data | Which app slot to boot, for over-the-air updates |
factory |
app | The app flashed over USB |
ota_0, ota_1 |
app | Two app slots that an update writes to in turn |
| a filesystem partition | data | Files, such as MicroPython's main.py and CircuitPython's code.py |
Below 0x8000 sits the second-stage bootloader, at an offset that depends on the chip.
Worked example
ESP-IDF ships a built-in table called "Factory app, two OTA definitions" for 4 MB boards. This is its CSV file from ESP-IDF 5.4.1 (official framework file, offsets left blank for the tool to fill):
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
ota_0, app, ota_0, , 1M,
ota_1, app, ota_1, , 1M,
Fill in the offsets by the rules. The table ends at 0x8000 + 0x1000 = 0x9000, so the first partition starts there.
| Partition | Offset | Size | Ends at | How the offset was chosen |
|---|---|---|---|---|
| (bootloader) | depends on chip | below 0x8000 | fixed per chip | |
| (partition table) | 0x00008000 | 4 KB | 0x00009000 | ESP-IDF default |
| nvs | 0x00009000 | 16 KB | 0x0000D000 | right after the table |
| otadata | 0x0000D000 | 8 KB | 0x0000F000 | right after nvs |
| phy_init | 0x0000F000 | 4 KB | 0x00010000 | right after otadata |
| factory | 0x00010000 | 1024 KB | 0x00110000 | already on a 64 KB boundary |
| ota_0 | 0x00110000 | 1024 KB | 0x00210000 | right after factory |
| ota_1 | 0x00210000 | 1024 KB | 0x00310000 | right after ota_0 |
A 4 MB chip ends at 0x00400000, so 0x400000 - 0x310000 = 0xF0000 bytes, which is 960 KB, are left over for a filesystem. ESP-IDF's own documentation lists the same offsets.
The Inspector's echo payloads use the simpler single-app default instead: nvs at 0x9000 (24 KB), phy_init at 0xF000 (4 KB), and one 1 MB factory app at 0x10000. The classic ESP32 echo app is 151,760 bytes, about 148 KB, so it fills under 15% of its slot. Almost all of that is ESP-IDF's runtime; the echo itself is 55 bytes.
In an ESP32 project
Partitions decide stage 3. Flashing a program means writing the bootloader, the table, and the app to the right offsets (flashing and the ROM bootloader), and booting means the bootloader reading the table to find the app (the boot sequence). They matter again in stage 5: over-the-air updates need the two-slot layout. Settings live in nvs. The author's USB keyboard bridge, for instance, keeps its key-remap profiles and radio peers there, so erasing that partition resets them to the defaults.
Common mistakes
- Flashing the app to an offset the partition table does not name. The bootloader looks where the table says, finds no valid app there, and prints an error on every boot.
- An app bigger than its partition. The ESP-IDF build stops with a size error. Choose a larger table or shrink the app.
- Expecting a variable to survive a reboot. Anything in RAM is gone after a reset or power cut. Save it to
nvsor a file. - Erasing all of flash to fix a bad app. That works, but it also wipes
nvsand the filesystem, so saved settings and uploaded files disappear. - Assuming the board's flash size from the chip name. The same S3 chip ships with 4 MB, 8 MB, or 16 MB beside it. Read the module marking or ask esptool.
Cost
Flash is cheap in money and space but slow and finite to write: every write needs a 4 KB erase first, so a program that saves a tiny setting often should batch its writes. Two OTA slots double the flash spent on apps (2 MB of a 4 MB chip in the worked example) to buy a safe update. SRAM is the scarce resource: a few hundred KB shared by the stack, the radio, and your buffers, which is why boards with PSRAM are chosen for displays and large downloads.
Going further
- ESP-IDF Programming Guide, "Partition Tables" and "Memory Types".
- The esptool documentation on flash erase and
read-flash. - Over-the-air updates, for how the two OTA slots and
otadataare used. - Memory maps and registers, for the address ranges where flash and RAM appear.
Leads to
Back to ESP32 development: assembly, C, MicroPython, and CircuitPython