文章目录
一.概要
GD32F407VET6USB虚拟串口是一种采用GD32F407VET6单片机,通过USB接口连接电脑,将电脑的USB接口转换成串口接口,实现与电脑的通信的一种转换器。它可以实现与电脑的通信,还可以实现与外部设备的通信,广泛应用于工业控制、智能家居、智能硬件等领域。
本文介绍了GD32单片机USB口的基本概念,内部结构,以及用USB虚拟串口进行数据通讯的例程。
二.USB2.0基本介绍及虚拟串口介绍
USB2.0使用一对差分信号传输数据,并可以为USB设备提供电源。差分信号名称一般标示为“D+”和“D-”。
USB2.0可以支持三种传输速率:低速USB设备传输速率为1.5Mbps,全速USB设备传输速率为12Mbps,高速USB设备传输速率为480Mbps。GD32F407VET6支持全速USB设备传输,最快12Mbps。
在硬件电路方面,全速USB设备内部的“D+”信号应该通过1.5K的电阻上拉到3~3.6V,单片机USB口原理图如下所示,PA11,PA12连接单片机对应的引脚。
USB虚拟串口,简称VCP,是Virtual COM Port的简写,它是利用 USB的 CDC类来实现的一种通信接口。我们可以利用GD32自带的USB功能,来实现一个USB虚拟串口,从而通过USB,实现电脑与GD32单片机的数据互传。
三.GD32单片机USB模块框图
USB全速(USBFS)控制器为便携式设备提供了一套USB通信解决方案。USBFS不仅提供了主机模式和设备模式,也提供了遵循HNP(主机协商协议)和SRP(会话请求协议)的OTG模式。USBFS包含了一个内部的全速USB PHY,并且不再需要外部PHY芯片。USBFS可提供USB 2.0协议所定义的所有四种传输方式(控制传输、批量传输、中断传输和同步传输)。
SIE
硬件识别同步信号、进行比特填充、产生以及校验CRC、产生以及验证PID、握手 。根据外设事件来产生SOF、复位信号等。
USB FS PHY
内部PHY支持主机模式下的全速和低速、设备模式下全速以及具备HNP和SRP的OTG协议。USBFS所使用的USB时钟需要配置为48MHz。该48MHz USB时钟从系统内部时钟产生,并且其时钟源和分频器需要在RCU模块中配置。
OTG Control
OTG Control模块主要用于管理其集成的USB On-The-Go(OTG)功能,实现设备在主机(Host)和从设备(Device)模式间的动态切换及控制。主要功能有模式切换(主机/设备)、电源管理(比如VBUS供电的控制)、会话请求协议(Session Request Protocol, SRP)和主机协商协议(Host Negotiation Protocol, HNP),以及相关的寄存器配置。
Host Port Control
Host Port Control模块专门用于管理和控制其USB OTG(On-The-Go)功能中的主机模式(Host Mode)操作。当单片机作为USB主机时,该模块负责与连接的USB从设备(如U盘、键盘、鼠标等)进行通信、供电及数据传输的底层控制。
根据USB标准定义,USB全速模块采用了固定的48MHz时钟。要使用USBD,需要打开两个时钟,一个是USB控制器时钟,它的频率必须配到48MHz,另一个是APB1到USB接口时钟,它也是APB1的总线时钟,其频率可以高于也可以低于48MHz。
四.GD32单片机USB设备模式
USB一般有两种模式,主机模式,设备模式。
USB主机模式:在主机模式下,单片机能够枚举外部USB设备,如键盘、鼠标、闪存盘等,并对其进行配置和管理。这种模式适用于需要同时连接多个外部设备并进行数据交换的复杂应用场景。
USB设备模式:在设备模式下,单片机作为USB设备的角色,可以与主机进行通信。这包括配置USB设备描述符、初始化USB控制器、编写类处理函数等,以实现特定的通信需求。GD32支持多种USB类,如CDC(通信设备类)、HID(人机接口设备类)、MSC(大容量存储类)等,以满足不同的应用场景。
GD32F407VET6支持主机模式也支持设备模式,在设备模式下,GD32可以模拟各种USB类设备,如键盘、鼠标、存储设备等,开发者需要配置USB接口并实现特定的USB类。
五.GD32F407VET6 USB设备CDC类
CDC(Communication Device Class)类是 USB2.0 标准下的一个子类,定义了通信相关设备的抽象集合,我们虚拟串口通信就是CDC类。USB2.0标准下定义了很多子类,有音频类,CDC类,HID,打印,大容量存储类HUB,智能卡等等,这些在urb.org 官网上有具体的定义,这里我们主要讲的是通信类CDC。
USB CDC类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上USB依然是可以通信的,之所以会有虚拟串口操作,主要是我们通常使用PC作为Host端,在PC端使用一个串口工具来与其进行通信,PC端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便PC端软件通过操作串口的方式来与其进行通信,但实际上,Host端与Device端物理上是通过USB总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的USB操作。这里需要注意地是,Host端与Device端的USB通信速率并不受所谓的串口波特率影响,它就是标准的USB2.0全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问USB外设有效速率(两边)以及外部环境对通信本身造成的干扰率等等因素组成。
USB CDC(Communication Device Class)类的枚举是USB设备插入主机时,主机识别其为通信设备(如虚拟串口)并完成配置的关键过程。其核心在于描述符的声明和接口的划分,确保主机能够正确加载驱动并建立通信通道。
CDC枚举的主要流程
1.设备插入与复位
2.设备描述符(Device Descriptor)
3.配置描述符(Configuration Descriptor)
4.CDC类特定描述符(Class-Specific Descriptors)
5.端点描述符(Endpoint Descriptor)
6.主机响应流程
CDC软件框架简介
当USBD设备初始化且枚举完成后,USB设备首先通过cdc_acm_check_ready()函数check是否准备数据发送,如果不需要发送就调用cdc_acm_data_receive()函数接收上位机发送的数据,如果需要发送就调用cdc_acm_data_send()将接收到的数据发送给主机。
设备描述符如下所示,其中bDeviceClass 为0x02,表明当前设备为CDC设备类。
__ALIGN_BEGIN const usb_desc_dev cdc_dev_desc __ALIGN_END =
{
.header =
{
.bLength = USB_DEV_DESC_LEN,
.bDescriptorType = USB_DESCTYPE_DEV,
},
.bcdUSB = 0x0200U,
.bDeviceClass = USB_CLASS_CDC,
.bDeviceSubClass = 0x00U,
.bDeviceProtocol = 0x00U,
.bMaxPacketSize0 = USB_FS_EP0_MAX_LEN,
.idVendor = USBD_VID,
.idProduct = USBD_PID,
.bcdDevice = 0x0100U,
.iManufacturer = STR_IDX_MFC,
.iProduct = STR_IDX_PRODUCT,
.iSerialNumber = STR_IDX_SERIAL,
.bNumberConfigurations = USBD_CFG_MAX_NUM,
};
由配置描述符可知,该USB虚拟串口设备包含两个接口:CMD命令接口和data数据接口。CMD命令接口包含一个IN端点,用于传输命令,该端点采用中断传输方式,轮询间隔为5ms,最大包长为8字节。data数据接口包含一个OUT端点和一个IN端点,这两个端点均采用批量传输方式,最大包长为USB_CDC_DATA_PACKET_SIZE(64)字节。另外,该配置描述符中包含了一些类特殊接口描述符,具体请读者参阅CDC类标准协议。
/* USB device configuration descriptor */
__ALIGN_BEGIN const usb_cdc_desc_config_set cdc_config_desc __ALIGN_END =
{
.config =
{
.header =
{
.bLength = sizeof(usb_desc_config),
.bDescriptorType = USB_DESCTYPE_CONFIG,
},
.wTotalLength = USB_CDC_ACM_CONFIG_DESC_SIZE,
.bNumInterfaces = 0x02U,
.bConfigurationValue = 0x01U,
.iConfiguration = 0x00U,
.bmAttributes = 0x80U,
.bMaxPower = 0x32U
},
.cmd_itf =
{
.header =
{
.bLength = sizeof(usb_desc_itf),
.bDescriptorType = USB_DESCTYPE_ITF
},
.bInterfaceNumber = 0x00U,
.bAlternateSetting = 0x00U,
.bNumEndpoints = 0x01U,
.bInterfaceClass = USB_CLASS_CDC,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_CDC_PROTOCOL_AT,
.iInterface = 0x00U
},
.cdc_header =
{
.header =
{
.bLength = sizeof(usb_desc_header_func),
.bDescriptorType = USB_DESCTYPE_CS_INTERFACE
},
.bDescriptorSubtype = 0x00U,
.bcdCDC = 0x0110U
},
.cdc_call_managment =
{
.header =
{
.bLength = sizeof(usb_desc_call_managment_func),
.bDescriptorType = USB_DESCTYPE_CS_INTERFACE
},
.bDescriptorSubtype = 0x01U,
.bmCapabilities = 0x00U,
.bDataInterface = 0x01U
},
.cdc_acm =
{
.header =
{
.bLength = sizeof(usb_desc_acm_func),
.bDescriptorType = USB_DESCTYPE_CS_INTERFACE
},
.bDescriptorSubtype = 0x02U,
.bmCapabilities = 0x02U,
},
.cdc_union =
{
.header =
{
.bLength = sizeof(usb_desc_union_func),
.bDescriptorType = USB_DESCTYPE_CS_INTERFACE
},
.bDescriptorSubtype = 0x06U,
.bMasterInterface = 0x00U,
.bSlaveInterface0 = 0x01U,
},
.cdc_cmd_endpoint =
{
.header =
{
.bLength = sizeof(usb_desc_ep),
.bDescriptorType = USB_DESCTYPE_EP,
},
.bEndpointAddress = CDC_CMD_EP,
.bmAttributes = USB_EP_ATTR_INT,
.wMaxPacketSize = USB_CDC_CMD_PACKET_SIZE,
.bInterval = 0x0AU
},
.cdc_data_interface =
{
.header =
{
.bLength = sizeof(usb_desc_itf),
.bDescriptorType = USB_DESCTYPE_ITF,
},
.bInterfaceNumber = 0x01U,
.bAlternateSetting = 0x00U,
.bNumEndpoints = 0x02U,
.bInterfaceClass = USB_CLASS_DATA,
.bInterfaceSubClass = 0x00U,
.bInterfaceProtocol = USB_CDC_PROTOCOL_NONE,
.iInterface = 0x00U
},
.cdc_out_endpoint =
{
.header =
{
.bLength = sizeof(usb_desc_ep),
.bDescriptorType = USB_DESCTYPE_EP,
},
.bEndpointAddress = CDC_DATA_OUT_EP,
.bmAttributes = USB_EP_ATTR_BULK,
.wMaxPacketSize = USB_CDC_DATA_PACKET_SIZE,
.bInterval = 0x00U
},
.cdc_in_endpoint =
{
.header =
{
.bLength = sizeof(usb_desc_ep),
.bDescriptorType = USB_DESCTYPE_EP
},
.bEndpointAddress = CDC_DATA_IN_EP,
.bmAttributes = USB_EP_ATTR_BULK,
.wMaxPacketSize = USB_CDC_DATA_PACKET_SIZE,
.bInterval = 0x00U
}
};
为了实现CDC设备类,设备需要支持一些设备类专用请求,这些类专用请求的处理在cdc_acm_req ()函数中,该函数的定义如下所示,其中SET_LINE_CODING命令用于响应主机向设备发送设备配置,包括波特率、停止位、字符位数等,收到的数据保存在noti_bu内。GET_LINE_CODING命令用于主机请求设备当前的波特率、停止位、奇偶校验位和字符位数,但在本例程中,主机并未请求该命令,所以设备所设置的串口数据并没有作用,主机可以选择任意波特率与设备进行通信。
static uint8_t cdc_acm_req (usb_dev *udev, usb_req *req)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
usb_transc *transc = NULL;
switch (req->bRequest) {
case SEND_ENCAPSULATED_COMMAND:
/* no operation for this driver */
break;
case GET_ENCAPSULATED_RESPONSE:
/* no operation for this driver */
break;
case SET_COMM_FEATURE:
/* no operation for this driver */
break;
case GET_COMM_FEATURE:
/* no operation for this driver */
break;
case CLEAR_COMM_FEATURE:
/* no operation for this driver */
break;
case SET_LINE_CODING:
transc = &udev->dev.transc_out[0];
/* set the value of the current command to be processed */
udev->dev.class_core->alter_set = req->bRequest;
/* enable EP0 prepare to receive command data packet */
transc->remain_len = req->wLength;
transc->xfer_buf = cdc->cmd;
break;
case GET_LINE_CODING:
transc = &udev->dev.transc_in[0];
cdc->cmd[0] = (uint8_t)(cdc->line_coding.dwDTERate);
cdc->cmd[1] = (uint8_t)(cdc->line_coding.dwDTERate >> 8);
cdc->cmd[2] = (uint8_t)(cdc->line_coding.dwDTERate >> 16);
cdc->cmd[3] = (uint8_t)(cdc->line_coding.dwDTERate >> 24);
cdc->cmd[4] = cdc->line_coding.bCharFormat;
cdc->cmd[5] = cdc->line_coding.bParityType;
cdc->cmd[6] = cdc->line_coding.bDataBits;
transc->xfer_buf = cdc->cmd;
transc->remain_len = 7U;
break;
case SET_CONTROL_LINE_STATE:
/* no operation for this driver */
break;
case SEND_BREAK:
/* no operation for this driver */
break;
default:
break;
}
return USBD_OK;
}
数据接收
通过cdc_acm_data_receive()函数实现,该函数的程序如下所示。在该函数中,首先将packet_receive标志位设置为0,表明接下来将进行接收数据,当接收完成时,在cdc_acm_out ()函数中,将packet_receive标志位置1,表明数据接收完成。usbd_ep_recev()用于配置接收操作,利用CDC_OUT_EP端点,将接收到的数据放置在用户缓冲区中。
/*!
\brief receive CDC ACM data
\param[in] udev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_receive (usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
cdc->packet_receive = 0U;
cdc->packet_sent = 0U;
usbd_ep_recev(udev, CDC_DATA_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_DATA_PACKET_SIZE);
}
/*!
\brief handle CDC ACM data
\param[in] udev: pointer to USB device instance
\param[in] ep_num: endpoint identifier
\param[out] none
\retval USB device operation status
*/
static uint8_t cdc_acm_out (usb_dev *udev, uint8_t ep_num)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
cdc->packet_receive = 1U;
cdc->receive_length = ((usb_core_driver *)udev)->dev.transc_out[ep_num].xfer_count;
return USBD_OK;
}
数据发送
通过cdc_acm_data_send()函数实现,该函数的程序如下所示。在该函数中,首先将packet_sent标志位设置为0,表明接下来将进行发送数据,当数据发送完成时,在cdc_acm_in ()函数中,将packet_sent标志位设置为1,表明数据发送完成。usbd_ep_send ()用于配置发送操作,利用CDC_IN_EP端点,将以 cdc->data地址为起始,cdc->receive_length长度的数据发送给主机。
/*!
\brief send CDC ACM data
\param[in] udev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_send (usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
if (0U != cdc->receive_length) {
cdc->packet_sent = 0U;
usbd_ep_send (udev, CDC_DATA_IN_EP, (uint8_t*)(cdc->data), cdc->receive_length);
cdc->receive_length = 0U;
}
}
/*!
\brief handle CDC ACM data
\param[in] udev: pointer to USB device instance
\param[in] ep_num: endpoint identifier
\param[out] none
\retval USB device operation status
*/
static uint8_t cdc_acm_in (usb_dev *udev, uint8_t ep_num)
{
usb_transc *transc = &udev->dev.transc_in[EP_ID(ep_num)];
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
if ((0U == transc->xfer_len % transc->max_len) && (0U != transc->xfer_len)) {
usbd_ep_send (udev, ep_num, NULL, 0U);
} else {
cdc->packet_sent = 1U;
}
return USBD_OK;
}
六.配置一个USB虚拟串口收发例程
STLINK接GD32F407VET6开发板,STLINK接电脑USB口,5V USB线接板子与电脑。
主要代码
#include "gd32f4xx.h"
#include "gd32f4xx_libopt.h"
#include "systick.h"
#include "usbd_conf.h"
#include "drv_usb_hw.h"
#include "cdc_acm_core.h"
usb_core_driver cdc_acm;
int main(void)
{
usb_gpio_config();
usb_rcu_config();
usb_timer_init();
usbd_init (&cdc_acm,
#ifdef USE_USB_FS
USB_CORE_ENUM_FS,
#elif defined(USE_USB_HS)
USB_CORE_ENUM_HS,
#endif /* USE_USB_FS */
&cdc_desc,
&cdc_class);
usb_intr_config();
#ifdef USE_IRC48M
/* CTC peripheral clock enable */
rcu_periph_clock_enable(RCU_CTC);
/* CTC configure */
ctc_config();
while (ctc_flag_get(CTC_FLAG_CKOK) == RESET) {
}
#endif /* USE_IRC48M */
/* main loop */
while (1) {
if (USBD_CONFIGURED == cdc_acm.dev.cur_status) {
if (0U == cdc_acm_check_ready(&cdc_acm)) {
cdc_acm_data_receive(&cdc_acm);//接收数据
} else {
cdc_acm_data_send(&cdc_acm);//发送数据
}
}
}
}
/*!
\brief receive CDC ACM data
\param[in] udev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_receive (usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
cdc->packet_receive = 0U;
cdc->packet_sent = 0U;
usbd_ep_recev(udev, CDC_DATA_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_DATA_PACKET_SIZE);
}
/*!
\brief send CDC ACM data
\param[in] udev: pointer to USB device instance
\param[out] none
\retval USB device operation status
*/
void cdc_acm_data_send (usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
if (0U != cdc->receive_length) {
cdc->packet_sent = 0U;
usbd_ep_send (udev, CDC_DATA_IN_EP, (uint8_t*)(cdc->data), cdc->receive_length);
cdc->receive_length = 0U;
}
}
实验效果
下载完程序,用 USB 线接板子USB 口,再接电脑,打开电脑上串口调试器,9600 波特率,8 位数据,无校验,发送 HELLOWORLD,板子就会返回 HELLOWORLD。
七.工程源代码下载
源代码下载链接如下:
CSDN
八.小结
USB虚拟串口可以实现与电脑的通信,还可以实现与外部设备的通信,广泛应用于工业控制、智能家居、智能硬件等领域。