原理讲解

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_OKUSBD_BUSY
  • CDC_Receive_FS(uint8_t *Buf, uint32_t *Len):接收PC发来的数据(回调函数),需在函数末尾重新调用USBD_CDC_ReceivePacket以开启下一次接收。

完整echo示例(将PC发送的数据原样返回):

/* 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循环中发送定长数据):

/* 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字节,UserRxBufferFSUserTxBufferFS默认为2048字节,但单次收发不能超过端点包长,否则需自行分包。
  • 中断安全性:尽量不在USB中断回调中做耗时操作;若使用CDC_Transmit_FS发送,优先在裸机循环或RTOS任务中执行,避免长时间占用USB中断。
  • 驱动安装:Windows系统首次插入时自动安装CDC驱动,有串口标志;Linux/macOS无需驱动。若识别异常,检查DP上拉电阻和USB差分线布线。
  • 波特率无效:PC端任意波特率设置都不会影响USB传输,实际吞吐率由USB总线决定。
  • 多实例问题:若需多个虚拟串口,可增加CDC接口数量和端点,但普通MCU资源有限,需谨慎设计。

通过以上步骤,你已能在STM32上实现稳定可靠的USB虚拟串口。CDC不仅节省了UART端口和电平转换芯片,还支持即插即用,非常适合批量下载、日志输出、上位机交互等场景。