For aUSB CDC-ACM (Virtual COM Port)device, the descriptor hierarchy typically looks like this:
Device Descriptor │ └── Configuration Descriptor │ ├── Interface 0 (Communication Class Interface, CIC) │ ├── Header Functional Descriptor │ ├── Call Management Functional Descriptor │ ├── ACM Functional Descriptor │ ├── Union Functional Descriptor │ └── Endpoint Descriptor (Interrupt IN) │ └── Interface 1 (Data Class Interface, DIC) ├── Endpoint Descriptor (Bulk OUT) └── Endpoint Descriptor (Bulk IN)Example CDC-ACM Descriptor Set
Communication Interface (Interface 0)
Interface Descriptor -------------------- bInterfaceClass = 0x02 (CDC) bInterfaceSubClass = 0x02 (ACM) bInterfaceProtocol = 0x01 (AT Commands) bNumEndpoints = 1Header Functional Descriptor
bFunctionLength = 5 bDescriptorType = 0x24 (CS_INTERFACE) bDescriptorSubType = 0x00 (Header) bcdCDC = 0x0120Call Management Descriptor
bFunctionLength = 5 bDescriptorType = 0x24 bDescriptorSubType = 0x01 bmCapabilities = 0x00 bDataInterface = 1ACM Functional Descriptor
bFunctionLength = 4 bDescriptorType = 0x24 bDescriptorSubType = 0x02 bmCapabilities: bit0 = Supports Set_Line_Coding bit1 = Supports Set_Control_Line_State bit2 = Supports Send_BreakCommon value:
bmCapabilities = 0x02 ``Union Functional Descriptor
bFunctionLength = 5 bDescriptorType = 0x24 bDescriptorSubType = 0x06 bMasterInterface = 0 bSlaveInterface0 = 1Notification Endpoint
Endpoint Descriptor ------------------- bEndpointAddress = 0x81 (Interrupt IN) bmAttributes = 0x03 wMaxPacketSize = 8 bInterval = 16Data Interface (Interface 1)
Interface Descriptor -------------------- bInterfaceClass = 0x0A (CDC Data) bInterfaceSubClass = 0x00 bInterfaceProtocol = 0x00 bNumEndpoints = 2Bulk OUT Endpoint
bEndpointAddress = 0x02 bmAttributes = 0x02 (Bulk) wMaxPacketSize = 64 (FS)Bulk IN Endpoint
bEndpointAddress = 0x82 bmAttributes = 0x02 (Bulk) wMaxPacketSize = 64 (FS)Descriptor Byte Layout
Many STM32/NXP/ESP32 examples use something close to:
/* Interface Association Descriptor (optional) */ 08 0B 00 02 02 02 01 00 /* Communication Interface */ 09 04 00 00 01 02 02 01 00 ** Header Functional */ 05 *4 00 20 01 /* Call Management */ 05 *4 01 00 01 /* ACM */ 04 24 02 02 /* Union */ 05 24 06 00 01 /* Interrupt IN EP */ 07 05*81 03 08 00 10* /* Data Interface */ 09 04 01 00 02 0A 00 00 00 /* Bulk OUT */ 07 05 02 02 40 00 00 /* Bulk IN */ 07 05 82 02 40 00 00Enumeration Result in Windows/Linux
After enumeration, the host issues CDC class requests such as:
SET_LINE_CODING GET_LINE_CODING SET_CONTROL_LINE_STATE SEND_BREAKSET_LINE_CODINGcarries UART settings:
dwDTERate = 115200 bCharFormat = 0 *(1 stop bit) bParityType = 0 (none) bDataBits = 8 ``This is why CDC-ACM appears as aCOM portin Windows and as/dev/ttyACM0in Linux. The actual UART settings are often ignored by MCU firmware unless bridged to a real UART. The CDC ACM subclass is defined within the USB Communications Device Class specification. [usb.org], [cscott.net]
Common Endpoint Map
EP0 Control EP1*IN Interrupt (Notifications) EP2*OUT Bulk (Host -> Device data) EP2*IN Bulk (Device -> Host data)This is the descriptor structure you'll see in STM32Cube USB CDC, TinyUSB CDC-ACM, Zephyr USB CDC, Linux gadget CDC-ACM, and most MCU virtual COM port implementations.