A recent project required me to reuse (once again) my USB HID device driver. This is my third or fourth project using this and I had started to find it annoying to need to hand-modify a heavily-commented, self-referencing array of uint8_t’s. I figured there must be a better way, so I decided to try something different.
In this post I will present a script that turns this madness, which lives in a separate file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
/** * Device descriptor */ static const USB_DATA_ALIGN uint8_t dev_descriptor[] = { 18, //bLength 1, //bDescriptorType 0x00, 0x02, //bcdUSB 0x00, //bDeviceClass (defined by interfaces) 0x00, //bDeviceSubClass 0x00, //bDeviceProtocl USB_CONTROL_ENDPOINT_SIZE, //bMaxPacketSize0 0xc0, 0x16, //idVendor 0xdc, 0x05, //idProduct 0x11, 0x00, //bcdDevice 1, //iManufacturer 2, //iProduct 0, //iSerialNumber, 1, //bNumConfigurations }; static const USB_DATA_ALIGN uint8_t hid_report_descriptor[] = { HID_SHORT(0x04, 0x00, 0xFF), //USAGE_PAGE (Vendor Defined) HID_SHORT(0x08, 0x01), //USAGE (Vendor 1) HID_SHORT(0xa0, 0x01), //COLLECTION (Application) HID_SHORT(0x08, 0x01), // USAGE (Vendor 1) HID_SHORT(0x14, 0x00), // LOGICAL_MINIMUM (0) HID_SHORT(0x24, 0xFF, 0x00), //LOGICAL_MAXIMUM (0x00FF) HID_SHORT(0x74, 0x08), // REPORT_SIZE (8) HID_SHORT(0x94, 64), // REPORT_COUNT(64) HID_SHORT(0x80, 0x02), // INPUT (Data, Var, Abs) HID_SHORT(0x08, 0x01), // USAGE (Vendor 1) HID_SHORT(0x90, 0x02), // OUTPUT (Data, Var, Abs) HID_SHORT(0xc0), //END_COLLECTION }; /** * Configuration descriptor */ static const USB_DATA_ALIGN uint8_t cfg_descriptor[] = { 9, //bLength 2, //bDescriptorType 9 + 9 + 9 + 7 + 7, 0x00, //wTotalLength 1, //bNumInterfaces 1, //bConfigurationValue 0, //iConfiguration 0x80, //bmAttributes 250, //bMaxPower /* INTERFACE 0 BEGIN */ 9, //bLength 4, //bDescriptorType 0, //bInterfaceNumber 0, //bAlternateSetting 2, //bNumEndpoints 0x03, //bInterfaceClass (HID) 0x00, //bInterfaceSubClass (0: no boot) 0x00, //bInterfaceProtocol (0: none) 0, //iInterface /* HID Descriptor */ 9, //bLength 0x21, //bDescriptorType (HID) 0x11, 0x01, //bcdHID 0x00, //bCountryCode 1, //bNumDescriptors 0x22, //bDescriptorType (Report) sizeof(hid_report_descriptor), 0x00, /* INTERFACE 0, ENDPOINT 1 BEGIN */ 7, //bLength 5, //bDescriptorType 0x81, //bEndpointAddress (endpoint 1 IN) 0x03, //bmAttributes, interrupt endpoint USB_HID_ENDPOINT_SIZE, 0x00, //wMaxPacketSize, 10, //bInterval (10 frames) /* INTERFACE 0, ENDPOINT 1 END */ /* INTERFACE 0, ENDPOINT 2 BEGIN */ 7, //bLength 5, //bDescriptorType 0x02, //bEndpointAddress (endpoint 2 OUT) 0x03, //bmAttributes, interrupt endpoint USB_HID_ENDPOINT_SIZE, 0x00, //wMaxPacketSize 10, //bInterval (10 frames) /* INTERFACE 0, ENDPOINT 2 END */ /* INTERFACE 0 END */ }; static const USB_DATA_ALIGN uint8_t lang_descriptor[] = { 4, //bLength 3, //bDescriptorType 0x09, 0x04 //wLANGID[0] }; static const USB_DATA_ALIGN uint8_t manuf_descriptor[] = { 2 + 15 * 2, //bLength 3, //bDescriptorType 'k', 0x00, //wString 'e', 0x00, 'v', 0x00, 'i', 0x00, 'n', 0x00, 'c', 0x00, 'u', 0x00, 'z', 0x00, 'n', 0x00, 'e', 0x00, 'r', 0x00, '.', 0x00, 'c', 0x00, 'o', 0x00, 'm', 0x00 }; static const USB_DATA_ALIGN uint8_t product_descriptor[] = { 2 + 14 * 2, //bLength 3, //bDescriptorType 'L', 0x00, 'E', 0x00, 'D', 0x00, ' ', 0x00, 'W', 0x00, 'r', 0x00, 'i', 0x00, 's', 0x00, 't', 0x00, 'w', 0x00, 'a', 0x00, 't', 0x00, 'c', 0x00, 'h', 0x00 }; const USBDescriptorEntry usb_descriptors[] = { { 0x0100, 0x0000, sizeof(dev_descriptor), dev_descriptor }, { 0x0200, 0x0000, sizeof(cfg_descriptor), cfg_descriptor }, { 0x0300, 0x0000, sizeof(lang_descriptor), lang_descriptor }, { 0x0301, 0x0409, sizeof(manuf_descriptor), manuf_descriptor }, { 0x0302, 0x0409, sizeof(product_descriptor), product_descriptor }, { 0x2200, 0x0000, sizeof(hid_report_descriptor), hid_report_descriptor }, { 0x0000, 0x0000, 0x00, NULL } }; |
Into these comment blocks which can live anywhere in the source and are somewhat more readable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/** * <descriptor id="device" type="0x01"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <word name="bcdUSB">0x0200</word> * <byte name="bDeviceClass">0</byte> * <byte name="bDeviceSubClass">0</byte> * <byte name="bDeviceProtocol">0</byte> * <byte name="bMaxPacketSize0">USB_CONTROL_ENDPOINT_SIZE</byte> * <word name="idVendor">0x16c0</word> * <word name="idProduct">0x05dc</word> * <word name="bcdDevice">0x0010</word> * <ref name="iManufacturer" type="0x03" refid="manufacturer" size="1" /> * <ref name="iProduct" type="0x03" refid="product" size="1" /> * <byte name="iSerialNumber">0</byte> * <count name="bNumConfigurations" type="0x02" size="1" /> * </descriptor> * <descriptor id="lang" type="0x03" first="first"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <foreach type="0x03" unique="unique"> * <echo name="wLang" /> * </foreach> * </descriptor> * <descriptor id="manufacturer" type="0x03" wIndex="0x0409"> * <property name="wLang" size="2">0x0409</property> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <string name="wString">kevincuzner.com</string> * </descriptor> * <descriptor id="product" type="0x03" wIndex="0x0409"> * <property name="wLang" size="2">0x0409</property> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <string name="wString">LED Wristwatch</string> * </descriptor> * <descriptor id="configuration" type="0x02"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <length name="wTotalLength" size="2" all="all" /> * <count name="bNumInterfaces" type="0x04" associated="associated" size="1" /> * <byte name="bConfigurationValue">1</byte> * <byte name="iConfiguration">0</byte> * <byte name="bmAttributes">0x80</byte> * <byte name="bMaxPower">250</byte> * <children type="0x04" /> * </descriptor> */ /** * <include>usb_hid.h</include> * <descriptor id="hid_interface" type="0x04" childof="configuration"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <index name="bInterfaceNumber" size="1" /> * <byte name="bAlternateSetting">0</byte> * <count name="bNumEndpoints" type="0x05" associated="associated" size="1" /> * <byte name="bInterfaceClass">0x03</byte> * <byte name="bInterfaceSubClass">0x00</byte> * <byte name="bInterfaceProtocol">0x00</byte> * <byte name="iInterface">0</byte> * <children type="0x21" /> * <children type="0x05" /> * </descriptor> * <descriptor id="hid" type="0x21" childof="hid_interface"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <word name="bcdHID">0x0111</word> * <byte name="bCountryCode">0x00</byte> * <count name="bNumDescriptors" type="0x22" size="1" associated="associated" /> * <foreach type="0x22" associated="associated"> * <echo name="bDescriptorType" /> * <echo name="wLength" /> * </foreach> * </descriptor> * <descriptor id="hid_in_endpoint" type="0x05" childof="hid_interface"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <inendpoint name="bEndpointAddress" define="HID_IN_ENDPOINT" /> * <byte name="bmAttributes">0x03</byte> * <word name="wMaxPacketSize">USB_HID_ENDPOINT_SIZE</word> * <byte name="bInterval">10</byte> * </descriptor> * <descriptor id="hid_out_endpoint" type="0x05" childof="hid_interface"> * <length name="bLength" size="1" /> * <type name="bDescriptorType" size="1" /> * <outendpoint name="bEndpointAddress" define="HID_OUT_ENDPOINT" /> * <byte name="bmAttributes">0x03</byte> * <word name="wMaxPacketSize">USB_HID_ENDPOINT_SIZE</word> * <byte name="bInterval">10</byte> * </descriptor> * <descriptor id="hid_report" childof="hid" top="top" type="0x22" order="1" wIndexType="0x04"> * <hidden name="bDescriptorType" size="1">0x22</hidden> * <hidden name="wLength" size="2">sizeof(hid_report)</hidden> * <raw> * HID_SHORT(0x04, 0x00, 0xFF), //USAGE_PAGE (Vendor Defined) * HID_SHORT(0x08, 0x01), //USAGE (Vendor 1) * HID_SHORT(0xa0, 0x01), //COLLECTION (Application) * HID_SHORT(0x08, 0x01), // USAGE (Vendor 1) * HID_SHORT(0x14, 0x00), // LOGICAL_MINIMUM (0) * HID_SHORT(0x24, 0xFF, 0x00), //LOGICAL_MAXIMUM (0x00FF) * HID_SHORT(0x74, 0x08), // REPORT_SIZE (8) * HID_SHORT(0x94, 64), // REPORT_COUNT(64) * HID_SHORT(0x80, 0x02), // INPUT (Data, Var, Abs) * HID_SHORT(0x08, 0x01), // USAGE (Vendor 1) * HID_SHORT(0x90, 0x02), // OUTPUT (Data, Var, Abs) * HID_SHORT(0xc0), //END_COLLECTION * </raw> * </descriptor> */ |
In most of my projects before this one I would have something like the first script shown above sitting in a file by itself, declaring a bunch of uint8_t arrays and a usb_descriptors[] table constant that would be consumed by my USB driver as it searched for USB descriptors. A header file that exposes the usb_descriptors[] table would also be found in the project. Any USB descriptor that had to be returned by the device would be found in this table. To make things more complex, descriptors like the configuration descriptor have to declare all of the device interfaces and so pieces and parts of each separate USB interface component would be interspersed inside of other descriptors.
I’ve been using this structure for some time after writing my first USB driver after reading through the Teensy driver. This is probably the only structural code that has made it all the way from the Teensy driver into all of my other code.
With this new script I’ve written there’s no more need for manually computing how long a descriptor is or needing to modify the configuration descriptor every time a new interface has been added. All the parts of a descriptor are self-contained in the source file that defines a particular interface and can be easily moved around from project to project.
All the code for this post lives here: