Author Topic: USB HID host polling - explanation needed  (Read 12092 times)

JustGreg

  • Member
  • ***
  • Posts: 2
USB HID host polling - explanation needed
« on: May 19, 2015, 07:31:53 am »
Hello to all forum members!

I've just started to experiment with USB HID. My initial goal is to replace virtual com ports with custom USB HID. I'm using a TI Tiva launchpad and I was able to come up with a working code modifying a member's code from TI's E2E forum (based on TI's usb library). I was studying the HID 1.11 document and also some other ones (SiLabs, TI etc.). So far my code is working with the SimpleHIDWrite application, my report is just one byte  (+report ID), 1 input interrupt endpoint, no output endpoint

- I can receive the GetReport event and can echo back the byte I received
- I can receive the GetReportBuffer, SetReport and TXComplete events

However, it is still not clear how to interprete the 'host polling' concept. So far I had to send a GetReport request in order to get a reply from my USB device. In the docs it was emphasized that always the host who requests a report. In the endpoint descriptor I set the interval but it's function is not clear...On the scope I can see packets at every ms. I am probably missing out from the code so any help or tutorial would be great.

Thank you,

JG

The relevant code is below:

usbhid_descriptors.c
Code: [Select]
#include <stdbool.h>
#include <stdint.h>

#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "driverlib/rom.h"

#include "customhid.h"

#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usbhid.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdhid.h"

#define NUM_STRING_DESCRIPTORS (sizeof(string_descriptors) /                \
                                sizeof(uint8_t *))

#define NUM_HID_SECTIONS        (sizeof(hid_sections) /                    \
                                 sizeof(hid_sections[0]))

#define USB_VID                0x2047        // Vendor ID (VID)
#define USB_PID                0x0301        // Product ID (PID)

databuffer_t rxdata;
databuffer_t txdata;

const uint8_t language_descriptor[] = {
4,
USB_DTYPE_STRING,
USBShort(USB_LANG_EN_US)
};

const uint8_t manufacturer_string[] = {
    (8 + 1) * 2,
    USB_DTYPE_STRING,
    'A', 0, 'B', 0, 'C', 0, 'D', 0, 'E', 0, 'F', 0, 'G', 0, 'H', 0,
};

const uint8_t product_string[] = {
8,
    USB_DTYPE_STRING,
    'X',0x00,'Y',0x00,'Z',0x00,
};

const uint8_t serialnumber_string[] = {
    (4 + 1) * 2,
    USB_DTYPE_STRING,
    'v', 0, '1', 0, '.', 0, '0', 0
};

const uint8_t interface_string[] = {
28,
    USB_DTYPE_STRING,
    'H',0x00,'I',0x00,'D',0x00,' ',0x00,'I',0x00,'n',0x00,
    't',0x00,'e',0x00,'r',0x00,'f',0x00,'a',0x00,'c',0x00,
    'e',0x00
};

// The descriptor string table
const uint8_t * const string_descriptors[] = {
    language_descriptor,
    manufacturer_string,
    product_string,
    serialnumber_string,
    interface_string
};

static const uint8_t datapipereport_descriptor[] = {

    // 0x06, 0x00, 0xff,
    // HUT chap 3 table 1
    // Vendor specific value 0xff00
    UsagePageVendor(0xff00),
    // 0x09, 0x01,
    // Arbitrary number as we are vendor specific
    Usage(0x01),
    // 0xa1, 0x01,
    Collection(USB_HID_APPLICATION),
        // 0x85, 0x3f,
        // Vendor specific. Appears arbitrary.
        // ID can be left undefined if only one report type is created
        ReportID(0x3f),
            // 0x95, 0x3f,
            // The number of items in the report
            ReportCount(HID_PACKET_SIZE-1),
            // 0x75, 0x08,
            // As this is just generic data, 8 bits in a byte
            ReportSize(0x08),
            // 0x25, 0x7F,
            LogicalMaximum(127),
            // 0x15, 0x01,
            LogicalMinimum(-128),
            // 0x09, 0x80,
            // Arbitrary number as we are vendor specific
            Usage(0x01),
            // 0x81, 0x02, Input (Data,Var,Abs)
            // This shows we are describing the report that goes to the host
            Input(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),
            // Second report, same except it's output
        ReportID(0x3f),
            ReportCount(HID_PACKET_SIZE-1),
            ReportSize(0x08),
            LogicalMaximum(127),
            LogicalMinimum(-128),
            Usage(0x01),
            Output(USB_HID_INPUT_DATA | USB_HID_INPUT_VARIABLE | USB_HID_INPUT_ABS),
    EndCollection
};

