Home STM32 STM32 HAL STM32 USB CDC Example – Virtual COM Port Send & Receive (No UART)

STM32 USB CDC Example – Send & Receive Data Without UART (Virtual COM Port)

This tutorial explains how to set up a STM32 USB CDC (Virtual COM Port) for bidirectional communication with a PC—no UART required. You’ll learn how to use the STM32CubeMX tool to configure USB as a Communication Device Class (CDC) and implement both CDC_Transmit_FS and CDC_Receive_FS to send and receive data directly via USB. This is a perfect method for STM32 USB virtual COM port applications like logging, file transfer, and serial communication over USB—especially useful when UART pins are limited or unavailable.

STM32 USB CDC

Up to this point, we have relied on UART communication to interface with the computer, enabling the transmission and reception of useful data. Many STM32 development boards, such as the Nucleo and Discovery series, include an on-board ST-Link debugger, which also provides a Virtual COM Port. This feature simplifies the process of sending data to the computer over UART without the need for additional external hardware.

However, when working with custom boards—such as the popular Bluepill board—there is no built-in ST-Link. In such cases, an external USB-to-UART converter module is typically required to enable data transfer between the microcontroller and the computer.

In this tutorial, we will explore how to utilize the USB peripheral of STM32 to communicate directly with a PC, eliminating the need for UART. You will learn how to configure STM32 USB to both send and receive data, in a manner similar to traditional UART communication.

VIDEO TUTORIAL

You can check the video to see the complete explanation and working of this project.

Check out the Video Below

Introducing the STM32 USB Communication Device Class

The USB Communication Device Class (USB CDC) in STM32 is a protocol that allows the microcontroller to emulate a Virtual COM Port (VCP) over USB. This enables seamless serial communication with a computer without relying on traditional UART interfaces or additional hardware like USB-to-UART converters. By configuring the STM32 USB peripheral in CDC mode, developers can transmit and receive data directly via USB, making it highly efficient for debugging, logging, and data exchange applications.

Features of USB CDC in STM32:

  1. Virtual COM Port Emulation:
    Creates a virtual serial port on the PC, enabling standard serial communication without physical UART hardware.
  2. High-Speed Data Transfer:
    Supports faster data rates compared to traditional UART, limited only by USB specifications.
  3. Plug-and-Play Support:
    Works seamlessly with most operating systems (Windows, Linux, macOS) without requiring specialized drivers.
  4. Bidirectional Data Exchange:
    Enables simultaneous data transmission and reception, making it suitable for real-time communication applications.

Why Use STM32 USB Instead of UART for PC Communication?

Using USB instead of UART in STM32 offers several advantages:

  • No extra USB-to-Serial hardware required
  • Faster data rates than traditional UART
  • Plug-and-play Virtual COM Port on most operating systems
  • Frees up UART pins for other uses
  • Works well for logging, firmware updates, and user interface communication

For custom boards like Bluepill that lack built-in ST-Link, USB CDC provides a clean alternative to external serial converters.

STM32 CubeMX Configuration

Let’s start by enabling the USB peripheral in the Device mode.

STM32 USB Device Enable

Open the USB_DEVICE and configure it as shown below.

STM32 USB DEVICE Configuration

In the USB_DEVICE, select the class as Communication Device Class (Virtual Port Com) and leave everything to default.

Make sure the USB clock is configured to run at 48MHz as shown below.

STM32 USB Clock Configuration

Send Data from STM32 to PC Using USB CDC (CDC_Transmit_FS Example)

To send data to a PC using USB CDC, we need to use specific functions provided in the STM32 USB middleware. These functions are implemented in the following file:

USB_DEVICE -> App -> usbd_cdc_if.c

Transmit Function

The primary function used to transmit data is:

CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);

Parameters:

  • Buf – Pointer to the data buffer containing the data to be sent.
  • Len – The number of bytes (length) to be transmitted from the buffer.

This function sends the data over the USB Virtual COM Port to the connected PC.

Example Code for Transmission

#include "main.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
#include "string.h"

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t *data = "Hello World from USB CDC\n";
/* USER CODE END 0 */

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();   // Initialize the USB device stack

  while (1)
  {
    CDC_Transmit_FS(data, strlen(data)); // Send data via USB
    HAL_Delay(1000);                      // Wait for 1 second
  }
}

