Firmware image size misteries (to me)


Case in point. Platformio | Zephyr | nrf52 board

  1. Build phase
    Checking size .pio/build/xpto/firmware.elf
    Advanced Memory Usage is available via “PlatformIO Home > Project Inspect”
    RAM: [=== ] 34.9% (used 22844 bytes from 65536 bytes)
    Flash: [=== ] 26.4% (used 138486 bytes from 524288 bytes)

  2. Upload phase
    Downloading file [.pio/build/xpto/firmware.hex]…
    Comparing flash [100%] Done.
    Erasing flash [100%] Done.
    Programming flash [100%] Done.
    J-Link: Flash download: Bank 0 @ 0x00000000: 1 range affected (143360 bytes)
    J-Link: Flash download: Total: 2.695s (Prepare: 0.045s, Compare: 0.016s, Erase: 0.081s, Program & Verify: 2.544s, Restore: 0.007s)
    J-Link: Flash download: Program & Verify speed: 55 KB/s

  3. As seen on disk
    ls -lh firmware
    -------- 2,7M -------- firmware.elf
    -------- 394K -------- firmware.hex
    -------- 2,7M -------- firmware-pre.elf

Question: Although it apparently gets stripped off during upload phase, what else in on that / those images to make them larger and what is it for?


This is the size of firmware binary that must be transported to the board as reported by PlatformIO. Around 135.24 kBytes. Note that PlatformIO determines this size by only looking at a few specific sections in the ELF, so if any custom ones are in there, those won’t be counted.

Flash is arranged in pages and blocks. When programming a device, the old content has to be erased first. Erasing is done in a block size. So really, the J-Link (or any debug probe) cannot just delete the exact 138486 Bytes in flash – it must always erase whole blocks. Per datasheet page 24 we see the

and read on page 29

When erase is enabled, the Flash can be erased page by page using the ERASEPAGE register.
As illustrated in Memory on page 23, the Flash is divided into multiple pages that are further divided into multiple blocks. […] See the memory size and organization in Memory onpage 23 for block size

As one can see from the addresses, one page is 0x1000 bytes, or 4096 bytes decimal, big, and flash has to be erased in multiples of 4 kBytes. However, the firmware size was not a multiple 4096, but we still have to erase in full blocks, thus leading to more bytes being erased than the firmware is actually big. This is perfectly normal.

A Intel hex file encodes the binary content (and addresses, and some other metainformation) in human-readable ASCII text. Binary to HEX conversion at least doubles the amount of space requires as compared to the pure binary format (.bin). So, the actual amount of bytes written will be less than half of the sie of the .hex file.

A 1:1 correlation would be there if the file was in .bin format – the size of that file matches 1:1 the length and binary contents of what’s actually written into flash then. In this case for flashing, PlatformIO doesn’t need the file in .bin format, so it doesn’t generate it. You can still generate it by converting it from the .hex file (or the .elf file), by running the toolchain tool objcopy, as in e.g.

~/.platformio/packges/toolchain-gccarmnoneeabi/bin/arm-none-eabi-objcopy -I ihex -O binary <path to firmware.hex> firmware.bin

will create firmware.bin, whose size is 1:1 representative as to that in flash, as said before.

1 Like

Thanks, this was very useful. My question was mainly around the big size of the image on my disk, I wasn’t aware that intel hex file has such a large overhead, I’ll go read.

I was aware of the flash operation on block issue. Although apparently 34 blocks (*4096 = 139264) should have been sufficient but that’s peanuts for me on this occasion.

One more question, besides the hex and elf that I can see, is platformio able to generate the pure bin?


Yes, but since the the main builder code doesn’t always generate the .bin (depending on the upload protocol, with upload_protocol = sam-ba e.g. it would), you have to add it explicitly as a post-step through Advanced scripting.

If you e.g. add

extra_scripts =

in the platformio.ini, and then in the root directory with content

# Custom BIN from ELF
    env.VerboseAction(" ".join([
        "$OBJCOPY", "-O", "binary",
        "$BUILD_DIR/${PROGNAME}.elf", "$BUILD_DIR/${PROGNAME}.bin"
    ]), "Building $BUILD_DIR/${PROGNAME}.bin")

it should generate the firmware.bin on the next clean & build.

In that case PlatformIO may have undercounted the firmware size due to missing some minor section. The .bin file holds the truth.

1 Like