Copying a microcontroller program is the process of reading the compiled binary firmware directly from a chip's non-volatile flash memory and saving it as a .hex or .bin file to clone it onto another identical chip.
In a real installation or repair scenario, this capability changes the game when you need to replace a fried custom controller—like a proprietary HVAC board or an industrial Arduino-based PLC—with a blank replacement chip without needing the original developer's source code. However, beginners commonly confuse copying the compiled binary with recovering the original C/C++ source code. A flash dump gives you raw machine code (0s and 1s), not the human-readable .ino or .cpp sketch file. You cannot simply open a dumped .bin file in the Arduino IDE and edit it.
The Memory Map: Where the Program Actually Lives
Before you can extract firmware, you must understand where the microcontroller stores it. Unlike a PC that loads programs from an SSD into RAM, a microcontroller executes code directly from its internal flash memory (or an external SPI flash chip, in the case of the ESP32). The memory map dictates the exact hexadecimal addresses your programmer must query.
| Microcontroller | Flash Size | Start Address | End Address | Page / Sector Size |
|---|---|---|---|---|
| ATmega328P (Arduino Uno) | 32 KB | 0x0000 | 0x7FFF | 128 bytes |
| STM32F103C8T6 (Blue Pill) | 64 KB | 0x08000000 | 0x0800FFFF | 1 KB |
| ESP32-WROOM-32 | 4 MB (External) | 0x00000000 | 0x003FFFFF | 64 KB (Sectors) |
| ATtiny85 | 8 KB | 0x0000 | 0x1FFF | 64 bytes |
Notice the STM32's start address. ARM Cortex-M chips map their flash memory to a specific peripheral bus address (0x08000000), whereas 8-bit AVR chips start at 0x0000. If you use a generic memory reader and fail to specify the correct base address, you will end up dumping the chip's RAM or reserved registers instead of the actual program.
Step-by-Step: Extracting the Binary
The toolchain you use depends entirely on the silicon architecture. Here is how the extraction works across the two most common hobbyist and prototyping ecosystems.
AVR (ATmega328P) via AVRDUDE and ISP
For 8-bit AVR chips, we use the In-System Programming (ISP) header (MISO, MOSI, SCK, RESET, VCC, GND) and a tool like AVRDUDE. Connect a USBasp programmer to the 6-pin ICSP header.
The Command:
avrdude -c usbasp -p m328p -U flash:r:backup.hex:i
Worked Numeric Example:
The ATmega328P holds exactly 32,768 bytes of flash. When you issue the read command over a standard 1 MHz ISP SPI clock, the theoretical transfer time for the raw payload is roughly 0.26 seconds. However, because the ISP protocol requires command-setup bytes and acknowledge polling for each page, the actual read operation takes about 3 to 4 seconds. The resulting Intel HEX file will map addresses from 0x0000 to 0x7FFF.
delay(1000) in the original logic will physically take 2 seconds on your new board because the compiled machine code relies on the hardware clock frequency defined at compile time.
ESP32 via esptool.py
The ESP32 does not have internal flash; it executes from an external SPI flash chip (usually 4MB or 8MB). You read it over the UART serial pins (TX, RX, GPIO0, EN) using Espressif's official esptool.
The Command:
esptool.py --port COM3 read_flash 0x00000 0x400000 esp32_full_dump.bin
This command tells the tool to start at address 0x00000 and read 0x400000 bytes (4,194,304 bytes, or 4MB). Because UART transfer is relatively slow, dumping a full 4MB ESP32 flash at a baud rate of 115200 will take roughly 6 minutes. Bumping the baud rate to 921600 cuts this down to under 45 seconds, provided your USB-to-Serial adapter (like the CP2102 or FT232RL) supports it without dropping packets.
Where You Meet This in Practice
Flash dumping is not just an academic exercise; it solves specific, high-stakes problems in the field and on the workbench.
- Proprietary Board Recovery: A voltage spike on a 24V industrial sensor line backfeeds through a linear regulator and fries the ATmega2560 on a custom 3D printer controller board. The manufacturer is out of business, and no source code exists. By desoldering the dead chip, reading the flash from a donor board (or using a high-voltage parallel programmer if the chip is just partially damaged), and writing it to a new ATmega2560, you restore a $2,000 machine for $8 in parts.
- Production Cloning: You prototype a commercial IoT device on an Arduino Nano. When it is time to manufacture 500 units, you do not install the Arduino IDE on the factory floor. Instead, you dump the final
.hexfile and load it into a standalone gang programmer (like the Xeltek SuperPro) or a custom bed-of-nails test jig, flashing 8 bare ATmega328P-PU chips simultaneously in under 10 seconds. - IoT Security Auditing: Security researchers extract firmware from smart home devices to look for hardcoded MQTT broker passwords, Wi-Fi credentials, or unpatched vulnerabilities. By dumping the ESP32 flash and running the binary through a tool like
binwalk, they can extract the embedded LittleFS or SPIFFS filesystem partitions where developers mistakenly stored plain-text API keys.
The Roadblock: Read Protection and Lock Bits
What happens when the original developer does not want you to copy their code? Microcontroller manufacturers implement hardware-level security fuses to prevent unauthorized reading of the flash memory.
On AVR chips, these are the Lock Bits (LB1 and LB2). If programmed, the ISP interface will refuse to output the flash contents, returning all 0xFF (blank) or 0x00 depending on the specific silicon revision. You can verify your fuse and lock bit settings using tools like the Eleccelerator AVR Fuse Calculator to ensure you aren't accidentally bricking your own prototypes.
On ARM Cortex-M chips (STM32), this is called Readout Protection (RDP). STM32 chips feature three levels of RDP. Level 1 prevents reading via the SWD/JTAG debug port but allows execution. Level 2 permanently disables the debug port entirely.
If a chip has Read Protection enabled, the only way to bypass it and copy the program is to unlock the chip. However, the silicon architecture dictates that removing read protection triggers a full mass erase of the flash memory. Therefore, you cannot extract a read-protected program without destroying the original firmware in the process. If you are dealing with a locked chip, the binary is gone.
The ESP32 takes this a step further with Flash Encryption. If the FLASH_CRYPT_CNT efuse is burned, the data stored on the external SPI flash is encrypted using AES-256. If you use esptool.py to dump the flash, you will successfully download the 4MB file, but it will be entirely unreadable ciphertext. As detailed in the Espressif Flash Encryption documentation, the decryption happens on-the-fly inside the silicon's secure boot ROM, meaning the raw binary cannot be cloned to a different ESP32 module because the new module lacks the original chip's unique, factory-burned AES key.
Frequently Asked Questions
Can I decompile a dumped .hex file back into Arduino C++ code?
No. You can use a disassembler (like avr-objdump or Ghidra) to convert the binary into Assembly language instructions (e.g., LDS R16, 0x0060), but the original variable names, comments, and C++ logic structures are destroyed during the compilation process. You will get the machine's logic, not the human's source code.
Does copying the program also copy the EEPROM data?
No. Flash memory (where the program lives) and EEPROM (where runtime settings and calibration data live) are separate physical memory blocks. You must issue a separate command to dump the EEPROM. In AVRDUDE, this is done by adding -U eeprom:r:backup.eep:i to your command string. If you forget this step, your cloned chip might boot up but fail to operate because it lacks its calibration constants.
Why does my ST-Link fail to read an STM32 chip?
The most common bench error is connecting to a chip that is in deep sleep mode or has its SWCLK/SWDIO pins repurposed as GPIOs in the firmware. You must hold the RESET button down on the target board, click 'Read' in your software (like STM32CubeProgrammer), and release the RESET button exactly one second later. This catches the chip in its bootloader state before the user code can reconfigure the debug pins.






