Programming the USB module of PIC32MZ without Harmony (4) – Descriptor
USB Device – Config – Interface – Endpoint Descriptor
- Each USB instance can have only 1 device descriptor.
- Each device can have only 1 config descriptor.
- Each config can have multiple interface descriptors.
- Each interface can have multiple endpoint descriptors.
To understand “one config – multiple interfaces”, just to think about a printer-scanner comb machine – it’s one device with two different functions (the printing and scanning interfaces).
To understand “one interface – multiple endpoints”, just to think about multiple data channels – the printing interface uses dedicated channels to transmit control signal and content data respectively.
In my last post about the PIC32MZ USB module initialization, I mentioned that the endpoint 0 will communicate with the host to finish enumeration, during which the descriptor information will be transmitted to the host. The following codes provides an example of the descriptors:
uint8_t USB_device_description[] = {
/* Descriptor Length */ 18, /* Always 18 or 0x12 */
/* DescriptorType: DEVICE */ 0x01,
/* bcdUSB (ver 2.0) */ 0x00,0x02,
/* bDeviceClass */ 0x00,
/* bDeviceSubClass */ 0x00,
/* bDeviceProtocol */ 0x00,
/* bMaxPacketSize0 */ 0x40, /* Always 64 or 0x40 for High Speed USB */
/* idVendor */ 0xD8,0x04, /* e.g. - 0x04D8 - Microchip VID */
/* idProduct */ 0x0A,0x00, /* e.g. - 0x000A */
/* bcdDevice */ 0x00,0x02, /* e.g. - 02.00 */
/* iManufacturer */ 0x01,
/* iProduct */ 0x02,
/* iSerialNumber */ 0x03,
/* bNumConfigurations */ 0x01
};
uint8_t USB_config_descriptor[] = {
// Referenced from: https://gist.github.com/tai/acd59b125a007ad47767
/*********************************************************************
Configuration Descriptors
*********************************************************************/
/* bLength (Descriptor Length) */ 9,
/* bDescriptorType: CONFIG */ 0x02,
/* wTotalLength */ 0x27,0x00,
/* bNumInterfaces */ 1,
/* bConfigurationValue */ 1,
/* iConfiguration */ 0,
/* bmAttributes */ 0x80, /* bit 6 set = bus powered = 0x80, 0xC0 is for self powered */
/* bMaxPower */ 0x32, /* Value x 2mA, set to 0 for self powered, was 0x32 */
/*********************************************************************
Interface 0 - Communications Class
*********************************************************************/
/* bLength */ 0x09,
/* bDescriptorType: INTERFACE */ 0x04,
/* bInterfaceNumber */ 0x00,
/* bAlternateSetting */ 0x00,
/* bNumEndpoints: 1 endpoint(s) */ 0x03,
/* bInterfaceClass: Custom */ 0xFF,
/* bInterfaceSubclass:Abstract Control Model*/ 0xFF,
/* bInterfaceProtocol:AT Commands V.25ter */ 0xFF,
/* iInterface */ 0x00,
/*********************************************************************
Endpoint 1 (Bulk OUT, command)
*********************************************************************/
/* bLength */ 0x07,
/* bDescriptorType: ENDPOINT */ 0x05,
/* bEndpointAddress: OUT Endpoint 1 */ 0x01,
/* bmAttributes: Bulk */ 0x02,
/* max packet size (LSB) */ 0x00,
/* max packet size (MSB) */ 0x01,
/* polling interval */ 0x00,
/*********************************************************************
Endpoint 2 (Bulk OUT, data)
*********************************************************************/
/* bLength */ 0x07,
/* bDescriptorType: ENDPOINT */ 0x05,
/* bEndpointAddress: OUT Endpoint 2 */ 0x02,
/* bmAttributes: BULK */ 0x02,
/* max packet size (LSB) */ 0x00,
/* max packet size (MSB) */ 0x02,
/* bInterval: None for BULK */ 0x00,
/*********************************************************************
Endpoint 2 (Bulk IN, data)
*********************************************************************/
/* bLength */ 0x07,
/* bDescriptorType: ENDPOINT */ 0x05,
/* bEndpointAddress: IN Endpoint 3 */ 0x83,
/* bmAttributes: BULK */ 0x02,
/* max packet size (LSB) */ 0x00,
/* max packet size (MSB) */ 0x02,
/* bInterval: None for BULK */ 0x00
};
#define STR_DESC(l) (((((l))<<1) & 0xFF) | 0x0300)
/* String descriptors. All have a common format:
* [byte] string_length,
* [byte] data_type (3 = string),
* UTF-16 encoded string, hence all the 0's between each 8-byte character
*/
// Language description
uint8_t string0[] = { 4, 0x03, 0x09, 0x04}; // 0x0409 = English
// Vendor description
uint16_t string1[] = { STR_DESC(17),
'y','o','u','r',' ','c','u','s','t','o','m',' ','n','a','m','e'};
// Product description
uint16_t string2[] = { STR_DESC(13),
'P','r','o','d','u','c','t',' ','d','e','s','c'};
// Serial number description
uint16_t string3[] = { STR_DESC(8),
'1','2','3','-','3','2','1'};
// Configuration description
uint16_t string4[] = { STR_DESC(7),
'C','o','n','f','i','g'};
// Interface description
uint16_t string5[] = { STR_DESC(13),
'P','r','o','d','u','c','t',' ','n','a','m','e'};
Please note, the information in the descriptor is not just meta data of the device, it actually impacts how the device performs. For example, payload size of each transaction is decided by the interface/endpoint descriptor (max packet size MSB/LSB), RXFIFOSZ/TXFIFOSZ doesn’t seem to matter, nor does TXMAXP/RXMAXP. TXMAXP only affects when TXPKTRDY is set to 1. TXPKTRDY will automatically be set to 1 (when AUTOSET == 1) when FIFO has been loaded with data that is equal in size to TXMAXP. Similarly, RXPKTRDY automatically set to 1 when RXMAXP size of data has been received in FIFO. In other words, TXMAXP/RXMAXP are just software thresholds defining when to update TXPKTRDY /RXPKTRDY.