← index #18098Issue #1044
Duplicate · high · value 4.567
QUERY · ISSUE

USB HID device on ESP32 has a problem!

openby ThomasAtBBTFopened 2025-09-19updated 2025-09-19
bug

Port, board and/or hardware

ESP32S3

MicroPython version

I created an issue on USB-LIB but I think the problem must be solved here...
https://github.com/micropython/micropython-lib/issues/1044

Reproduction

The code for reproducing the problem has been tried on MicroPython 1.26.1 and also on the new 1.27 releases.

It is not ESP board related. I tried the examples on different boards with different MicroPython versions.

I can also share Wireshark logs if necessary, but the bug is easily reproducible with the unmodified USB HID device samples from the lib directory.

Expected behaviour

The samples should "just work" also for the ESP32S3 boards.

Observed behaviour

The LED out reports are arriving nicely in the code, but all keystrokes that are sent are arriving with all bytes "zero" at the host. (Confirmed by Wireshark )

Additional Information

See the debug code changes documented in the original lib-issue!

Code of Conduct

Yes, I agree

CANDIDATE · ISSUE

USB HID Implementation for ESP32S3 has problems !

openby ThomasAtBBTFopened 2025-09-19updated 2025-10-27

I tried to run the different usb.device examples on different ESP32S3 board all with the same results.
Since version 1.26.1 and also in version 1.27.x receiving the LED information from the host works fine.
Sending keypress information to the host is working (as I confirmed with wireshark) but the report "arrives"
at the host always with only 8 bytes of "0" ! Regardless what has been send by the python code.
I recompiled the micropython image and added debug information in:

