A driver that brings up an RTL8188EU USB Wi-Fi dongle on an ESP32-S3, using the SoC's built-in USB OTG host controller — no external USB host chip required.
The goal is a self-contained, second Wi-Fi radio for the ESP32 that behaves like esp_wifi.h: promiscuous-mode sniffing and raw 802.11 frame injection, driven entirely from a bit-level reimplementation of the Realtek rtl8188eu Linux driver's init/PHY/RF sequence (no vendor blob, no Linux kernel).
- RX / promiscuous sniffing: working. The chip's USB enumeration, firmware download, MAC/BB/RF bring-up, and calibration sequence are ported and functional; real 802.11 frames (beacons, probes, data) from nearby APs/stations are captured with plausible RSSI on the bulk-IN endpoint.
- TX / raw frame injection: working and confirmed on air. The chip accepts injected frames, the firmware reports successful transmission (CCX TX report,
pkt_ok=1), and the frames have been confirmed in the air with an external sniffer.
This started as (and largely remains) a reverse-engineering / bring-up project rather than a polished library — expect rough edges outside the core RX/TX path.
The ESP32's integrated Wi-Fi radio doesn't support monitor-mode injection/reception the way a dedicated USB dongle chipset does, and most RTL8188EU support assumes a full Linux host. This project ports just enough of the real Realtek driver logic — traced from USB captures and the lwfinger/rtl8188eu Linux driver source — to run standalone on ESP-IDF's USB Host Library, using the ESP32-S3's OTG peripheral.
- An ESP32-S3 board with USB OTG (native USB, not just USB-UART) — developed against a generic
esp32-s3-devkitc-1. - A USB Wi-Fi dongle based on the Realtek RTL8188EU chipset (Vendor/Product IDs matched in the USB host code).
- A USB OTG cable/adapter to connect the dongle to the ESP32-S3's OTG port.
This is a PlatformIO project targeting the espidf framework.
git clone https://github.com/Alexxdal/esp_wifi_usb.git
cd esp_wifi_usb
pio run # build
pio run -t upload # flash
pio device monitor # serial logEnvironment (platformio.ini):
[env:esp32s3]
platform = espressif32@^6.5.0
framework = espidf
board = esp32-s3-devkitc-1
build_type = releaselib/esp_wifi_usb/ the driver, packaged as a PlatformIO library
src/
esp_wifi_usb.h/.c public API + task/lifecycle glue
rtl8188e_usb.c/.h USB host (enumeration, bulk transfers, VID/PID match)
rtl8188e_hal.c/.h chip bring-up: power-on, firmware download, MAC init,
queue/LLT setup, TX/RX pipeline configuration
rtl8188e_phy.c/.h PHY/RF register table interpreter, channel switching
rtl8188e_iqk.c/.h IQ / LC calibration
rtl8188e_rx.c/.h RX descriptor parsing, promiscuous callback dispatch
rtl8188e_tables.c/.h MAC/BB/RF/AGC register tables (ported from the
reference driver's PHY config files)
rtl8188e_reg.h register and bitfield definitions
rtl8188e_spec.h hardware constants / descriptor layout
rtl8188eu_fw.h embedded chip firmware blob
examples/usage_example.c promiscuous sniffing + periodic beacon injection
src/main.c example application (same as usage_example.c)
The public API in esp_wifi_usb.h mirrors ESP-IDF's esp_wifi.h naming, with usb inserted after esp_wifi_, so application code looks familiar:
#include "esp_wifi_usb.h"
static void rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
{
wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf;
ESP_LOGI(TAG, "ch=%u rssi=%d len=%u",
pkt->rx_ctrl.channel, pkt->rx_ctrl.rssi, pkt->rx_ctrl.sig_len);
}
void app_main(void)
{
esp_wifi_usb_init(15000); /* wait for the dongle, bring up the chip */
esp_wifi_usb_set_promiscuous_rx_cb(rx_cb);
esp_wifi_usb_set_promiscuous(true);
esp_wifi_usb_set_channel(1);
esp_wifi_usb_start(); /* start the RX task */
esp_wifi_usb_80211_tx(frame, frame_len, false); /* inject a raw 802.11 frame */
}Known limitations of the API (documented in esp_wifi_usb.h):
- No station/AP mode — only promiscuous RX and raw frame TX (sniffing/injection only, no association).
esp_wifi_usb_set_promiscuous(false)pauses delivery to the callback; it does not disable the hardware's promiscuous receive filter.- TX power control is a single coarse "volume" (0–63 hardware index) applied uniformly across rates, with no known dBm calibration curve.
- The channel range and 20 MHz-only bandwidth reflect the chip being 1T1R b/g/n.
The driver logic isn't guessed — it's reconstructed by comparing this project's C code, register-by-register and bitfield-by-bitfield, against the real Realtek Linux driver (lwfinger/rtl8188eu) source, plus real USB captures of the vendor Windows driver talking to the same physical dongle. Each bring-up/debugging session's findings (bugs found, registers added, ordering fixes, things deliberately left unimplemented) are logged in detail as the work progresses, which is how issues like a byte-swapped Link List Table register and mis-ordered initialization stages were tracked down.
No license file is currently included in the repository; treat the code as all-rights-reserved by the author unless/until a license is added.