文章目录

  • 目的
  • 基础说明
  • 使用STM32CubeIDE配置生成代码
  • 用户代码分析
  • 回环测试
  • 串口参数设置
  • USB HS使用与演示
  • 通讯速率测试
    • 测试代码
    • USB FS测试
    • USB HS测试
    • 影响速度的因素
  • 总结

目的

以往来说单片机和电脑通讯通常使用串口,串口使用起来很方便,不管是单片机本身启用串口还是上位机些串口程序都很简单。但是串口速度并不快,如果想要更加高速的和上位机通讯可以使用USB接口。

使用USB中的CDC类来虚拟串口 Virtual COM Port (VCP)进行通讯是一种非常好用的方式,一方面对于上位机来说显示出来的就是个串口,所有操作都还是对串口的操作;另一方面实际数据传输是基于USB的,数据传输速度得到大大提升。本文将对STM32作为从设备使用USB的CDC类虚拟串口(VCP)进行通讯的相关内容做个说明。

基础说明

USB相对来说是一个比较复杂的东西,涉及的东西挺多,这里只对本文使用上需要了解的相关内容进行简单的说明。

从USB版本来说目前STM32系列MCU可以认为都是USB2.0的(现在还有了UCPD,对外接口外形可以是Type-C的,但是这个是只能用于PD3.0充电使用的,无法用于数据通讯)。

从硬件接口功能上来说STM32系列MCU的USB分为 USB_FSUSB_OTG_FSUSB_OTG_HS 三种。其中的FS指的是全速(Full Speed),HS指的是高速(High Speed)。OTG指的是既可以作为Device(从设备)使用,也可以作为Host(主机)使用。

Full Speed 理论上速度为12Mbit/s,High Speed 理论上速度为480Mbit/s ,当然这都是理论速度,实际上通讯速度还依赖于所用通讯方式和设备性能。

对于STM32系列MCU而言,USB FS的使用只要使用 DM / D-DP / D+ 这两个引脚就行了,最多也就加上ID、SOF、VBUS这三个引脚。而使用USB HS大多数还需要外接PHY芯片(比如USB3300),这样使用的引脚就多了,至少也要用到12个引脚。STM32系列MCU中目前只有STM32F723内置USB HS PHY功能,不需要外接PHY芯片。

STM32系列MCU在使用USB功能的时候建议使用外部时钟,外部无源晶体或有源晶振这些,因为USB对时钟精度要求比较高

STM32 CDC VCP对于win10和较新版本的linux来说是免驱的,对于低版本的windows系统需要安装驱动,驱动下载地址如下:
STSW-STM32102 STM32 Virtual COM Port Driver
https://www.st.com/en/development-tools/stsw-stm32102.html

使用STM32CubeIDE配置生成代码

用STM32CubeIDE来实现VCP功能非常简单,只要在接口中启用USB,然后在中间件中使用USB库的CDC功能就行:

上面配置中主要涉及三个部分:

  • 时钟配置
    使用USB推荐使用外部时钟,对于USB_FS而言其总线时钟一般为48MHz;
  • 启用USB接口
    接口中启用USB,这里仅作为从设备使用(Device_Only);
  • 使用USB设备库
    中间件中启用USB_DEVICE库,使用CDC类(Communication Device Class Virtual Port Com);
    参数设置主要与设备接口数量、电源、收发缓存等有关,一般默认即可;
    设备描述主要为VID和PID以及其文本描述;
    VID为厂商编码,可以在 https://www.usb.org/ 的 Developers 中找到,比如 1155 为 STMicroelectronics,如果你所在单位有自己的编码这里的VID和下面的厂商描述字符串就可以改成自己的;
    PID为产品编码,你可以自行更改这个编码和其下面的产品描述字符串;

按照上面的方式配置完成生成代码,直接编译下载到开发板中就可以进行测试了:

上图中可以看到在WIN10中显示出了该设备,设备被识别为串口设备,并且VID和PID和我们设置的相同。


上图是在树莓派中查看的,也可以被正确识别。

用户代码分析

上述配置生成的代码中,对于用户来说USB使用相关的代码都在 USB_DEVICE > App 中,这其中最重要的就是 usbd_cdc_if.c 文件,大多数时候我们只要改写这个文件就可以实现相关需求了,该文件主要结构与说明如下:

#include "usbd_cdc_if.h"// 数据收发缓存,这部分也可以完全由用户自行定义
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE]; // 接收缓存
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE]; // 发送缓存extern USBD_HandleTypeDef hUsbDeviceFS;// 初始化USB_CDC
static int8_t CDC_Init_FS(void)
{USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0); // 设置发送缓存USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS); // 设置接收缓存
}// 反初始化USB_CDC
static int8_t CDC_DeInit_FS(void){}// 来自主机的请求处理
// cmd: 命令代码
// pbuf & length: 请求数据指针与长度
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length)
{switch(cmd){/***********************************************************************************************//* Line Coding Structure                                                                       *//*---------------------------------------------------------------------------------------------*//* Offset | Field       | Size | Description                                                   *//* 0      | dwDTERate   |   4  | Data terminal rate, in bits per second                        *//* 4      | bCharFormat |   1  | Stop bits: 0 - 1 Stop bit; 1 - 1.5 Stop bits; 2 - 2 Stop bits *//* 5      | bParityType |   1  | Parity: 0 - None; 1 - Odd; 2 - Even; 3 - Mark; 4 - Space      *//* 6      | bDataBits   |   1  | Data bits (5, 6, 7, 8 or 16).                                 *//***********************************************************************************************/case CDC_SET_LINE_CODING: break; // 主机设置串口参数}
}// 接收回调函数
// Buf & Len: 当前收到这一包数据指针与长度
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); // 重新设置接收缓存// 注意默认情况下上面一行代码相当于 USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);USBD_CDC_ReceivePacket(&hUsbDeviceFS); // 重新启动数据接收
}// 数据发送函数
// Buf & Len: 要发送的数据指针与长度
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;if (hcdc->TxState != 0){return USBD_BUSY; // 如果当前USB繁忙则返回USBD_BUSY}USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len); // 设置要发送的数据result = USBD_CDC_TransmitPacket(&hUsbDeviceFS); // 发送数据
}// 发送完成回调函数
// Buf & Len: 所送的数据指针与长度
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum){}

上面代码中最常处理的只有下面四个函数:
CDC_Control_FS() 来自主机请求的回调函数
CDC_Receive_FS() 接收数据回调函数;
CDC_Transmit_FS() 用来发送数据;
CDC_TransmitCplt_FS() 发送完成回调函数;

回环测试

在官方生成的代码中只要添加一行代码就可以进行通讯回环测试了:

上面中在 CDC_Receive_FS 中添加了一行 CDC_Transmit_FS(Buf, *Len); 代码,可以看到演示中实现了回环效果,上位机不管发送什么数据下位机都会原样返回。并且可以发现不管上位机软件中波特率设置为多少都可以正常通讯,因为使用USB虚拟串口的时候真正数据传输用的是USB,串口本身参数这些已经无所谓了。

上面方式演示中其实存在一个问题,演示中并未体现出来,如果你尝试在上位机一次向下发送超过64个字节数据,那么你收到的返回数据可能就只有64个字节。USB数据的收发都是以一个一个包的形式进行的,包的大小一方面和USB协议有关,另一方面和程序有关。在上面工程的 usbd_cdc.h 文件中可以找到相关定义:

/* CDC Endpoints parameters:                                                         */
/* you can fine tune these values depending on the needed baudrates and performance. */
#define CDC_DATA_HS_MAX_PACKET_SIZE    512U  /* Endpoint IN & OUT Packet size */
#define CDC_DATA_FS_MAX_PACKET_SIZE    64U   /* Endpoint IN & OUT Packet size */
#define CDC_CMD_PACKET_SIZE            8U    /* Control Endpoint Packet size */#define CDC_DATA_HS_IN_PACKET_SIZE     CDC_DATA_HS_MAX_PACKET_SIZE
#define CDC_DATA_HS_OUT_PACKET_SIZE    CDC_DATA_HS_MAX_PACKET_SIZE#define CDC_DATA_FS_IN_PACKET_SIZE     CDC_DATA_FS_MAX_PACKET_SIZE
#define CDC_DATA_FS_OUT_PACKET_SIZE    CDC_DATA_FS_MAX_PACKET_SIZE

USB的 IN / OUT 都是从HOST而言说的,对于Device而言IN指的是发送,OUT指的是接收。 默认定义下USB_FS收发每一个包的大小均为64字节。 前面提到的大于64字节出现的问题中,第一次进入 CDC_Receive_FS 将接收到64个字节数据,这时候调用了 CDC_Transmit_FS 回发数据;接着重启接收将立马接到第二个包数据,然后第二次调用 CDC_Transmit_FS ,这个时候前一次的发送还未处理完成,此次调用将失败。

