This one is about the wire, not the chip. Nothing below changes with the board you picked, which is why the chip and language switches are not on it. They come back on the ESP32 pages this one sits underneath.
Firmware in two slots
The code receiving the update is the code being replaced, so it cannot overwrite itself. It writes into a second slot and switches on reboot — which is why OTA costs half your flash and why a bad build does not mean a ladder.
Size a sketch, pick a layout, upload. One of the two layouts cannot do this at all.
Two slots, one running
Flash is divided by a partition table: a bootloader, a little storage for settings, one or two slots for your program, and whatever is left for files. An over-the-air update writes the new build into the slot that is not running, then sets a flag saying which slot to boot next.
Three consequences fall straight out of that, and they are the whole subject:
- Your sketch can never exceed half the flash. A 4 MB module gives roughly 1.25 MB per slot with the default layout. A 1.4 MB build compiles, uploads over a cable, and fails over the air — the arithmetic, not the network.
- Losing power mid-update is harmless. The running firmware is untouched. The board reboots into exactly what it was running before.
- A bad build is recoverable. The bootloader marks a new slot as pending; if the new firmware does not confirm it is alive, the next boot goes back to the old one.
The two ways to send it
ArduinoOTA — the board advertises itself on the network and appears as a port in the IDE. Ideal on the bench, wrong in the field: it needs the IDE, the same network, and it waits to be pushed to.
HTTP update — the board asks a URL whether there is a newer version and pulls the binary itself. This is the one that scales, because the board can check on a timer, and because "the update server" can be any static file host.
// once a day, or on a button
httpUpdate.rebootOnUpdate(true);
t_httpUpdate_return r = httpUpdate.update(client, "https://example.com/fw.bin");
if (r == HTTP_UPDATE_FAILED) Serial.println(httpUpdate.getLastErrorString());The five rules
- Put a version in the firmware and print it at boot. Without it you cannot tell an update that failed from one that never started.
- Serve the binary over HTTPS, or sign it. An update channel is the most powerful thing on your board — anyone who can answer that URL owns it.
- Check free space and heap before starting. A failed allocation halfway through a download is a wasted five minutes on every board at once.
- Never update on a schedule that fires while the device is doing its job. A pump controller mid-cycle is not somewhere to reboot.
- Keep the old binary. The rollback in the figure only works because the previous build is still in the other slot; on your side, keep the file too.
Before you need it
Choose the partition layout when you start the project, not when the board is in the ceiling — changing it is a full cable-flash, which is the exact thing OTA exists to avoid. The layouts and their sizes are the partition table topic, and the update itself needs Wi-Fi station mode up first. If several boards will be updated, having them subscribe to a topic that names the current version is the cheapest fleet management you will ever write.
That is the network. The next chapter is the other radio, and the one a phone can talk to without joining anything.
BLE server and client →Edit this page — content/esp32/firmware-in-two-slots.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.