EmbeddedRelated.com

USB CDC

Category: Protocols

USB CDC (Communications Device Class) is a USB device class specification that defines a family of communication interfaces for USB devices. While it encompasses multiple subclasses - including Ethernet networking (ECM/NCM) and others - it is most commonly encountered in embedded contexts as CDC-ACM (Abstract Control Model), which allows a USB device to expose a serial-like interface to a host operating system. CDC-ACM is most commonly used to implement a virtual COM port, making a USB-connected microcontroller appear as a serial port (e.g., COM3 on Windows or /dev/ttyACM0 on Linux) without requiring custom drivers on modern operating systems.

In practice

USB CDC is one of the most common ways embedded developers add USB connectivity to a microcontroller-based device when the goal is simple bidirectional data exchange with a PC. Windows 10 (build 1703+), Linux, and macOS include built-in CDC-ACM drivers in most configurations, so no custom INF or kernel module is typically needed. Older Windows versions (7, 8) often required a vendor-supplied INF file pointing to usbser.sys. The host side sees a standard serial port, so any terminal emulator or serial library can communicate with the device without USB-specific code on the host.

On the firmware side, most MCU vendor SDKs and popular middleware stacks (STM32 USB Device Library, TinyUSB, USB CDC in Zephyr, Arduino CDC-ACM) provide a ready-made CDC-ACM class implementation. Depending on the stack, the developer may interact with a small set of transmit/receive API calls, though integration often still requires configuring descriptors, endpoints, or class callbacks. One common pitfall is assuming CDC behaves exactly like a hardware UART: the host OS can open and close the virtual port at will, and the device must handle the case where no host application has the port open, including handling DTR/RTS line-state changes that some stacks use as a connect signal.

Throughput is bounded by the USB full-speed (12 Mbps) or high-speed (480 Mbps) bus, the CDC bulk endpoint size (typically 64 bytes at full-speed), and host-side scheduling latency. In practice, full-speed CDC can achieve roughly 1-2 Mbps of actual data throughput in favorable conditions, though real-world results vary significantly with firmware buffering, packetization, and host stack behavior. Latency per transaction can be milliseconds due to OS USB scheduling. For applications needing lower latency or higher throughput, USB CDC with high-speed endpoints or alternative classes (HID, vendor-specific bulk) are worth evaluating.

A practical nuisance on Linux is that the device node name (/dev/ttyACM0, /dev/ttyACM1, etc.) is assigned dynamically based on enumeration order, and it can change between reboots or when other USB devices are connected. Using udev rules to assign persistent symlinks is a well-documented workaround, as covered in "How to Give Persistent Names To USB-Serial Devices on Ubuntu 14.04".

Discussed on EmbeddedRelated

Frequently asked

What is the difference between USB CDC-ACM and USB CDC?
USB CDC is the broad Communications Device Class umbrella, which includes multiple subclasses: ACM (Abstract Control Model, used for virtual serial ports), ECM/NCM (Ethernet networking), and others. In embedded contexts, 'USB CDC' almost always means CDC-ACM, the subclass that emulates a serial modem/UART interface. Strictly speaking, if your firmware enumerates as a virtual COM port, it is CDC-ACM.
Do I need to set the baud rate on the device side to match what the host configures?
No, in most CDC-ACM implementations the baud rate set by the host is advisory and is ignored by the firmware. The host sends a SET_LINE_CODING request containing the baud rate, but since data travels over USB bulk endpoints at USB speeds regardless, most firmware implementations simply acknowledge the request and discard the value. The exception is if your device bridges CDC to a real hardware UART: in that case, applying the received baud rate to the UART peripheral is common, though some bridge implementations ignore the host baud setting or only act on a subset of the line coding parameters.
Why does my CDC device stop sending data when no terminal is open on the host?
Many CDC-ACM implementations use the DTR (Data Terminal Ready) line-state signal, set by the host when a terminal application opens the port, as a flow-control or 'host connected' indicator. If the firmware checks DTR before transmitting and no application has the port open, transmissions are silently dropped or blocked. This behavior is configurable: some stacks transmit unconditionally regardless of DTR, while others gate transmission on it. Check your middleware's documentation and decide based on whether data loss during disconnection is acceptable.
What are the /dev/ttyACM* vs /dev/ttyUSB* device nodes on Linux?
/dev/ttyACM* nodes are created by the cdc_acm kernel driver for devices that correctly enumerate as USB CDC-ACM. /dev/ttyUSB* nodes are created by drivers for USB-to-serial bridge chips (FTDI FT232, Silicon Labs CP210x, Prolific PL2303, etc.), which appear as vendor-specific USB devices and use a different driver. If your microcontroller firmware implements CDC-ACM natively, it will appear as /dev/ttyACM*. If you use an external USB-UART bridge chip, it will appear as /dev/ttyUSB*.
Can I run USB CDC on an 8-bit or low-end microcontroller?
Yes, though with constraints. Parts like the Microchip PIC18F with a full-speed USB peripheral (e.g., PIC18F4550, PIC18F45K50) or the AVR with hardware USB (ATmega32U4, as used on Arduino Leonardo/Micro) can run CDC-ACM. Some software USB stacks (such as V-USB) can implement CDC-like functionality on AVRs without hardware USB, though support varies by stack and reliability and throughput are reduced. The main constraints are code size (a CDC stack can consume on the order of several to tens of KB of flash, depending on the implementation) and RAM for endpoint buffers, which can be tight on very low-RAM devices.

Differentiators vs similar concepts

USB CDC-ACM is often confused with USB-to-UART bridge chips (FTDI FT232R/FT2232, Silicon Labs CP210x, Prolific PL2303). Both present a virtual COM port to the host, but they work differently: an external bridge chip is a dedicated silicon component that sits between the host USB and a hardware UART line. Many such chips now work with inbox drivers on major operating systems, though driver requirements vary by chip and OS version. USB CDC-ACM is a firmware-only implementation running directly on a microcontroller with a native USB peripheral, using the OS's built-in cdc_acm driver. The end result looks the same to host software, but CDC-ACM eliminates the external chip and associated cost, at the expense of firmware complexity. USB CDC is also sometimes confused with USB HID or vendor-specific bulk endpoints, which are alternative approaches to moving data over USB that do not present a COM port abstraction to the host.