针对这个问题在使用的时候要合理设计数据收发逻辑。对于接收而言可以设计特殊字符用于标示一帧数据结束,或是设计超时时间来判断一帧数据结束。对于发送而言通常不会有太大问题,一次性发送大量数据也行,在全部发送完成后会触发发送完成回调函数CDC_TransmitCplt_FS;

串口参数设置

上一节中有说使用USB虚拟串口的时候真正数据传输用的是USB,串口本身参数这些已经无所谓了。不过有一种情况会需要这些串口参数,比如单片机一方面与上位机通过USB虚拟串口方式进行通讯,另一方面通过物理串口和其它模块进行通讯,单片机只做数据转发工作。这个时候单片机物理串口的参数就必须要能与其它模块匹配,这个参数如果要通过上位机来设置的话就需要对 CDC_Control_FS() 中的 CDC_SET_LINE_CODING 节点进行处理了:

要注意的是上位机每次设置串口参数可能会进入 CDC_SET_LINE_CODING 好几次。

USB HS使用与演示

目前来说除了STM32F723,其它支持USB HS的STM32系列MCU都需要外界的USB HS PHY芯片才能真正支持USB HS,最常见的比如USB3300,可以在某宝上买到相关的模块用来测试:

这个模块主要都是微雪电子或是防为微雪电子的,资料可以在这里找到 https://www.waveshare.net/wiki/USB3300_USB_HS_Board 。

这个模块支持做主从机和OTG切换实验用的,这里我们只用来测试当从机使用。作为从机使用时接线方式如下:

MCU端 USB HS模块 ULPI接口 USB3300模块端 ULPI接口 MCU端 USB HS模块 ULPI接口 USB3300模块端 ULPI接口
3.3V 3.3V GND GND
USB_HS_ULPI_STP STP USB_HS_ULPI_NXT NXT
USB_HS_ULPI_DIR DIR USB_HS_ULPI_CK CLK
USB_HS_ULPI_D0 DATA0 USB_HS_ULPI_D1 DATA1
USB_HS_ULPI_D2 DATA2 USB_HS_ULPI_D3 DATA3
USB_HS_ULPI_D4 DATA4 USB_HS_ULPI_D5 DATA5
USB_HS_ULPI_D6 DATA6 USB_HS_ULPI_D7 DATA7

接线上特别需要注意的一点是USB3300模块上的5V针脚并不能向外供电,而是使用OTG功能的时候需要从单片机向这里供电。另外在作为从机使用的时候USB线是插在这个模块的OTG接口上的。

下面是USB HS的配置与回环测试:

