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.
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:
- One mutex. Every SD access, from either side, is serialised.
- 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.
- 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_MODEmust be0. The stock board definition hard-codes1, which compilesUSBMSCout entirely. Override throughboard_build.extra_flags— appending tobuild_flagsonly yields a macro redefinition.ARDUINO_USB_CDC_ON_BOOTmust be0. It impliesARDUINO_USB_ON_BOOT, which makes the core callUSB.begin()inapp_main()beforesetup()— 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.