Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't instanciate multiple HID devices #9973

Open
1 task done
tankyx opened this issue Jul 1, 2024 · 11 comments
Open
1 task done

Can't instanciate multiple HID devices #9973

tankyx opened this issue Jul 1, 2024 · 11 comments
Labels
Status: Awaiting triage Issue is waiting for triage

Comments

@tankyx
Copy link

tankyx commented Jul 1, 2024

Board

ESP32S3

Device Description

DevkitM1, plain board, both USB ports connected to host

Hardware Configuration

Optical sensor connected to GPIO21, GPIO38, GPIO47, GPIO48, GND and 3v3

Version

latest master (checkout manually)

IDE Name

Arduino IDE 2.3.2

Operating System

Windows 10

Flash frequency

80MHz

PSRAM enabled

no

Upload speed

921600

Description

I am implementing an optical sensor using the USBHID.h library. It works well and I am able to send movements to the host. As soon as I instantiate a Custom HID device, the host won't receive the movements (I don't know if it is the board not sending or the host not receiving).

Sketch

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_MOUSE16(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
    HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
        HID_USAGE_MIN   ( 1                                      ) ,\
        HID_USAGE_MAX   ( 5                                      ) ,\
        HID_LOGICAL_MIN ( 0                                      ) ,\
        HID_LOGICAL_MAX ( 1                                      ) ,\
        /* Left, Right, Middle, Backward, Forward buttons */ \
        HID_REPORT_COUNT( 5                                      ) ,\
        HID_REPORT_SIZE ( 1                                      ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* 3 bit padding */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 3                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
        /* X, Y position [-32767, 32767] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
        HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
        HID_LOGICAL_MIN_N ( 0x8001, 2                            ) ,\
        HID_LOGICAL_MAX_N ( 0x7fff, 2                            ) ,\
        HID_REPORT_COUNT( 2                                      ) ,\
        HID_REPORT_SIZE ( 16                                     ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
        /* Verital wheel scroll [-127, 127] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
        HID_LOGICAL_MIN ( 0x81                                   )  ,\
        HID_LOGICAL_MAX ( 0x7f                                   )  ,\
        HID_REPORT_COUNT( 1                                      )  ,\
        HID_REPORT_SIZE ( 8                                      )  ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER ), \
       /* Horizontal wheel scroll [-127, 127] */ \
        HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2           ), \
        HID_LOGICAL_MIN ( 0x81                                   ), \
        HID_LOGICAL_MAX ( 0x7f                                   ), \
        HID_REPORT_COUNT( 1                                      ), \
        HID_REPORT_SIZE ( 8                                      ), \
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
    HID_COLLECTION_END                                            , \
  HID_COLLECTION_END

static const uint8_t rel16_mouse_report_descriptor[] = {
    TUD_HID_REPORT_DESC_MOUSE16(HID_REPORT_ID(HID_REPORT_ID_MOUSE))
};

enum MousePositioning_t {
  HID_MOUSE_RELATIVE,
  HID_MOUSE_ABSOLUTE
};

typedef struct TU_ATTR_PACKED
{
    uint8_t buttons;
    int16_t  x;
    int16_t  y;
    int8_t  wheel;
    int8_t  pan;
} hid_mouse_report_16_t;

struct HIDMouseType_t {
  MousePositioning_t positioning;
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDMouseType_t HIDMouseRel16 = { HID_MOUSE_RELATIVE, rel16_mouse_report_descriptor, sizeof(rel16_mouse_report_descriptor), sizeof(hid_mouse_report_16_t) };

class USBHIDRelativeMouse16 : public USBHIDDevice {
private:
  USBHID &_hid;
  uint8_t _buttons;

public:
    USBHIDRelativeMouse16(USBHID& hid) : _hid(hid) {
      _hid = hid;
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDMouseRel16.descriptor_size);
      }
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDMouseRel16.report_descriptor, HIDMouseRel16.descriptor_size);
      return HIDMouseRel16.descriptor_size;
    }

    void move(int16_t x, int16_t y, int8_t wheel = 0, int8_t pan = 0) {
        hid_mouse_report_16_t report = {
            .buttons = _buttons,
            .x       = x,
            .y       = y,
            .wheel   = wheel,
            .pan     = pan
        };
        _hid.SendReport(HID_REPORT_ID_MOUSE, &report, sizeof(hid_mouse_report_16_t));
    }

    void click(uint8_t b) {
        _buttons = b;
        move(0,0);
        _buttons = 0;
        move(0,0);
    }

    void buttons(uint8_t b) {
        if (b != _buttons) {
            _buttons = b;
            move(0,0);
        }
    }
};

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_CUSTOM(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_VENDOR     )                   ,\
  HID_USAGE      ( 0x01                                   )       ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION )                    ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( 0x02                                   )       ,\
    HID_LOGICAL_MIN ( 0                                      )       ,\
    HID_LOGICAL_MAX ( 0xFF                                   )       ,\
    HID_REPORT_SIZE ( 8                                      )       ,\
    HID_REPORT_COUNT( 64                                     )       ,\
    HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE )       ,\
    HID_OUTPUT      ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE )       ,\
  HID_COLLECTION_END

