文章目錄
- 一.概要
- 二.USB2.0基本介紹及虛擬串口介紹
- 三.GD32單片機USB模塊框圖
- 四.GD32單片機USB設備模式
- 五.GD32F407VET6 USB設備CDC類
- 六.配置一個USB虛擬串口收發例程
- 七.工程源代碼下載
- 八.小結
一.概要
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_FSUSB_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虛擬串口可以實現與電腦的通信,還可以實現與外部設備的通信,廣泛應用于工業控制、智能家居、智能硬件等領域。