STM32 USB 从机:CDC 虚拟串口实现详解
👁 3 阅读 · 2026-08-14 · STM32 USB
STM32的USB从机功能可模拟虚拟串口,使MCU与PC通信时无需RS232电平转换,且即插即用。本文从USB CDC类原理入手,详细讲解基于STM32CubeMX和HAL库的CDC虚拟串口配置方法,提供完整的收发代码示例,并总结常见陷阱,帮助嵌入式开发者快速将USB CDC集成到实际项目中。
## 原理讲解
USB CDC(Communication Device Class)是USB标准中专门定义通信设备的类,其下分ACM(Abstract Control Model)、ECM等子类。STM32虚拟串口通常采用ACM子类,它包含两个数据端点(Bulk OUT/IN)和一条可选的通知端点(Interrupt IN)。数据端点负责传输用户数据,通知端点传输线路状态、编码格式等控制信息。
**与物理串口的区别:** 物理UART是异步串行,有波特率、停止位等参数;USB CDC将这些参数抽象为请求,实际传输是USB包格式,因此PC端虚拟串口的波特率设置不会影响实际传输速率,仅用于兼容应用层。
STM32的USB外设(如USB_OTG_FS、USB_FS Device)支持全速设备模式,使用内部48MHz时钟,配合DP上拉电阻实现连接检测。CubeMX自动生成初始化代码和CDC类驱动,开发者只需专注收发处理。
## 配置步骤
以下是基于STM32CubeMX(以F401RE为例)的配置流程:
1. **创建工程**:选择芯片,配置时钟树,确保USB外设时钟为48MHz(例如使用HSE+PLL,或内部48MHz时钟)。
2. **使能USB设备**:在`Connectivity`中找到`USB_OTG_FS`,选择`Device_Only`模式;或使用`USB_Device`(某些系列)。
3. **添加USB类**:在`Middleware`中点击`USB_DEVICE`,将`Class for FS IP`改为`Communication Device Class (VCP)`,生成代码时自动添加`usbd_cdc_if`等文件。
4. **确认端点配置**(可在`USB_DEVICE`配置面板查看):
- CDC_NOTIFICATION_EP:通常为端点1,Interrupt IN,最大包长8字节。
- CDC_DATA_OUT_EP / CDC_DATA_IN_EP:通常为端点2,Bulk,最大包长64字节。
5. **生成代码**:勾选`Generate peripheral initialization as a pair of .c/.h files`,生成后工程已包含标准CDC回调接口。
## 代码实现
核心文件是`usbd_cdc_if.c`,其中提供两个关键接口:
- `CDC_Transmit_FS(uint8_t *Buf, uint16_t Len)`:通过CDC发送数据到PC,返回`USBD_OK`或`USBD_BUSY`。
- `CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)`:接收PC发来的数据(回调函数),需在函数末尾重新调用`USBD_CDC_ReceivePacket`以开启下一次接收。
**完整echo示例**(将PC发送的数据原样返回):
```c
/* usbd_cdc_if.c */
#include "usbd_cdc_if.h"
static int8_t CDC_Init_FS(void) {
/* 初始化时启动接收 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return USBD_OK;
}
static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len) {
/* 将接收到的数据原样发送回PC */
uint8_t data[64];
uint16_t size = *Len;
memcpy(data, Buf, size);
CDC_Transmit_FS(data, size);
/* 重新开启下一次接收 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, Buf);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return USBD_OK;
}
```
**主动发送数据**(例如在main循环中发送定长数据):
```c
/* main.c */
#include "usbd_cdc_if.h"
extern USBD_HandleTypeDef hUsbDeviceFS;
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USB_DEVICE_Init(); /* 初始化USB CDC */
uint8_t msg[] = "Hello USB CDC\r\n";
while (1) {
CDC_Transmit_FS(msg, sizeof(msg)-1);
HAL_Delay(1000);
}
}
```
**重要提示**:`CDC_Transmit_FS`在数据发送完成前会返回`USBD_BUSY`,不可重复调用同一缓冲区,否则数据会被覆盖。建议使用无冲突的静态缓冲区或等待上一次发送完成。
## 注意事项
- **接收回调的“重臂”机制**:每次`CDC_Receive_FS`被调用后,必须重新调用`USBD_CDC_ReceivePacket`,否则后续数据无法接收。
- **端点缓冲区大小**:全速CDC数据端点最大包长64字节,`UserRxBufferFS`和`UserTxBufferFS`默认为2048字节,但单次收发不能超过端点包长,否则需自行分包。
- **中断安全性**:尽量不在USB中断回调中做耗时操作;若使用`CDC_Transmit_FS`发送,优先在裸机循环或RTOS任务中执行,避免长时间占用USB中断。
- **驱动安装**:Windows系统首次插入时自动安装CDC驱动,有串口标志;Linux/macOS无需驱动。若识别异常,检查DP上拉电阻和USB差分线布线。
- **波特率无效**:PC端任意波特率设置都不会影响USB传输,实际吞吐率由USB总线决定。
- **多实例问题**:若需多个虚拟串口,可增加CDC接口数量和端点,但普通MCU资源有限,需谨慎设计。
通过以上步骤,你已能在STM32上实现稳定可靠的USB虚拟串口。CDC不仅节省了UART端口和电平转换芯片,还支持即插即用,非常适合批量下载、日志输出、上位机交互等场景。