-
-
Notifications
You must be signed in to change notification settings - Fork 2
Virtual COM Port
A CDC device consists of two interfaces and two endpoints. One endpoint is used for device management (setting the baud-rate, parity, stop bits, flow control, reporting changes of the serial line, etc.) and the other is used for the actual data.
The documentation for the definition is found in the CDC120 and PSTN120 documents from the USBorg. They are pretty readable at only ~30 pages each.
The device needs to mark itself as a CDC device, define which kind of control is required (modem, telephone, serial, ethernet, etc.) and depending on the control type also the version of the control sequences. Serial devices use the ACM (Abstract Control Model) for device management.
Field | Value | Meaning |
---|---|---|
Class | 0x02 | Communication Device Class |
SubClass | 0x00 | Refer to interface for value |
Protocol | 0x00 | Refer to interface for value |
Attention: When using an IAD, you'll need to define the SubClass & Protocol correctly (ie. as in the control interface) and can't defer the resolution to the interface.
We need to define two interfaces. One will contain the CDC-Definition with the management endpoint while the other will contain the data endpoint. The Control Interface will contain quite a bit of metadata to bundle everything together.
Field | Value | Meaning |
---|---|---|
Class | 0x02 | CDC |
SubClass | 0x02 | Use ACM |
Protocol | 0x01 | Use V.250 commands for ACM |
The control interface will include functional descriptors before the endpoint descriptors to define how the CDC-Device behaves, which features are supported and which interfaces belong to this function.
Source: CDC120, Table 15
This descriptor starts the list of function descriptors.
Field | Size | Value | Meaning |
---|---|---|---|
Length | 1 | 5 | Length of the Header descriptor |
Descriptor Type | 1 | 0x24 | This descriptor belongs to an interface descriptor |
Descriptor Subtype | 1 | 0x00 | This is a header descriptor |
CDC-Version | 2 | 0x0110 | Version 1.1 of the CDC definition |
Source: PSTN120, Table 4
This descriptor defines which control capabilities this CDC-Device supports (Network, Break, Flow control, Device specific communication). A Virtual COM Port only needs to support the Line Coding and Serial/Control line state.
Field | Size | Value | Meaning |
---|---|---|---|
Length | 1 | 4 | |
Descriptor Type | 1 | 0x24 | The descriptor belongs to an interface |
Descriptor SubType | 1 | 0x02 | This is an ACM descriptor |
Capabilities | 1 | 0x02 | Which capabilities this ACM descriptor supports; 0x02 for COM ports |
Source: CDC120, Table 16
This descriptor combines several interfaces into one functionality, in our case the notification and data interface.
Field | Size | Value | Meaning |
---|---|---|---|
Length | 1 | 5 | |
Descriptor Type | 1 | 0x24 | This descriptor belongs to an interface |
Descriptor SubType | 1 | 0x06 | This is a Union functional descriptor |
Management Interface ID | 1 | 0x00 | The ID of this interface, which contains all the declaration stuff and management endpoints |
Subinterface ID | 1 | 0x01 | The ID of the Data Interface that belongs to this CDC-Instance |
The Control Interface needs one Notification Interrupt IN-Endpoint when used with ATM to report changes in the serial connection (for virtual ports those don't have to be implemented).
Field | Value | Meaning |
---|---|---|
Class | 0x0A | Data Interface Class |
SubClass | 0x00 | Unused |
Protocol | 0x00 | No class specific protocol required |
The Data Interface needs an IN- and an OUT-Endpoint of type Bulk or Isochronous. Interrupt-endpoints are not supported.
Here is an example of a complete working CDC-Descriptor
Descriptor Configuration
// Example definition for a Virtual COM Port
static const USB_DESCRIPTOR_DEVICE DeviceDescriptor = {
.Length = 18,
.Type = 0x01,
.USBVersion = 0x0200,
.DeviceClass = 0x02,
.DeviceSubClass = 0x00,
.DeviceProtocol = 0x00,
.MaxPacketSize = 64,
.VendorID = 0xDEAD, // 0x0483,
.ProductID = 0xBEEF, // 0x5740, to show as STM32 Virtual Com Port
.DeviceVersion = 0x0100,
.strManufacturer = 0,
.strProduct = 0,
.strSerialNumber = 0,
.Configurations = 1};
// We need two interfaces. One for the CDC-Definition and one for the Data
static const USB_DESCRIPTOR_CONFIG ConfigDescriptor = {
.Length = 9,
.Type = 0x02,
.TotalLength = 62,
.Interfaces = 2,
.ConfigurationID = 1,
.strConfiguration = 0,
.Attributes = (1 << 7),
.MaxPower = 50};
// The CDC-Management interface
static const USB_DESCRIPTOR_INTERFACE CDCManagementInterface = {
.Length = 9,
.Type = 0x04,
.InterfaceID = 0,
.AlternateID = 0,
.Endpoints = 1,
.Class = 0x02,
.SubClass = 0x02,
.Protocol = 0x01,
.strInterface = 0};
// Some CDC Functional Headers
static const USB_DESC_FUNC_HEADER CDCFuncHeader = {
.Length = 5,
.Type = 0x24,
.SubType = 0x00,
.CDCVersion = 0x0110};
static const USB_DESC_FUNC_ACM CDCFuncACM = {
.Length = 4,
.Type = 0x24,
.SubType = 0x02,
.Capabilities = (1 << 1)};
static const USB_DESC_FUNC_UNION1 CDCFuncUnion = {
.Length = 5,
.Type = 0x24,
.SubType = 0x06,
.ControlInterface = 0,
.SubInterface0 = 1};
static const USB_DESCRIPTOR_ENDPOINT CDCNotificationEndpoint = {
.Length = 7,
.Type = 0x05,
.Address = (1 << 7) | 1,
.Attributes = 0x03,
.MaxPacketSize = 8,
.Interval = 0x10};
// The Data-Interface with two Bulk-Endpoints
static const USB_DESCRIPTOR_INTERFACE CDCDataInterface = {
.Length = 9,
.Type = 0x04,
.InterfaceID = 1,
.AlternateID = 0,
.Endpoints = 2,
.Class = 0x0A,
.SubClass = 0x00,
.Protocol = 0x00,
.strInterface = 0};
static const USB_DESCRIPTOR_ENDPOINT CDCDataEndpoints[2] = {
{.Length = 7,
.Type = 0x05,
.Address = (1 << 7) | 2,
.Attributes = 0x02,
.MaxPacketSize = 64,
.Interval = 0x00},
{.Length = 7,
.Type = 0x05,
.Address = 2,
.Attributes = 0x02,
.MaxPacketSize = 64,
.Interval = 0x00}};
char *USB_GetConfigDescriptor(short *length) {
if (ConfigurationBuffer[0] == 0) {
short offset = 0;
AddToDescriptor(&ConfigDescriptor, &offset);
AddToDescriptor(&CDCManagementInterface, &offset);
AddToDescriptor(&CDCFuncHeader, &offset);
AddToDescriptor(&CDCFuncACM, &offset);
AddToDescriptor(&CDCFuncUnion, &offset);
AddToDescriptor(&CDCNotificationEndpoint, &offset);
AddToDescriptor(&CDCDataInterface, &offset);
AddToDescriptor(&CDCDataEndpoints[0], &offset);
AddToDescriptor(&CDCDataEndpoints[1], &offset);
}
*length = sizeof(ConfigurationBuffer);
return ConfigurationBuffer;
}
The only thing we need to do is to handle the SetLineCoding
(0x20) and GetLineCoding
(0x21) control packets. They send / expect a seven byte array to configure the baud rate, stop bits, parity etc.
// Setup packets that are specific to CDC Devices
#define CDC_CONFIG_SETLINECODING 0x20
#define CDC_CONFIG_GETLINECODING 0x21
#define CDC_CONFIG_CONTROLLINESTATE 0x22
char buffer[64];
char lineCoding[7];
char CDC_SetupPacket(USB_SETUP_PACKET *setup, char *data, short length) {
// Windows requires us to remember the line coding
switch (setup->Request) {
case CDC_CONFIG_CONTROLLINESTATE:
break;
case CDC_CONFIG_GETLINECODING:
USB_Transmit(0, lineCoding, 7);
break;
case CDC_CONFIG_SETLINECODING:
for (int i = 0; i < 7; i++) {
lineCoding[i] = data[i];
}
return USB_OK;
break;
}
}
void CDC_HandlePacket(char ep, short length) {
// Just mirror the text
USB_Fetch(2, buffer, &length);
// do NOT busy wait. We are still in the ISR, it will never clear.
if (!USB_IsTransmitPending(2)) {
USB_Transmit(2, buffer, length);
}
}
Now the last part, wiring up the CDC-Device in usb_config
:
char *USB_GetConfigDescriptor(short *length) {
if (ConfigurationBuffer[0] == 0) {
short offset = 0;
AddToDescriptor(&ConfigDescriptor, &offset);
AddToDescriptor(&CDCManagementInterface, &offset);
AddToDescriptor(&CDCFuncHeader, &offset);
AddToDescriptor(&CDCFuncACM, &offset);
AddToDescriptor(&CDCFuncUnion, &offset);
AddToDescriptor(&CDCNotificationEndpoint, &offset);
AddToDescriptor(&CDCDataInterface, &offset);
AddToDescriptor(&CDCDataEndpoints[0], &offset);
AddToDescriptor(&CDCDataEndpoints[1], &offset);
}
*length = sizeof(ConfigurationBuffer);
return ConfigurationBuffer;
}
void USB_ConfigureEndpoints() {
// Configure all endpoints and route their reception to the functions that need them
USB_CONFIG_EP Notification = {
.EP = 1,
.RxBufferSize = 0,
.TxBufferSize = 8,
.Type = USB_EP_INTERRUPT};
USB_CONFIG_EP DataEP = {
.EP = 2,
.RxBufferSize = 64,
.TxBufferSize = 64,
.RxCallback = CDC_HandlePacket,
.Type = USB_EP_BULK};
USB_SetEPConfig(Notification);
USB_SetEPConfig(DataEP);
}
char USB_HandleClassSetup(USB_SETUP_PACKET *setup, char *data, short length) {
// Route the setup packets based on the Interface / Class Index
return CDC_SetupPacket(setup, data, length);
}