2021-01-13 08:33 AM
Hi,
I'm developing USB composite HID+HID (joystick and custom) device on STM32F103C8 controller. I'm not using HAL drivers for the code-size reasons so I go with USB-FS-Device library.
I can get the host recognizing composite device itself and 1st HID device also but I can not make 2nd HID device work.
The main problem I can not understand where and when I should put the report descriptor for the 2nd HID device to be read by host.
I couldn't find any example code for composite multiple HID devices with no HAL library used. Anyone can help or share his code for solving the issue?
2021-01-15 01:32 PM
Is your host a "normal" computer (Windows, Linux, OSX)? If so, the HID+HID device does not need to be composite, it can be done with multiple top level collections in one function. Use report IDs to distinguish reports from different sub-functions.
Google for examples, or copy a multiple collections descriptor from devices such as keyboard with media and sleep keys (each of them is a separate HID collection of the keyboard USB function).
-- pa
2021-01-15 09:39 PM
Thank you for reply!
Yes, my host is supposed to be a Windows/Linix/OSX computer. Multiple collections with different report IDs is exactly what I have now. For some reasons (mostly related to host application) I need to split it out to have 2 separate endpoints.
I have no issues with config descriptor so my device is seen as "composite" with all its HID and endpoints settings (checking by USBlyzer).
I also wrote two HID report descriptors and I see at least one of them is requested by host while "class data setup" operation. So HID device for "first" endpoint is also seen normally.
What I can not get is how should I send the report descriptor for 2nd endpoint or how should I make host request it
2021-01-16 04:20 AM
Number of the interface is in some field of the get descriptor request. There you get it.
-- pa
2021-01-17 08:50 AM
Analyzing USB setup sequence by USBlyzer i found out that wIndex field is responsible for selecting current interface. So my host generates two different requests with wIndex = 0 and wIndex = 1:
I also have modified the code to manage these indexes during class data setup:
RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{
uint8_t *(*CopyRoutine)(uint16_t);
// if (pInformation->USBwIndex != 0) // I had to exclude this code to allow
// return USB_UNSUPPORT; // setup of Interface #1
CopyRoutine = NULL;
if ((RequestNo == GET_DESCRIPTOR)
&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
)
{
if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
{
if (pInformation->USBwIndex == 0) // Interface #0
{
CopyRoutine = JoystickHID_GetReportDescriptor;
}
else if (pInformation->USBwIndex == 1) // Interface #1
{
CopyRoutine = CustomHID_GetReportDescriptor;
}
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
if (pInformation->USBwIndex == 0) // Interface #0
{
CopyRoutine = JoystickHID_GetHIDDescriptor;
}
else if (pInformation->USBwIndex == 1) // Interface #1
{
CopyRoutine = CustomHID_GetHIDDescriptor;
}
}
} /* End of GET_DESCRIPTOR */
/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
{
switch( RequestNo )
{
case GET_PROTOCOL:
CopyRoutine = CustomHID_GetProtocolValue;
break;
case SET_REPORT:
CopyRoutine = CustomHID_SetReport_Feature;
Request = SET_REPORT;
break;
default:
break;
}
}
if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return USB_SUCCESS;
}
The problem is I never get into conditions
(RequestNo == GET_DESCRIPTOR) && (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT)) && (pInformation->USBwIndex == 1)
so it gonna be I've missed something. Are there any settings that may affect on receiving requests?
I attach USBlyzer log file for my device initialization
2021-01-17 11:38 AM
Hi Yuriy,
2021-01-17 11:57 AM
Hi and thanks for reply!
2021-01-17 12:33 PM
I use the new HAL library. Don't know the SPL USB library.
Modified the HID class example as follows:
static uint8_t USBD_CUSTOM_HID_Setup (USBD_HandleTypeDef *pdev,
USBD_SetupReqTypedef *req)
{
uint8_t intf_no = 0;
if ((req->bmRequest & 0x1F /*USB_REQ_RECIPIENT_MASK*/) == USB_REQ_RECIPIENT_INTERFACE) {
intf_no = req->wIndex & 0xFF;
}
// else if request to device: leave intf_no = 0
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;
switch (req->bmRequest & USB_REQ_TYPE_MASK)
{
case USB_REQ_TYPE_CLASS :
switch (req->bRequest)
{
case CUSTOM_HID_REQ_SET_PROTOCOL:
hhid->intfs[intf_no].Protocol = (uint8_t)(req->wValue);
break;
case CUSTOM_HID_REQ_GET_PROTOCOL:
USBD_CtlSendData (pdev, (uint8_t *)&hhid->intfs[intf_no].Protocol, 1);
break;
.......
case USB_REQ_TYPE_STANDARD:
switch (req->bRequest)
{
case USB_REQ_GET_DESCRIPTOR:
if( (req->wValue) >> 8 == CUSTOM_HID_REPORT_DESC)
{
pbuf = USBD_CUSTOM_HID_GetReportDesc(&len, req->wIndex);
len = MIN(len, req->wLength);
}
else if( (req->wValue >> 8) == CUSTOM_HID_DESCRIPTOR_TYPE)
{
pbuf = USBD_CUSTOM_HID_GetHidDesc(&len, req->wIndex);
len = MIN(len, req->wLength);
}
USBD_CtlSendData (pdev,
pbuf,
len);
break;
..........
}
Of course Ben K. has a better nicer composite library.
-- pa
2021-01-17 01:58 PM
One of the listed example projects has 2 HID interfaces: https://github.com/benedekkupper/DebugDongleFW But it's not really that complicated to make 2 HID interfaces with it, once you got one working, it just takes two calls of USBD_HID_MountInterface()
2021-01-19 10:39 AM
Well, I made it work with USB-FS-Device_Lib. My problem was I shouldn't specify interface number 1 for 2nd HID interface in config descriptor. If it help someone in future I leave my config descriptor and Data_Setup code that works for HID+HID composite with different endpoints. Thanks everyone for your help!
uint8_t Composite_ConfigDescriptor[Composite_SIZ_CONFIG_DESC] =
{
0x09, /* bLength: Configuration Descriptor size */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
Composite_SIZ_CONFIG_DESC,
/* wTotalLength: Bytes returned */
0x00,
0x02, /* bNumInterfaces: 2 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing
the configuration*/
0x80, /* bmAttributes: Bus powered */
0x32, /* MaxPower 100 mA: this current is used for detecting Vbus */
/************** Descriptor of Joystick HID interface ****************/
/* 09 */
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x00, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0, /* iInterface: Index of string descriptor */
/******************** Descriptor of Joystick HID HID ********************/
/* 18 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x10, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
LOBYTE(JoystickHID_SIZ_REPORT_DESC),/* wItemLength: Total length of Report descriptor */
HIBYTE(JoystickHID_SIZ_REPORT_DESC),
/******************** Descriptor of Joystick HID endpoints ******************/
/* 27 */
0x07, /* bLength: Joystick HID Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x81, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x10, /* bInterval: Polling Interval (16 ms) */
/* 34 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
/* Endpoint descriptor type */
0x01, /* bEndpointAddress: */
/* Endpoint Address (OUT) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x10, /* bInterval: Polling Interval (16 ms) */
/* 41 */
/************** Descriptor of Custom HID interface ****************/
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
0x01, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x00, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0, /* iInterface: Index of string descriptor */
/******************** Descriptor of Custom HID HID ********************/
/* 50 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x10, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
LOBYTE(CustomHID_SIZ_REPORT_DESC),/* wItemLength: Total length of Report descriptor */
HIBYTE(CustomHID_SIZ_REPORT_DESC),
/******************** Descriptor of Custom HID endpoints ******************/
/* 59 */
0x07, /* bLength: Custom HID Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x82, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x10, /* bInterval: Polling Interval (16 ms) */
/* 66 */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
/* Endpoint descriptor type */
0x02, /* bEndpointAddress: */
/* Endpoint Address (OUT) */
0x03, /* bmAttributes: Interrupt endpoint */
0x40, /* wMaxPacketSize: 64 Bytes max */
0x00,
0x10, /* bInterval: Polling Interval (16 ms) */
/* 73 */
}
The issue was I had number "1" in line 69.
RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{
uint8_t *(*CopyRoutine)(uint16_t);
// if (pInformation->USBwIndex != 0) // I had to exclude this code to allow
// return USB_UNSUPPORT; // programm setup Interface #1
CopyRoutine = NULL;
if ((RequestNo == GET_DESCRIPTOR)
&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
)
{
if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
{
if (pInformation->USBwIndex0 == 0) // Interface #0
{
CopyRoutine = JoystickHID_GetReportDescriptor;
}
else if (pInformation->USBwIndex0 == 1) // Interface #1
{
CopyRoutine = CustomHID_GetReportDescriptor;
}
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
if (pInformation->USBwIndex0 == 0) // Interface #0
{
CopyRoutine = JoystickHID_GetHIDDescriptor;
}
else if (pInformation->USBwIndex0 == 1) // Interface #1
{
CopyRoutine = CustomHID_GetHIDDescriptor;
}
}
} /* End of GET_DESCRIPTOR */
/*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
{
switch( RequestNo )
{
case GET_PROTOCOL:
CopyRoutine = CustomHID_GetProtocolValue;
break;
case SET_REPORT:
CopyRoutine = CustomHID_SetReport_Feature;
Request = SET_REPORT;
break;
default:
break;
}
}
if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return USB_SUCCESS;
}