Close

ESP32-S3 fails to read DJI motion Controller

A project log for Hacking DJI motion controller as regular joystick

I wanted to use my Avata Motion controller as a regular joystick

jp-gleyzesJP Gleyzes 12/21/2025 at 17:490 Comments

ESP32-S3 fails to read DJI motion Controller

I tried to read the HID joystick from DJImotion Controller with my ESP32-S3 board.

It was connected to the ESP32Beans/USBhostHID library .

But It fails at startup with this error message :

E (419) HCD DWC: EP MPS (512) exceeds supported limit (128)

E (420) USBH: EP Alloc error: ESP_ERR_NOT_SUPPORTED

E (420) USB HOST: EP allocation error ESP_ERR_NOT_SUPPORTED

E (424) USB HOST: Claiming interface error: ESP_ERR_NOT_SUPPORTED

ESP_ERROR_CHECK failed: esp_err_t 0x106 (ESP_ERR_NOT_SUPPORTED) at 0x420025e8

Investigating with the help of Gemini gave me no hope... 

The error you are seeing is a hardware limitation of the ESP32-S2/S3 USB peripheral. It is not a bug in your code, but a physical constraint of how the ESP32 handles USB packets.

So we tried with another board MAX3421E. This chip is used in the "USB Host Shield" and can handle larger buffers than the ESP32's built-in peripheral. 

But result was also a failure, and the final conclusion was :

The Verdict: A Hardware "Hard Limit"
This confirms why you see USB State: 90 (the device is connected and recognized) but no data arrives.

The DJI Controller: It is a "High-Speed" device. It is telling the USB Host, "I will only send data in chunks of 512 bytes."

The MAX3421E: This chip is a "Full-Speed" controller. It has a physical internal memory (FIFO) of only 64 bytes.

The Conflict: When the DJI controller tries to send its 512-byte HID report, the MAX3421E physically cannot fit it into its memory. The hardware simply ignores the packet or throws an internal overflow error that the library doesn't display.

At this point I stopped investigating !

FInally all this is confirmed by Claude AI: 

ESP32-S3 USB Host cannot support DJI Motion Controller's 512-byte endpoints

The DJI Motion Controller's 512-byte Maximum Packet Size (MPS) is fundamentally incompatible with ESP32-S3 USB Host hardware, which limits periodic endpoint MPS to 128 bytes. This is a hardware constraint of the DWC_OTG controller's internal FIFO sizing—not a software-configurable limitation. However, several workarounds exist depending on whether the device's descriptors are misconfigured or genuinely require High-Speed USB.

The root cause is Full-Speed versus High-Speed USB incompatibility

ESP32-S3's USB Host controller only supports USB Full-Speed (12 Mbps) and Low-Speed (1.5 Mbps)—it lacks the High-Speed PHY interface entirely. The chip uses Synopsys DesignWare DWC_OTG controller configured with OTG_HSPHY_INTERFACE = 0, permanently disabling High-Speed capability. Only the newer ESP32-P4 supports USB High-Speed (480 Mbps).

Per USB 2.0 specifications, endpoint MPS limits differ dramatically by speed class:

USB SpeedBulk MPS MaxInterrupt MPS MaxESP32-S3 Support
Full-Speed (12 Mbps)64 bytes64 bytes✓ Supported
High-Speed (480 Mbps)512 bytes1024 bytes✗ Not supported

When the DJI Motion Controller reports 512-byte MPS for its interrupt endpoint, it's operating with High-Speed descriptors. A USB 2.0 compliant device should fall back to Full-Speed operation when connected to a Full-Speed-only host, presenting descriptors with ≤64-byte MPS. The DJI Motion Controller appears to violate this requirement by only offering High-Speed configuration.

ESP32-S3 hardware imposes fixed MPS ceilings

The error "EP MPS (512) exceeds supported limit (128)" originates from ESP-IDF's hcd_dwc.c driver, which enforces hard limits based on the DWC_OTG controller's 1024-byte total FIFO partitioned between three buffers:

Transfer TypeMaximum MPSFIFO Allocation
IN endpoints (all types)408 bytesRX FIFO shared by all IN
Non-periodic OUT (bulk/control)256 bytesNPTX FIFO
Periodic OUT (interrupt/iso)128 bytesPTX FIFO

The 128-byte limit for periodic transfers directly blocks the DJI Motion Controller's 512-byte interrupt endpoint. ESP-IDF's CONFIG_USB_HOST_HW_BUFFER_BIAS_* Kconfig options theoretically allow rebalancing these FIFOs, but they are non-functional according to GitHub issue #14941—all three settings produce identical limits. Espressif closed this issue as "Won't Do," indicating a hardware-imposed constraint.

Workaround 1: Override wMaxPacketSize during enumeration

An Espressif engineer suggested patching endpoint descriptors after device enumeration but before interface claiming. This works only if the device actually operates at Full-Speed but incorrectly reports High-Speed descriptors:

###b class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copier dans le presse-papiers" data-state="closed"###

c

// After usb_host_device_open() and usb_host_get_active_config_descriptor()
// Traverse to endpoint descriptor using usb_parse_*() functions
usb_ep_desc_t *ep_desc = (usb_ep_desc_t *)endpoint_descriptor_ptr; // Cast away const
ep_desc->wMaxPacketSize = 64;  // Override to Full-Speed compliant value
// Now usb_host_interface_claim() should succeed

This approach requires modifying the ESP-IDF USB Host example code or creating a custom HID driver. It will fail if the device genuinely requires 512-byte packets for data transfer—you'd receive truncated or corrupted reports.

