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 Speed | Bulk MPS Max | Interrupt MPS Max | ESP32-S3 Support |
|---|---|---|---|
| Full-Speed (12 Mbps) | 64 bytes | 64 bytes | ✓ Supported |
| High-Speed (480 Mbps) | 512 bytes | 1024 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 Type | Maximum MPS | FIFO Allocation |
|---|---|---|
| IN endpoints (all types) | 408 bytes | RX FIFO shared by all IN |
| Non-periodic OUT (bulk/control) | 256 bytes | NPTX FIFO |
| Periodic OUT (interrupt/iso) | 128 bytes | PTX 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:
- Android phone connects to DJI Motion Controller via USB OTG
- Android GamePad APIs read the HID joystick data natively (Android's USB stack has no MPS limits)
- Phone forwards decoded axis/button data via UDP over WiFi to ESP32-S3
- ESP32-S3 exposes the data as a BLE joystick to the target system
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
| Platform | USB High-Speed | Notes |
|---|---|---|
| ESP32-P4 | ✓ 480 Mbps | First ESP32 with USB HS; 16 channels, higher MPS limits |
| Raspberry Pi Pico 2 | ✗ Full-Speed only | Same limitation as ESP32-S3 |
| STM32F4/F7/H7 | ✓ with external PHY | Requires USB3300 or similar HS PHY |
| Teensy 4.1 | ✓ Built-in HS | NXP 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:
- wMaxPacketSize override hack if the device actually sends ≤64-byte packets despite advertising 512
- Android/Raspberry Pi bridge as confirmed working by the Hackaday community
- ESP32-P4 migration for native High-Speed USB support
- Alternative joystick like the Thrustmaster T16000M that works because it properly supports Full-Speed USB
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:
-
The motion data is on interface 6
-
Not interface 0
-
Not interface 1
-
Not even interface 2
This alone already explains everything you observed.
HID descriptor (Type 0x21)
Contents: 01 01 00 01 22 32 00 07 05
Decoded:
-
HID v1.01
-
1 report descriptor
-
Report descriptor length =
0x0032(50 bytes)
So:
-
HID itself is standard
-
Not encrypted
-
Not malformed
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:
-
DJI Motion Controller uses 512-byte interrupt endpoints
-
It has both IN and OUT interrupt endpoints
-
Motion data is on EP7 IN
-
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
-
USB Host Shield Library:
-
Always binds to interface 0
-
You cannot select interface 6
-
No API, no workaround
-
➡ Your motion data is on interface 6 → unreachable.
❌ Endpoint packet size
-
Endpoint MPS = 512 bytes
-
MAX3421E is Full-Speed only
-
USB Host Shield Library:
-
Assumes interrupt MPS ≤ 64
-
Does not split interrupt transfers correctly for HS-declared endpoints
-
➡ Even if interface 6 were reachable, transfers would stall.
JP Gleyzes
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.