cancel
Showing results for 
Search instead for 
Did you mean: 

USB Composite HID+HID with USB-FS-Device_Driver library

Yuriy Vostrenkov
Associate II

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?

9 REPLIES 9
Pavel A.
Evangelist III

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

Yuriy Vostrenkov
Associate II

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

Number of the interface is in some field of the get descriptor request. There you get it.

-- pa

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:

0693W000007BYxRQAW.jpg0693W000007BYxWQAW.jpgI 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

    

Ben K
Senior III

Hi Yuriy,

  1. It's possible to present one USB-HID interface as multiple HID devices to the OS, by defining multiple Top Level Collections in the HID report descriptor. That means that you can concatenate the two HID report descriptors to one, ensuring that the different HID reports all have unique report IDs (per report type - IN/OUT/Feature). Plus you need to manage the USB endpoint sharing between your applications.
  2. You can use https://github.com/IntergatedCircuits/USBDevice to easily manage multiple interfaces on a single USB device.

Hi and thanks for reply!

  1. As I mentioned above I already have multiple Top Level Collections configuration (actually it has 5 different report IDs) and i'd like to split it to 2 separate HID devices
  2. I have seen this library and it looks like a solution. But it seems to me that I'm very close to make it work with my USB-FS_Device_Lib and I wonder what I missed. Unfortunately I couldn't find HID + HID examples for this lib on web and reading HID specification don't help much
Pavel A.
Evangelist III

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

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()

Yuriy Vostrenkov
Associate II

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;
}