static const uint8_t custom_hid_report_descriptor[] = {
    TUD_HID_REPORT_DESC_CUSTOM(HID_REPORT_ID(69))
};

typedef struct TU_ATTR_PACKED
{
    uint8_t data[64];
} custom_hid_report_t;

struct HIDCustomType_t {
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDCustomType_t HIDCustom = { custom_hid_report_descriptor, sizeof(custom_hid_report_descriptor), sizeof(custom_hid_report_t) };

class USBHIDCustomDevice : public USBHIDDevice {
private:
  USBHID &_hid;
  custom_hid_report_t _report;

public:
    USBHIDCustomDevice(USBHID& hid) : _hid(hid) { 
      _hid = hid;
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDCustom.descriptor_size);
      }
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDCustom.report_descriptor, HIDCustom.descriptor_size);
      return HIDCustom.descriptor_size;
    }

    void sendReport(const uint8_t* data, size_t length) {
        if (length > sizeof(_report.data)) length = sizeof(_report.data);
        memcpy(_report.data, data, length);
        _hid.SendReport(69, &_report, sizeof(custom_hid_report_t));
    }

    void receiveReport(uint8_t* data, size_t length) {
        if (length > sizeof(_report.data)) length = sizeof(_report.data);
        memcpy(data, _report.data, length);
    }

    void _onOutput(uint8_t *report, size_t len) {
      // Handle the received report
      receiveReport(report, len);
    }
};


#include "Mouse16.h"
#include "PAW3370.h"
#include "CustomHIDDevice.h"

USBHID hid;

USBHIDRelativeMouse16 Mouse16(hid);
USBHIDCustomDevice customHID(hid);
PAW3370 sensor;

void setup() {
    Serial.begin(115200);
    SPI.begin(PIN_CLK, PIN_MISO, PIN_MOSI, PIN_CS);

    if (!sensor.init()) {
        Serial.println("Failed to initialize PAW3370");
        while (1);
    }
    hid.begin();
    USB.begin();
}

void loop() {
    int16_t delta_x, delta_y;

    if (!sensor.read_motion(&delta_x, &delta_y)) {
        Serial.println("Failed to read motion data");
    }

    uint8_t receivedData[64];
    //customHID.receiveReport(receivedData, sizeof(receivedData));
    Mouse16.move(delta_x, delta_y, 0, 0);
}

Debug Message

Board Info:
------------------------------------------
  Arduino Board     : ESP32S3_DEV
  Arduino Variant   : esp32s3
  Arduino FQBN      : esp32:esp32:esp32s3:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,CPUFreq=240,FlashMode=qio,FlashSize=4M,PartitionScheme=default,DebugLevel=verbose,PSRAM=disabled,LoopCore=1,EventsCore=1,EraseFlash=none,JTAGAdapter=default,ZigbeeMode=default
