Automatically flash RP2040 or RP2350 firmware from a Raspberry Pi Zero on boot.
When an RP2040/RP2350 board is connected in BOOTSEL mode, the Pi Zero detects the USB MSC drive by its volume label and copies the appropriate UF2 file.
The newest UF2 file (by modification timestamp) in the target directory is used, so updating firmware is as simple as dropping a new file in.
Shorting a GPIO pin to GND activates "nuke mode" which writes flash_nuke.uf2 instead (erases the target flash completely).
Once set up, this system runs fully headless. No display, keyboard, or SSH session is required during operation. Power on the Pi Zero, connect an RP2 board in BOOTSEL mode, and flashing happens automatically. The ACT LED signals progress.
In workshops involving RP2040 or RP2350 boards, participants often encounter difficulties when trying to install UF2 firmware files from their company-issued PCs. Stringent security policies may block USB Mass Storage devices or prevent copying files to external drives.
This system provides a "Firmware Flashing Station" using a Raspberry Pi Zero. Workshop owners can prepare this dedicated installer to ensure all participants can successfully flash the required firmware regardless of their own PC's security settings.
Pi Zero W (with WiFi) is required. WiFi is used for SSH and internet access, leaving the USB port exclusively for OTG host mode (flashing RP2 boards).
Pi Zero (without WiFi) is not supported. Its single USB port cannot serve both OTG host mode and network access simultaneously.
- Micro-USB OTG adapter (micro-USB male to USB-A female) for connecting RP2 boards
- USB-A to micro-USB cable to connect to the RP2040/RP2350 board
- Optional but recommended: 10 kOhm resistor from GPIO17 to 3.3V (hardware pull-up for nuke pin)
Recommended: Raspberry Pi OS Lite (64-bit or 32-bit), Bookworm release.
- Download from https://www.raspberrypi.com/software/operating-systems/
- Flash to a microSD card using Raspberry Pi Imager
- In Imager's OS Customisation settings:
- Enable SSH
- Set username (
pi) and password - Configure WiFi (SSID and password)
- Set locale and timezone
- Boot the Pi Zero W and SSH in:
ssh pi@raspberrypi.local
Pre-installed packages required by this installer (all present on Raspberry Pi OS):
util-linux(providesmount,umount,lsblk)systemdraspi-gpio(for GPIO pull-up; install if missing:sudo apt install raspi-gpio)
This is a one-time setup. UF2 firmware files are deployed separately after installation and can be swapped out at any time without touching the service.
SSH in over WiFi, then:
sudo apt update && sudo apt install -y git
git clone https://github.com/picoruby/r2p2-installer.git
cd r2p2-installersudo bash setup.shThis script:
- Installs
flash_installer.shto/opt/r2p2-installer/ - Creates
/opt/r2p2-installer/firmware/rp2040/andfirmware/rp2350/ - Installs
flash_installer.serviceto/etc/systemd/system/ - Enables and starts the service immediately
The service starts polling right away. UF2 files can be added at any time (see Deploying Firmware below).
# Check service status
systemctl status flash_installer.service
# Watch live logs
journalctl -u flash_installer.service -fYou should see:
[r2p2-installer] Polling for RPI-RP2 / RP2350 drives every 2s...
That is all. The Pi Zero is now ready for headless operation.
The Pi Zero has two micro-USB ports. The one labeled USB (away from the corner, closer to the center of the board) is the OTG port. The other is power.
Pi Zero["USB" port (PWR IN)] --- [Micro-USB to 5V power (Laptop USB port, mobile battery, or AC adapter)]
["USB" port (OTG)]
|
[Micro-USB to Type-A *OTG* adapter](*1)
|
[Type-A to Micro-B cable](*2)
|
[RP2040/RP2350 board in BOOTSEL mode]
Combining these should work for this purpose:
- (*1): https://akizukidenshi.com/catalog/g/g113430/
- (*2): https://akizukidenshi.com/catalog/g/g117016/
In normal operation the pin is held HIGH internally (no jumper needed). To activate nuke mode, short Pin 11 to GND before connecting the RP2 board.
Pi Zero 40-pin GPIO header (physical pin numbers):
3.3V [ 1][ 2] 5V
SDA1 [ 3][ 4] 5V
SCL1 [ 5][ 6] GND
GPIO4 [ 7][ 8] TXD0
GND [ 9][10] RXD0
GPIO17 [11][12] GPIO18 <-- NUKE PIN (BCM 17)
GPIO27 [13][14] GND
GPIO22 [15][16] GPIO23
3.3V [17][18] GPIO24
MOSI [19][20] GND
MISO [21][22] GPIO25
SCLK [23][24] CE0
GND [25][26] CE1
To enable nuke mode:
Connect Pin 11 (GPIO17) <---[jumper]---> Pin 9 or Pin 6 (GND)
To return to normal flash mode:
Remove the jumper.
Recommended: add a 10 kOhm resistor between Pin 11 (GPIO17) and Pin 1 (3.3V) as a hardware pull-up. This prevents the pin from floating if raspi-gpio is unavailable and the sysfs fallback is used instead.
- Put your RP2040 or RP2350 board into BOOTSEL mode (hold BOOTSEL, connect USB, release BOOTSEL)
- Connect the board to the Pi Zero via the OTG adapter
- The Pi Zero will:
- Detect the
RPI-RP2orRP2350drive - Mount it temporarily
- Copy the appropriate UF2 file
- Unmount (the RP2 board reboots automatically)
- Detect the
- LED goes off when done
- Short GPIO17 (Pin 11) to GND (Pin 9) with a jumper wire
- Connect the RP2 board in BOOTSEL mode
flash_nuke.uf2is written; the board's flash is erased- Remove the jumper to restore normal flash mode
Once setup.sh has been run, no display, keyboard, or SSH session is needed during normal operation.
Daily workflow:
- Power on the Pi Zero (boots in ~20 seconds, service starts automatically)
- Connect an RP2 board in BOOTSEL mode via the OTG adapter
- Watch the ACT LED: solid on during flash, off when done
- Disconnect the board; repeat for the next one
Updating firmware: SSH in over WiFi, drop a new UF2 file into the appropriate directory, disconnect. The next flash uses it automatically. See Deploying Firmware.
| State | ACT LED |
|---|---|
| Booting (before service start) | Blinks (SD card activity) |
| Service running, idle | Off (= ready) |
| Actively copying firmware | Solid on |
| Flash completed successfully | Off |
| Flash failed | Blinks 10 s, then off |
"Off" after boot means the service is running and waiting for an RP2 board. Wait for the LED to go off before connecting a board.
Edit /opt/r2p2-installer/flash_installer.sh to change these variables near the top:
| Variable | Default | Description |
|---|---|---|
GPIO_NUKE_PIN |
17 |
BCM GPIO number for nuke mode pin |
POLL_INTERVAL |
2 |
Seconds between drive detection scans |
FIRMWARE_DIR |
/opt/r2p2-installer/firmware |
Directory containing UF2 files |
MOUNT_POINT |
/tmp/rp2-mount |
Temporary mount point |
After editing, restart the service:
sudo systemctl restart flash_installer.servicePlacing and updating UF2 files is the primary day-to-day operation. The service is always running; just drop a file in and the next flash will use it. No service restart, no re-running setup.
The installer always picks the newest file by modification timestamp from each directory, so you can freely accumulate versions or replace them. Filenames do not matter.
/opt/r2p2-installer/firmware/
flash_nuke.uf2 (bundled - do not remove)
rp2040/ <- drop RP2040 UF2 files here
rp2350/ <- drop RP2350 UF2 files here
SSH in over WiFi and run:
# RP2040 firmware
sudo bash download_firmware.sh rp2040 \
https://github.com/picoruby/picoruby/releases/download/3.4.2/R2P2-for-RP2040.uf2.gz
# RP2350 firmware
sudo bash download_firmware.sh rp2350 \
https://github.com/picoruby/picoruby/releases/download/3.4.2/R2P2-for-RP2350.uf2.gz# Shows newest file first
ls -lt /opt/r2p2-installer/firmware/rp2040/
ls -lt /opt/r2p2-installer/firmware/rp2350/Old files are ignored automatically, but can be deleted to keep things tidy:
sudo rm /opt/r2p2-installer/firmware/rp2040/old_firmware.uf2# Check if the drive label appears
ls -la /dev/disk/by-label/
# List USB block devices
lsblk -rno NAME,LABEL,TRAN | grep usbIf no label appears, the board may not be in BOOTSEL mode or the OTG cable/adapter may be faulty.
# Check recent kernel USB messages
dmesg | tail -30
# Check if another process holds the device
fuser /dev/sda 2>/dev/null || echo "not in use"# Verify raspi-gpio is installed
which raspi-gpio || echo "not installed"
# Read current pin state
raspi-gpio get 17
# Normal (HIGH): "GPIO 17: level=1 fsel=0 alt=0 func=INPUT pull=UP"
# Nuke (LOW): "GPIO 17: level=0 ..."If raspi-gpio is missing: sudo apt install raspi-gpio
journalctl -u flash_installer.service --no-pager -n 50
systemctl status flash_installer.serviceA common cause is missing firmware files. Check:
ls -la /opt/r2p2-installer/firmware/
ls -la /opt/r2p2-installer/firmware/rp2040/
ls -la /opt/r2p2-installer/firmware/rp2350/
# rp2040/ and rp2350/ must each contain at least one .uf2 file
# flash_nuke.uf2 must be present at the firmware/ levelFlashed devices are tracked in /tmp/r2p2-flashed/ (cleared on reboot).
If you need to reflash without rebooting:
sudo rm -rf /tmp/r2p2-flashed/journalctl -u flash_installer.service --since "10 minutes ago"/opt/r2p2-installer/
flash_installer.sh Main script
firmware/
flash_nuke.uf2 Erase firmware (bundled)
rp2040/
R2P2-for-RP2040.uf2 RP2040 firmware (newest file is used)
rp2350/
R2P2-for-RP2350.uf2 RP2350 firmware (newest file is used)
/etc/systemd/system/
flash_installer.service
- On boot,
flash_installer.servicelaunchesflash_installer.shas root. - The script polls
/dev/disk/by-label/every 2 seconds forRPI-RP2orRP2350symlinks. - When found, it reads GPIO17 to decide normal flash or nuke mode.
- For normal mode, it selects the newest
.uf2file (by mtime) from the matching subdirectory (firmware/rp2040/orfirmware/rp2350/). - It mounts the drive to
/tmp/rp2-mountand waits forINFO_UF2.TXTto appear. - It copies the selected UF2 file. The RP2 bootloader writes the firmware and reboots.
- The Pi Zero unmounts, turns off the LED, and resumes polling.
- The Pi Zero resumes polling, ready to flash the same or a different device again.
Copyright © 2026 HASUMI Hitoshi. See MIT-LICENSE for further details.
flash_nuke.uf2 is provided by Raspberry Pi Ltd under the BSD 3-Clause License.