Step-by-Step Explanation of the Code

  1. Include Headers:
    The required header files (usb_device.h, usbd_cdc_if.h, and string.h) are included to use USB functions and string operations.
  2. Define Data Buffer:
    We define a pointer data containing the string "Hello World from USB CDC\n", which will be transmitted.
  3. Initialization:
    • HAL_Init() – Initializes the HAL Library.
    • SystemClock_Config() – Configures the system clock.
    • MX_GPIO_Init() – Initializes the GPIOs.
    • MX_USB_DEVICE_Init() – Sets up the USB device for CDC communication.
  4. Data Transmission Loop:
    Inside the while(1) loop, the function CDC_Transmit_FS() sends the content of data every 1 second using HAL_Delay(1000).

Verifying Data on the PC

STM32 USB CDC Detected on computer
  • When the code runs on the MCU, the PC detects the STM32 as a Virtual COM Port (VCP).
  • Open any serial monitor software (e.g., Hercules, Tera Term, or PuTTY) and select the correct COM port.
  • You will see the message “Hello World from USB CDC” printed every second.
Data received on computer via CDC

HOW TO RECEIVE DATA

CDC_Transmit_FS() is globally available, so we could call it directly from main.c.
CDC_Receive_FS(), however, is a static function defined inside USB_DEVICE → App → usbd_cdc_if.c, so it cannot be called or accessed from outside that file.
To use the received data in main.c, we expose a buffer defined in main.c and reference it from usbd_cdc_if.c.

The original receive callback

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  return (USBD_OK);
  /* USER CODE END 6 */
}

By default, it just re-arms the USB stack to receive the next packet and returns.

Create an application buffer in main.c

// main.c
uint8_t buffer[64];

This is where we’ll store the last packet received from the PC so it can be accessed in the main loop (or elsewhere in the application).

Extern the buffer inside usbd_cdc_if.c

Add an external declaration so the callback can write into that buffer:

/* USER CODE BEGIN PRIVATE_TYPES */
extern uint8_t buffer[64];
/* USER CODE END PRIVATE_TYPES */

Modify CDC_Receive_FS() to copy data into the buffer

static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  
  memset(buffer, '\0', 64);           // 1) clear our application buffer
  uint8_t len = (uint8_t)*Len;        // 2) get the actual received length
  memcpy(buffer, Buf, len);           // 3) copy data from USB buffer to our buffer
  memset(Buf, '\0', len);             // 4) clear the USB buffer too (prevents stale data)
  
  return (USBD_OK);
  /* USER CODE END 6 */
}

What those steps do

  1. Clear the destination buffer to remove any data from a previous reception.
  2. Read the actual received length from *Len.
  3. Copy the received bytes from Buf (USB driver buffer) into our global buffer[64].
  4. Clear the USB buffer (Buf) as well—otherwise the old data may persist.

STM32 USB Receive Example Explained (CDC_Receive_FS)

Receiving data via USB CDC requires modifying the default CDC_Receive_FS function. This callback reads data from the PC and passes it into a global buffer. You can:

  • Parse commands
  • Store data
  • Trigger application-specific events

To do this, expose a global buffer and use memcpy() in CDC_Receive_FS() to transfer incoming data. This lets your main code handle USB messages just like UART receive interrupts—but over USB.

RESULT

The result of the above code is shown in the images below

STM32 USB CDC Example

The controller was keep transmitting “Hello World from USB CDC“. When the PC sent the data “123456789” (Represented by Pink color on the console) to the controller, it gets stored in the buffer.
You can see the data stored in the buffer in the debugger’s Live Expression window as shown in the image above.

STM32 USB CDC Example

The same behavior was observed in this case as well. This time, a different set of data was transmitted, and it was successfully stored in the buffer.

Since the buffer is accessible from the main.c file, the received data can be processed further in any desired manner, such as parsing commands, logging information, or triggering specific application tasks.

With this STM32 USB CDC setup, you now have a flexible way to exchange serial data with a PC—no UART needed. You’ve learned to configure CubeMX, implement cdc_transmit_fs and cdc_receive_fs, and access USB buffers directly in main.c. This method supports fast communication and is ideal for projects with USB logging, user commands, or USB-based file exchange—all using a single USB cable.

