iSCSI USB Bridge - or, making a $5 devkit pretend to be a flash drive

Connecting external PCI Express devices to Raspberry Pi was one thing. This time the question was simpler, but somehow more annoying: what's the laziest possible way to get files from a NAS onto a device that only understands USB mass storage?

Not "install an iSCSI initiator" — I mean plug something into a USB port and the host sees a disk. No drivers, no network stack, no configuration. A car stereo should work. A BIOS flasher should work. A Raspberry Pi 1B+ with no iSCSI support should work.

Turns out the ESP32-S3 is almost purpose-built for this.

The hardware

The ESP32-S3 has two USB controllers — a USB OTG peripheral and a fixed-function Serial/JTAG controller — but they share the same two pins (GPIO 19/20) through an internal mux. Only one can drive the bus at a time. The firmware uses the OTG controller to enumerate as a USB Mass Storage device via TinyUSB.

The practical implication: if your devkit only has one USB connector (like the ESP32-S3-Zero or FeatherS3), the OTG and Serial/JTAG controllers fight over that single port. You can flash, or you can bridge, but not both at once. Boards with an external USB-UART chip — like the DevKitC-1, which has a CP2102N on a separate connector — give you a genuinely independent serial port, so you can flash and monitor while the bridge is running on the other port. That's the recommended setup.

The firmware sits between TinyUSB on the USB MSC side and a bare iSCSI client on the network side. When the host sends a SCSI READ or WRITE over USB, the firmware translates it into an iSCSI READ or WRITE (6/10/16) sent over TCP to whatever target you've configured — TrueNAS, Linux targetcli, Synology, anything RFC 3720-compliant.

The host has no idea there's a network involved. It just sees a disk.

Setup

On a DevKitC-1 (or any board with an external UART chip), connect the UART/COM port — that's the connector labelled COM, closest to the board edge. The other one (labelled USB) is the OTG port, which is how the bridge talks to the host machine. Don't plug into the OTG port for flashing — if the browser shows "USB JTAG/serial debug unit" instead of a CP210x port, you've got the wrong connector.

Flashing happens from a browser (Chrome or Edge 89+, thanks to ESP Web Tools). If you're on Firefox or Safari, there's a manual esptool.py path with a downloadable firmware bundle. No IDE or toolchain needed either way.

On first boot the board enters provisioning mode. It broadcasts an open Wi-Fi network called iSCSI-Bridge-XXXX and serves a captive portal at 192.168.4.1. Fill in your Wi-Fi SSID and password, iSCSI target IP, port (almost always 3260), and the target IQN. Hit save. The board reboots, connects to your network, logs into the iSCSI target, and starts advertising the LUN as a USB drive.

The whole thing takes about two minutes, assuming you already know your target's IQN (on TrueNAS: Sharing → iSCSI → Targets, copy the Target Name).

If you mess up the config or your network changes, the board detects the failed connection and drops back into provisioning mode automatically. No factory reset needed. But if you want one anyway — hold BOOT for 3 seconds at power-on. Release early and it aborts cleanly, no harm done.

Where this is actually useful

The use case that got me started was legacy devices. I have a few things around the house and workshop that read from USB but have zero concept of networking — and sneakernetting files to a flash drive every time is tedious when the data already lives on a NAS.

The more interesting case might be air-gapped transfers. If you have a machine that's deliberately kept off the network, the bridge becomes the only link between it and your storage server. Physically plugged in, physically removable. The host never touches the network.

And for embedded boards that lack iSCSI support (looking at you, every Raspberry Pi that isn't a Pi 4 with a full Linux stack) — this is a way to get network storage without fighting the OS.

Caveats

This is an early prototype, emphasis on early. Performance is limited by the ESP32-S3's 2.4 GHz Wi-Fi throughput — don't expect SSD speeds, or even spinning-rust speeds over a good Ethernet link. Only one host can mount the drive at a time (the bridge holds the iSCSI LUN exclusively). And 2.4 GHz means you're sharing spectrum with every microwave and baby monitor in range.

One thing to watch: if you're using a single-connector board, you'll need to physically swap cables between serial monitoring and bridge operation. It's workable for deployment (flash once, then just use the OTG side), but annoying during development. Get a DevKitC-1 if you want a smoother experience.

But for the use cases where it fits — trickling media to a legacy device, providing boot storage to a board that can't do it natively, bridging an air gap — it works. The hardware cost is a ~$5 devkit and two USB cables.

Try it

The project page is at scsipub.com/initiators/ESP32-iSCSI-USB — browser-based flasher (plus a manual esptool path), setup walkthrough, troubleshooting guide, the works. Firmware is built with ESP-IDF v5.4.

If you've got a spare ESP32-S3 collecting dust and an iSCSI target on your network, give it a shot. If you don't have an iSCSI target - give scsipub.com a shot :)

Return to blog