上面演示中使用了USB_OTG_HS,使能外部PHY为Devic only,要注意的是使用外部PHY时要保证AHB时钟频率不小于30MHz。(USB_OTG_HS也可以使能内部FS PHY,这样就可以作为另一个USB FS使用了。

USB HS的代码和USB FS差不多,主要是各个函数与变量中的的FS字符变成了HS而已。上面演示中在 CDC_Receive_HS 中添加了一行 CDC_Transmit_HS(Buf, *Len); 代码进行回环测试。 默认定义下USB_HS收发每一个包的大小均为512字节。

通讯速率测试

测试代码

相比硬件串口而言,USB虚拟的串口速度可以变得非常快,图形化的串口工具已经无法用来测速了,这里用python写了个脚本进行测试,测试脚本如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-from typing import Counter
import serial
import serial.tools.list_ports
import time
import datetimeprint('正在搜索串口……')
port_list = list(serial.tools.list_ports.comports())
print('发现串口:')
for i in range(0, len(port_list)):print(port_list[i])
print('')port = input('请输入串口号,并按回车确认: ')
print('')ser = serial.Serial(port, 22333, timeout=5)run = True
count = 0
starttime = int(round(time.time() * 1000))print(datetime.datetime.now().strftime('%H:%M:%S.%f') + ':开始测试单片机向上位机发送数据……')
ser.write('S'.encode('utf-8'))
while (run):ser.read(2048) # 接收来自单片机的数据count += 1currenttime = int(round(time.time() * 1000))run = False if (currenttime - starttime) >= 1000 else Trueser.write('E'.encode('utf-8'))
print(datetime.datetime.now().strftime('%H:%M:%S.%f') + ':结束测试,速度约为 ' + str(count * 2048 / 1000) + 'K Byte/s\n')sendbuf = bytes(2048)
run = True
count = 0
starttime = int(round(time.time() * 1000))print(datetime.datetime.now().strftime('%H:%M:%S.%f') + ':开始测试单片机接收上位机的数据……')
while (run):count += ser.write(sendbuf) # 向单片机发送数据currenttime = int(round(time.time() * 1000))run = False if (currenttime - starttime) >= 1000 else Trueprint(datetime.datetime.now().strftime('%H:%M:%S.%f') + ':结束测试,速度约为 ' + str(count / 1000) + 'K Byte/s\n')ser.close()
exit()

该脚本需要配合单片机程序使用,STM32CubeIDE中使用前面演示的默认配置生成代码后对 usbd_cdc_if.c 文件部分内容进行修改:

/****************************** 第一处修改点 ******************************/
int flag = 0;static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{if(Buf[0]=='S'){flag = 1;CDC_Transmit_FS(UserTxBufferFS, APP_TX_DATA_SIZE);}if(Buf[0]=='E'){flag = 0;}/* USER CODE BEGIN 6 */USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);USBD_CDC_ReceivePacket(&hUsbDeviceFS);return (USBD_OK);/* USER CODE END 6 */
}/****************************** 第二处修改点 ******************************/
static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{uint8_t result = USBD_OK;/* USER CODE BEGIN 13 */if(flag){CDC_Transmit_FS(UserTxBufferFS, APP_TX_DATA_SIZE);}UNUSED(Buf);UNUSED(Len);UNUSED(epnum);/* USER CODE END 13 */return result;
}

USB FS测试

准备好上面的内容就可以进行测速了(注意编译固件时使用Release版本):


上面两幅图分别为使用USB FS在win10和树莓派中的测试过程,可以看到使用USB FS虚拟串口向上位机发送数据的速度约为 1000KByte/s ,接收来自上位机数据的速度约为 900KByte/s。这个数据如果换算成串口波特率的话分别约为 10000000 和 9000000 ,可以看到这个速度非常不错了。

USB HS测试

接下来看看USB HS的速度表现(注意编译固件时使用Release版本):


上面测试中可以看到使用USB HS虚拟串口向上位机发送数据在win10和树莓派中速度分别约为 27000KByte/s16000KByte/s ,接收来自上位机数据的速度约为 11000KByte/s。换算成串口波特率的话最大都已经达到 270000000 了,比常见的9600波特率快了27000倍不止。

影响速度的因素

上面几个测试中可以看到速度还是有差异的,特别是USB HS中上位机接收来自单片机的测速中win10和树莓派中速度出现了较大差异,这里列一下影响速度的一些因素:

  • USB是主从协议,速度差异首先来自主机端

    • 主机硬件与系统性能; (USB HS中win10和树莓派速度差异来自于此)
    • 主机上对虚拟串口读写程序的性能; (上面的测试代码如果优化的好win10和树莓派中也可以测出相同的结果,我甚至于测出过30MByte/s的速度,不过那个代码不易于阅读)
  • 排除了主机端的因素后就是单片机自身了
  • 单片机性能;
  • 固件编译参数; (试试用Debug版本编译,USB HS的速度会差非常多)
  • 库程序性能; (比如调整前面提到的包大小这些就会影响速度)
  • 用户程序性能; (实际使用中这个才是最影响性能的)

总结

STM32 USB使用CDC类虚拟串口(VCP)进行通讯主要就是上面这些内容了,更多内容可以在 https://www.stmcu.com.cn/ 搜索 《USB CDC类入门培训》 ,这个文档讲的还算简单明了:

