3.8 CDC: Virtual Serial Port - How to use

The USB CDC Class Virtual Serial Port (VSP) implementation handles setup for serial communication over USB. It consists of a handler function and helper functions to access and transmit data.

The initialization of the Virtual Serial Port is done by calling the CDC Class initialization. No other initialization is needed. The virtual serial port is double buffered in terms of receiving data from the USB host. This is done because the host needs a buffer to store transmitted data and will overwrite any data that’s there.

The USB_CDCVirtualSerialPortHandler function handles all Virtual Serial Port related tasks. It needs to run in the main loop of the program or be called by interrupts when needed to ensure that the buffers don’t fill up resulting in loss of incoming data.

The handler takes care of both in- and outgoing transactions and deals with outgoing transactions as follows:
  1. Checks if the transmit buffer is empty - Skips the rest if it is
  2. Checks if the transmit pipe is busy - Skips the rest if it is
  3. Transmits data stored in the transmit buffer and calls the USB_CDCDataTransmitted callback function
The incoming transactions are handled as follows:
  1. Checks if the outgoing transactions succeeded - Skips the rest if not
  2. Checks if there is enough room in buffer to receive maximum size transaction - Skips rest if not
  3. Checks if the receive pipe is busy - Skips if it is
  4. Receives data if it is available and writes it to a temporary buffer and calls the USB_CDCDataReceived callback function to handle the received data

The USB_CDCDataReceived callback function moves the received data from the temporary receive buffer and adds it to a circular buffer that’s used for external communication. The USB_CDCDataTransmitted callback function resets the transmit buffer so that its ready for new data.

To access the received data, the USB_CDCRead function returns the next data byte from the receive buffer. The USB_CDCWrite function adds one provided byte to the transmit buffer. The CDC_TxBufferFull function returns true if the transmit buffer is full and can’t handle more data. These functions and the USB_CDCVirtualSerialPortHandler function use functions from the circular buffer implementation to add and retrieve data from the buffers.

The circular buffers are implemented to allow the USART and CDC to transmit data without having to worry about losing data. The CIRCBUF_Enqueue function adds data to buffer if the buffer is not full and returns the status of the action. The CIRCBUF_Dequeue function pulls one byte from the buffer as long as there is data in the buffer and returns the status of the action. The buffer implementation also has some helper functions: CIRCBUF_Empty and CIRCBUF_Full check if the buffer is empty or full while CIRCBUF_FreeSpace returns the number of empty slots in the buffer. A new buffer is created by creating an uint8_t array of the size of the resulting buffer and then using the CIRCULAR_BUFFER_t type defines to create a circular buffer using the previously created array:
STATIC uint8_t bufferArray[BUFFER_SIZE];
STATIC CIRCULAR_BUFFER_t circularBuffer = {
    .content = bufferArray,
    .head = 0,
    .tail = 0,
    .maxLength = BUFFER_SIZE
};