Skip to main content
Jerry Hancock
Associate III
November 1, 2018
Question

STM32F469 HID - add a keyboard to a custom HID

  • November 1, 2018
  • 26 replies
  • 7310 views

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

    This topic has been closed for replies.

    26 replies

    Pavel A.
    November 4, 2018

    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 III
    November 4, 2018

    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

    Pavel A.
    November 4, 2018

    > 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

    Jerry Hancock
    Associate III
    November 4, 2018

    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

    Pavel A.
    November 4, 2018

    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 III
    November 4, 2018

    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 III
    November 5, 2018

    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 III
    November 6, 2018

    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 III
    November 6, 2018

    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

    Jerry Hancock
    Associate III
    November 7, 2018

    OK, new issue. the keyboard which is the first interface, is working fine. The second interface, the encoder, has a problem.

    I get this during enumeration:

    This device cannot start. (Code 10)

    Insufficient resources to allocate needed memory.

    Haven't seen this before. If I look further down into the device information in the Device Manager, I see this:

    {Buffer Too Small}

    The buffer is too small to contain the entry. No information has been written to the buffer.

    The second report descriptor is slightly larger than the first but I don't see that mattering. I'm struggling with where to begin shooting this bug.

    One thing I notice as in my prior comment is that my code returns the entire combined descriptor three times (prompted by the computer). Is this correct or should it return just the applicable report descriptor?

    Thanks

    ps. sorry for flooding this board with my silly problem.

    I am getting closer though, much closer.

    Thanks for all the help.

    Jerry

    Jerry Hancock
    Associate III
    November 7, 2018

    I'm hoping someone finds all this useful in the future.

    I was able to get around the resources issue by replying to the config descriptor request with just the HID report of the respective interface. Both devices enumerate now without obvious errors.

    I then tried sending keyboard characters and it doesn't work. This worked before so there could be a couple of issues. When I was returning the entire HID containing both HID report ID 01 and 02, the keyboard still worked but the encoder wouldn't enumerate and had the error above.

    1) Since I now have to prepend a 0x01 on the front of the keyboard buffer and the length is still 8 bytes, maybe the driver is throwing away the incoming report?

    2) I tried offsetting the keyboard character so instead of it being in position 2 with with the interface number in position 00, I shifted it over to position 3. This didn't help.

    So maybe I have to change the incoming HID report length? Ideas ? I haven't tried the encoder. The encoder software finds the device but it could b just finding the keyboard since the both have the same VID/PID. But if that was the case, then the keyboard wouldn't have worked when the encoder software was running, and it did. Also, he keyboard doesn't work with the encoder software shutdown so that can't be impacting it.

    I'm getting closer all the time. I think I am on the verge of it working. I checked another composite device and it sends separate HID descriptors instead of one longer, combined one. So I am going to keep comparing against it.

    Anyone out there? :)

    Jerry

    Jerry Hancock
    Associate III
    November 7, 2018

    OK, daily update...

    The keyboard is working to some extent. Though I am trying to send a '1' every 5 seconds using the following code, I get 898 ones. It's like the keyboard repeat key is stuck between transmission. I send 00 00 1E 00 00 00 00 00 followed by 00 00 00 00 00 00 00 00. The reportid is 00 in this case.

    I tried clearly the buffer between transmission, etc. No luck.

    while (1)

    {

    for (x=0;x<10;x++) HIDbuffer[x]=0x00;

    HAL_Delay(5);

    HIDbuffer[0]=0x00;

    HIDbuffer[2] = 0x1e; // usb keyboard id for '1'

    HIDbuffer[3] = 0x00;

    buf = HIDbuffer;

    USBD_HID_SendReport(&hUsbDeviceFS, buf, 8);

    HAL_Delay(5);

    for (x=0;x<10;x++) HIDbuffer[x]=0x00;

    HIDbuffer[2] =0x00;

    HIDbuffer[3] =0x00;

    buf = HIDbuffer;

    USBD_HID_SendReport(&hUsbDeviceFS, buf, 8);

    HAL_Delay(5); // wait 5ms

    HAL_Delay(5000); // wait 5 seconds

    }

    I traced the code (0x1e) and release key (0x00) and see my character and character release getting to the PC in the proper time.

    Anyway, I'm making progress.

    Jerry