This tutorial demonstrated how to establish communication between an STM32 microcontroller and a PC using USB CDC (Virtual COM Port) instead of UART. We configured the USB peripheral in Device Mode, used CDC_Transmit_FS() to send data, and modified CDC_Receive_FS() to store received data in a global buffer accessible from main.c. This method removes the need for external USB-to-UART converters, offers faster data transfer, and frees up UART interfaces for other tasks. As USB CDC is detected as a standard COM port by most operating systems, it provides a simple and efficient way to exchange data with the PC.

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

STM32 UART FAQs

You May Also Like

Subscribe
Notify of

24 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Ali
6 months ago

Hello Sir, hope you are doing well, i followed the steps from tutorial but the device is not showing up in com port of PC, i have tried different codes like led blink and all which works fine but the CDC doesn’t work , the code gets uploaded successfully , but no com port show on device manager . pls help.

Log output file: C:\Users\NW\AppData\Local\Temp\STM32CubeProgrammer_a05264.log
ST-LINK SN : 211405003212364D434B4E00
ST-LINK FW : V2J45S7
Board : —
Voltage : 3.19V
SWD freq : 4000 KHz
Connect mode: Under Reset
Reset mode : Hardware reset
Device ID : 0x410
Revision ID : Rev X
Device name : STM32F101/F102/F103 Medium-density
Flash size : 64 KBytes
Device type : MCU
Device CPU : Cortex-M3
BL Version : —

Opening and parsing file: ST-LINK_GDB_server_a05264.srec

Memory Programming …
File : ST-LINK_GDB_server_a05264.srec
Size : 29.15 KB
Address : 0x08000000

Erasing memory corresponding to segment 0:
Erasing internal memory sectors [0 29]
Download in Progress:

File download complete
Time elapsed during download operation: 00:00:01.887

Verifying …

Download verified successfully

Shutting down…
Exit.

saeed
10 months ago

Hi,
Thanks for the tenurial you have made.
please make and share another one for USB BULK TRANSFER application if it is viable for you.
many thanks

Mohammad
11 months ago

Thank you so much 🙏

mjh
1 year ago

I’m using a blue pill with stm32f103c8t6 using STlink-V2 when I flash the file to the blue pill the stlink disconnected and not able to debug and see the data I sent to the board.
How did you connect your blue pill board and stlink.
Very good tutorial.

Mehrdad
1 year ago

Hi, how should I receive unknown length of data from usb com port?I know that such operation in uart could be done using Idle line detector. but how about usb cdc receive?

Andy
1 year ago

after I press the reset button, data is no longer received by Hercules. I had to reconnect the USB to get it working again. Is there a way to prevent that?

neeraj
1 year ago

i am using STM32F413zh nucleo when i connect the micro-USB cable to PC there is nothing as appearance in device manager????
i connect D+ and D- in p12 and p11.
in device manage only st-link cable was showing.
what i do please help….

Karim
3 years ago

Salut, j’utilise un STM32H735G-DK. J’ai suivi votre tuto, et vu que les désignations de fonction diffèrent (STM32F & STM32H) j’ai du modifier le “F” de CDC_Transmit_FS par “H” ce qui m’a donné CDC_Transmit_HS. Malgré toutes mes tentatives, mon pc ne détecte pas le port vituel de mon MCU. Svp j’ai besoin d’aide . . . vous pouvez me contacter à mon adresse m.a.i.l: karimnana520 @gmail.com ou sur mon whatsapp actuel: +237 658747103

babak
3 years ago

I’m using stm32f429zit6 but my board is not detected by hercule. what am i do?

RyanSIlvers
3 years ago

This tutorial in Stdlibrary instead please

12val12
4 years ago

How recieve packet from PC

B0=254; // sync
B1 //number of variable
B2//Low byte
B3// byte
B4// byte
B5=//High Byte
Var[B1]=B2+B3<<8+B4<<16+B5<<32
If the stream fails, then in the incoming stream we look for synchrobyte 254 (skip bytes until we have received 254)

Nguyễn Tuấn Anh
4 years ago

how can i transmit a HEX value instead of char?
Thanks!

rajkumar
4 years ago

Hai, how can we send/transmit the adc value through the usb cdc virtual comfort, I am new to stm32 ,if anyone known please help me

ahmed
4 years ago

stm32f4 ws2812b with cubeide.

Tan Sang
4 years ago

That great! You can make video with NRF24L01, please!

reza
4 years ago

it’s great 😍

Last edited 4 years ago by reza
Peter
Reply to  reza
4 years ago

The USB_CDC_ReceivePacket is the acknowledge of receiving. It should be called after copy the data.

keyboard_arrow_up