三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

STM32CubeMX + HAL库:5分钟搞定USB虚拟串口(CDC)双向通信,含代码示例

STM32CubeMX + HAL库:5分钟搞定USB虚拟串口(CDC)双向通信,含代码示例

STM32CubeMX + HAL库:5分钟实现USB虚拟串口双向通信实战指南

当我们需要在嵌入式设备与PC之间建立快速可靠的数据通道时,USB CDC(通信设备类)协议无疑是最便捷的选择之一。相比传统串口转换芯片方案,内置USB CDC功能不仅能省去额外硬件成本,还能获得更高的传输速率和更稳定的连接。本文将手把手带您用STM32CubeMX和HAL库,在5分钟内构建完整的USB虚拟串口通信系统。

1. 环境准备与工程创建

在开始前,请确保已安装:

  • STM32CubeMX 6.x或更新版本
  • 对应系列芯片的HAL库(如STM32F4xx_DFP)
  • IDE(Keil MDK/IAR/STM32CubeIDE任选)

关键配置步骤:

  1. 新建工程选择目标芯片型号
  2. 在Pinout视图中启用USB_OTG_FS或USB_OTG_HS
  3. 在Middleware选项卡启用USB_DEVICE,选择CDC类
  4. 配置时钟树确保USB时钟为48MHz(重要!)

注意:对于F103等全速USB设备,必须使用外部晶振提供精确时钟源

时钟配置示例(STM32F407):

// System Clock Configuration RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // 168MHz RCC_OscInitStruct.PLL.PLLQ = 7; // 48MHz for USB

2. USB CDC核心函数解析

工程生成后,重点关注usbd_cdc_if.c中的两个关键函数:

2.1 数据接收处理

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len) { // 示例:将接收数据原样回传 CDC_Transmit_HS(Buf, *Len); // 必须重新设置接收缓冲区 USBD_CDC_SetRxBuffer(&hUsbDeviceHS, Buf); USBD_CDC_ReceivePacket(&hUsbDeviceHS); return (USBD_OK); }

2.2 数据发送接口

uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len) { uint8_t result = USBD_OK; // 检查上次传输是否完成 if (hUsbDeviceHS.pClassData != NULL) { if( ((USBD_CDC_HandleTypeDef*)hUsbDeviceHS.pClassData)->TxState != 0) return USBD_BUSY; } // 执行发送 USBD_CDC_SetTxBuffer(&hUsbDeviceHS, Buf, Len); result = USBD_CDC_TransmitPacket(&hUsbDeviceHS); return result; }

常见问题排查表:

现象可能原因解决方案
设备管理器显示未知设备未正确安装驱动安装STM32虚拟串口驱动
能识别但无法通信端点配置错误检查CubeMX中Endpoints配置
数据传输不稳定缓冲区溢出增大APP_RX_DATA_SIZE值

3. 实战:双向通信实现

3.1 数据回环测试

修改接收函数实现自发自收测试:

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len) { // 添加自定义处理逻辑 for(uint32_t i=0; i<*Len; i++){ Buf[i]++; // 简单数据变换 } // 回传处理后的数据 CDC_Transmit_HS(Buf, *Len); // 必须调用以下两行维持接收 USBD_CDC_SetRxBuffer(&hUsbDeviceHS, Buf); USBD_CDC_ReceivePacket(&hUsbDeviceHS); return (USBD_OK); }

3.2 主动发送数据示例

在主循环中添加定时发送:

while (1) { static uint8_t counter = 0; uint8_t msg[32]; int len = sprintf((char*)msg, "Count: %d\r\n", counter++); if(CDC_Transmit_HS(msg, len) != USBD_OK) { // 处理发送失败情况 } HAL_Delay(1000); }

4. 性能优化技巧

4.1 提升吞吐量的关键参数

// 修改usbd_conf.h中的以下定义 #define CDC_DATA_HS_MAX_PACKET_SIZE 512 // 最大包大小 #define APP_RX_DATA_SIZE 2048 // 接收缓冲区 #define APP_TX_DATA_SIZE 2048 // 发送缓冲区

4.2 DMA传输配置

  1. 在CubeMX中启用USB DMA
  2. 确保缓冲区地址对齐:
__ALIGN_BEGIN uint8_t UserRxBuffer[APP_RX_DATA_SIZE] __ALIGN_END; __ALIGN_BEGIN uint8_t UserTxBuffer[APP_TX_DATA_SIZE] __ALIGN_END;

4.3 流控制实现

添加发送状态检查机制:

uint8_t CDC_IsTransmitComplete(void) { return ((USBD_CDC_HandleTypeDef*)hUsbDeviceHS.pClassData)->TxState == 0; } // 使用示例 while(!CDC_IsTransmitComplete()) { osDelay(1); // 在RTOS中友好等待 }

5. 跨平台兼容性处理

不同操作系统对CDC设备的识别有所差异,可通过修改设备描述符提高兼容性:

// 修改usbd_cdc.c中的描述符 const USBD_DescriptorsTypeDef CDC_Desc = { .GetDeviceDescriptor = CDC_GetDeviceDescriptor, .GetLangIDStrDescriptor = CDC_GetLangIDStrDescriptor, .GetManufacturerStrDescriptor = CDC_GetManufacturerStrDescriptor, .GetProductStrDescriptor = CDC_GetProductStrDescriptor, .GetSerialStrDescriptor = CDC_GetSerialStrDescriptor, .GetConfigurationStrDescriptor = CDC_GetConfigurationStrDescriptor, .GetInterfaceStrDescriptor = CDC_GetInterfaceStrDescriptor };

Windows/Linux/macOS识别测试结果对比:

系统自动识别需额外驱动备注
Windows 10×需.inf文件
Linux×内置驱动
macOS×10.9+支持

实际项目中遇到的一个典型问题:当连续发送大量数据时,偶尔会出现数据包丢失。通过增加软件流控制(XON/XOFF)和双缓冲机制后,稳定性得到显著提升。具体实现是在应用层添加握手协议,当接收方缓冲区即将满时发送XOFF字符(0x13),空闲时发送XON字符(0x11)。

← 返回列表