static const uint8_t * const datapipeclass_descriptors[] = {
        datapipereport_descriptor
};

uint8_t datapipe_descriptor[] = {
    //
    // Configuration descriptor header.
    //
    9,                          // Size of the configuration descriptor.
    USB_DTYPE_CONFIGURATION,    // Type of this descriptor.
    USBShort(34),               // The total size of this full structure.
    1,                          // The number of interfaces in this
                                // configuration.
    1,                          // The unique value for this configuration.
    5,                          // The string identifier that describes this
                                // configuration.
    USB_CONF_ATTR_SELF_PWR,     // Bus Powered, Self Powered, remote wake up.
    250,                        // The maximum power in 2mA increments.
};

static const tHIDDescriptor datapipehid_descriptor = {
    9, // bLength
    USB_HID_DTYPE_HID, // bDescriptorType
    0x111, // bcdHID (version 1.11 compliant)
    0, // bCountryCode (not localized)
    1, // bNumDescriptors
    USB_HID_DTYPE_REPORT, // Report descriptor
    sizeof(datapipereport_descriptor) // Size of report descriptor
};

uint8_t hidinterface_descriptor[HIDINTERFACE_SIZE] =
{
    //
    // HID Device Class Interface Descriptor.
    //
    9,                          // Size of the interface descriptor.
    USB_DTYPE_INTERFACE,        // Type of this descriptor.
    0,                          // The index for this interface.
    0,                          // The alternate setting for this interface.
    1,                          // The number of endpoints used by this
                                // interface.
    USB_CLASS_HID,              // The interface class
    USB_HID_SCLASS_NONE,        // The interface sub-class.
    USB_HID_PROTOCOL_NONE,     // The interface protocol for the sub-class
                                // specified above.
    5,                          // The string index for this interface.
};

const uint8_t interruptINendpoint_descriptor[HIDINENDPOINT_SIZE] =
{
    //
    // Interrupt IN endpoint descriptor
    //
    7,                          // The size of the endpoint descriptor.
    USB_DTYPE_ENDPOINT,         // Descriptor type is an endpoint.
    USB_EP_DESC_IN | USBEPToIndex(USB_EP_1),
    USB_EP_ATTR_INT,            // Endpoint is an interrupt endpoint.
    USBShort(USBFIFOSizeToBytes(USB_FIFO_SZ_64)),
                                // The maximum packet size.
    2,                         // The polling interval for this endpoint.
};

// Not needed until the out interrupt is enabled
// TODO: enable by adding this array to g_psHIDSections
//      and setting g_sHIDDataPipeDevice.bUseOutEndpoint to true
//
const uint8_t interruptOUTendpoint_descriptor[HIDOUTENDPOINT_SIZE] =
{
    //
    // Interrupt Out endpoint descriptor
    //
    7,                          // The size of the endpoint descriptor.
    USB_DTYPE_ENDPOINT,         // Descriptor type is an endpoint.
    USB_EP_DESC_OUT | USBEPToIndex(USB_EP_1),
    USB_EP_ATTR_INT,            // Endpoint is an interrupt endpoint.
    USBShort(USBFIFOSizeToBytes(USB_FIFO_SZ_64)),
                                // The maximum packet size.
    16,                         // The polling interval for this endpoint.
};

//-------------------------------------------------------------------------------------------------------------------
const tConfigSection hidconfig_section = {
    sizeof(datapipe_descriptor),
    datapipe_descriptor
};

const tConfigSection hidinterface_section = {
    sizeof(hidinterface_descriptor),
    hidinterface_descriptor
};

const tConfigSection hidINendpoint_section = {
    sizeof(interruptINendpoint_descriptor),
    interruptINendpoint_descriptor
};

const tConfigSection hidOUTendpoint_section = {
    sizeof(interruptOUTendpoint_descriptor),
    interruptOUTendpoint_descriptor
};

tConfigSection hiddescriptor_section = {
   sizeof(datapipehid_descriptor),
   (const uint8_t *)&datapipehid_descriptor
};
//-------------------------------------------------------------------------------------------------------------------

const tConfigSection *hid_sections[] = {
    &hidconfig_section,
    &hidinterface_section,
    &hiddescriptor_section,
    &hidINendpoint_section,
//    &hidOUTendpoint_section,
};

tConfigHeader hidconfig_header = {
    NUM_HID_SECTIONS,
    hid_sections
};

const tConfigHeader * const hidconfig_descriptors[] = {
    &hidconfig_header
};

tHIDReportIdle report_idle[1] = {
        { 0, 0x3f, 0, 0 } // Report 0x3f never polled
};

tUSBDHIDDevice hiddatapipe_device = {
    //
    // Stealing the MSP430 id so it works with the java app
    //
    //
    USB_VID,
    //
    // The product ID you have assigned for this device.
    //
    USB_PID,
    //
    // The power consumption of your device in milliamps.
    //
    250,
    //
    // The value to be passed to the host in the USB configuration descriptor’s
    // bmAttributes field.
    //
    USB_CONF_ATTR_BUS_PWR,
    //
    // This mouse supports the boot subclass.
    //
    USB_HID_SCLASS_NONE,
    //
    // This device supports the BIOS mouse report protocol.
    //
    USB_HID_PROTOCOL_NONE,
    //
    // The device has a single input report.
    //
    1,
    //
    // A pointer to our array of tHIDReportIdle structures. For this device,
    // the array must have 1 element (matching the value of the previous field).
    //
    report_idle,
    //
    // A pointer to your receive callback event handler.
    //
    rxhandler,
    //
    // A value that you want passed to the receive callback alongside every
    // event.
    //
    (void *)&rxdata,
    //
    // A pointer to your transmit callback event handler.
    //
    rxhandler,
    //
    // A value that you want passed to the transmit callback alongside every
    // event.
    //
    (void *)&txdata,
    //
    // This device does not want to use a dedicated interrupt OUT endpoint
    // since there are no output or feature reports required.
    //
    false,
    //
    // A pointer to the HID descriptor for the device.
    //
    &datapipehid_descriptor,
    //
    // A pointer to the array of HID class descriptor pointers for this device.
    // The number of elements in this array and their order must match the
    // information in the HID descriptor provided above.
    //
    datapipeclass_descriptors,
    //
    // A pointer to your string table.
    //
    string_descriptors,
    //
    // The number of entries in your string table. This must equal
    // (1 + (5 + (num HID strings)) * (num languages)).
    //
    NUM_STRING_DESCRIPTORS,

    // Config section
    hidconfig_descriptors

};

customhid.c
Code: [Select]
#include <stdint.h>
#include <stdbool.h>
#include "customhid.h"

#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usbhid.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdhid.h"

#include "board.h"

extern databuffer_t rxdata;
extern databuffer_t txdata;
volatile uint8_t usbstate = 0;
diagnostic_t diagnostic;
extern int ledstate;
int volatile timecount;

extern uint32_t volatile handler_id[10];
extern int volatile num_handler_id;

uint32_t txhandler(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgData, void *pvMsgData) {
int i;

    rxdata.state=0;
    rxdata.size=0;
    for(i = 0; i < HID_PACKET_SIZE; i++) {
            rxdata.buffer[i]=0;
        }
    return 0;
}

