cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F469 HID - add a keyboard to a custom HID

Jerry Hancock
Associate II

I have my custom HID device running and working fine. I would like it to also emulate a standard PC keyboard at the same time. So when my board enumerates, I would like the attached PC to find my custom device as well as a keyboard.

I think I can just add another device config descriptor, interface and end points to the existing custom HID descriptors and then add another report descriptor for the keyboard. Please confirm.

I then tried of setting up two complete sets of descriptors and calling USBD_init, USBD_RegisterClass and USBD_Start for each set of descriptors. I tried this and was never able to get the keyboard to enumerate, could be any of many reasons. Also, it looked like it was killing the first device, my custom HID.

Not being sure how to do this, I would appreciate advice. If both ways would work, then I guess I would use the first as it would require fewer lines of code.

I hesitate to try to the first way I described above as adding all the device descriptors, etc, are a lot of work to get them all aligned.

Thank you.

Jerry

28 REPLIES 28
Pavel A.
Evangelist III

Apologies for delay. Jerry, if you take variant A, there will be only one interface, no need to change USBD_HID_Setup. All the difference is in the HID report descriptor.

Below is example of HID report descriptor with 3 collections: Keyboard, application keys and mouse.

You can keep the keyboard, maybe app. keys too, and replace the last one with your encoder.

__ALIGN_BEGIN uint8_t CUSTOM_HID_ReportDesc_FS[] __ALIGN_END =
{
  // KEYBOARD
  0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
  0x09, 0x06,        // Usage (Keyboard)
  0xA1, 0x01,        // Collection (Application)
  0x85, 0x01,        //   Report ID (1)
	/* Modifier keys, 1 byte */
  0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
  0x75, 0x01,        //   Report Size (1)
  0x95, 0x08,        //   Report Count (8)
  0x19, 0xE0,        //   Usage Minimum (0xE0)
  0x29, 0xE7,        //   Usage Maximum (0xE7)
  0x15, 0x00,        //   Logical Minimum (0)
  0x25, 0x01,        //   Logical Maximum (1)
  0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
	/* 6 bytes key codes */
  0x95, 0x06,        //   Report Count (6)
  0x75, 0x08,        //   Report Size (8)
  0x15, 0x00,        //   Logical Minimum (0)
  0x25, 0x7f,        //   Logical Maximum (127)
  0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
  0x19, 0x00,        //   Usage Minimum (0x00)
  0x29, 0x7f,        //   Usage Maximum (0x7f)
  0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0xC0,              // End Collection
 
  // App keys
  0x05, 0x0C,        // Usage Page (Consumer)
  0x09, 0x01,        // Usage (Consumer Control)
  0xA1, 0x01,        // Collection (Application)
  0x85, 0x02,        //   Report ID (2)
  0x05, 0x0C,        //   Usage Page (Consumer)
  0x15, 0x00,        //   Logical Minimum (0)
  0x25, 0x01,        //   Logical Maximum (1)
  0x75, 0x01,        //   Report Size (1)
  0x95, 0x08,        //   Report Count (8)
  0x09, 0xB5,        //   Usage (Scan Next Track)
  0x09, 0xB6,        //   Usage (Scan Previous Track)
  0x09, 0xB7,        //   Usage (Stop)
  0x09, 0xB8,        //   Usage (Eject)
  0x09, 0xCD,        //   Usage (Play/Pause)
  0x09, 0xE2,        //   Usage (Mute)
  0x09, 0xE9,        //   Usage (Volume Increment)
  0x09, 0xEA,        //   Usage (Volume Decrement)
  0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0xC0,              // End Collection
 
  0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  0x09, 0x02,                    // USAGE (Mouse)
  0xa1, 0x01,                    // COLLECTION (Application)
  0x85, 0x03,                    //   Report ID (3)
  0x09, 0x01,                    //   USAGE (Pointer)
  0xa1, 0x00,                    //   COLLECTION (Physical)
  0x05, 0x09,                    //     USAGE_PAGE (Button)
  0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
  0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
  0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
  0x95, 0x03,                    //     REPORT_COUNT (3)
  0x75, 0x01,                    //     REPORT_SIZE (1)
  0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  0x95, 0x01,                    //     REPORT_COUNT (1)
  0x75, 0x05,                    //     REPORT_SIZE (5)
  0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
  0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
  0x09, 0x30,                    //     USAGE (X)
  0x09, 0x31,                    //     USAGE (Y)
  0x09, 0x38,                    //     Usage (Wheel)
  0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
  0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
  0x75, 0x08,                    //     REPORT_SIZE (8)
  0x95, 0x03,                    //     REPORT_COUNT (3)
  0x81, 0x06,                    //     INPUT (Data,Var,Rel)
  0xc0,                          //   END_COLLECTION
  0xc0,                          // END_COLLECTION
}; 

-- pa

Pavel A.
Evangelist III

And the config descriptor:

