STMicroelectronics / stm32_mw_usb_host

Provides the USB Host library part of the STM32Cube MCU Component "middleware" for all STM32xx series.
Other
32 stars 15 forks source link

Add support for multiple HID interfaces #14

Closed rtek1000 closed 7 months ago

rtek1000 commented 1 year ago

Hi, very good to know that this library has had recent modifications, less than a year.

I found out about this repository only today, through this post on the ST forum.

In order to get the built-in touchpad keyboard working, I needed access to more than one HID interface.

With that I was able to access the multimedia part of another keyboard that doesn't have a touchpad, but has two HID interfaces.

Due to the support of the HID 3-0-0 interface I was also able to get data from a joypad.

I needed to add some variables to be able to handle new HID interfaces, but the core is still compatible with other classes like MSC, CDC etc.

It's not a very beautiful code, but it worked, I believe it can serve as a basis for improvements to the official code, take a look:

static USBH_StatusTypeDef USBH_HID_InterfaceInit(USBH_HandleTypeDef *phost) {
    USBH_StatusTypeDef status;
    HID_HandleTypeDef *HID_Handle;
    uint8_t max_ep;
    uint8_t ep_index = 0U;
    uint8_t interface_check;
    uint8_t interface_index;

    USBH_UsrLog("USBH_HID_InterfaceInit");

    // dump everything
    uint8_t num_interfaces = phost->device.CfgDesc.bNumInterfaces;

    for (interface_index = 0; interface_index < num_interfaces;
            interface_index++) {

        interface_check = USBH_CheckInterface(phost, interface_index,
                phost->pActiveClass->ClassCode, HID_BOOT_CODE, 0xFFU);

        if ((interface_check == 0xFFU)
                || (interface_check >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */
                {

//          if (interface_index == 0) {
            USBH_UsrLog("Interface %d, cannot find the HID_BOOT_CODE, skip",
                    interface_index);

            USBH_UsrLog("Interface %d, try to find the HID_CUSTOM_BOOT_CODE",
                    interface_index);

            interface_check = USBH_CheckInterface(phost, interface_index,
                    phost->pActiveClass->ClassCode, HID_CUSTOM_BOOT_CODE,
                    0xFFU);
//          }
        }

        // USBH_UsrLog("interface_check: %d", interface_check);

        if ((interface_check == 0xFFU)
                || (interface_check >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */
                {
            phost->device.skip_interface = 1U;
            USBH_UsrLog("Interface %d, cannot find the HID_BOOT_CODE, skip",
                    interface_index);
            USBH_UsrLog("(This interface can be for Multimedia functions)\n");
            //return USBH_FAIL;

            HID_Handle =
                    (HID_HandleTypeDef*) phost->pActiveClass->pData_array[interface_index];
            //return USBH_OK;
        } else {
            phost->device.skip_interface = 0U;
            status = USBH_SelectInterface(phost, interface_index);

            if (status != USBH_OK) {
                return USBH_FAIL;
            }

            USBH_UsrLog("Class    : %xh",
                    phost->device.CfgDesc.Itf_Desc[interface_index].bInterfaceClass);
            USBH_UsrLog("SubClass : %xh",
                    phost->device.CfgDesc.Itf_Desc[interface_index].bInterfaceSubClass);
            USBH_UsrLog("Protocol : %xh",
                    phost->device.CfgDesc.Itf_Desc[interface_index].bInterfaceProtocol);

            phost->pActiveClass->pData_array[interface_index] =
                    (HID_HandleTypeDef*) USBH_malloc(sizeof(HID_HandleTypeDef));
            HID_Handle =
                    (HID_HandleTypeDef*) phost->pActiveClass->pData_array[interface_index];

            if (HID_Handle == NULL) {
                USBH_DbgLog("Cannot allocate memory for HID Handle");
                return USBH_FAIL;
            }

            /* Initialize hid handler */
            (void) USBH_memset(HID_Handle, 0U, sizeof(HID_HandleTypeDef));

            HID_Handle->state = HID_ERROR;

            /*Decode Bootclass Protocol: Mouse or Keyboard*/
            uint8_t Iprot =
                    phost->device.CfgDesc.Itf_Desc[interface_index].bInterfaceProtocol;

            if ((Iprot == HID_KEYBRD_BOOT_CODE)
                    || (Iprot == HID_MOUSE_BOOT_CODE)
                    || (Iprot == HID_CUSTOM_BOOT_CODE)) {

                if (Iprot == HID_KEYBRD_BOOT_CODE) {
                    USBH_UsrLog("KeyBoard device found!");
                    HID_Handle->Init = USBH_HID_KeybdInit;
                    HID_Handle->state = HID_INIT;
                    HID_Handle->ctl_state = HID_REQ_INIT;

                    if (USBH_KEYBOARD_LED_NUM_LOCK_START == 1U) {
                        phost->device.kbd_LED_status = 0xFFU;
                        phost->device.kbd_LED_supported = 1U; // todo
                    }

                } else if (Iprot == HID_MOUSE_BOOT_CODE) {
                    USBH_UsrLog("Mouse device found!");
                    HID_Handle->Init = USBH_HID_MouseInit;
                    HID_Handle->state = HID_INIT;
                    HID_Handle->ctl_state = HID_REQ_INIT;
                    phost->device.kbd_LED_supported = 0U; // todo

                } else { // if (Iprot == HID_CUSTOM_BOOT_CODE) {
                    USBH_UsrLog("Custom device found!");
                    HID_Handle->Init = USBH_HID_CustomInit;
                    HID_Handle->state = HID_INIT;
                    HID_Handle->ctl_state = HID_REQ_INIT;
                }

                /* Check of available number of endpoints */
                /* Find the number of EPs in the Interface Descriptor */
                /* Choose the lower number in order not to overrun the buffer allocated */
                if (phost->device.CfgDesc.Itf_Desc[interface_index].bNumEndpoints
                        <= USBH_MAX_NUM_ENDPOINTS) {
                    max_ep =
                            phost->device.CfgDesc.Itf_Desc[interface_index].bNumEndpoints;
                } else {
                    max_ep = USBH_MAX_NUM_ENDPOINTS;
                }

                USBH_UsrLog("max_ep: %d", max_ep);

                /* Decode endpoint IN and OUT address from interface descriptor */
                for (ep_index = 0U; ep_index < max_ep; ep_index++) {
                    USBH_InterfaceDescTypeDef *interface_x =
                            &phost->device.CfgDesc.Itf_Desc[interface_index];

                    USBH_EpDescTypeDef *ep_desc =
                            &interface_x->Ep_Desc[ep_index];

                    if (ep_desc->bEndpointAddress & 0x80U) {
                        HID_Handle->InEp =
                                (phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bEndpointAddress);
                        HID_Handle->InPipe = USBH_AllocPipe(phost,
                                HID_Handle->InEp);

                        HID_Handle->ep_addr_array[0] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bEndpointAddress;
                        HID_Handle->length_array[0] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].wMaxPacketSize;
                        HID_Handle->poll_array[0] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bInterval;

                        if (HID_Handle->poll_array[0] < HID_MIN_POLL) {
                            HID_Handle->poll_array[0] = HID_MIN_POLL;
                        }

                        /* Open pipe for IN endpoint */
                        (void) USBH_OpenPipe(phost, HID_Handle->InPipe,
                                HID_Handle->InEp, phost->device.address,
                                phost->device.speed, USB_EP_TYPE_INTR,
                                HID_Handle->length_array[0]);

                        (void) USBH_LL_SetToggle(phost, HID_Handle->InPipe, 0U);
                    } else {
                        HID_Handle->OutEp =
                                (phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bEndpointAddress);
                        HID_Handle->OutPipe = USBH_AllocPipe(phost,
                                HID_Handle->OutEp);

                        HID_Handle->ep_addr_array[1] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bEndpointAddress;
                        HID_Handle->length_array[1] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].wMaxPacketSize;
                        HID_Handle->poll_array[1] =
                                phost->device.CfgDesc.Itf_Desc[interface_index].Ep_Desc[ep_index].bInterval;

                        if (HID_Handle->poll_array[1] < HID_MIN_POLL) {
                            HID_Handle->poll_array[1] = HID_MIN_POLL;
                        }

                        /* Open pipe for OUT endpoint */
                        (void) USBH_OpenPipe(phost, HID_Handle->OutPipe,
                                HID_Handle->OutEp, phost->device.address,
                                phost->device.speed, USB_EP_TYPE_INTR,
                                HID_Handle->length_array[1]);

                        (void) USBH_LL_SetToggle(phost, HID_Handle->OutPipe,
                                0U);
                    }

                    USBH_UsrLog(
                            "Interface %u, endpoint #%u: address 0x%02x, attributes 0x%02x\n",
                            interface_index, ep_index,
                            ep_desc->bEndpointAddress, ep_desc->bmAttributes);
                }
            } else {
                USBH_UsrLog("Protocol not supported.");
                phost->device.skip_interface = 1U;
                //return USBH_FAIL;
            }
        }
    }

    interface_index = 0;

    status = USBH_SelectInterface(phost, interface_index);

    if (status != USBH_OK) {
        return USBH_FAIL;
    }

    return USBH_OK;
}

Scanning the HID interfaces is very simple, I still don't know if there is a need to improve this, I'm still starting to edit the USB Host core code, until then I was just trying to use the code with some simpler adaptations to force the keyboard LED to light up, now the the LED is a little better adapted.

static void USBH_HID_IncrementInterface(USBH_HandleTypeDef *phost) {
    uint8_t max_loop = phost->device.CfgDesc.bNumInterfaces;

    while (max_loop--) {
        // switch interface
        phost->device.current_interface++;

        if (phost->device.current_interface
                >= phost->device.CfgDesc.bNumInterfaces) {
            phost->device.current_interface = 0U;
        }

        if (phost->device.skip_interface == 0U) {
            //USBH_UsrLog("current_interface: %d",
            //      phost->device.current_interface);
            break;
        }
    }
}

As you can see, it was tested on an STM32F407VGT6, but I believe it's not difficult to port to other lines of STM32. Project code with all classes supported by STM32CubeIDE is accessible here:

https://github.com/rtek1000/STM32F4HUB_modified/blob/main/Project-STM32CubeIDE_USBH_ASC/F407_USBH_ASC_GAMEPAD/F407_USBH_ASC/Middlewares/ST/STM32_USB_Host_Library/Class/HID/Src/usbh_hid.c


It would be nice to be able to try using USB HUB too, despite the warning in the Errata document.

The original code I found for HUB support is this one, in case it is possible to implement the HUB class in the current version of this library.

ALABSTM commented 1 year ago

ST Internal Reference: 158668

ALABSTM commented 1 year ago

Hi @rtek1000,

Glad to read your comment. Thank you also for this interesting proposal. It has been forwarded to our development teams for further analysis. I will keep you informed. This may take some time. Thank you in advance for your comprehension.

With regards,

rtek1000 commented 1 year ago

I'd like to add a note about the handling of the keyboard LED (which although I have worked around my usage by preventing the LED from being used when a Mouse interface is found). The descriptor needs to be correctly handled so that custom workarounds are not required (I left a ToDo note on this line).

Code ref.:

static USBH_StatusTypeDef USBH_HID_InterfaceInit(USBH_HandleTypeDef *phost) {
.
.
.
...} else if (Iprot == HID_MOUSE_BOOT_CODE) {
                    USBH_UsrLog("Mouse device found!");
                    HID_Handle->Init = USBH_HID_MouseInit;
                    HID_Handle->state = HID_INIT;
                    HID_Handle->ctl_state = HID_REQ_INIT;
                    phost->device.kbd_LED_supported = 0U; // todo

External ref.:

6.2.2 Report Descriptor The Report descriptor is unlike other descriptors in that it is not simply a table of values. The length and content of a Report descriptor vary depending on the number of data fields required for the device’s report or reports. The Report descriptor is made up of items that provide information about the device. The first part of an item contains three fields: item type, item tag, and item size. Together these fields identify the kind of information the item provides. There are three item types: Main, Global, and Local. There are five Main item tags currently defined:

  • Input item tag: Refers to the data from one or more similar controls on a device. For example, variable data such as reading the position of a single axis or a group of levers or array data such as one or more push buttons or switches.
  • Output item tag: Refers to the data to one or more similar controls on a device such as setting the position of a single axis or a group of levers (variable data). Or, it can represent data to one or more LEDs (array data).
  • Feature item tag: Describes device input and output not intended for consumption by the end user —for example, a software feature or Control Panel toggle.
  • Collection item tag: A meaningful grouping of Input, Output, and Feature items—for example, mouse, keyboard, joystick, and pointer.
  • End Collection item tag: A terminating item used to specify the end of a collection of items.

The Report descriptor provides a description of the data provided by each control in a device. Each Main item tag (Input, Output, or Feature) identifies the size of the data returned by a particular control, and identifies whether the data is absolute or relative, and other pertinent information. Preceding Local and Global items define the minimum and maximum data values, and so forth. A Report descriptor is the complete set of all items for a device. By looking at a Report descriptor alone, an application knows how to handle incoming data, as well as what the data could be used for.

Source: https://www.usb.org/sites/default/files/documents/hid1_11.pdf

ALABSTM commented 7 months ago

Hi @rtek1000,

I hope you are doing well. Unfortunately, there is no plan currently to support multiple interfaces. Please allow me to close this issue. I will let you know should there be any change in the plan. Thank you for your comprehension and thank you again for your interesting proposal.

With regards,

rtek1000 commented 7 months ago

Thank you, I understand that it is very difficult to do code just for fun. Maybe in the future someone can dedicate more time and complete what is missing.

Mitch-Martinez commented 5 months ago

@rtek1000 Do you have a forked version that has support for multiple hid interfaces? I've been trying to understand why I can't receive media key input for a while and finally ran across this thread. I've implemented the changes in this thread but I'm still not getting any events on the second interface.

rtek1000 commented 5 months ago

@rtek1000 Do you have a forked version that has support for multiple hid interfaces? I've been trying to understand why I can't receive media key input for a while and finally ran across this thread. I've implemented the changes in this thread but I'm still not getting any events on the second interface.

In fact, the ST library only supports the first HID interface, if the device has more active interfaces, it will not work. For example the Mini Keyboard with builtin touchpad, the keyboard part is recognized, but when it is touched on the touchpad, the second interface tries to send data and the library cannot handle it, so the keyboard stops being recognized correctly.

Normally the HID interface has its own treatment for each device, which the device itself informs how it should be treated by the host, so I didn't do this part, I just made the library switch between the first and second interface, and already knowing that one is keyboard and another is mouse.

Another problem is support for USB HUBs, there is a physical problem (hardware) that makes certain applications impossible, and ends up discouraging those who were trying to implement support for HUBs. See ERRATA documents.

If you want a project from another platform that was able to recognize more than one HID interface, there are two that I know of:

For ESP32-S3 (It only has 1 USB Host/Device port, but no HUB support yet. Despite this, there is the ESP32-S3 mini board, which can be used as an interface between any microcontroller and a USB device, and can also operate as a device. I needed to make very few changes to the library to work with the mini keyboard, at the moment it's my favorite)

For Arduino + MAX3421 (Should only work with the Shield for UNO (and MegaAdk), other shields may have defective batches):

Another option is to use a dedicated IC, I saw a video talking about it (for MSC, U-disk, there is also the CH375):

rtek1000 commented 5 months ago

Maybe this document can help you understand how it works (the documents made by USB-IF are not very practical):

The Human Interface Device (HID) class specification allows designers to create USB-based devices and applications without the need for custom driver development. Their high levels of on-chip integration and robust USB interfaces make Silicon Laboratories microcontrollers ideal devices for HID designs

https://www.silabs.com/documents/public/application-notes/AN249.pdf

rtek1000 commented 5 months ago

Maybe this document can help you understand how it works (the documents made by USB-IF are not very practical):

* HUMAN INTERFACE DEVICE TUTORIAL

The Human Interface Device (HID) class specification allows designers to create USB-based devices and applications without the need for custom driver development. Their high levels of on-chip integration and robust USB interfaces make Silicon Laboratories microcontrollers ideal devices for HID designs

https://www.silabs.com/documents/public/application-notes/AN249.pdf

7.2.6. Report Descriptor The report descriptor contains the definition for a report that includes one bit describing the state of the left mouse button and two bytes describing the relative X- and Y- axis positions of the mouse pointer. This example will require that only one report be defined. This example’s only report groups all data inside an application collection that contains generic desktop information pertaining to a mouse. Inside this application collection is a physical collection pertaining to a pointer that contains all information about a single data point (in this case, information about the mouse). This physical collection will group a byte of data containing the bit of data describing the left mouse button state and seven bits of padding with two bytes of data describing the X- and Y- axis positions of the pointer.