Workaround 2: Control transfers via GET_REPORT polling

HID Input reports can theoretically be retrieved through control transfers using the standard GET_REPORT request instead of interrupt endpoints:

###b class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copier dans le presse-papiers" data-state="closed"###

c

// USB HID GET_REPORT control transfer
// bmRequestType: 0xA1 (Device-to-host, Class, Interface)
// bRequest: 0x01 (GET_REPORT)
// wValue: (Report Type << 8) | Report ID
// wIndex: Interface number

Control transfers use endpoint 0, which has different constraints than interrupt endpoints. However, this approach fails because enumeration itself crashes when the USB Host stack attempts to claim the interface containing the 512-byte endpoint. You cannot reach the point of sending GET_REPORT requests without first modifying the endpoint descriptor as described above.

Workaround 3: Modify ESP-IDF FIFO allocation directly

Users on GitHub issue #17707 reported successfully modifying hcd_dwc.c to change FIFO line allocation:

###b class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copier dans le presse-papiers" data-state="closed"###

c

// In components/usb/hcd_dwc.c
// Modify port->fifo_config.*_fifo_lines values

This requires recompiling ESP-IDF from source—not possible in pure Arduino environment without building the arduino-esp32 core from source. Even then, increasing periodic FIFO beyond 128 bytes would decrease RX FIFO capacity, potentially breaking other functionality. One user reported achieving 1024-byte isochronous transfers for audio with "no weird side effects," but this is uncharted territory.

Workaround 4: Use an intermediate bridge device

A Hackaday.io project documenting exactly your issue with the DJI Motion Controller found a working solution using an Android phone as USB host bridge:

This architecture adds latency and complexity but is the only confirmed working solution for the DJI Motion Controller with ESP32-S3. A Raspberry Pi Zero could serve the same purpose with a wired UART/SPI connection.

Alternative hardware with High-Speed USB support

PlatformUSB High-SpeedNotes
ESP32-P4✓ 480 MbpsFirst ESP32 with USB HS; 16 channels, higher MPS limits
Raspberry Pi Pico 2✗ Full-Speed onlySame limitation as ESP32-S3
STM32F4/F7/H7✓ with external PHYRequires USB3300 or similar HS PHY
Teensy 4.1✓ Built-in HSNXP iMXRT1062, excellent USB Host support

The ESP32-P4 is Espressif's first chip with integrated USB High-Speed PHY and would natively support the DJI Motion Controller's 512-byte endpoints.

USB hubs cannot solve this problem

While ESP-IDF now supports USB hubs (CONFIG_USB_HOST_HUBS_SUPPORTED), a hub cannot change device descriptors. The hub handles physical layer speed translation, but the DJI Motion Controller still reports 512-byte MPS in its configuration descriptor regardless of connection path. A USB 1.1-only hub might theoretically force descriptor re-enumeration with Full-Speed values—but only if the device actually contains alternate Full-Speed descriptors, which the DJI Motion Controller appears to lack.

Verify if Full-Speed descriptors exist

Before investing in workarounds, use a Linux machine to dump the device's complete USB descriptors:

###b class="inline-flex items-center justify-center relative shrink-0 can-focus select-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none disabled:drop-shadow-none border-transparent transition font-base duration-300 ease-[cubic-bezier(0.165,0.85,0.45,1)] h-8 w-8 rounded-md active:scale-95 backdrop-blur-md Button_ghost__BUAoh" type="button" aria-label="Copier dans le presse-papiers" data-state="closed"###

bash

lsusb -v -d 2CA3:  # Replace with actual VID
# Look for alternate configurations or interfaces with smaller wMaxPacketSize

If the output shows only 512-byte endpoints with no alternate interfaces offering ≤64-byte MPS, the device genuinely requires High-Speed USB and no ESP32-S3 software workaround will function correctly.

Conclusion: hardware limitation requires hardware solution

The ESP32-S3's 128-byte periodic endpoint MPS limit is a fixed hardware constraint of the DWC_OTG controller's FIFO architecture. The DJI Motion Controller violates USB 2.0 specification by not providing valid Full-Speed fallback descriptors. Your viable paths forward are:


And confirmed by ChatGPT

What your USB_Desc log tells us (decoded)

Interface descriptor

Intf.number:        06
Intf. Class:        03  (HID)
Intf. Subclass:     00
Intf. Protocol:     00


➡ This confirms:

This alone already explains everything you observed.

HID descriptor (Type 0x21)

Contents: 01 01 00 01 22 32 00 07 05


Decoded:

So:

Endpoint descriptors (this is critical)

IN endpoint

Endpoint address:  87  (IN, EP7)
Attr.:             03  (Interrupt)
Max.pkt size:      0200  (512 bytes)
Polling interval:  04


OUT endpoint

Endpoint address:  05  (OUT, EP5)
Attr.:             03  (Interrupt)
Max.pkt size:      0200  (512 bytes)
Polling interval:  04


➡ This tells us four extremely important facts:

  1. DJI Motion Controller uses 512-byte interrupt endpoints

  2. It has both IN and OUT interrupt endpoints

  3. Motion data is on EP7 IN

  4. This interface is HS-oriented HID

2️⃣ Why USB Host Shield Library cannot work (now proven)

Let’s connect this to what the library can and cannot do.

Hard limitations you have hit

❌ Interface selection

➡ Your motion data is on interface 6 → unreachable.

❌ Endpoint packet size

➡ Even if interface 6 were reachable, transfers would stall.

Discussions