__ALIGN_BEGIN uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
  0x09, /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  USB_CUSTOM_HID_CONFIG_DESC_SIZ,  /* wTotalLength */
  0x00,
  0x01,         /*bNumInterfaces: 1 interface*/
  0x01,         /*bConfigurationValue: Configuration value*/
  0x00,         /*iConfiguration: no string*/
  0xC0,         /*bmAttributes: bus powered */
  0x32,         /*MaxPower 100 mA */
 
  /************** Descriptor of interface #0 ****************/
  /* 09 */
  0x09,         /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE, /*bDescriptorType: Interface*/
  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 interface string descriptor*/
  /******************** Descriptor of CUSTOM_HID *************************/
  /* 18 */
  0x09,         /*bLength: HID Descriptor size*/
  CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
  0x11,         /*bHID: HID Class Spec release number 1.11*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  sizeof(CUSTOM_HID_ReportDesc_FS), /*wItemLength*/
  0x00,
  /******************** Descriptor of Custom HID endpoints ********************/
  /* 27 */
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
 
  CUSTOM_HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize */
  0x00,
  0x20,          /*bInterval: Polling Interval (32 ms)*/
  /* 34 */
 
  0x07,	         /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,	/* bDescriptorType: */
  CUSTOM_HID_EPOUT_ADDR,  /*bEndpointAddress: Endpoint Address (OUT)*/
  0x03,	/* bmAttributes: Interrupt endpoint */
  CUSTOM_HID_EPOUT_SIZE,	/* wMaxPacketSize  */
  0x00,
  0x20,	/* bInterval: Polling Interval (32 ms) */
  /* 41 */
};

-- pa

Jerry Hancock
Associate II

Pavel, Thank you. But here's what I don't understand. The custom application looks for the device based on the VID/PID. When I have my collection, the application is finding two devices. The first I assume is my encoder as I had it first in the collection. The second is the keyboard which it doesn't understand. The VID is 0C26 and the PID is 001E. Most keyboards and HID collections I've seen are like 045E, 046d, etc.

The other issue in my coding was that my keyboard ended up being interface 0x01 but the encoder requires a 0x01 in all incoming packets in position[0]. So I keep thinking I need to have a separate device descriptor for the encoder (0c26/001e) and the keyboard (046D/C52b) for example. Otherwise the encoder application and DLL must be grabbing both the encoder and keyboard.

I'll look through your example but correct me if I'm wrong, my application won't find the encoder as in your device descriptor, your don't have an address coded and I would think I would need the descriptor to look like this:

0x12 bLength

0x01 bDescriptorType

0x0200 bcdUSB

0x00 bDeviceClass    

0x00 bDeviceSubClass  

0x00 bDeviceProtocol  

0x40 bMaxPacketSize0  (64 bytes)

0x0C26 idVendor

0x001E idProduct

0x0001 bcdDevice

0x01 iManufacturer  "Hanler Inc."

0x02 iProduct  "HANLER REMOTE ENCODER"

0x03 iSerialNumber  "HE-XX 01010101" // Test device serial number

0x01 bNumConfigurations

Note that this descriptor is running stand alone (not a collection) in this case.

Thanks again for the reply. I am so close, yet really stuck. I didn't write the application code or HIDCTRL.DLL.

Jerry

Jerry Hancock
Associate II

Pavel, can you also send me your devicedesciptor, string table and hid descriptor? Also, in USBD_HID.c they have the HID separated out as in:

/* USB HID device Configuration Descriptor */

__ALIGN_BEGIN static uint8_t USBD_HID_Desc[USB_HID_DESC_SIZ] __ALIGN_END =

{

 /* 18 */

 0x09,     /*bLength: HID Descriptor size*/

 0x21, /*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*/

 0x2d, /*was 2d wItemLength: Total length of Report descriptor*/

 0x00,

};

It is read and returned separately.

Thanks again but I think I am hitting a wall because of the custom application and DLL. I was thinking that even if I get the keyboard working, it will only work with the transceiver unless the PC application has focus. This isn't a bad thing.

Icom makes a pricey rotary encoder that attaches to both their radios and PC remote control software. I've reverse engineered it and have the encoder functions working great. I want a small 8 button keypad to integrate into the device on the touchscreen. I am, or was, hoping to use a USB keyboard. The alternative is to use FET switches and a second cable that runs to the back of the radio. I don't see this as clean when the USB encoder is already attached. If I can just get both the encoder working and the keypad I can wrap this up and decide what to do with it commercially, if at all, or just give it away to colleagues.

Thanks for your help along with Ben.

Jerry

> The custom application looks for the device based on the VID/PID.

Assuming it is Windows application and you don't have custom drivers - vendor and product ID do not matter, Windows recognizes HID devices entirely based on USB class.

If class & subclass are 0s in device descriptor, they are specified in the interface descriptor.

I don't know what is HIDCTRL.DLL. It is not a in-box DLL of Windows. If it does not understand HID-enumerated devices (collection-based) or report IDs, and wants entire device or function - too bad. In that case maybe a composite device design (plan B) could help.

-- pa

Here is the device descriptor:

