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.

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
- Introducing the STM32 USB Communication Device Class
- Why Use STM32 USB Instead of UART for PC Communication?
- STM32 CubeMX Configuration
- Send Data from STM32 to PC Using USB CDC (CDC_Transmit_FS Example)
- HOW TO RECEIVE DATA
- STM32 USB Receive Example Explained (CDC_Receive_FS)
- RESULT
- PROJECT DOWNLOAD
- STM32 UART FAQs
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:
- Virtual COM Port Emulation:
Creates a virtual serial port on the PC, enabling standard serial communication without physical UART hardware. - High-Speed Data Transfer:
Supports faster data rates compared to traditional UART, limited only by USB specifications. - Plug-and-Play Support:
Works seamlessly with most operating systems (Windows, Linux, macOS) without requiring specialized drivers. - 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.
Open the USB_DEVICE and configure it as shown below.
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.
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
- Include Headers:
The required header files (usb_device.h
,usbd_cdc_if.h
, andstring.h
) are included to use USB functions and string operations. - Define Data Buffer:
We define a pointerdata
containing the string"Hello World from USB CDC\n"
, which will be transmitted. - 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.
- Data Transmission Loop:
Inside thewhile(1)
loop, the functionCDC_Transmit_FS()
sends the content ofdata
every 1 second usingHAL_Delay(1000)
.
Verifying Data on the PC
- 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.
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
- Clear the destination buffer to remove any data from a previous reception.
- Read the actual received length from
*Len
. - Copy the received bytes from
Buf
(USB driver buffer) into our globalbuffer[64]
. - 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
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.
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
USB CDC (Communication Device Class) allows an STM32 microcontroller to emulate a Virtual COM Port over USB. This enables seamless data exchange with a PC, similar to UART, but without the need for an external USB-to-UART converter.
Most STM32 boards with a built-in USB peripheral support USB CDC. Boards like the Bluepill require enabling USB Device Mode in CubeMX and proper clock configuration to work as a Virtual COM Port.
You can use the CDC_Transmit_FS()
function from the usbd_cdc_if.c
file. It transmits data buffers directly to the PC over USB.
Data reception is handled inside the CDC_Receive_FS()
callback. By defining a buffer in main.c
and modifying this callback, you can store and process incoming data.
USB CDC provides faster data rates, does not require external hardware, and is recognized as a standard COM port by most operating systems, making development simpler and more efficient.
You May Also Like
🙏 Support Us by Disabling Adblock
We rely on ad revenue to keep Controllerstech free and regularly updated. If you enjoy the content and find it helpful, please consider whitelisting our website in your ad blocker.
We promise to keep ads minimal and non-intrusive.
Thank you for your support! 💙
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.
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
Thank you so much 🙏
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.
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?
The function int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) already receives the data of unknown length. Here Len is the pointer to the number of data byte received.
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?
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….
1. Why are you even doing this on the nucleo board ? You can simply send the data to the PC via the STlink using the UART2.
2. Nucleo STM32F413zh has one user USB at the bottom. Use it.
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
USB HS Does not supports virtual Communication. You have to use FS only
I’m using stm32f429zit6 but my board is not detected by hercule. what am i do?
if you are using the nucleo or discovery boards, make sure you connect to the USER USB, not the ST-Link
Also check if the cable supports the data transfer or not
This tutorial in Stdlibrary instead please
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)
how can i transmit a HEX value instead of char?
Thanks!
just send it simply. What’s the issue?
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
try sending the address to the value.
If can’t, then convert it to string and send
https://www.digikey.com/en/maker/projects/getting-started-with-stm32-working-with-adc-and-dma/f5009db3a3ed4370acaf545a3370c30c
stm32f4 ws2812b with cubeide.
That great! You can make video with NRF24L01, please!
it’s great 😍
The USB_CDC_ReceivePacket is the acknowledge of receiving. It should be called after copy the data.