soldr.ai

Docs  /  When it doesn't work

It compiled. It flashed. The board still won't boot.

An ESP32-S3 that accepts a sketch and then loops on Invalid image block, can't boot is almost never a problem with your code. It is the board settings the image was built with.

4 min read When it doesn't work ESP32-S3

What you're seeing

The upload succeeds. The serial monitor then prints the same block over and over, a few times a second:

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x7 (TG0WDT_SYS_RST),boot:0x8 (SPI_FAST_FLASH_BOOT)
Saved PC:0x40043ac8
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce2820,len:0x10cc
load:0x3cc0c8ff,len:0x3cc11c40
Invalid image block, can't boot.
ets_main.c 329

Nothing in that block is produced by your sketch. Every line comes from the mask ROM bootloader, which runs before a single instruction of your code. That is the first and most useful thing it tells you.

The second load: line is the whole answer

Read the two load lines against each other:

LineAddressLengthVerdict
first segment0x3fce28200x10cc = 4,300 bytessane — valid internal SRAM, a believable size
second segment0x3cc0c8ff0x3cc11c40 = 1,019,288,640 bytesimpossible

The second length is roughly 972 MB. The chip has 512 KB of internal SRAM and at most 32 MB of flash. The address is also unaligned — it ends in ff, and segment addresses are four-byte aligned.

Look closer and the shape gives the mechanism away: 0x3cc0c8ff and 0x3cc11c40 are both values in the 0x3Cxx_xxxx range. The loader is reading an address where a length should be. It is not parsing a slightly wrong image — it has lost its place in the header entirely, so everything after that point is noise.

rst:0x7 (TG0WDT_SYS_RST) on the first line is the watchdog resetting a boot that never completed. That is the loop, not the cause. And boot:0x8 (SPI_FAST_FLASH_BOOT) confirms the board is trying a normal flash boot, so it is not stuck in download mode and the strapping pins are fine.

Why "it flashed successfully" proves nothing here

A successful flash verifies the write, not the boot. The uploader streams bytes to the chip and checks that what came back matches what it sent. That is a test of the transfer. Booting is a different operation: the ROM re-reads those bytes later, using the flash mode, speed and size recorded in the image header. Bytes that verify on write can still decode as garbage on read if that configuration does not match the actual hardware.

Which is why the two facts in the same sentence — "it flashed perfectly" and "invalid image block" — are not a contradiction. They are measurements of two different things.

Fixing it

The settings that matter are the ones baked into the image header, in this order:

  1. Flash size. If the build says 4 MB and the module has 16 MB, the partition table lands where the ROM is not looking. This alone produces exactly the log above.
  2. PSRAM type. An R8 module has octal PSRAM and needs the OPI setting. Built as QSPI or disabled, it will not come up.
  3. Flash mode. QIO / DIO / OPI must match the part. A mode mismatch is the classic cause of reads that come back shifted.
  4. Flash frequency. Some modules — clones especially — are not reliable at 80 MHz. 40 MHz is the safe test.

Then erase the whole chip and reflash. If only the sketch was rewritten, a bootloader or partition table left over from a differently-configured build will disagree with the new image, and the symptom survives every subsequent upload. In the Arduino IDE that is Erase All Flash Before Sketch Upload; with esptool it is erase_flash.

The variant trap: N16R8 is not "an ESP32-S3"

The suffix on the module is not decoration. On an ESP32-S3-DevKitC-1 N16R8, N16 is 16 MB of flash and R8 is 8 MB of octal PSRAM. An S3 SuperMini is a different part with different memory. Selecting a generic "ESP32S3 Dev Module" and leaving the defaults gives you 4 MB flash and no PSRAM — wrong on both counts for an N16R8.

Check the marking on the metal can before anything else. N16R8, N8R2, N8 and the rest each need different settings, and the boot log cannot tell you which board you have. One look at the module answers it.

Letting Soldr do it

Flashing straight from the browser sidesteps the whole category, because the target is chosen from the board in your build rather than from a menu you set by hand. If you have already taken the code into your own IDE, paste the boot log into Soldr and it will read the segment lines with you.

Diagnosis is free Pasting a boot log and asking what it means never costs credits.

If it still won't boot

Work down, stopping when the second load: line becomes believable:

The test throughout is that one line. A board that is going to boot prints a second segment with a plausible address and a length in the tens or hundreds of kilobytes.