============ Before Setup End ============
[   608][V][esp32-hal-uart.c:408] uartBegin(): UART0 baud(115200) Mode(800001c) rxPin(44) txPin(43)
[   617][V][esp32-hal-uart.c:497] uartBegin(): UART0 not installed. Starting installation
[   625][V][esp32-hal-uart.c:544] uartBegin(): UART0 initialization done.
[   632][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_SCK (34) successfully set to 0x42006440
[   644][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_MISO (35) successfully set to 0x42006368
[   656][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_MOSI (36) successfully set to 0x42006290
[   668][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type SPI_MASTER_SS (37) successfully set to 0x4200617c
[   680][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   691][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 48 successfully set to type GPIO (1) with bus 0x31
[   701][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 48 successfully set to type SPI_MASTER_SCK (34) with bus 0x1
[   712][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   723][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 47 successfully set to type GPIO (1) with bus 0x30
[   732][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 47 successfully set to type SPI_MASTER_MISO (35) with bus 0x1
[   743][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   754][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 38 successfully set to type GPIO (1) with bus 0x27
[   764][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 38 successfully set to type SPI_MASTER_MOSI (36) with bus 0x1
[   875][V][esp32-hal-periman.c:235] perimanSetBusDeinit(): Deinit function for type GPIO (1) successfully set to 0x420322a8
[   886][V][esp32-hal-periman.c:160] perimanSetPinBus(): Pin 21 successfully set to type GPIO (1) with bus 0x16
INIT : Wrote to Power Up
........
[  1610][D][esp32-hal-tinyusb.c:655] tinyusb_load_enabled_interfaces(): Load Done: if_num: 1, descr_len: 41, if_mask: 0x4
=========== After Setup Start ============
INTERNAL Memory Info:
------------------------------------------
  Total Size        :   386140 B ( 377.1 KB)
  Free Bytes        :   344736 B ( 336.7 KB)
  Allocated Bytes   :    35988 B (  35.1 KB)
  Minimum Free Bytes:   339688 B ( 331.7 KB)
  Largest Free Block:   319476 B ( 312.0 KB)
------------------------------------------
GPIO Info:
------------------------------------------
  GPIO : BUS_TYPE[bus/unit][chan]
  --------------------------------------  
    21 : GPIO
    38 : SPI_MASTER_MOSI[0]
    43 : UART_TX[0]
    44 : UART_RX[0]
    47 : SPI_MASTER_MISO[0]
    48 : SPI_MASTER_SCK[0]
[  1944][V][USBHID.cpp:240] tud_hid_set_idle_cb(): instance: 0, idle_rate:0
[  1952][V][USBHID.cpp:219] tud_hid_descriptor_report_cb(): instance: 0
[  1960][D][USBHID.cpp:176] tinyusb_load_enabled_hid_devices(): Loaded HID Descriptor with the following reports:
[  1970][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   2, Type:   INPUT, Size:  7, Usage:    MOUSE
[  1980][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:  69, Type:   INPUT, Size: 64, Usage:  GENERIC
[  1990][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:  69, Type:  OUTPUT, Size: 64, Usage:  GENERIC
[  2042][E][USBHID.cpp:360] SendReport(): report 2 wait failed
[  2047][E][USBHID.cpp:347] SendReport(): not ready

Other Steps to Reproduce

I can see the following error in USBLogView Device Description

Unknown USB Device (Device Descriptor Request Failed)

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.
@tankyx tankyx added the Status: Awaiting triage Issue is waiting for triage label Jul 1, 2024
@me-no-dev
Copy link
Member

I tried your exact code, with the exception that I do not have PAW3370, so instead my main sketch looks like:

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  hid.begin();
  USB.begin();
  pinMode(0, INPUT_PULLUP);
}

void loop() {
  static bool btn = digitalRead(0);
  bool btn_now = digitalRead(0);
  if(btn != btn_now){
    btn = btn_now;
    if(!btn_now){
      Mouse16.click(1);
    }
  }
  delay(10);
}

Mouse works fine on my computer and I can see both devices in the USB tool I have. Main difference is that I am on a Mac and have no idea what things look like on Windows. Code looks fine and works fine, except you should not call customHID.receiveReport yourself. That will be called once the host sends data to the device.

@tankyx
Copy link
Author

tankyx commented Jul 2, 2024

Indeed, I tried on both MBP M1 and MBP M3 and it was working without any issues, with both devices showing up. Probably some windows shenanigans happening. Could you please investigate ?

@me-no-dev
Copy link
Member

I can't. I do not have a windows machine and the colleague that does just went out on a month long leave... One suggestion for you is to set the USB mode in the board menu to TinyUSB (I saw in the logs it was the default USB/JTAG selected)

@tankyx
Copy link
Author

tankyx commented Jul 2, 2024

Yeah I have done that :

USB Mode to USB-OTG (TinyUSB)
Upload mode UART0/Hardware CDC

I dont really understand why Windows is not allowing the Arduino to work as intended

@me-no-dev
Copy link
Member

Maybe it does not like something in the vendor descriptor? Can you try with a standard TinyUSB one? https://github.com/espressif/arduino-esp32/blob/master/libraries/USB/examples/HIDVendor/HIDVendor.ino

@tankyx
Copy link
Author

tankyx commented Jul 2, 2024

On Windows I have the following output :

[   890][E][USBHID.cpp:347] SendReport(): not ready
[   894][E][USBHID.cpp:347] SendReport(): not ready
[   899][E][USBHID.cpp:347] SendReport(): not ready
[   904][E][USBHID.cpp:347] SendReport(): not ready
[   908][E][USBHID.cpp:347] SendReport(): not ready
[   913][E][USBHID.cpp:347] SendReport(): not ready
[   918][E][USBHID.cpp:347] SendReport(): not ready
[   922][E][USBHID.cpp:347] SendReport(): not ready
[   927][E][USBHID.cpp:347] SendReport(): not ready
[   931][E][USBHID.cpp:347] SendReport(): not ready
[   936][E][USBHID.cpp:347] SendReport(): not ready
[   941][E][USBHID.cpp:347] SendReport(): not ready
[   945][E][USBHID.cpp:347] SendReport(): not ready
[   950][E][USBHID.cpp:347] SendReport(): not ready
[   955][E][USBHID.cpp:347] SendReport(): not ready
[   959][E][USBHID.cpp:347] SendReport(): not ready
[   964][E][USBHID.cpp:347] SendReport(): not ready
[   969][E][USBHID.cpp:347] SendReport(): not ready
[   973][E][USBHID.cpp:347] SendReport(): not ready
[   978][V][USBHID.cpp:240] tud_hid_set_idle_cb(): instance: 0, idle_rate:0
[   987][V][USBHID.cpp:219] tud_hid_descriptor_report_cb(): instance: 0
[   993][D][USBHID.cpp:176] tinyusb_load_enabled_hid_devices(): Loaded HID Descriptor with the following reports:
[  1004][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type:   INPUT, Size: 63, Usage:   VENDOR
[  1014][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type:  OUTPUT, Size: 63, Usage:   VENDOR
[  1024][D][USBHID.cpp:179] tinyusb_load_enabled_hid_devices():   ID:   6, Type: FEATURE, Size: 63, Usage:   VENDOR

And I can see it being plugged in as a USB Input Device with device type HID (Human Interface Device). Should I use that standard HIDVendor in my code ?

@tankyx
Copy link
Author

tankyx commented Jul 2, 2024

I added the standard HIDVendor device to my code, I still get the issue on Windows. Not on MacOS though

#include "Mouse16.h"
#include "CustomHIDDevice.h"
#include "PAW3370.h"
#include "USBHIDVendor.h"

USBHIDRelativeMouse16 Mouse16;
//USBHIDCustomDevice customHID;
USBHIDVendor Vendor;
PAW3370 sensor;

static void vendorEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
  if (event_base == ARDUINO_USB_HID_VENDOR_EVENTS) {
    arduino_usb_hid_vendor_event_data_t *data = (arduino_usb_hid_vendor_event_data_t *)event_data;
    switch (event_id) {
      case ARDUINO_USB_HID_VENDOR_GET_FEATURE_EVENT: Serial.printf("HID VENDOR GET FEATURE: len:%u\n", data->len); break;
      case ARDUINO_USB_HID_VENDOR_SET_FEATURE_EVENT:
        Serial.printf("HID VENDOR SET FEATURE: len:%u\n", data->len);
        for (uint16_t i = 0; i < data->len; i++) {
          Serial.printf("0x%02X ", *(data->buffer));
        }
        Serial.println();
        break;
      case ARDUINO_USB_HID_VENDOR_OUTPUT_EVENT:
        Serial.printf("HID VENDOR OUTPUT: len:%u\n", data->len);
        for (uint16_t i = 0; i < data->len; i++) {
          Serial.printf("0x%02X ", *(data->buffer));
        }
        break;

      default: break;
    }
  }
}

void setup() {
    Serial.begin(115200);
    SPI.begin(PIN_CLK, PIN_MISO, PIN_MOSI, PIN_CS);

    if (!sensor.init()) {
        Serial.println("Failed to initialize PAW3370");
        while (1);
    }
    Vendor.onEvent(vendorEventCallback);
    Mouse16.begin();
    Vendor.begin();
    USB.begin();
}

void loop() {
    int16_t delta_x, delta_y;

    if (!sensor.read_motion(&delta_x, &delta_y)) {
        Serial.println("Failed to read motion data");
    }

    if (delta_x != 0 || delta_y != 0) {
      Mouse16.move(delta_x, delta_y, 0, 0);
    }

    if (Vendor.available()) {
      Vendor.read();
    }
}

@tankyx
Copy link
Author

tankyx commented Jul 4, 2024

@me-no-dev Couple of updates on the matter :

  • Uploading the code with both the Mouse and HIDVendor devices on my XIAO Seeedstudio ESP32S3 works on Windows.
  • Uploading the code with both the Mouse and the CustomHID triggers an error 10 in the device manager and doesn't work.

@me-no-dev
Copy link
Member

That means that Windows does not like something in your vendor descriptor. I wonder what... what is different from the default one?

@tankyx
Copy link
Author

tankyx commented Jul 10, 2024

I don't really know to be fair. Now I have the custom device showing up, but I can't communicate with it.

#pragma once
#include <USB.h>
#include <USBHID.h>
#include "Mouse16.h"

#define HID_VENDOR_REPORT_SIZE 64

static const uint8_t report_descriptor[] = {
    0x06, 0x00, 0xff,              // Usage Page (Vendor Defined 0xFF00)
    0x09, 0x01,                    // Usage (Vendor Usage 0x01)
    0xa1, 0x01,                    // Collection (Application)
    0x85, 0x06,                    //   Report ID (6)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x26, 0xff, 0x00,              //   Logical Maximum (255)
    0x75, 0x08,                    //   Report Size (8 bits)
    0x95, 0x01,                    //   Report Count (1)
    0x09, 0x01,                    //   Usage (Vendor Usage 0x01)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0x15, 0x00,                    //   Logical Minimum (0)
    0x26, 0xff, 0x7f,              //   Logical Maximum (32767)
    0x75, 0x10,                    //   Report Size (16 bits)
    0x95, 0x02,                    //   Report Count (2)
    0x09, 0x02,                    //   Usage (Vendor Usage 0x02)
    0x09, 0x03,                    //   Usage (Vendor Usage 0x03)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0x75, 0x08,                    //   Report Size (8 bits)
    0x95, 0x39,                    //   Report Count (57)
    0x09, 0x04,                    //   Usage (Vendor Usage 0x04)
    0x81, 0x02,                    //   Input (Data, Var, Abs)
    0xc0                           // End Collection
};

struct HIDReport {
    uint8_t reportId;
    int16_t dx;
    int16_t dy;
    uint8_t padding[57];
};

class USBHIDCustomDevice : public USBHIDDevice {
private:
    USBHID _hid;
    USBHIDRelativeMouse16 _mouse;

public:
    USBHIDCustomDevice(USBHIDRelativeMouse16& mouse) : _hid() {
        static bool initialized = false;
        if (!initialized) {
            initialized = true;
            _hid.addDevice(this, sizeof(report_descriptor));
        }
        _mouse = mouse;
    }

    void begin() {
        _hid.begin();
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
        memcpy(dst, report_descriptor, sizeof(report_descriptor));
        return sizeof(report_descriptor);
    }

    void receiveReport(const uint8_t* data, size_t length) {
        int16_t dx = (int16_t)(data[1] | (data[2] << 8));
        int16_t dy = (int16_t)(data[3] | (data[4] << 8));
        _mouse.move(dx, dy, 0, 0);
    }

    void _onSetFeature(uint8_t report_id, const uint8_t *buffer, uint16_t len) {
      receiveReport(buffer, len);
    }

    void _onOutput(uint8_t *report, size_t len) {
        receiveReport(report, len);
    }
};

#pragma once
#include <USB.h>
#include <USBHID.h>

#define TUD_HID_REPORT_DESC_MOUSE16(...) \
  HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP      )                   ,\
  HID_USAGE      ( HID_USAGE_DESKTOP_MOUSE     )                   ,\
  HID_COLLECTION ( HID_COLLECTION_APPLICATION  )                   ,\
    /* Report ID if any */\
    __VA_ARGS__ \
    HID_USAGE      ( HID_USAGE_DESKTOP_POINTER )                   ,\
    HID_COLLECTION ( HID_COLLECTION_PHYSICAL   )                   ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_BUTTON  )                   ,\
        HID_USAGE_MIN   ( 1                                      ) ,\
        HID_USAGE_MAX   ( 5                                      ) ,\
        HID_LOGICAL_MIN ( 0                                      ) ,\
        HID_LOGICAL_MAX ( 1                                      ) ,\
        /* Left, Right, Middle, Backward, Forward buttons */ \
        HID_REPORT_COUNT( 5                                      ) ,\
        HID_REPORT_SIZE ( 1                                      ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
        /* 3 bit padding */ \
        HID_REPORT_COUNT( 1                                      ) ,\
        HID_REPORT_SIZE ( 3                                      ) ,\
        HID_INPUT       ( HID_CONSTANT                           ) ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_DESKTOP )                   ,\
        /* X, Y position [-32767, 32767] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_X                    ) ,\
        HID_USAGE       ( HID_USAGE_DESKTOP_Y                    ) ,\
        HID_LOGICAL_MIN_N ( 0x8001, 2                            ) ,\
        HID_LOGICAL_MAX_N ( 0x7fff, 2                            ) ,\
        HID_REPORT_COUNT( 2                                      ) ,\
        HID_REPORT_SIZE ( 16                                     ) ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
        /* Verital wheel scroll [-127, 127] */ \
        HID_USAGE       ( HID_USAGE_DESKTOP_WHEEL                )  ,\
        HID_LOGICAL_MIN ( 0x81                                   )  ,\
        HID_LOGICAL_MAX ( 0x7f                                   )  ,\
        HID_REPORT_COUNT( 1                                      )  ,\
        HID_REPORT_SIZE ( 8                                      )  ,\
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE )  ,\
      HID_USAGE_PAGE  ( HID_USAGE_PAGE_CONSUMER ), \
       /* Horizontal wheel scroll [-127, 127] */ \
        HID_USAGE_N     ( HID_USAGE_CONSUMER_AC_PAN, 2           ), \
        HID_LOGICAL_MIN ( 0x81                                   ), \
        HID_LOGICAL_MAX ( 0x7f                                   ), \
        HID_REPORT_COUNT( 1                                      ), \
        HID_REPORT_SIZE ( 8                                      ), \
        HID_INPUT       ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
    HID_COLLECTION_END                                            , \
  HID_COLLECTION_END

static const uint8_t rel16_mouse_report_descriptor[] = {
    TUD_HID_REPORT_DESC_MOUSE16(HID_REPORT_ID(HID_REPORT_ID_MOUSE))
};

enum MousePositioning_t {
  HID_MOUSE_RELATIVE,
  HID_MOUSE_ABSOLUTE
};

typedef struct TU_ATTR_PACKED
{
    uint8_t buttons;
    int16_t  x;
    int16_t  y;
    int8_t  wheel;
    int8_t  pan;
} hid_mouse_report_16_t;

struct HIDMouseType_t {
  MousePositioning_t positioning;
  const uint8_t *report_descriptor;
  size_t descriptor_size;
  size_t report_size;
};

HIDMouseType_t HIDMouseRel16 = { HID_MOUSE_RELATIVE, rel16_mouse_report_descriptor, sizeof(rel16_mouse_report_descriptor), sizeof(hid_mouse_report_16_t) };

class USBHIDRelativeMouse16 : public USBHIDDevice {
private:
  USBHID _hid;
  uint8_t _buttons;

public:
    USBHIDRelativeMouse16() : _hid() {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        _hid.addDevice(this, HIDMouseRel16.descriptor_size);
      }
    }

    void begin() {
      _hid.begin();
    }

    uint16_t _onGetDescriptor(uint8_t *dst) {
      memcpy(dst, HIDMouseRel16.report_descriptor, HIDMouseRel16.descriptor_size);
      return HIDMouseRel16.descriptor_size;
    }

    void move(int16_t x, int16_t y, int8_t wheel = 0, int8_t pan = 0) {
        hid_mouse_report_16_t report = {
            .buttons = _buttons,
            .x       = x,
            .y       = y,
            .wheel   = wheel,
            .pan     = pan
        };
        Serial.printf("Sending x = %d | y = %d\n", x, y);
        _hid.SendReport(HID_REPORT_ID_MOUSE, &report, sizeof(hid_mouse_report_16_t));
    }

    void click(uint8_t b) {
        _buttons = b;
        move(0,0);
        _buttons = 0;
        move(0,0);
    }

    void buttons(uint8_t b) {
        if (b != _buttons) {
            _buttons = b;
            move(0,0);
        }
    }
};

This is the definition of my two devices.

Following is my host code :

void MouseController::sendHIDReport(int16_t dx, int16_t dy) {
    HIDReport report;

    std::cout << "Sending HID report: " << dx << " " << dy << std::endl;

    report.reportId = 0x06; // Use HID_REPORT_ID_VENDOR = 0x06
    report.dx = dx;
    report.dy = dy;
    memset(report.padding, 0, sizeof(report.padding));

    DWORD bytesWritten;
    std::cout << "Size of report: " << sizeof(report) << std::endl;
    //BOOL res = WriteFile(hidDevice, &report, sizeof(report), &bytesWritten, NULL);
    BOOL res = HidD_SetOutputReport(hidDevice, &report, sizeof(report));
    if (!res) {
        int err = GetLastError();
        std::cerr << "Failed to send HID report: " << err << std::endl;
        if (err == 995 || err == 1167) {
            std::cerr << "Device disconnected" << std::endl;
            CloseHandle(hidDevice);
            hidDevice = nullptr;
            Sleep(2000); // Wait for device to reconnect
            ConnectToDevice();
        } else {
            exit(1);
        }
    } else {
        std::cout << "HID report sent successfully: " << dx << " " << dy << std::endl;
    }
}

It seems also that my device reboots when it receives too much data for too long

@ChrGri
Copy link

ChrGri commented Aug 23, 2024

Not sure if this is related, but I do have trouble using CDC and HID simultaneously. After some time, one or the other seems to stall, see #9582 (comment).

BR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Awaiting triage Issue is waiting for triage
Projects
None yet
Development

No branches or pull requests

3 participants