PrintDrop
Browse documentation

Getting started

A Wi-Fi flash drive for 3D printers. Drop a print job from your desk instead of walking a USB stick to the machine.

Platform Framework Build USB UI License Status Issues Last commit


PrintDrop plugs into a 3D printer's USB port and appears as an ordinary USB flash drive — no printer firmware changes, no cloud service, no vendor account. At the same time it joins the local network and serves a web UI, so anyone can drag a .gcode file onto it from their desk and then walk over and press print.

It is a flash drive that happens to be reachable over Wi-Fi. The printer sees nothing unusual: a USB mass storage device with a FAT32 volume. An SD card is simply the storage medium behind it.

Built for printers that only accept USB media and have no networking of their own.

Working on hardware: the card enumerates as a USB drive, the web UI serves from LittleFS, and files uploaded over Wi-Fi appear to the USB host without a reboot.

Measurement Value Notes
Read (USB, uncached) — SPI 485 KB/s 4-wire SPI @ 20 MHz
Write (USB) — SPI 248 KB/s card is the bottleneck
Read (USB, uncached) — SDIO 4-bit ~3 200 KB/s * SDIO @ 20 MHz, projected — ~6× SPI (~6 000 KB/s raw at 40 MHz on short wiring)
Write (USB) — SDIO 4-bit ~2 000 KB/s * same wiring, 6 wires (~3 500 KB/s at 40 MHz)
Raw SD throughput — SDIO ~3 500 KB/s * vs ~910 KB/s SPI — 20 MB in ~6 s not ~80 s (~6 000 KB/s at 40 MHz)
SD clock — SPI 20 MHz (verified clean to 25 MHz) legacy bus
SD clock — SDIO 20 MHz stable (40 MHz with short wiring) SDMMC host, 4-bit
Web UI size 62 KB, served from LittleFS
Card tested 32 GB SDHC, FAT32

* SPI figures are bench-measured; SDIO figures are projected from the SDMMC host at 20 MHz / 4-bit (stable on jumper wiring, 40 MHz with short wiring). Re-measure after wiring the 6-wire breakout.

How the USB and Wi-Fi sides coexist

This is the part that matters, and the part that is easy to get wrong.

A USB host caches the FAT. If the ESP32 writes to the card while the printer has it mounted, the host's cached allocation table goes stale and the next write from either side corrupts the filesystem. The reverse is also true — the ESP32's own FATFS cache goes stale if the host writes sectors underneath it.

src/printdrop/storage.cpp enforces three rules:

  1. One mutex. Every SD access, from either side, is serialised.
  2. Withdraw before writing. Before the ESP32 modifies the card, the media is withdrawn from the USB host, then re-presented afterwards. The printer sees a card removal and re-reads its file list, so uploads appear without a reboot.
  3. Remount when the host writes. MSC write callbacks set a flag; the ESP32 remounts FATFS before trusting its own view of the filesystem again.

MSC callbacks take the mutex with a short timeout and fail the transfer rather than stall the USB task, so a slow Wi-Fi upload can never hang the printer.

The full design is in docs/architecture.md.

Hardware

  • ESP32-S3-DevKitC-1 (configured for a 4 MB flash, no PSRAM board)
  • microSD breakout — SDIO 4-bit (recommended, 6 wires), SPI (legacy, 4 wires)
  • A FAT32 card — exFAT will not mount
  • Printer on the native USB port; the UART bridge carries the console

Wiring

SDIO 4-bit (recommended, 6 wires):

Signal GPIO Notes
CLK 40
CMD 14 10 k pull-up
D0 39 10 k pull-up
D1 12 10 k pull-up
D2 13 10 k pull-up
D3 15 10 k pull-up

Reuses the four SPI pins plus two new data lines — an SPI-wired board migrates with two jumpers. Requires a 3.3 V-native microSD breakout (no AMS1117/LC125) with pull-ups on CMD/D0-D3. Jumpers at 20 MHz are stable; 40 MHz wants short, equal-length wires. 1-bit mode (SDMMC_WIDTH=1) works with only CLK/CMD/D0 for bring-up.

SPI — main legacy (4 wires):

Signal GPIO
CS 12
MISO 39
MOSI 14
CLK 40

Build with pio run -e printdrop_spi to stay on SPI. See docs/hardware.md for both wirings, power notes, and measured clocks.

Power the breakout from 3V3. AMS1117/LC125 modules also run on 5 V, but their pull-ups then reference the 5 V rail and drive the ESP32's GPIOs above 3.3 V, outside the SoC's absolute maximum. SDIO must use a 3.3 V-native breakout.

Flashing

pio run -e printdrop -t upload      # firmware
pio run -e printdrop -t uploadfs    # web UI into LittleFS

Both are needed on a first install. See Reflashing for how to get the board into download mode on this hardware.

First run

With no credentials stored, PrintDrop raises its own access point:

Network PrintDrop-Setup
Password printdrop
Setup page http://192.168.4.1

Join it, open the page, and set the Wi-Fi network and hostname under Settings. The device reboots and reappears on the office network.

Credentials are stored in NVS and never enter the source tree.

Provisioning over serial instead

Faster on a bench, and the only option if the AP is inconvenient:

wifi <ssid> <password>     join a network and reboot
hostname <name>            set the mDNS name and reboot
status                     show device state
forget                     clear Wi-Fi settings
help                       list commands

Send these as lines to the UART bridge (COM5) at 115200 baud.

Finding it on the network

