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
#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
#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
#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;
}
}
}