STM32 USB使用记录:使用CDC类虚拟串口(VCP)进行通讯相关推荐

  1. STM32Cube MX USB双设备MSC+CDC 实现虚拟U盘+虚拟串口

    前言 在上一篇文章实现USB虚拟U盘之后,项目需要用同一个USB口同时实现MSC和CDC功能,既能进行串口通信又能读取片外FLASH虚拟U盘.对于USB通用串行总线如果要真正搞明白这个协议还是比较困难 ...

  2. RT_Thread Studio使用——USB虚拟串口(VCP)

    硬件:正点原子阿波罗F429开发板,主控STM32F429IGT6 软件:RT-Thread Studio 2.2.5 RT-Thread 版本:4.1.0 在RT-Thread Studio中开启外 ...

  3. 如何让CDC类USB设备批量接收64字节以上数据

    很多STM32开发者在实现CDC类虚拟串口与PC主机通信过程中,有时会遇到点麻烦而不得其解.那就是当主机端单次发送的数据不超过64字节时,接收正常.一旦发送数据量大于64字节时就接收失败,总是出现丢包 ...

  4. STM32 USB开发

    作者 QQ群:852283276 微信:arm80x86 微信公众号:青儿创客基地 B站:主页 https://space.bilibili.com/208826118 参考 STM32 使用Cube ...

  5. USB VCP虚拟串口通讯详细配置步骤(STM32H732)

    USB VCP虚拟串口通讯详细配置步骤(STM32H732) 一.软件版本 二.CodeMX配置 1.使能外部高速时钟. 2.开启USB外设,选择USB Device功能,速度为默认全速USB设备12 ...

  6. android usb虚拟串口,USB 虚拟串口简介

    1. USB虚拟串口简介 USB虚拟串口属于USB通信设备类.在物理层通过USB总线,采用虚拟串口的方式为主机提供一个物理串口.在系统内部,USB控制器提供了一个批量传输IN端点和一个批量传输的OUT ...

  7. USB 虚拟串口简介

    1. USB虚拟串口简介 USB虚拟串口属于USB通信设备类.在物理层通过USB总线,采用虚拟串口的方式为主机提供一个物理串口.在系统内部,USB控制器提供了一个批量传输IN端点和一个批量传输的OUT ...

  8. STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)

    STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL ) STM32的USB功能模块可以配置为虚拟串口(VCOM: Visual Port Com)或人机交互设备(HID: Hu ...

  9. 自制ST-Link V2.1教程(SWD调试+虚拟串口+虚拟U盘)

    文章目录 一. 关于ST-LINK 二. 自制ST-LINK硬件 1. 原理图 2. PCB图 3. BOM表 三. 固件烧录 四. 固件更新 五. 上电测试 六. 相关链接 一. 关于ST-LINK ...

最新文章

  1. Java学习总结:42(字节流和字符流)
  2. 检测用户命令序列异常——使用LSTM分类算法【使用朴素贝叶斯,类似垃圾邮件分类的做法也可以,将命令序列看成是垃圾邮件】...
  3. Python3 数据结构:列表List中的方法
  4. 自己实现strstr函数与strchr函数
  5. kali linux u盘自启,如何实现Kali linux系统下的U盘启动(小白指导)
  6. Centos8 加密 GRUB 防破解root密码
  7. 【电脑使用】插入SD卡图标是灰色的,点击显示“请将磁盘插入驱动器”
  8. TI CC1310 sub1G的SDK开发之入门
  9. adguard home上网慢_AdGuard Home:用 DNS 巧去广告,所有设备都能用
  10. 抵制微信公众号,从我做起
  11. Golang多线程文件传输
  12. python心得总结知识点和收获,千锋Python培训学员心得 在总结中收获提升
  13. 03_跳转sucess方法和error方法
  14. php 2个数组并集,php中数组的并集、交集和差集函数介绍_PHP教程
  15. Python 医学知识图谱问答系统(一),建立医学知识图谱,基于neo4j知识图谱的医学问答体系
  16. java socket 域名解析_在java中,使用域名进行socket通讯
  17. 信息学奥赛对大学计算机专业,区别大盘点:信息学竞赛、信息学奥赛、NOI和IOI傻傻分不清楚...
  18. 计算智能课程设计报告
  19. 医学图像处理(一)——分割中常用的度量指标
  20. 高师培训计算机心得体会,双师型教师计算机培训心得体会

热门文章

  1. 归并排序详解(Acwing 归并排序y总模板)
  2. 归并排序-拓展至三路归并
  3. 用python画哆啦a梦的头,用python画多来a梦-【Python】绘制哆啦A梦
  4. win10 android软件下载,windows10模拟器安卓版
  5. MInd+实例4——公园人数计数系统
  6. c语言课程设计学生信息管理系统
  7. 大唐:我家阁楼通公主府(三)
  8. 实施化工厂人员定位的原因详解--新导智能
  9. Linux积累 - scp 远程复制 加端口
  10. awvs安装及问题解决