Files
dspico-usb-examples/examples/usb-video/arm7/source/usb_descriptors.cpp
2025-12-14 10:47:56 +01:00

338 lines
12 KiB
C++

#include "common.h"
#include "UsbStringDescriptor.h"
#include "tusb.h"
#include "usb_descriptors.h"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] VIDEO | AUDIO | MIDI | HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VIDEO, 5) | _PID_MAP(VENDOR, 6) )
#define USB_VID 0xCafe
#define USB_BCD 0x0200
enum
{
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
STRID_UVC_CONTROL,
STRID_UVC_STREAMING
};
const u8* tud_descriptor_device_cb(void)
{
static const tusb_desc_device_t deviceDescriptor =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = USB_BCD,
// Use Interface Association Descriptor (IAD) for Video
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_VID,
.idProduct = USB_PID,
.bcdDevice = 0x0100,
.iManufacturer = STRID_MANUFACTURER,
.iProduct = STRID_PRODUCT,
.iSerialNumber = STRID_SERIAL,
.bNumConfigurations = 0x01
};
return (const u8*)&deviceDescriptor;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
/* Time stamp base clock. It is a deprecated parameter. */
#define UVC_CLOCK_FREQUENCY 27000000
/* video capture path */
#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01
#define UVC_ENTITY_CAP_PROCESSING 0x02
#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x03
enum
{
ITF_NUM_VIDEO_CONTROL,
ITF_NUM_VIDEO_STREAMING,
ITF_NUM_TOTAL
};
#define EPNUM_VIDEO_IN 0x81
#define USE_ISO_STREAMING (!CFG_TUD_VIDEO_STREAMING_BULK)
typedef struct TU_ATTR_PACKED
{
tusb_desc_interface_t itf;
tusb_desc_video_control_header_1itf_t header;
tusb_desc_video_control_camera_terminal_t camera_terminal;
u8 processing_unit[13];
tusb_desc_video_control_output_terminal_t output_terminal;
} uvc_control_desc_t;
/* Windows support YUY2 and NV12
* https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview */
typedef struct TU_ATTR_PACKED
{
tusb_desc_interface_t itf;
tusb_desc_video_streaming_input_header_1byte_t header;
tusb_desc_video_format_uncompressed_t format;
tusb_desc_video_frame_uncompressed_2int_t frame;
tusb_desc_video_streaming_color_matching_t color;
#if USE_ISO_STREAMING
// For ISO streaming, USB spec requires to alternate interface
tusb_desc_interface_t itf_alt;
#endif
tusb_desc_endpoint_t ep;
} uvc_streaming_desc_t;
typedef struct TU_ATTR_PACKED
{
tusb_desc_configuration_t config;
tusb_desc_interface_assoc_t iad;
uvc_control_desc_t video_control;
uvc_streaming_desc_t video_streaming;
} uvc_cfg_desc_t;
const u8* tud_descriptor_configuration_cb(u8 index)
{
static const uvc_cfg_desc_t configurationDescriptor =
{
.config = {
.bLength = sizeof(tusb_desc_configuration_t),
.bDescriptorType = TUSB_DESC_CONFIGURATION,
.wTotalLength = sizeof(uvc_cfg_desc_t),
.bNumInterfaces = ITF_NUM_TOTAL,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = TU_BIT(7),
.bMaxPower = 100 / 2
},
.iad = {
.bLength = sizeof(tusb_desc_interface_assoc_t),
.bDescriptorType = TUSB_DESC_INTERFACE_ASSOCIATION,
.bFirstInterface = ITF_NUM_VIDEO_CONTROL,
.bInterfaceCount = 2,
.bFunctionClass = TUSB_CLASS_VIDEO,
.bFunctionSubClass = VIDEO_SUBCLASS_INTERFACE_COLLECTION,
.bFunctionProtocol = VIDEO_ITF_PROTOCOL_UNDEFINED,
.iFunction = 0
},
.video_control = {
.itf = {
.bLength = sizeof(tusb_desc_interface_t),
.bDescriptorType = TUSB_DESC_INTERFACE,
.bInterfaceNumber = ITF_NUM_VIDEO_CONTROL,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = TUSB_CLASS_VIDEO,
.bInterfaceSubClass = VIDEO_SUBCLASS_CONTROL,
.bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15,
.iInterface = STRID_UVC_CONTROL
},
.header = {
.bLength = sizeof(tusb_desc_video_control_header_1itf_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VC_HEADER,
.bcdUVC = VIDEO_BCD_1_50,
.wTotalLength = sizeof(uvc_control_desc_t) - sizeof(tusb_desc_interface_t)/* - sizeof(tusb_desc_endpoint_t) - 5*/, // CS VC descriptors only
.dwClockFrequency = UVC_CLOCK_FREQUENCY,
.bInCollection = 1,
.baInterfaceNr = { ITF_NUM_VIDEO_STREAMING }
},
.camera_terminal = {
.bLength = sizeof(tusb_desc_video_control_camera_terminal_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VC_INPUT_TERMINAL,
.bTerminalID = UVC_ENTITY_CAP_INPUT_TERMINAL,
.wTerminalType = VIDEO_ITT_CAMERA,
.bAssocTerminal = 0,
.iTerminal = 0,
.wObjectiveFocalLengthMin = 0,
.wObjectiveFocalLengthMax = 0,
.wOcularFocalLength = 0,
.bControlSize = 3,
.bmControls = { 0, 0, 0 }
},
.processing_unit = { 0x0D, 0x24, 0x05, UVC_ENTITY_CAP_PROCESSING, UVC_ENTITY_CAP_INPUT_TERMINAL, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 },
.output_terminal = {
.bLength = sizeof(tusb_desc_video_control_output_terminal_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VC_OUTPUT_TERMINAL,
.bTerminalID = UVC_ENTITY_CAP_OUTPUT_TERMINAL,
.wTerminalType = VIDEO_TT_STREAMING,
.bAssocTerminal = 0,
.bSourceID = UVC_ENTITY_CAP_PROCESSING,
.iTerminal = 0
}
},
.video_streaming = {
.itf = {
.bLength = sizeof(tusb_desc_interface_t),
.bDescriptorType = TUSB_DESC_INTERFACE,
.bInterfaceNumber = ITF_NUM_VIDEO_STREAMING,
.bAlternateSetting = 0,
.bNumEndpoints = CFG_TUD_VIDEO_STREAMING_BULK, // bulk 1, iso 0
.bInterfaceClass = TUSB_CLASS_VIDEO,
.bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING,
.bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15,
.iInterface = STRID_UVC_STREAMING
},
.header = {
.bLength = sizeof(tusb_desc_video_streaming_input_header_1byte_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VS_INPUT_HEADER,
.bNumFormats = 1,
.wTotalLength = sizeof(uvc_streaming_desc_t) - sizeof(tusb_desc_interface_t)
- sizeof(tusb_desc_endpoint_t) - (USE_ISO_STREAMING ? sizeof(tusb_desc_interface_t) : 0) , // CS VS descriptors only
.bEndpointAddress = EPNUM_VIDEO_IN,
.bmInfo = 0,
.bTerminalLink = UVC_ENTITY_CAP_OUTPUT_TERMINAL,
.bStillCaptureMethod = 0,
.bTriggerSupport = 0,
.bTriggerUsage = 0,
.bControlSize = 1,
.bmaControls = { 0 }
},
.format = {
.bLength = sizeof(tusb_desc_video_format_uncompressed_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED,
.bFormatIndex = 1, // 1-based index
.bNumFrameDescriptors = 1,
.guidFormat = { TUD_VIDEO_GUID_YUY2 },
.bBitsPerPixel = 16,
.bDefaultFrameIndex = 1,
.bAspectRatioX = 0,
.bAspectRatioY = 0,
.bmInterlaceFlags = 0,
.bCopyProtect = 0
},
.frame = {
.bLength = sizeof(tusb_desc_video_frame_uncompressed_2int_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED,
.bFrameIndex = 1, // 1-based index
.bmCapabilities = 0,
.wWidth = FRAME_WIDTH,
.wHeight = FRAME_HEIGHT,
.dwMinBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * 1,
.dwMaxBitRate = FRAME_WIDTH * FRAME_HEIGHT * 16 * FRAME_RATE,
.dwMaxVideoFrameBufferSize = FRAME_WIDTH * FRAME_HEIGHT * 16 / 8,
.dwDefaultFrameInterval = 10000000 / FRAME_RATE,
.bFrameIntervalType = 2, // 2 discrete
.dwFrameInterval = {
10000000 / FRAME_RATE,
10000000
}
},
.color = {
.bLength = sizeof(tusb_desc_video_streaming_color_matching_t),
.bDescriptorType = TUSB_DESC_CS_INTERFACE,
.bDescriptorSubType = VIDEO_CS_ITF_VS_COLORFORMAT,
.bColorPrimaries = VIDEO_COLOR_PRIMARIES_BT709,
.bTransferCharacteristics = VIDEO_COLOR_XFER_CH_BT709,
.bMatrixCoefficients = VIDEO_COLOR_COEF_BT709
},
#if USE_ISO_STREAMING
.itf_alt = {
.bLength = sizeof(tusb_desc_interface_t),
.bDescriptorType = TUSB_DESC_INTERFACE,
.bInterfaceNumber = ITF_NUM_VIDEO_STREAMING,
.bAlternateSetting = 1,
.bNumEndpoints = 1,
.bInterfaceClass = TUSB_CLASS_VIDEO,
.bInterfaceSubClass = VIDEO_SUBCLASS_STREAMING,
.bInterfaceProtocol = VIDEO_ITF_PROTOCOL_15,
.iInterface = STRID_UVC_STREAMING
},
#endif
.ep = {
.bLength = sizeof(tusb_desc_endpoint_t),
.bDescriptorType = TUSB_DESC_ENDPOINT,
.bEndpointAddress = EPNUM_VIDEO_IN,
.bmAttributes = {
.xfer = CFG_TUD_VIDEO_STREAMING_BULK ? TUSB_XFER_BULK : TUSB_XFER_ISOCHRONOUS,
.sync = CFG_TUD_VIDEO_STREAMING_BULK ? 0 : 1 // asynchronous
},
.wMaxPacketSize = CFG_TUD_VIDEO_STREAMING_BULK ? 64 : CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE,
.bInterval = 1
}
}
};
return (const u8*)&configurationDescriptor;
}
const u16* tud_descriptor_string_cb(u8 index, u16 langid)
{
switch (index)
{
case STRID_LANGID:
{
static const UsbStringDescriptor<2> descriptor(0x0409);
return (const u16*)&descriptor;
}
case STRID_MANUFACTURER:
{
return USB_STRING_DESCRIPTOR(u"LNH");
}
case STRID_PRODUCT:
{
return USB_STRING_DESCRIPTOR(u"DSpico Camera Example");
}
case STRID_SERIAL:
{
return USB_STRING_DESCRIPTOR(u"123456789");
}
case STRID_UVC_CONTROL:
{
return USB_STRING_DESCRIPTOR(u"UVC Control");
}
case STRID_UVC_STREAMING:
{
return USB_STRING_DESCRIPTOR(u"UVC Streaming");
}
default:
{
return nullptr;
}
}
}