uint32_t rxhandler(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgData, void *pvMsgData) {
uint32_t readPacketSize;
    // uint32_t writePacketSize;
    // uint32_t retCode;
    // databuffer_t *myData = pvCBData;
// handler_id = ui32Event;

handler_id[num_handler_id++] = ui32Event;

switch(ui32Event) {

    case USB_EVENT_CONNECTED:
    diagnostic.events.connected = 1;
        usbstate|= 0x01;
        break;

    case USB_EVENT_DISCONNECTED:
    diagnostic.events.disconnected = 1;
        usbstate &= ~0x01;
        break;

    case USB_EVENT_RX_AVAILABLE:
    //Ignore the USB_EVENT_RX_AVAILABLE case as it was coded for the OUT endpoint 1 event.
    //USB_EVENT_RX_AVAILABLE would happen if you receive the HID report on OUT Endpoint 1.
        diagnostic.events.rxavailable = 1;
        break;

    case USBD_HID_EVENT_IDLE_TIMEOUT:
        // Not defining a timeout.
    diagnostic.events.idle_timeout = 1;
        break;

    case USBD_HID_EVENT_GET_REPORT_BUFFER:
    //*****************************************************************************
    //
    //! This event indicates that the host has sent a Set_Report request to
    //! the device and requests that the device provide a buffer into which the
    //! report can be written.  The ui32MsgValue parameter contains the received
    //! report type in the high byte and report ID in the low byte (as passed in
    //! the wValue field of the USB request structure).  The pvMsgData parameter
    //! contains the length of buffer requested.  Note that this is the actual
    //! length value cast to a "void *" type and not a pointer in this case.
    //! The callback must return a pointer to a suitable buffer (cast to the
    //! standard "uint32_t" return type for the callback).
    //
    //*****************************************************************************
        // TODO: Check report id
    set_blue();

    timecount = 4;
    diagnostic.events.getreportbuffer = 1;
        readPacketSize=(uint32_t)pvMsgData;
        if(readPacketSize>HID_PACKET_SIZE) {
                while(1) //Shouldn't happen
                ;
            }

        return((uint32_t)&rxdata.buffer);
        break;

    case USBD_HID_EVENT_GET_REPORT:
    set_green();
    timecount = 4;
    diagnostic.events.getreport = 1;
        break;

    case USBD_HID_EVENT_SET_REPORT:
    set_red();
    timecount = 4;
    diagnostic.events.setreport = 1;
        // TODO: check report ID
        //set size to let the application know that there is data.
        rxdata.size=(uint16_t) (ui32MsgData&0xffff);
        rxdata.state=1;
        break;

    case USBD_HID_EVENT_SET_PROTOCOL:
    diagnostic.events.setprotocol = 1;
    break;

    case USB_EVENT_TX_COMPLETE:
diagnostic.events.txcomplete = 1;
break;

    default:
    set_green();
    diagnostic.events.defaultevent = 1;
        break;
    }
return 0;
}

main part
Code: [Select]
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"

#include "driverlib/gpio.h"

#include "driverlib/sysctl.h"
#include "driverlib/rom.h"
#include "driverlib/systick.h"
#include "driverlib/uart.h"


#include "customhid.h"
#include "uart.h"
#include "board.h"
#include "utils/uartstdio.h"
// USB
#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usbhid.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdhid.h"

extern databuffer_t rxdata;
extern databuffer_t txdata;
extern tUSBDHIDDevice hiddatapipe_device;
extern diagnostic_t diagnostic;

int ledstate;
int volatile timecount;
int volatile sendcount;
uint32_t volatile handler_id[20];
int volatile num_handler_id;

void systickhandler(void) {
int i;

if (!timecount--) {
clear_red();
clear_blue();
clear_green();
UARTprintf("Diag: 0x%04x\n", diagnostic.byte);
for (i = 0; i < num_handler_id; i++)
UARTprintf("    Visited handlerID: 0x%04x  \n", handler_id[i]);

num_handler_id = 0;
diagnostic.byte = 0;
}
}


int main(void) {
int i;

    //
    // Enable lazy stacking for interrupt handlers.  This allows floating-point
    // instructions to be used within interrupt handlers, but at the expense of
    // extra stack usage.
    //
    ROM_FPULazyStackingEnable();

    //
    // Set the clocking to run from the PLL at 50MHz.
    //
    ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                       SYSCTL_XTAL_16MHZ);

    //
    // Enable the GPIO peripheral used for USB, and configure the USB
    // pins.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
    ROM_GPIOPinTypeUSBAnalog(GPIO_PORTD_BASE, GPIO_PIN_4 | GPIO_PIN_5);

    //
    // Configure SysTick
    //
    ROM_SysTickPeriodSet(ROM_SysCtlClockGet() / 10);
    ROM_SysTickEnable();
    ROM_SysTickIntEnable();

    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
ROM_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1 + GPIO_PIN_2 + GPIO_PIN_3);
ROM_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1 + GPIO_PIN_2 + GPIO_PIN_3, 0);

    // Init buffer
    rxdata.state=0;
    rxdata.size=0;

    // Init diagnostic
    diagnostic.byte = 0;
    for (i = 0; i < 10; i++)
    handler_id[i] = 0;
    num_handler_id = 0;
    sendcount = 10;
    ConfigureUART();

    UARTprintf("Configuring USB\n");
    //
    // Set the USB stack mode to Device mode with VBUS monitoring.
    //
    USBStackModeSet(0, eUSBModeForceDevice, 0);

    //
    // Pass our device information to the USB library and place the device
    // on the bus.
    //
    USBDHIDInit(0, &hiddatapipe_device);

    while(1) {
        if(rxdata.state==1) {
                // Echo the report back to PC
                USBDHIDReportWrite(&hiddatapipe_device, rxdata.buffer, rxdata.size, 0);
//                UARTprintf("Data in hex: ");
//                for (i = 0; i < rxdata.size; i++)
//                UARTprintf("%2x ", rxdata.buffer[i]);
//                UARTprintf("\n");
                rxdata.state=2;
            }
    }
}

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: USB HID host polling - explanation needed
« Reply #1 on: May 19, 2015, 09:49:36 am »
The host has two ways to receive report data, from the interrupt endpoint (under Windows, use ReadFile or a FileStream Read function) or from the control endpoint (HID Get_Report request, Windows HidD_GetReport function). 