__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  0x00,                       /*bcdUSB */
  0x02,
  0x00,                       /*bDeviceClass*/
  0x00,                       /*bDeviceSubClass*/
  0x00,                       /*bDeviceProtocol*/
  USB_MAX_EP0_SIZE,         /*bMaxPacketSize*/
  LOBYTE(USBD_VID),          /*idVendor*/
  HIBYTE(USBD_VID),          /*idVendor*/
  LOBYTE(USBD_PID),         /*idProduct*/
  HIBYTE(USBD_PID),          /*idProduct*/
  0x00,                       /*bcdDevice rel. 2.00*/
  0x02,
  USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
};

Jerry Hancock
Associate II

Thx. I am going to take another crack at using your descriptors. Today I built a simple keyboard project and kept changing things towards making it the encoder and I've gotten pretty far and the driver isn't grabbing the keyboard.

Jerry Hancock
Associate II

Pavel, Ben, Here's where I am:

I looked at Pavel's code and then tried this:

Descriptor

Config Desc with 2 interfaces

Interface for Keyboard

Hid for Keyboard

End Point for Keyboard

Interface for encoder

HID for encoder

Endpoint for encoder

Endpoint for encoder

My code is below. It matches a set of descriptors almost exactly coded for a PIC that another person did. I need the second HID to as the class descriptors between the keyboard and encoder are different. I also need to set string IDs differently for the keyboard and encoder. The PC never gets to the point where it reads the report descriptors. It just sends requests for the Device Descriptor and Config descriptors three times and quits with an error. I am going to paste my descriptors below but at this point I am at a loss. I'll keep messing with it, there must be something wrong with the config descriptor itself but given it never reads the reports, what could it be? I've been over the settings and must have tried a hundred times with traces and dumps.

If anyone can glance at it, I would appreciate it.

Here's my Device Descriptor:

 0x12,            /*bLength */

 USB_DESC_TYPE_DEVICE,  

 0x02,

 0x00,

 0x00,           

 0x00,          

 0x00,           

 USB_MAX_EP0_SIZE,      

 LOBYTE(USBD_VID),  

 HIBYTE(USBD_VID),   

 LOBYTE(USBD_PID_FS),

 HIBYTE(USBD_PID_FS), 

 0x02,            

 0x00,

 USBD_IDX_MFC_STR,   

 USBD_IDX_PRODUCT_STR,   

 USBD_IDX_SERIAL_STR,   

0x01

And config desciptor:

__ALIGN_BEGIN static uint8_t USBD_HID_CfgDesc[66] __ALIGN_END =

{

 0x09,

 USB_DESC_TYPE_CONFIGURATION,

0x42, /* wTotalLength: Bytes returned 66 bytes */

 0x00,

 0x02,  

 0x01,  

 0x00,  

 0xE0,     

 0x32,     

  

 /************** Descriptor of keyboard interface ****************/

 /* 09 */

 0x09,    

 USB_DESC_TYPE_INTERFACE,

 0x00,   

 0x00,    

 0x01,    

 0x03,   

 0x01,    

 0x01,   

 0x02,  

 /******************** Descriptor of keyboard HID ********************/

 /* 18 */

 0x09,    

 HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/

 0x11,  

 0x01,

 0x00,    

 0x01,     

 0x22,   

 0x3f,

 0x00,

 /* 27 */

 0x07,    

 USB_DESC_TYPE_ENDPOINT,

 HID_EPIN_ADDR,  

 0x03,    

 HID_EPIN_SIZE,

 0x00,

 HID_FS_BINTERVAL,    

 /* 34 */

 /************** Descriptor of encoder interface ****************/

  0x09,     

  0x04,

  0x01,     

  0x00,    

  0x02,    

  0x03,    

  0x01,   

  0x00,    

  0x02,   

  /******************** Descriptor of encoder HID ********************/

  /* 09 */

  0x09,    

  0x21,

  0x11,    

  0x01,

  0x00,    

  0x01,    

  0x22,    

  0x2e,

  0x00,

  /******************** Descriptor of encoder In endpoint ********************/

  /* 18 */

  0x07,    

  0x05,

  0x81,   

  0x03,    

  0x40,

  0x00,

  0x01,    

  /* 25 */

  0x07,    

  0x05,

  0x01,   

  0x03,    

  0x40,

  0x00,

  0x01,    

  /* 32 */

// total is 34 + 32 == 66

} ;

Thank you! I really appreciate the help.

Jerry

Jerry Hancock
Associate II

By the way, the device descriptor has the 0x02 in byte 2 one byte higher due to a screwup when I copied it in.​

Jerry Hancock
Associate II

don't know why my last didn't come thru...

When I plugged it into another computer the device enumerated and the keyboard is working. This is great. The encoder isn't working. The encoder is the second interface. When I traced the enumeration, it looks like the report descriptors are being requested three times. In all cases, the report returned is the combined report. Should the stm board be returning the report for each interface or both at once?

I think this is clearly progress as the config descriptor now looks fine. Just need to know what the board should be returning in a composite - the entire combined or just the individual reports.

Thanks for all the help, I feel like progress has been made!

Jerry