Published: Tue, Jul 08, 25

This project demonstrates how to remotely upgrade the firmware of a Raspberry Pi Pico W.


Pico W Remote UF2 Flashing (OTA) – By aMiscreant

This project demonstrates how to remotely upgrade the firmware of a Raspberry Pi Pico W by downloading a .uf2 file from a server and flashing it directly into flash memory. Unlike standard MicroPython OTA examples, this approach writes the raw UF2 payload to flash, effectively performing a true firmware upgrade over Wi-Fi.

WARNING:

This method is dangerous. Writing directly to flash can permanently brick your Pico W if done incorrectly. Only use on test devices and understand the risks.

Overview

Server:

The server hosts the UF2 firmware files via Flask:
@app.route("/firmware/<path:filename>", methods=["GET"])
@require_key
def firmware_files(filename):
    """
    Serve UF2 files from the firmware directory.
    Example: /firmware/nuke.uf2
    """
    return send_from_directory(UPLOAD_DIR, filename, as_attachment=True)
Files are served over HTTP with an API key.
Any Pico W on the network can fetch the UF2 if it has the correct key.

Pico W

The Pico W script handles:
Connecting to Wi-Fi.
Fetching the UF2 file from the server.
Extracting payload blocks from the UF2.
Overwriting flash memory.
Rebooting into the new firmware.

Important Flash Parameters:

FLASH_BASE = 0x10000000  # RP2040 flash base
FLASH_SIZE = 2 * 1024 * 1024  # 2MB example
UF2_PAYLOAD_OFFSET = 0x200  # skip UF2 header to raw flash data

Firmware Upgrade Function:

def fetch_nuke_brick_mode():
    url = f"http://{SERVER_IP}:{SERVER_PORT}/firmware/nuke.uf2"
    local_path = "/nuke.uf2"

    # Download UF2
    response = urequests.get(url, headers={"X-API-KEY": API_KEY}, stream=True)
    # ... handle download

    # Read UF2 and extract payload
    uf2_data = open(local_path, "rb").read()
    payloads = [uf2_data[i + UF2_PAYLOAD_OFFSET:i + 512] for i in range(0, len(uf2_data), 512)]

    # Direct flash write (dangerous!)
    for idx, payload in enumerate(payloads):
        addr = FLASH_BASE + idx * len(payload)
        for i in range(len(payload)):
            machine.mem32[addr + i] = payload[i]

    # Reboot into new firmware
    machine.reset()

Key points:

Each UF2 block is 512 bytes; the first 32 bytes are header.
We skip the header (UF2_PAYLOAD_OFFSET) and write the raw data.
Uses machine.mem32 to write directly to flash — high risk of bricking.

Requirements:

Wi-Fi connection to reach the server.
API key to access the UF2 files.
Tested only on Raspberry Pi Pico W running MicroPython.
Method Description Risk
Standard MicroPython OTA Fetch .py files or use Git/SSH Low
Custom Bootloader (Jakub Zimnol) Switch firmware using a bootloader Medium
UF2 Direct Flash (this project) Fetch .uf2, write raw flash, reboot High

Most existing guides update code, not firmware. This approach replaces the firmware itself.


Warnings:

[!]️ Direct flash writes are irreversible if done incorrectly.

[!]️ Always test on disposable Pico W devices first.

[!]️ This method assumes 2MB flash layout; adjust for other models.


Working Example:

PicoW

Server