To use the interrupt endpoint, device firmware should arm (enable) the interrupt IN endpoint to send data on receiving an IN token packet. Don't wait for an interrupt to do this; the endpoint has to be ready to send data immediately on receiving the token packet. After sending data, when new data is ready, place the data in the endpoint's buffer and rearm the endpoint. Exactly how to do this varies with the device architecture.

The host sends token packets to the interrupt IN endpoint at a frequency determined by the endpoint's bInterval value. The device should return report data if available, otherwise NAK. The host stores any received data in a buffer where applications can request it.

USB always refers to things from the host's perspective. Thus Get Report means the host receives a report, and Set Report means the host sends a report. Your customhid.c code appears to mix the terminology, for example: 

 case USBD_HID_EVENT_GET_REPORT_BUFFER:
          //*****************************************************************************
          //
          //! This event indicates that the host has sent a Set_Report request

And this appears to say that you are not using the interrupt OUT endpoint:

case USB_EVENT_RX_AVAILABLE:
          //Ignore the USB_EVENT_RX_AVAILABLE case as it was coded for the OUT endpoint 1 event.
          //USB_EVENT_RX_AVAILABLE would happen if you receive the HID report on OUT Endpoint 1.
           diagnostic.events.rxavailable = 1;


JustGreg

  • Member
  • ***
  • Posts: 2
Re: USB HID host polling - explanation needed
« Reply #2 on: May 19, 2015, 12:09:00 pm »
Hi Jan,

thank you for the quick response. I'm sorry for the messy code I provided, it is just being under constant development and at some places it may be confusing.
Anyway, what i was trying to do is to have a control endpoint (which is default) and one interrupt IN endpoint. The code I copied/modified had an option to have an interrupt OUT endpoint but it was commented out from the beginning as the author was not inteded to use the interrupt OUT endpoint. So the USB_EVENT_RX_AVAILABLE event is not expected to happen.

So far I understand that (correct me if I'm wrong):

- USBD_HID_EVENT_GET_REPORT_BUFFER event: the host 'asks' for a valid buffer pointer from the device which will be used to store the subsequent OUT Report data. This is via the Control endpoint.

- USBD_HID_EVENT_SET_REPORT event: the host actually send the OUT report which is going to be in the previously acquired buffer. This is via the Control endpoint.

- USBD_HID_EVENT_GET_REPORT event: the host 'asks' for an IN report via the Control endpoint. The event handler has to provide a pointer to the IN report's first byte and it has to return the size of the report.

So I modified my rxhandler function:

Code: [Select]
case USBD_HID_EVENT_GET_REPORT:

    txdata.buffer[0] = 0x01;
    txdata.buffer[1] = 0x02;
    *(uint8_t **) pvMsgData = &txdata.buffer[0];
    return 1;
        break;

Strangely, when I press Getreport button on the SimpleHIDWrite (SHW) with the report ID 0x3f, I receive 2 bytes, like:
rd 01 0D

where I expect only one byte with a value of 0x01.

Anyway, what is not clear that how the interrupt IN endpoint provide the report? Is it polled constantly by the host from software or is it constantly polled from the host by the host hardware? Because, in a USB HID mouse example the device application (running in a Stellaris MCU) updates and sends the report to the host periodically using the MCU's timer...which is not quite poll based.

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: USB HID host polling - explanation needed
« Reply #3 on: May 19, 2015, 09:16:16 pm »
In your report descriptor, I see only one report ID (0x3f). If using only one report ID, it should be the default (0), and the report descriptor doesn't declare it.

If using multiple report IDs, each report is preceded by its ID on the bus. If using the default report ID, no report ID travels on the bus.

Delete the report ID item in the report descriptor.

The host's HID driver requests the host's host controller to send token packets to the interrupt IN endpoint at a frequency determined by the endpoint's bInterval value. In response to the token packets, the device should return report data if available, otherwise NAK. The host stores any received data in a buffer where applications can request it.