| USBD ROM Stack 2.0 ROM based USB device stack

How to implement simple HID device ?

The HID class consists primarily of devices that are used by humans to control the operation of computer systems. Typical examples of HID class devices include Keyboards and pointing devices — for example, standard mouse devices, trackballs, and joysticks.

# Initialization

After successfully initializing the USBD ROM stack i.e. USBD_HW_API_T::Init, the HID class driver needs to be initialized as follows:

- Configure initialization parameters of the HID class driver
  USBD_HID_INIT_PARAM_T hid_param;
  memset((void*)&hid_param, 0, sizeof(USBD_HID_INIT_PARAM_T));
- Set the memory location where the HID class driver can allocate buffers and global variables. All the init() routines of the ROM components are written in such a way that the mem_base and mem_size members of XXX_INIT_PARAM_T are updated with free location and size before returning. So that the next component XXX_INIT_PARAM_T can use the update values for its init() routine. Applications can cascade the component initialization this way without worrying about memory wastage/overlap issues.
  hid_param.mem_base = usb_param.mem_base;
  hid_param.mem_size = usb_param.mem_size;
- Now set the HID specific parameters. The report_data points to an array of HID Reports (USB_HID_REPORT_T), the max_reports indicates the number of reports for the HID device. The USBD stack uses this to respond to Get HID Report Descriptor requests.
  /* Init reports_data */
  hid_param.max_reports = 1;
  reports_data[0].len = Mouse_ReportDescSize;
  reports_data[0].idle_time = 0;
  reports_data[0].desc = (uint8_t *) &Mouse_ReportDescriptor[0];
  hid_param.report_data = reports_data;
- Obtain the address location of the interface descriptor, within the device configuration descriptor array, to which the class driver has to be attached.
  pIntfDesc =
  (USB_INTERFACE_DESCRIPTOR*)(&USB_FsConfigDescriptor[sizeof(USB_CONFIGURATION_DESCRIPTOR)]);
  /* check we are referencing to the proper interface descriptor */
  if ((pIntfDesc == 0) ||
  (pIntfDesc->bInterfaceClass != USB_DEVICE_CLASS_HUMAN_INTERFACE) )
  return ERR_FAILED;
  hid_param.intf_desc = (uint8_t*)pIntfDesc;
- Define and set the HID class callback routines. The Get and Set Report callback routines are mandated by the USBD stack, if these callback handles are not provided the HID Init will fail. The HID EP In/Out Handlers could also be provided in the Init Params, these callbacks will be called whenever data is expected/received on the HID Interrupt endpoint. There are a few optional callbacks that could be provided for Set Idle, Set Protocol, Get Physical Descriptor and Get Report Descriptor. If the Physical descriptor call back is not provided the USBD stack will stall the endpoint if the host requests for it. If the report descriptor is not provided then the stack will extract the report descriptor from the HID reports data structure i.e. report_data. Optionally the application can also register its own class handler by providing the handle in the HID Init Parameters, the USBD stack returns the default handler in the same parameter after the HID initialization call. So the application could override or implement specific class requests and could call the default handler for requests that there not handled. Refer Customizing USBD ROM Core Layer to understand how to change the default stack behavior by implementing custom overrides.
  /* user defined functions */
  hid_param.HID_GetReport = Mouse_GetReport;
  hid_param.HID_SetReport = Mouse_SetReport;
  hid_param.HID_EpIn_Hdlr = Mouse_EpIN_Hdlr;
  hid_param.HID_EpOut_Hdlr = Mouse_EpOut_Hdlr; /* Optional if there is an Interrupt OUT endpoint */
  /* Optional */
  hid_param.HID_GetPhysDesc = Mouse_GetPhyDesc;
  hid_param.HID_SetIdle = Mouse_SetIdle;
  hid_param.HID_SetProtocol = Mouse_SetProtocol;
  hid_param.HID_GetReportDesc = Mouse_GetReportDesc;
  hid_param.HID_Ep0_Hdlr = Mouse_Ep0_Hdlr;
- Now call the USBD_HID_API_T::init routine of the HID class.
  ret = USBD_API->hid->init(hUsb, &hid_param);
  if (ret != LPC_OK) {
  vCatchError(0); //"usb hid init error!!!"
  }
- After a successful HID Init update the memory base address and memory size from the HID Init Param to the USB Init Param, so that any other class driver could start using the memory from the right location.
  usb_param.mem_base = hid_param.mem_base;
  usb_param.mem_size = hid_param.mem_size;

## Defining USB Descriptors

The following example shows the configuration descriptor for a USB HID Mouse device:

/* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
ALIGNED(4) uint8_t USB_FsConfigDescriptor[] = {
/* Configuration 1 */
USB_CONFIGURATION_DESC_SIZE, /* bLength */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL( /* wTotalLength */
USB_CONFIGURATION_DESC_SIZE +
USB_INTERFACE_DESC_SIZE +
HID_DESC_SIZE +
USB_ENDPOINT_DESC_SIZE
),
0x01, /* bNumInterfaces */
0x01, /* bConfigurationValue */
0x00, /* iConfiguration */
USB_CONFIG_SELF_POWERED, /* bmAttributes */
USB_CONFIG_POWER_MA(2), /* bMaxPower */
/* Interface 0, Alternate Setting 0, HID Class */
USB_INTERFACE_DESC_SIZE, /* bLength */
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x01, /* bNumEndpoints */
USB_DEVICE_CLASS_HUMAN_INTERFACE, /* bInterfaceClass */
HID_SUBCLASS_BOOT, /* bInterfaceSubClass */
HID_PROTOCOL_MOUSE, /* bInterfaceProtocol */
0x04, /* iInterface */
/* HID Class Descriptor */
/* HID_DESC_OFFSET = 0x0012 */
HID_DESC_SIZE, /* bLength */
HID_HID_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(0x0111), /* bcdHID : 1.11*/
0x00, /* bCountryCode */
0x01, /* bNumDescriptors */
HID_REPORT_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(sizeof(Mouse_ReportDescriptor)), /* wDescriptorLength */
/* Endpoint Descriptor, HID Interrupt In */
USB_ENDPOINT_DESC_SIZE, /* bLength */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
HID_EP_IN, /* bEndpointAddress */
USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */
WBVAL(0x0008), /* wMaxPacketSize */
HID_MOUSE_REPORT_INTERVAL, /* bInterval */
/* Terminator */
0 /* bLength */
};

The following example shows the Report Descriptor for the USB HID Mouse Device

const uint8_t Mouse_ReportDescriptor[] = {
HID_UsagePage(HID_USAGE_PAGE_GENERIC),
HID_Usage(HID_USAGE_GENERIC_MOUSE),
HID_Collection(HID_Application),
HID_Usage(HID_USAGE_GENERIC_POINTER),
HID_Collection(HID_Physical),
HID_UsagePage(HID_USAGE_PAGE_BUTTON),
HID_UsageMin(1),
HID_UsageMax(3),
HID_LogicalMin(0),
HID_LogicalMax(1),
HID_ReportCount(3), /* Indicates three bit field data for buttons */
HID_ReportSize(1), /* 1 bit data field */
HID_Input(HID_Data | HID_Variable | HID_Absolute), /* Data field is Input */
HID_ReportCount(1), /* Indicates one 5 bit field data for padding */
HID_ReportSize(5),
HID_Input(HID_Constant), /* Constant data for padding */
HID_UsagePage(HID_USAGE_PAGE_GENERIC),
HID_Usage(HID_USAGE_GENERIC_X),
HID_Usage(HID_USAGE_GENERIC_Y),
HID_LogicalMin( (uint8_t) -127),
HID_LogicalMax(127),
HID_ReportSize(8), /* 8 bit data field */
HID_ReportCount(2), /* Indicates 2 bytes for X, Y data */
HID_Input(HID_Data | HID_Variable | HID_Relative), /* Data field is Input */
HID_EndCollection,
HID_EndCollection,
};

# Execution

After a successful HID Initialization the stack is ready to receive the packets from host. Even if the device is physically connected to the host using an USB cable the host does not recognize the presence of device until the D+ line is pulled up using 1.5K ohm resistor (LPC device controllers operate in high-speed or full-speed mode only). To enable the connection the application software should call USBD_HW_API_T::Connect with enable set to 1. Before enabling the connection the application should enable the USB interrupt.

NVIC_EnableIRQ(USB0_IRQn); // enable USB0 interrupts
/* now connect */
USBD_API->hw->Connect(g_hUsb, 1);

After connection the HID class executes through callback functions setup during initialization. The application could also have a task for collecting the report data and preparing the HID report, which would be provided whenever the Get Report or EP In call back is called by the stack.

## Installing IRQ handlers

USBD ROM stack implements the interrupt handler for USB device controller and invokes the USBD ROM stack routines according to the event type. Hence application should connect this handler to the application vector table. This is done by calling USBD_HW_API_T::ISR from the USB IRQ handler as shown in the code snippet below. The USBD ROM handle passed to the ISR() routine is the one obtained in previous step.

void USB0_IRQHandler(void)
{
USBD_API->hw->ISR(g_hUsb);
}

# Implementing callback

The USBD Stack mandates the Get Report and Set report call back to be implemented by the application. These callbacks are provided with the SETUP packet data that was received for the request, a double pointer to the data buffer and the length. With the SETUP packet data the callback should parse the report type and the report ID and provide the appropriate data or act on the data.

The Endpoint Handlers are called upon receiving/requesting data on the HID Endpoint. The stack provides the pointer to the Core Control Structure, HID control structure and the event that occurred on the endpoint. The application could implement these call backs to read or write data to the endpoint depending on the event.

Subsections:

- Page_HIDDemo