cancel
Showing results for 
Search instead for 
Did you mean: 

How can I implement multiple USB HID devices on STM32F042 or STM32F103?

Nickelgrass
Senior

Hello,

I already got the STM32Cube Mouse to work an modified it to work as keyboard using some other example. I have read documentation like USB in a nutshell and others alike.

My problem is that I understand how the USB works in theory but I don't see how to actually implement it in practice.

What I would like to achieve is to have a STM32 work as a USB HID and be able to switch on or off different devices. It should work as a keyboard, gamepad, mouse and CDC simultaneously. But I would like to be able to activate or deactivate each device separately.

Is there any implementation guide for this? I dont understand where I can configure the descriptors for each device.

Am thankful for any guidance.

Best regards.

5 REPLIES 5
Pavel A.
Evangelist III

There are basically two variants: a composite (multiple interfaces) device and multiple HID collections on one function (or device).

The later is more economical as all functions share a single endpoint. The host must understand multiple collections and report IDs.

All decent operating systems do this, but some devices with crippled USB support don't (like the ST own "middleware").

Composite design has wider compatibility, but each function requires separate endpoint.

In any case, you'll need to compose descriptors in RAM and serve them when asked to.

-- pa

Nickelgrass
Senior

Ok, thanks for the reply! The collection was the hint I had missed.

So far I have a HID device collection that can be configured as keyboard (with/without mediakeys and with/without leds) and/or mouse and/or 2 gamepads. The leds and the gamepads are still work in progress.

How would I now add a second interface with CDC serial port? I thought of generating a second project in STM32CudeIDE with the CDC example and then transfering parts of the code to the other project. But what exactly do I need for a second interface? I would like the serial port to be switchable in runtime for configuration.

Also I would like to do the thing with STM32F103 where an SD card can be used by the microcontroller or be served to the PC via USB as mass storage device. Are there any tutorials on that?

Best regards

Pavel A.
Evangelist III

Hi @Nickelgrass​,

The 2nd interface is just a usual "composite" design. By itself it is not a big deal, examples are available.

But it looks like you want that sometimes the device is a composite, and other times - single function.

This can be a problem with certain hosts such as Windows. Because, composite devices and single function devices have a different structure of driver stack.

IIRC it is possible to add a Microsoft specific descriptor which instructs Windows to always handle the device as composite, even if it has only one function.

So when the CDC function disappears, it won't break things.

For more details on this, google for "MSOS 2.0 Descriptor" or ask on the Microsoft (MSDN) forums.

Another option is to treat two configurations as different devices from the host POV: have different device IDs for composite and one-function.

Regards,

-- pa

Nickelgrass
Senior

Hello,

thanks again for the answear.

I noticed some strange thing with my descriptor. All works nicly, but the keyboard only works if I have the LED function activated. Here is my decriptor:

