28. USB-虚拟串口实验
28.1 实验内容
通过本实验主要学习以下内容:
28.2 实验原理
USB的CDC类是USB通信设备类 (Communication Device Class)的简称。CDC类是USB组织定义的一类专门给各种通信设备使用的USB子类。该设备类采用批量传输。
本例程中实现了CDC设备类的相关请求,包括SET_LINE_CODING、GET_LINE_CODING、SET_CONTROL_LINE_STATE等。后续将会在代码解析章节进行介绍。
有关CDC协议可以通过以下USB官网下载或者通过红枫派开发板配套资料获取。
大家可以在学习的过程中结合历程代码和协议进行理解。
28.3 硬件设计
USB虚拟键盘实验章节已介绍。
28.4 代码解析
本例程主要实现USB虚拟串口的效果,在PC端可以通过串口调试助手或者设备管理器查到虚拟串口设备,并可实现通过该虚拟串口进行通信的现象。
本例程主函数如下,该函数架构与虚拟键盘例程相似,当USBD设备初始化且枚举完成后,USB设备首先通过cdc_acm_check_ready()函数check是否准备数据发送,如果不需要发送就调用cdc_acm_data_receive()函数接收上位机发送的数据,如果需要发送就调用cdc_acm_data_send()将接收到的数据发送给主机,主机再回显到串口调试助手的接收显示界面中。
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); } } } |
下面为大家介绍下虚拟串口设备所使用的设备及配置描述符。
设备描述符如下所示,其中bDevcieClass为0x02,表明当前设备为CDC设备类。
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虚拟串口设备包含两个接口:CMD命令接口和data数据接口。CMD命令接口包含一个IN端点,用于传输命令,该端点采用中断传输方式,轮询间隔为10ms,最大包长为8字节。data数据接口包含一个OUT端点和一个IN端点,这两个端点均采用批量传输方式,最大包长为64字节。另外,该配置描述符中包含了一些类特殊接口描述符,具体请读者参阅CDC类标准协议。
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 } }; |
为了实现CDC设备类,设备需要支持一些设备类专用请求,这些类专用请求的处理在cdc_acm_req_handler()函数中,该函数的定义如下所示,其中SET_LINE_CODING命令用于响应主机向设备发送设备配置,包括波特率、停止位、字符位数等,收到的数据保存在noti_bu内。GET_LINE_CODING命令用于主机请求设备当前的波特率、停止位、奇偶校验位和字符位数,但在本例程中,主机并未请求该命令,所以设备所设置的串口数据并没有作用,主机可以选择任意波特率与设备进行通信。其他的命令在本例程中并未进行处理,读者可以参考标准CDC类协议。
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虚拟串口设备数据的收发。
数据接收通过cdc_acm_data_receive()函数实现,该函数的程序如下所示。在该函数中,首先将packet_receive标志位设置为0,表明接下来将进行接收数据,当接收完成时,在cdc_acm_data_out()函数中,将packet_receive标志位置1,表明数据接收完成。usbd_ep_recev()用于配置接收操作,利用CDC_OUT_EP端点,将接收到的数据放置在cdc->data用户缓冲区中。
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; } |
数据发送通过cdc_acm_data_send()函数实现,该函数的程序如下所示。在该函数中,首先将packet_sent标志位设置为0,表明接下来将进行发送数据,当数据发送完成时,在cdc_acm_data_in()函数中,将packet_sent标志位设置为1,表明数据发送完成。usbd_ep_send()用于配置发送操作,利用CDC_IN_EP端点,将以cdc->data地址为起始data_len长度的数据发送给主机。
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 实验结果
将本例程烧录到红枫派开发板中,并通过TypeC数据线连接USB通信接口和PC,在WIN7上虚拟串口需要安装驱动,在WIN8 WIN10以及后续版本的系统上不需要安装驱动。
下面介绍WIN7系统的驱动安装过程。
在WIN7系统上,将Tyep C数据线连接到PC后,将会在设备管理器中发现一个未知设备,通过以下连接可以下载官方提供的虚拟串口驱动:https://www.gd32mcu.com/download/down/document_id/44/path_type/1
下载驱动并进行安装,之后将会在设备管理器中发现虚拟串口设备已经识别。
之后即可通过串口调试助手与MCU进行CDC通信,在串口调试助手中打开对应虚拟串口的端口,然后输入任意字符,进行发送,将会在接收窗口中看到MCU返回的接收数据,具体现象如下所示。
本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网
全部0条评论
快来发表一下你的评论吧 !