28. USB-虛擬串口實(shí)驗(yàn)
28.1 實(shí)驗(yàn)內(nèi)容
通過本實(shí)驗(yàn)主要學(xué)習(xí)以下內(nèi)容:
? CDC虛擬串口協(xié)議原理及使用
? CDC虛擬串口通信操作
28.2 實(shí)驗(yàn)原理
USB的CDC類是USB通信設(shè)備類 (Communication Device Class)的簡稱。CDC類是USB組織定義的一類專門給各種通信設(shè)備使用的USB子類。該設(shè)備類采用批量傳輸。
本例程中實(shí)現(xiàn)了CDC設(shè)備類的相關(guān)請求,包括SET_LINE_CODING、GET_LINE_CODING、SET_CONTROL_LINE_STATE等。后續(xù)將會在代碼解析章節(jié)進(jìn)行介紹。
有關(guān)CDC協(xié)議可以通過以下USB官網(wǎng)下載或者通過紅楓派開發(fā)板配套資料獲取。
大家可以在學(xué)習(xí)的過程中結(jié)合歷程代碼和協(xié)議進(jìn)行理解。
28.3 硬件設(shè)計(jì)
USB虛擬鍵盤實(shí)驗(yàn)章節(jié)已介紹。
28.4 代碼解析
本例程主要實(shí)現(xiàn)USB虛擬串口的效果,在PC端可以通過串口調(diào)試助手或者設(shè)備管理器查到虛擬串口設(shè)備,并可實(shí)現(xiàn)通過該虛擬串口進(jìn)行通信的現(xiàn)象。
本例程主函數(shù)如下,該函數(shù)架構(gòu)與虛擬鍵盤例程相似,當(dāng)USBD設(shè)備初始化且枚舉完成后,USB設(shè)備首先通過cdc_acm_check_ready()函數(shù)check是否準(zhǔn)備數(shù)據(jù)發(fā)送,如果不需要發(fā)送就調(diào)用cdc_acm_data_receive()函數(shù)接收上位機(jī)發(fā)送的數(shù)據(jù),如果需要發(fā)送就調(diào)用cdc_acm_data_send()將接收到的數(shù)據(jù)發(fā)送給主機(jī),主機(jī)再回顯到串口調(diào)試助手的接收顯示界面中。
C
int main(void)
{
/* system clocks configuration */
rcu_config();
/* GPIO configuration */
gpio_config();
/* USB device configuration */
usbd_init(&usbd_cdc, &cdc_desc, &cdc_class);
/* NVIC configuration */
nvic_config();
/* enabled USB pull-up */
usbd_connect(&usbd_cdc);
while (USBD_CONFIGURED != usbd_cdc.cur_status) {
/* wait for standard USB enumeration is finished */
}
while (1) {
if (0U == cdc_acm_check_ready(&usbd_cdc)) {
cdc_acm_data_receive(&usbd_cdc);
} else {
cdc_acm_data_send(&usbd_cdc);
}
}
}
下面為大家介紹下虛擬串口設(shè)備所使用的設(shè)備及配置描述符。
設(shè)備描述符如下所示,其中bDevcieClass為0x02,表明當(dāng)前設(shè)備為CDC設(shè)備類。
C
usb_desc_dev cdc_dev_desc =
{
.header =
{
.bLength ?????????= USB_DEV_DESC_LEN,
.bDescriptorType ?= USB_DESCTYPE_DEV,
},
.bcdUSB ???????????????= 0x0200U,
.bDeviceClass ?????????= USB_CLASS_CDC,
.bDeviceSubClass ??????= 0x00U,
.bDeviceProtocol ??????= 0x00U,
.bMaxPacketSize0 ??????= USBD_EP0_MAX_SIZE,
.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虛擬串口設(shè)備包含兩個接口:CMD命令接口和data數(shù)據(jù)接口。CMD命令接口包含一個IN端點(diǎn),用于傳輸命令,該端點(diǎn)采用中斷傳輸方式,輪詢間隔為10ms,最大包長為8字節(jié)。data數(shù)據(jù)接口包含一個OUT端點(diǎn)和一個IN端點(diǎn),這兩個端點(diǎn)均采用批量傳輸方式,最大包長為64字節(jié)。另外,該配置描述符中包含了一些類特殊接口描述符,具體請讀者參閱CDC類標(biāo)準(zhǔn)協(xié)議。
C
usb_cdc_desc_config_set cdc_config_desc =
{
.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 ?????= CDC_ACM_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_OUT_EP,
.bmAttributes ????????= USB_EP_ATTR_BULK,
.wMaxPacketSize ??????= CDC_ACM_DATA_PACKET_SIZE,
.bInterval ???????????= 0x00U
},
.cdc_in_endpoint =
{
.header =
{
.bLength ????????= sizeof(usb_desc_ep),
.bDescriptorType = USB_DESCTYPE_EP
},
.bEndpointAddress ????= CDC_IN_EP,
.bmAttributes ????????= USB_EP_ATTR_BULK,
.wMaxPacketSize ??????= CDC_ACM_DATA_PACKET_SIZE,
.bInterval ???????????= 0x00U
}
};
為了實(shí)現(xiàn)CDC設(shè)備類,設(shè)備需要支持一些設(shè)備類專用請求,這些類專用請求的處理在cdc_acm_req_handler()函數(shù)中,該函數(shù)的定義如下所示,其中SET_LINE_CODING命令用于響應(yīng)主機(jī)向設(shè)備發(fā)送設(shè)備配置,包括波特率、停止位、字符位數(shù)等,收到的數(shù)據(jù)保存在noti_bu內(nèi)。GET_LINE_CODING命令用于主機(jī)請求設(shè)備當(dāng)前的波特率、停止位、奇偶校驗(yàn)位和字符位數(shù),但在本例程中,主機(jī)并未請求該命令,所以設(shè)備所設(shè)置的串口數(shù)據(jù)并沒有作用,主機(jī)可以選擇任意波特率與設(shè)備進(jìn)行通信。其他的命令在本例程中并未進(jìn)行處理,讀者可以參考標(biāo)準(zhǔn)CDC類協(xié)議。
C
static uint8_t cdc_acm_req_handler (usb_dev *udev, usb_req *req)
{
uint8_t status = REQ_NOTSUPP, noti_buf[10] = {0U};
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
acm_notification *notif = (void *)noti_buf;
switch (req->bRequest) {
case SEND_ENCAPSULATED_COMMAND:
break;
case GET_ENCAPSULATED_RESPONSE:
break;
case SET_COMM_FEATURE:
break;
case GET_COMM_FEATURE:
break;
case CLEAR_COMM_FEATURE:
break;
case SET_LINE_CODING:
/* set the value of the current command to be processed */
udev->class_core->req_cmd = req->bRequest;
usb_transc_config(&udev->transc_out[0U], (uint8_t *)&cdc->line_coding, req->wLength, 0U);
status = REQ_SUPP;
break;
case GET_LINE_CODING:
usb_transc_config(&udev->transc_in[0U], (uint8_t *)&cdc->line_coding, 7U, 0U);
status = REQ_SUPP;
break;
case SET_CONTROL_LINE_STATE:
notif->bmRequestType = 0xA1U;
notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE;
notif->wIndex = 0U;
notif->wValue = 0U;
notif->wLength = 2U;
noti_buf[8] = (uint8_t)req->wValue & 3U;
noti_buf[9] = 0U;
status = REQ_SUPP;
break;
case SEND_BREAK:
break;
default:
break;
}
return status;
}
下面為大家介紹USBD虛擬串口設(shè)備數(shù)據(jù)的收發(fā)。
數(shù)據(jù)接收通過cdc_acm_data_receive()函數(shù)實(shí)現(xiàn),該函數(shù)的程序如下所示。在該函數(shù)中,首先將packet_receive標(biāo)志位設(shè)置為0,表明接下來將進(jìn)行接收數(shù)據(jù),當(dāng)接收完成時,在cdc_acm_data_out()函數(shù)中,將packet_receive標(biāo)志位置1,表明數(shù)據(jù)接收完成。usbd_ep_recev()用于配置接收操作,利用CDC_OUT_EP端點(diǎn),將接收到的數(shù)據(jù)放置在cdc->data用戶緩沖區(qū)中。
C
void cdc_acm_data_receive(usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
cdc->packet_receive = 0U;
cdc->pre_packet_send = 0U;
usbd_ep_recev(udev, CDC_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_RX_LEN);
}
static void cdc_acm_data_out (usb_dev *udev, uint8_t ep_num)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
cdc->packet_receive = 1U;
cdc->receive_length = udev->transc_out[ep_num].xfer_count;
}
數(shù)據(jù)發(fā)送通過cdc_acm_data_send()函數(shù)實(shí)現(xiàn),該函數(shù)的程序如下所示。在該函數(shù)中,首先將packet_sent標(biāo)志位設(shè)置為0,表明接下來將進(jìn)行發(fā)送數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)送完成時,在cdc_acm_data_in()函數(shù)中,將packet_sent標(biāo)志位設(shè)置為1,表明數(shù)據(jù)發(fā)送完成。usbd_ep_send()用于配置發(fā)送操作,利用CDC_IN_EP端點(diǎn),將以cdc->data地址為起始data_len長度的數(shù)據(jù)發(fā)送給主機(jī)。
C
void cdc_acm_data_send (usb_dev *udev)
{
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
uint32_t data_len = cdc->receive_length;
if ((0U != data_len) && (1U == cdc->packet_sent)) {
cdc->packet_sent = 0U;
usbd_ep_send(udev, CDC_IN_EP, (uint8_t*)(cdc->data), (uint16_t)data_len);
cdc->receive_length = 0U;
}
}
static void cdc_acm_data_in (usb_dev *udev, uint8_t ep_num)
{
usb_transc *transc = &udev->transc_in[ep_num];
usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
if (transc->xfer_count == transc->max_len) {
usbd_ep_send(udev, EP_ID(ep_num), NULL, 0U);
} else {
cdc->packet_sent = 1U;
cdc->pre_packet_send = 1U;
}
}
28.5 實(shí)驗(yàn)結(jié)果
將本例程燒錄到紅楓派開發(fā)板中,并通過TypeC數(shù)據(jù)線連接USB通信接口和PC,在WIN7上虛擬串口需要安裝驅(qū)動,在WIN8 WIN10以及后續(xù)版本的系統(tǒng)上不需要安裝驅(qū)動。
下面介紹WIN7系統(tǒng)的驅(qū)動安裝過程。
在WIN7系統(tǒng)上,將Tyep C數(shù)據(jù)線連接到PC后,將會在設(shè)備管理器中發(fā)現(xiàn)一個未知設(shè)備,通過以下連接可以下載官方提供的虛擬串口驅(qū)動:https://www.gd32mcu.com/download/down/document_id/44/path_type/1
下載驅(qū)動并進(jìn)行安裝,之后將會在設(shè)備管理器中發(fā)現(xiàn)虛擬串口設(shè)備已經(jīng)識別。
之后即可通過串口調(diào)試助手與MCU進(jìn)行CDC通信,在串口調(diào)試助手中打開對應(yīng)虛擬串口的端口,然后輸入任意字符,進(jìn)行發(fā)送,將會在接收窗口中看到MCU返回的接收數(shù)據(jù),具體現(xiàn)象如下所示。
紅楓派開發(fā)板使用手冊:??????????????????????????????????????????????????GD32F303紅楓派使用手冊 - 飛書云文檔 (feishu.cn)