__ALIGN_BEGIN static uint8_t HID_ReportDesc[]  __ALIGN_END =
{
	#if USB_KEYBOARD == 1
		Usage_Page(Generic_Desktop)
	    Usage(Keyboard)
	    Collection(Application)
			Report_ID(ID_KEYBOARD)
			Usage_Page(Keyboard_Keypad)
			Usage_Minimum(KEY_LCTRL)
			Usage_Maximum(KEY_RGUI)
			Logical_Minimum(OFF)
			Logical_Maximum(ON)
			Report_Count(1)
			Report_Size(8)
			Input(iData | Variable | Absolute | No_Wrap | Linear | Preferred_State | No_Null_position | Bit_field)
			Report_Count(1)
			Report_Size(8)
			Input(Constant | Variable | Absolute | No_Wrap | Linear | Preferred_State | No_Null_position | Bit_field)
			Report_Count(6)
			Report_Size(8)
			Logical_Minimum(UNDEFINED)
			Logical_Maximum(254)
			Usage_Page(Keyboard_Keypad)
			Usage_Minimum(UNDEFINED)
			Usage_Maximum(254)
			Input(iData | Array | Absolute | No_Wrap | Linear | Preferred_State | No_Null_position | Bit_field)
		#if USB_KEYBOARD_LEDS == 1
			Report_Count(5)
			Report_Size(1)
			Usage_Page(LEDs)
			Usage_Minimum(1)
			Usage_Maximum(5)
			Output(iData | Variable | Absolute | No_Wrap | Linear | Preferred_State | No_Null_position | Bit_field)
			Report_Count(1)
			Report_Size(3)
			Output(Constant | Variable | Absolute | No_Wrap | Linear | Preferred_State | No_Null_position | Bit_field)
		#endif
		End_Collection
	#endif

If I set USB_KEYBOARD_LEDS to 0 no buttonpress is sent.

Also the stringdescriptors are only showd in USB Device Tree View if the LEDs ar DEactivated. If I activate the LEDs they show an error " *!*ERROR String descriptor not found".

How could this be??? I defined the size of the reportdescriptor with the sizeof function. Could it be that it doesnt work? Here are the other USB related things:

USBD_ClassTypeDef  USBD_HID =
{
  USBD_HID_Init,
  USBD_HID_DeInit,
  USBD_HID_Setup,
  NULL, /*EP0_TxSent*/
  NULL, /*EP0_RxReady*/
  USBD_HID_DataIn, /*DataIn*/
#if USB_KEYBOARD_LEDS == 1
  USBD_HID_DataOut, /*DataOut*/
#else
  NULL,
#endif
  NULL, /*SOF */
  NULL,
  NULL,
  USBD_HID_GetCfgDesc,
  USBD_HID_GetCfgDesc,
  USBD_HID_GetCfgDesc,
  USBD_HID_GetDeviceQualifierDesc,
};
 
/* USB HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ]  __ALIGN_END =
{
  0x09, /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  USB_HID_CONFIG_DESC_SIZ,
  /* wTotalLength: Bytes returned */
  0x00,
  0x01,         /*bNumInterfaces: 1 interface*/
  0x01,         /*bConfigurationValue: Configuration value*/
  0x00,         /*iConfiguration: Index of string descriptor describing
  the configuration*/
  0xE0,         /*bmAttributes: bus powered and Support Remote Wake-up */
  0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/
 
  /************** Descriptor of Joystick Mouse interface ****************/
  /* 09 */
  0x09,         /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  0x00,         /*bInterfaceNumber: Number of Interface*/
  0x00,         /*bAlternateSetting: Alternate setting*/
#if USB_KEYBOARD_LEDS == 1
  0x02,         /*bNumEndpoints*/
#else
  0x01,
#endif
  0x03,         /*bInterfaceClass: HID*/
  0x01,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x02,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  0,            /*iInterface: Index of string descriptor*/
  /******************** Descriptor of Joystick Mouse HID ********************/
  /* 18 */
  0x09,         /*bLength: HID Descriptor size*/
  HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
  0x11,         /*bcdHID: HID Class Spec release number*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  sizeof(HID_ReportDesc),/*wItemLength: Total length of Report descriptor*/
  0x00,
  /******************** Descriptor of Mouse endpoint ********************/
  /* 27 */
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
 
  HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */
  0x00,
  HID_FS_BINTERVAL,          /*bInterval: Polling Interval (10 ms)*/
 
 
  /* 34 */
#if USB_KEYBOARD_LEDS == 1
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
 
  HID_EPOUT_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max */
  0x00,
  HID_FS_BINTERVAL          /*bInterval: Polling Interval (10 ms)*/
#endif
} ;
 
__ALIGN_BEGIN static uint8_t USBD_HID_Desc[USB_HID_DESC_SIZ]  __ALIGN_END  =
{
  /* 18 */
  0x09,         /*bLength: HID Descriptor size*/
  HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
  0x11,         /*bcdHID: HID Class Spec release number*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  sizeof(HID_ReportDesc),/*wItemLength: Total length of Report descriptor*/
  0x00,
};
 
__ALIGN_BEGIN static uint8_t USBD_HID_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC]  __ALIGN_END =
{
  USB_LEN_DEV_QUALIFIER_DESC,
  USB_DESC_TYPE_DEVICE_QUALIFIER,
  0x00,
  0x02,
  0x00,
  0x00,
  0x00,
  0x40,
  0x01,
  0x00,
};

The USB_HID_CONFIG_DESC_SIZ is set as needed to either 34 or 41. How can the reportdescriptor influence the string descriptor? Is it a stack or heap size issue? I already raised them. Or could some descriptor size be wrongly set or is there something with how and where the descriptors are saved in the flash?

Pavel A.
Evangelist III

> If I set USB_KEYBOARD_LEDS to 0 no buttonpress is sent.

Perhaps the host's parser wants integral number of bytes. try to replace the LEDs by dummy bits (const is the name?)

-- pa