RP2350 OTA Firmware Update
RP2350 microcontrollers offer multiple firmware update mechanisms. The most common are these two:
- Built-in RP2350 System Bootloader
- In-Application Programming
Built-in RP2350 System Bootloader
The RP2350 comes with a bootloader baked into ROM, which means it’s always there and you can’t erase it or brick the chip. You don’t need to flash anything first, buy a programmer, or set up special tools just to get code onto the device. It works straight out of the box.
The most common way you’ll use it is the USB UF2 bootloader. When the RP2350 enters bootloader mode, it pretends to be a USB flash drive. You just drag a .uf2 firmware file onto it, the chip reboots automatically, and your code starts running. That’s it. No flashing tools, no drivers, no drama.
To enter the bootloader on a typical RP2350 board (like Pico-2-style boards), unplug the USB cable, press and hold the BOOTSEL button (which is wired to the corresponding BOOTSEL GPIO pin), plug the USB cable back in, then release the button. Your computer will show a new USB drive. Copy your .uf2 file to that drive and you’re done.
If you don’t have a BOOTSEL button or you’re building a custom board, the RP2350 can also enter its ROM bootloader using pin strapping at reset. In that case the ROM listens on UART instead of USB and accepts firmware over serial. This is mainly used for factory flashing or recovery, not day-to-day development.
In short, the RP2350 bootloader is always there, super hard to break, and designed to make flashing firmware as painless as possible.
In-Application Programming
With IAP, your device updates itself while it's running.
How it's usually set up:
- A small custom bootloader at the start of flash, and a firmware resides in a separate flash area, OR
- A bootloader is embedded in the firmware itself
How updates work:
- The device downloads new firmware (UART, USB, Ethernet, etc.)
- The firmware is stored temporarily (RAM, internal flash, or external flash)
- The bootloader erases the old app and writes the new one
- The device resets and runs the new firmware
This is a very flexible mechanism that can be used for OTA updates. The biggest concern is that it can brick the device if power is lost during the update process.
One of the simplest approaches is to put the bootloader inside the firmware itself. This means you don't need a separate bootloader image. Updates work by overwriting the existing firmware in flash, which is handy on small devices or when there's no external flash.
A common and easy way to do this is to split the flash into two equal parts:
- The first part runs the current firmware
- The second part stores the new firmware
On reboot, the bootloader code checks the second slot. If it finds a valid and newer firmware, it copies it over the first slot and restarts. The device then boots into the updated firmware - and the bootloader gets updated too.
How exactly a firmware could write and replace firmwares on flash? This is covered in the next section.
RP2350 flash ROM API
RP2350 needs a ROM flash API for a pretty basic reason: it doesn’t have any internal flash. Your firmware lives in an external QSPI/QMI flash chip, and the CPU normally executes straight out of that flash using XIP (execute-in-place). The catch is that the moment you try to erase/program that same flash, XIP can stall, and if you’re running code from flash at that moment you can easily faceplant. So the ROM provides “known-good” routines that can keep the system alive while the external flash is busy, and the Pico SDK wraps those routines so you can update flash without reinventing the whole flash driver stack.
In the Pico SDK world, you usually feel like you’re just calling normal functions such as flash_range_erase() and flash_range_program() from the flash library, but under the hood the implementation leans on ROM-entrypoint lookups and ROM-resident helpers where appropriate, especially around the “exit XIP / do the operation / restore XIP” dance.
The ROM side of the story is defined via the bootrom interface headers. The core “how do I find a ROM function at runtime?” pieces are declared in pico/bootrom.h (stuff like ROM table lookup helpers and function lookup helpers).
The numeric IDs/“codes” used to identify particular ROM functions and data items are declared in the bootrom constants headers, exposed in the SDK as boot/bootrom_constants.h which pulls in the underlying boot definitions.
Practical implementation
The video linked at the beginning of this article demonstrates a practical
implementation of the OTA firmware update implemented by Mongoose. It uses
flash ROM API described above to implemented Mongoose's OTA API functions
ota_begin(), ota_write() and ota_end().