PrintDrop advertises itself over mDNS, so http://printdrop.local works on most machines out of the box.

For a fixed address, either set a static IP under Settings → Network, or reserve one on the DHCP server and point a DNS entry at it. With AdGuard Home, add a DNS rewrite from e.g. printdrop.office.lan to the reserved address.

Using it

  • Upload — drag files anywhere onto the page, or use Choose files. Progress, transfer rate and ETA are shown per file. 20 MB takes ~80 s over SPI, ~6 s over SDIO 4-bit at 20 MHz (~4 s at 40 MHz) — the card, not the network, is the bottleneck.
  • Browse — grid or list view, sorted by name or size, with folder navigation. Print jobs get their own icon colour so they stand out.
  • Eject / refresh printer view — forces the printer to re-read the card. Uploads do this automatically; the button is for when a printer needs nudging.

The experience

Feature What it does See
WebSocket progress ws://<host>:81/ pushes upload progress & status (polling stays as fallback) src/printdrop/ws.*, data/app.js
LED + button LED idle 2 s blink / activity fast blink / error double-blink; button short = eject, long 5 s = factory reset (clears Wi-Fi + login) config.h:84, docs/hardware.md
Discovery mDNS http://printdrop.local + LLMNR http://printdrop (Windows bare name) docs/discovery.md
Web UI auth HTTP Basic, SHA-256 in NVS, seed from platformio.ini PRINTDROP_AUTH_*, set via serial auth or web Settings docs/auth.md
OTA POST /api/ota (bin upload) + SD firmware.bin+firmware.json popup, dual OTA slots on 4 MB docs/ota.md, partitions_printdrop_ota.csv

LED GPIO 38 active-high, button GPIO 4 active-low with pull-up by default — override with -D PRINTDROP_LED_PIN etc. src/printdrop/config.h:84/platformio.ini:150.

Build environments

Env Purpose Console
printdrop The product — SDIO 4-bit, USB drive plus Wi-Fi web UI UART0 (COM5)
printdrop_spi The product — SPI legacy (4-wire) UART0 (COM5)
msc USB mass storage only, no networking UART0 (COM5)
ramdisk RAM-backed FAT12 volume; proves USB MSC without the SD card UART0 (COM5)
diag SPI speed sweep, card geometry, MBR dump, root listing USB/JTAG (COM11)
diag_sdio SDIO 4-bit bring-up — bus width test, throughput sweep USB/JTAG (COM11)
scan Pin health, line voltages, pin-permutation sweep USB/JTAG (COM11)

Troubleshooting order: scan when the card is not detected, diag when it mounts but misbehaves, ramdisk to prove the USB path independently.

Reflashing

Firmware that owns the native USB port replaces the USB-Serial-JTAG device, so esptool cannot reach the board there. Every firmware watches UART0 for the word BOOTLOADER and reboots into download mode:

"BOOTLOADER" | Out-File -Encoding ascii COM5

On this board RTS drives EN, so opening or closing COM5 resets the chip and knocks it straight back out of download mode. Any tool used between the hatch and the flash must hold RTS and DTR deasserted.

Manual fallback: hold BOOT, tap RESET, release BOOT.

Layout

src/printdrop/    the product: storage arbitration, networking, HTTP API
src/diag/         SD and USB diagnostics
src/legacy/       USB mass storage only
src/common/       shared helpers
data/             web UI, flashed to LittleFS
docs/             architecture, hardware, bugs, flashing notes
assets/           branding
website/          GitHub Pages source (Next.js, statically exported)

Partitions

4 MB flash, single app image — there is no room for two OTA slots.

Partition Offset Size
nvs 0x9000 20 KB
app0 0x10000 2688 KB
spiffs (LittleFS) 0x2B0000 1280 KB
coredump 0x3F0000 64 KB

Gotchas

Three details stop the USB drive appearing at all, and none produce a useful error message:

  • ARDUINO_USB_MODE must be 0. The stock board definition hard-codes 1, which compiles USBMSC out entirely. Override through board_build.extra_flags — appending to build_flags only yields a macro redefinition.
  • ARDUINO_USB_CDC_ON_BOOT must be 0. It implies ARDUINO_USB_ON_BOOT, which makes the core call USB.begin() in app_main() before setup() — freezing the USB descriptor before the sketch can add its MSC interface.
  • SD cards must be identified at ≤400 kHz before the clock is raised.

These and the rest of the investigation are written up in docs/bugs.md.

Documentation

docs/architecture.md How the pieces fit: USB/Wi-Fi arbitration, module layout, partitions, performance
docs/hardware.md The board as measured, wiring, power requirements, verified SPI clocks, LED/button
docs/bugs.md Every fault found during the port and its root cause
docs/flashing.md Getting this board into download mode
docs/sdio.md SDIO 4-bit migration (feat/sdio)
docs/auth.md Web UI login, NVS, Basic auth
docs/discovery.md mDNS + LLMNR (printdrop.local / printdrop)
docs/ota.md Dual OTA slots, HTTP + SD-card update

Contributing

Build instructions, the branch model, and the hardware traps worth knowing about are in CONTRIBUTING.md. Bug reports are most useful with the UART0 boot log and, for card problems, the output of the diag or scan environment.

Credits

Made by Akash P | CTO, Kabani Tech Private Limited

MIT licensed — see LICENSE. The USB mass storage layer derives from the Esp32-USB-Stick project for the M5Stack Cardputer.

Edit this page on GitHub