单片机中的USB

一般来说单片机与电脑的通讯常常使用串口,但是串口的速度并不快,想要更加高速的和上位机通讯可以使用USB接口。

USB中的CDC类来虚拟串口Virtual COM Port(VCP)就可以做到这个。

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

从硬件接口功能上来说STM32系列MCU的USB分为 USB_FS 、 USB_OTG_FS 、 USB_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芯片。

在HAL库CubeMX上的使用

在CubeMX上使用比较简单这边就先介绍这种用法

  1. 使用外部时钟,对于USB_FS来说总线时钟一般为48MHz
  2. 设置为Device_Only只作为从设备使用(根据具体使用情况来)
  3. 中间件中启用USB_DEVICE库,使用CDC类(Communication Device Class Virtual Port Com)

相关的代码在USB_DEVICE下的App中,最重要的就是 usbd_cdc_if.c 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#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() 发送完成回调函数;

一些需要注意的设置

USB收发数据都是以一个包一个包的形式进行的,包的大小一方面和USB的协议有关,一方面和程序有关,查看usbd_cdc.h文件

1
2
3
4
5
6
7
8
9
10
11
12
/* 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

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

用USB虚拟串口的时候真正数据传输用的是USB,串口本身的参数这些已经关系不大了,但是当单片机一方面要用USB虚拟串口又要用串口和其他模块进行通讯的时候就会出现问题。这时候就要对CDC_SET_LINE_CODING节点进行处理了。