static mp_obj_t usb_device_submit_xfer(mp_obj_t self, mp_obj_t ep, mp_obj_t buffer) {
    mp_obj_usb_device_t *usbd = (mp_obj_usb_device_t *)MP_OBJ_TO_PTR(self);
    int ep_addr;
    mp_buffer_info_t buf_info = { 0 };
    bool result;

    usb_device_check_active(usbd);

    // Unmarshal arguments, raises TypeError if invalid
    ep_addr = mp_obj_get_int(ep);
    mp_get_buffer_raise(buffer, &buf_info, ep_addr & TUSB_DIR_IN_MASK ? MP_BUFFER_READ : MP_BUFFER_RW);

    uint8_t ep_num = tu_edpt_number(ep_addr);
    uint8_t ep_dir = tu_edpt_dir(ep_addr);

    if (ep_num == 0 || ep_num >= CFG_TUD_ENDPPOINT_MAX) {
        // TinyUSB usbd API doesn't range check arguments, so this check avoids
        // out of bounds array access, or submitting transfers on the control endpoint.
        //
        // This C layer doesn't otherwise keep track of which endpoints the host
        // is aware of (or not).
        mp_raise_ValueError(MP_ERROR_TEXT("ep"));
    }

    if (!usbd_edpt_claim(USBD_RHPORT, ep_addr)) {
        mp_raise_OSError(MP_EBUSY);
    }
    
    size_t len = buf_info.len;
    const byte *data = buf_info.buf;
    
    mp_printf(&mp_plat_print, "----Debug: (len=%d): ", len);
    for (size_t i = 0; i < len; i++) {
        mp_printf(&mp_plat_print, "%02x ", data[i]);
    }
    mp_printf(&mp_plat_print, "\n"); 

And also in :


bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) {
  uint8_t const epnum = tu_edpt_number(ep_addr);
  uint8_t const dir = tu_edpt_dir(ep_addr);

  
  if (ep_addr == 0x83) {
    esp_rom_printf("DCD: xfer ep=%02X len=%u : ", ep_addr, (unsigned)total_bytes);

    // Print a limited number of bytes to stay ISR-friendly
    const uint8_t *data = (const uint8_t *)buffer;
    uint16_t limit = total_bytes;
    if (limit > 32) limit = 32;   // cap output (optional)

    for (uint16_t i = 0; i < limit; i++) {
      esp_rom_printf("%02X ", data[i]);
    }
    if (limit < total_bytes) esp_rom_printf("... ");
    esp_rom_printf("\n");

  }
  DCD_ENTER_CRITICAL();


```and both logs shows that the keyboard buffer bytes are fine until these log-points.

But nonetheless  at the host side only "zeros" are arriving .

Can anyone using a ESP32S3 create a working USB HID keyboard from the sample code provided ?
11 comments
Josverl · 2025-09-19

Can you add a link to the sample you used please?
Also, what host OS are you using?

ThomasAtBBTF · 2025-09-19

Sure!
Here are the standard examples from the USB library I used:

https://github.com/micropython/micropython-lib/blob/master/micropython/usb/examples/device/keyboard_example.py

https://github.com/micropython/micropython-lib/blob/master/micropython/usb/examples/device/hid_custom_keypad_example.py

I used both with the same results.
For the "normal" keyboard example, I see in the debug logs the 8-byte keyboard buffer with a value in the third byte for the make press, and then another 8-byte keyboard buffer with all bytes zero for the release in the debug log.

But in the Wireshark log, I see both HID in reports to the host, both times with only zeros.

And I am using Windows and an iPhone...

And a Freather S3 board and a "vanilla" WROOM1 board.

Always the same..
In the iPhone, I have no Wireshark, but I do not get keys...

But I was wondering why I did not get keys... So I switched to Windows with Wireshark.
Before I recompiled the MicroPython 1.26.1 image with the modification from above.

AND: I do get out reports for the LEDs !!!!!
So the driver is accepted in both Windows and IOS and happily sends LED values.
I think the driver in IOS also gets the in reports, but for make and break always "zeros"!

Just try it!

ThomasAtBBTF · 2025-09-19

And I am happy for any sample code which just works on a ESP32S3 !

With no complicated pin checking, just sending an "a" every couple of seconds...

ThomasAtBBTF · 2025-09-20

boot.py

# boot.py — ESP32-S3, MicroPython 1.26.1
import sys
import time
import machine
import usb.device
from usb.device.keyboard import KeyboardInterface, KeyCode, LEDCode

_boot_logs = []            # ring buffer for early logs

def _log(msg):
    # keep ~50 lines
    if len(msg) > 50:
        msg = msg[:50] + '...'
    _boot_logs.append(msg)
    if len(_boot_logs) > 50:
        _boot_logs.pop(0)

_log("[BOOT] boot.py start")

# ---------- create boot singleton -----------------
_log("[BOOT] create boot singleton !")

class BootPyInterface:
    def __init__(self):
        self.boot_logs = _boot_logs
        self.usbdev = usb.device.get()
        self.keyboard = None
        self.keyboard_led_bits = 0
        self.keyboard_led_changed = 0
        self.keyboard_led_callback = None
        self.keyboard_print_led = True

    def print_boot_log(self):
        for line in self.boot_logs:
            print(line)

boot_py = BootPyInterface()
sys.modules["boot_py"] = boot_py

# --------------------------------------------------
usekeyboard = True
if usekeyboard:

    class ExampleKeyboard(KeyboardInterface):
        def on_led_update(self, led_bits):
            boot_py.keyboard_led_bits = led_bits
            boot_py.keyboard_led_changed += 1
            msg = f"*************************************************LED:  led_mask={hex(led_bits)}"
            if boot_py.keyboard_print_led:
                print(msg)
            if boot_py.keyboard_led_callback:
                try:
                    boot_py.keyboard_led_callback()
                except Exception as e:
                    sys.print_exception(e)
            # debug_broadcast.send_debug(socket, msg, bcast)
    _log("[BOOT] keyboard = ExampleKeyboard()")
    boot_py.keyboard = ExampleKeyboard()
    _log("[BOOT] keyboard done.")

    # ---------- here the USB device is configured ----------
    _log("[BOOT] usbdev.init(bootinterface.keyboard, builtin_driver=True)")
    boot_py.usbdev.init(boot_py.keyboard, builtin_driver=True)
    _log("[BOOT] usbdev.init done.")
else:
    _log("[BOOT] don't use keyboard.!")
_log("[BOOT] boot.py done!")

main.py

# main.py
print("[MAIN] start")
import sys
import time

testboot = True
if testboot:
    try:
        import boot_py   # created in boot.py
        # (optional) show early logs
        try:
            print("[MAIN] early boot logs ------------------------------")
            boot_py.print_boot_log()
            print("[MAIN] early logs done------------------------------")
        except Exception as e:
            print(f"[MAIN] early Exception")
            sys.print_exception(e)
            pass
    except Exception as e:
        sys.print_exception(e)

if boot_py.keyboard:
    print(f"[MAIN] use Keyboard from boot.py")
    keys = []
    last_time_num = time.time()
    last_time_esc = time.time()
    while True:
        this_time = time.time()
        if this_time - last_time_num >= 10:
            last_time_num = this_time
            print("-----------------------------------------------------------------------------------num_lock")
            keys.clear()
            keys.append(83)  # NUM_LOCK hid code decimal
            boot_py.keyboard.send_keys(keys)
            print("...........................................................................pressed")
            time.sleep_ms(10)
            keys.clear()
            boot_py.keyboard.send_keys(keys)
            print("...........................................................................released")
            time.sleep_ms(10)
            last_time_esc = this_time
        if this_time - last_time_esc >= 3:
            last_time_esc = this_time
            print("-------------------------------------------------------------------------------------escape")
            keys.clear()
            keys.append(41)  # ESCAPE hid code decimal
            boot_py.keyboard.send_keys(keys)
            print("...........................................................................pressed")
            time.sleep_ms(10)
            keys.clear()
            boot_py.keyboard.send_keys(keys)
            print("...........................................................................released")
            time.sleep_ms(10)
else:
    print(f"[MAIN] no Keyboard in boot.py")

ThomasAtBBTF · 2025-09-20

Log Output:

MicroPython v1.26.1 on 2025-09-18 19:21:56; MY_ESP32_S3_BUILD
Type "help()" for more information.
>>>
>>>
>>>

MPY: soft reboot
[MAIN] start
[MAIN] early boot logs ------------------------------
[BOOT] boot.py start
[BOOT] create boot singleton !
[BOOT] keyboard = ExampleKeyboard()
[BOOT] keyboard done.
[BOOT] usbdev.init(bootinterface.keyboard, builtin...
[BOOT] usbdev.init done.
[BOOT] boot.py done!
[MAIN] early logs done------------------------------
[MAIN] use Keyboard from boot.py
*************************************************LED:  led_mask=0x0      !!EDIT This comes from the initialization of the keyboard by the OS!
-------------------------------------------------------------------------------------escape
----Debug: (len=8): 00 00 29 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 29 00 00 00 00 00
...........................................................................pressed
----Debug: (len=8): 00 00 00 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 00 00 00 00 00 00
...........................................................................released
-------------------------------------------------------------------------------------escape
----Debug: (len=8): 00 00 29 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 29 00 00 00 00 00
...........................................................................pressed
----Debug: (len=8): 00 00 00 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 00 00 00 00 00 00
...........................................................................released

!!!!!!!!!!!!!!!!!!!!!!!! Edit: Here I pressed CAPS-LOCK on my computer keyboard and the ESP32S3 keyboard is notified!
*************************************************LED:  led_mask=0x2
*************************************************LED:  led_mask=0x0
-------------------------------------------------------------------------------------escape
----Debug: (len=8): 00 00 29 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 29 00 00 00 00 00
...........................................................................pressed
----Debug: (len=8): 00 00 00 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 00 00 00 00 00 00
...........................................................................released
!!!!!!!!!!!!!!!!!!!!!!!! Edit: Here I pressed NUM-LOCK on my computer keyboard and the ESP32S3 keyboard is notified!
*************************************************LED:  led_mask=0x1
*************************************************LED:  led_mask=0x0
-----------------------------------------------------------------------------------num_lock
----Debug: (len=8): 00 00 53 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 53 00 00 00 00 00
...........................................................................pressed
----Debug: (len=8): 00 00 00 00 00 00 00 00
DCD: xfer ep=83 len=8 : 00 00 00 00 00 00 00 00
...........................................................................released
Traceback (most recent call last):
  File "main.py", line 29, in <module>
KeyboardInterrupt:
MicroPython v1.26.1 on 2025-09-18 19:21:56; MY_ESP32_S3_BUILD
Type "help()" for more information.
>>>

Watch the debug lines from my modified micropython build as documented initially!

And "YES", I first tried with a stock micropython build and had the same result that no key is generated!

ThomasAtBBTF · 2025-09-20

The wireshark log:

<img width="1825" height="1003" alt="Image" src="https://github.com/user-attachments/assets/1848e827-9542-477d-b145-e807230401cc" />

ThomasAtBBTF · 2025-09-20

Please take a look!

Sorry I don't have another non ESP32S3 board available for the moment!

@Josverl
@projectgus

chiefenne · 2025-10-02

I also have problems getting this running. I tested on ESP32 DevkitC v4 and Xiao ESP32-S3. Did not work at all.

When i ask ChatGPT it tells me that the HID is not enabled in the precompiled binaries for the ESPs. I then with the help of GPT tried to compile a Micropython version with HID enabled but miserably failed. Although it really seemed that some config does not have HID not enabled by default. As i was guided by GPT and I myself am not qualified enough, it could all be AI hallucination as well. I cant proof that.

I am on MacOS.

ThomasAtBBTF · 2025-10-02

I also have problems getting this running. I tested on ESP32 DevkitC v4 and Xiao ESP32-S3. Did not work at all.

When i ask ChatGPT it tells me that the HID is not enabled in the precompiled binaries for the ESPs. I then with the help of GPT tried to compile a Micropython version with HID enabled but miserably failed. Although it really seemed that some config does not have HID not enabled by default. As i was guided by GPT and I myself am not qualified enough, it could all be AI hallucination as well. I cant proof that.

I am on MacOS.

I am doing this now with CircuitPython, and the USB HID is working okay there...
But I really would prefer to have it working on MicroPython !

chiefenne · 2025-10-03

i switched to CircuitPython as well now.
Waiting like you for the MP integration of that feature.
Thanks

dpgeorge · 2025-10-27

It looks like the issue occurs when using PSRAM on an S2/S3 board. Because USB DMA cannot read from PSRAM, so the data sent to the host ends up as all zeros.

See https://github.com/micropython/micropython/pull/18332 for a fix.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied