[研电赛技术支持] 【GD32F303红枫派使用手册】第二十八讲 USB-虚拟串口实验

[复制链接]
 楼主| 聚沃科技 发表于 2024-7-3 10:44 | 显示全部楼层 |阅读模式
红枫派首图.png
28.1 实验内容
通过本实验主要学习以下内容:
• CDC虚拟串口协议原理及使用
• CDC虚拟串口通信操作
28.2 实验原理
USBCDC类是USB通信设备类 (Communication Device Class)的简称。CDC类是USB组织定义的一类专门给各种通信设备使用的USB子类。该设备类采用批量传输。
本例程中实现了CDC设备类的相关请求,包括SET_LINE_CODINGGET_LINE_CODINGSET_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()将接收到的数据发送给主机,主机再回显到串口调试助手的接收显示界面中。
  1. C
  2. int main(void)
  3. {
  4.     /* system clocks configuration */
  5.     rcu_config();

  6.     /* GPIO configuration */
  7.     gpio_config();

  8.     /* USB device configuration */
  9.     usbd_init(&usbd_cdc, &cdc_desc, &cdc_class);

  10.     /* NVIC configuration */
  11.     nvic_config();

  12.     /* enabled USB pull-up */
  13.     usbd_connect(&usbd_cdc);

  14.     while (USBD_CONFIGURED != usbd_cdc.cur_status) {
  15.         /* wait for standard USB enumeration is finished */
  16.     }

  17.     while (1) {
  18.         if (0U == cdc_acm_check_ready(&usbd_cdc)) {
  19.             cdc_acm_data_receive(&usbd_cdc);
  20.         } else {
  21.             cdc_acm_data_send(&usbd_cdc);
  22.         }
  23.     }
  24. }
下面为大家介绍下虚拟串口设备所使用的设备及配置描述符。
设备描述符如下所示,其中bDevcieClass0x02,表明当前设备为CDC设备类。
  1. C
  2. usb_desc_dev cdc_dev_desc =
  3. {
  4.     .header =
  5.      {
  6.          .bLength          = USB_DEV_DESC_LEN,
  7.          .bDescriptorType  = USB_DESCTYPE_DEV,
  8.      },
  9.     .bcdUSB                = 0x0200U,
  10.     .bDeviceClass          = USB_CLASS_CDC,
  11.     .bDeviceSubClass       = 0x00U,
  12.     .bDeviceProtocol       = 0x00U,
  13.     .bMaxPacketSize0       = USBD_EP0_MAX_SIZE,
  14.     .idVendor              = USBD_VID,
  15.     .idProduct             = USBD_PID,
  16.     .bcdDevice             = 0x0100U,
  17.     .iManufacturer         = STR_IDX_MFC,
  18.     .iProduct              = STR_IDX_PRODUCT,
  19.     .iSerialNumber         = STR_IDX_SERIAL,
  20.     .bNumberConfigurations = USBD_CFG_MAX_NUM,
  21. };
配置描述符如下所示,由配置描述符可知,该USB虚拟串口设备包含两个接口:CMD命令接口和data数据接口。CMD命令接口包含一个IN端点,用于传输命令,该端点采用中断传输方式,轮询间隔为10ms,最大包长为8字节。data数据接口包含一个OUT端点和一个IN端点,这两个端点均采用批量传输方式,最大包长为64字节。另外,该配置描述符中包含了一些类特殊接口描述符,具体请读者参阅CDC类标准协议。
  1. C
  2. usb_cdc_desc_config_set cdc_config_desc =
  3. {
  4.     .config =
  5.     {
  6.         .header =
  7.          {
  8.              .bLength         = sizeof(usb_desc_config),
  9.              .bDescriptorType = USB_DESCTYPE_CONFIG,
  10.          },
  11.         .wTotalLength         = USB_CDC_ACM_CONFIG_DESC_SIZE,
  12.         .bNumInterfaces       = 0x02U,
  13.         .bConfigurationValue  = 0x01U,
  14.         .iConfiguration       = 0x00U,
  15.         .bmAttributes         = 0x80U,
  16.         .bMaxPower            = 0x32U
  17.     },

  18.     .cmd_itf =
  19.     {
  20.         .header =
  21.          {
  22.              .bLength         = sizeof(usb_desc_itf),
  23.              .bDescriptorType = USB_DESCTYPE_ITF
  24.          },
  25.         .bInterfaceNumber     = 0x00U,
  26.         .bAlternateSetting    = 0x00U,
  27.         .bNumEndpoints        = 0x01U,
  28.         .bInterfaceClass      = USB_CLASS_CDC,
  29.         .bInterfaceSubClass   = USB_CDC_SUBCLASS_ACM,
  30.         .bInterfaceProtocol   = USB_CDC_PROTOCOL_AT,
  31.         .iInterface           = 0x00U
  32.     },

  33.     .cdc_header =
  34.     {
  35.         .header =
  36.          {
  37.             .bLength         = sizeof(usb_desc_header_func),
  38.             .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
  39.          },
  40.         .bDescriptorSubtype  = 0x00U,
  41.         .bcdCDC              = 0x0110U
  42.     },

  43.     .cdc_call_managment =
  44.     {
  45.         .header =
  46.          {
  47.             .bLength         = sizeof(usb_desc_call_managment_func),
  48.             .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
  49.          },
  50.         .bDescriptorSubtype  = 0x01U,
  51.         .bmCapabilities      = 0x00U,
  52.         .bDataInterface      = 0x01U
  53.     },

  54.     .cdc_acm =
  55.     {
  56.         .header =
  57.          {
  58.             .bLength         = sizeof(usb_desc_acm_func),
  59.             .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
  60.          },
  61.         .bDescriptorSubtype  = 0x02U,
  62.         .bmCapabilities      = 0x02U,
  63.     },

  64.     .cdc_union =
  65.     {
  66.         .header =
  67.          {
  68.             .bLength         = sizeof(usb_desc_union_func),
  69.             .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
  70.          },
  71.         .bDescriptorSubtype  = 0x06U,
  72.         .bMasterInterface    = 0x00U,
  73.         .bSlaveInterface0    = 0x01U,
  74.     },

  75.     .cdc_cmd_endpoint =
  76.     {
  77.         .header =
  78.          {
  79.             .bLength         = sizeof(usb_desc_ep),
  80.             .bDescriptorType = USB_DESCTYPE_EP,
  81.          },
  82.         .bEndpointAddress    = CDC_CMD_EP,
  83.         .bmAttributes        = USB_EP_ATTR_INT,
  84.         .wMaxPacketSize      = CDC_ACM_CMD_PACKET_SIZE,
  85.         .bInterval           = 0x0AU
  86.     },

  87.     .cdc_data_interface =
  88.     {
  89.         .header =
  90.          {
  91.             .bLength         = sizeof(usb_desc_itf),
  92.             .bDescriptorType = USB_DESCTYPE_ITF,
  93.          },
  94.         .bInterfaceNumber    = 0x01U,
  95.         .bAlternateSetting   = 0x00U,
  96.         .bNumEndpoints       = 0x02U,
  97.         .bInterfaceClass     = USB_CLASS_DATA,
  98.         .bInterfaceSubClass  = 0x00U,
  99.         .bInterfaceProtocol  = USB_CDC_PROTOCOL_NONE,
  100.         .iInterface          = 0x00U
  101.     },

  102.     .cdc_out_endpoint =
  103.     {
  104.         .header =
  105.          {
  106.              .bLength         = sizeof(usb_desc_ep),
  107.              .bDescriptorType = USB_DESCTYPE_EP,
  108.          },
  109.         .bEndpointAddress     = CDC_OUT_EP,
  110.         .bmAttributes         = USB_EP_ATTR_BULK,
  111.         .wMaxPacketSize       = CDC_ACM_DATA_PACKET_SIZE,
  112.         .bInterval            = 0x00U
  113.     },

  114.     .cdc_in_endpoint =
  115.     {
  116.         .header =
  117.          {
  118.              .bLength         = sizeof(usb_desc_ep),
  119.              .bDescriptorType = USB_DESCTYPE_EP
  120.          },
  121.         .bEndpointAddress     = CDC_IN_EP,
  122.         .bmAttributes         = USB_EP_ATTR_BULK,
  123.         .wMaxPacketSize       = CDC_ACM_DATA_PACKET_SIZE,
  124.         .bInterval            = 0x00U
  125.     }
  126. };
为了实现CDC设备类,设备需要支持一些设备类专用请求,这些类专用请求的处理在cdc_acm_req_handler()函数中,该函数的定义如下所示,其中SET_LINE_CODING命令用于响应主机向设备发送设备配置,包括波特率、停止位、字符位数等,收到的数据保存在noti_bu内。GET_LINE_CODING命令用于主机请求设备当前的波特率、停止位、奇偶校验位和字符位数,但在本例程中,主机并未请求该命令,所以设备所设置的串口数据并没有作用,主机可以选择任意波特率与设备进行通信。其他的命令在本例程中并未进行处理,读者可以参考标准CDC类协议。
  1. C
  2. static uint8_t cdc_acm_req_handler (usb_dev *udev, usb_req *req)
  3. {
  4.     uint8_t status = REQ_NOTSUPP, noti_buf[10] = {0U};
  5.     usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];

  6.     acm_notification *notif = (void *)noti_buf;

  7.     switch (req->bRequest) {
  8.     case SEND_ENCAPSULATED_COMMAND:
  9.         break;

  10.     case GET_ENCAPSULATED_RESPONSE:
  11.         break;

  12.     case SET_COMM_FEATURE:
  13.         break;

  14.     case GET_COMM_FEATURE:
  15.         break;

  16.     case CLEAR_COMM_FEATURE:
  17.         break;

  18.     case SET_LINE_CODING:
  19.         /* set the value of the current command to be processed */
  20.         udev->class_core->req_cmd = req->bRequest;

  21.         usb_transc_config(&udev->transc_out[0U], (uint8_t *)&cdc->line_coding, req->wLength, 0U);

  22.         status = REQ_SUPP;
  23.         break;

  24.     case GET_LINE_CODING:
  25.         usb_transc_config(&udev->transc_in[0U], (uint8_t *)&cdc->line_coding, 7U, 0U);

  26.         status = REQ_SUPP;
  27.         break;

  28.     case SET_CONTROL_LINE_STATE:
  29.         notif->bmRequestType = 0xA1U;
  30.         notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE;
  31.         notif->wIndex = 0U;
  32.         notif->wValue = 0U;
  33.         notif->wLength = 2U;
  34.         noti_buf[8] = (uint8_t)req->wValue & 3U;
  35.         noti_buf[9] = 0U;

  36.         status = REQ_SUPP;
  37.         break;

  38.     case SEND_BREAK:
  39.         break;

  40.     default:
  41.         break;
  42.     }

  43.     return status;
  44. }
下面为大家介绍USBD虚拟串口设备数据的收发。
数据接收通过cdc_acm_data_receive()函数实现,该函数的程序如下所示。在该函数中,首先将packet_receive标志位设置为0,表明接下来将进行接收数据,当接收完成时,在cdc_acm_data_out()函数中,将packet_receive标志位置1,表明数据接收完成。usbd_ep_recev()用于配置接收操作,利用CDC_OUT_EP端点,将接收到的数据放置在cdc->data用户缓冲区中。
  1. C
  2. void cdc_acm_data_receive(usb_dev *udev)
  3. {
  4.     usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];

  5.     cdc->packet_receive = 0U;
  6.     cdc->pre_packet_send = 0U;

  7.     usbd_ep_recev(udev, CDC_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_RX_LEN);
  8. }
  9. static void cdc_acm_data_out (usb_dev *udev, uint8_t ep_num)
  10. {
  11.     usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];

  12.     cdc->packet_receive = 1U;

  13.     cdc->receive_length = udev->transc_out[ep_num].xfer_count;
  14. }
数据发送通过cdc_acm_data_send()函数实现,该函数的程序如下所示。在该函数中,首先将packet_sent标志位设置为0,表明接下来将进行发送数据,当数据发送完成时,在cdc_acm_data_in()函数中,将packet_sent标志位设置为1,表明数据发送完成。usbd_ep_send()用于配置发送操作,利用CDC_IN_EP端点,将以cdc->data地址为起始data_len长度的数据发送给主机。
  1. C
  2. void cdc_acm_data_send (usb_dev *udev)
  3. {
  4.     usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];
  5.     uint32_t data_len = cdc->receive_length;

  6.     if ((0U != data_len) && (1U == cdc->packet_sent)) {
  7.         cdc->packet_sent = 0U;
  8.         usbd_ep_send(udev, CDC_IN_EP, (uint8_t*)(cdc->data), (uint16_t)data_len);
  9.         cdc->receive_length = 0U;
  10.     }
  11. }
  12. static void cdc_acm_data_in (usb_dev *udev, uint8_t ep_num)
  13. {
  14.     usb_transc *transc = &udev->transc_in[ep_num];
  15.     usb_cdc_handler *cdc = (usb_cdc_handler *)udev->class_data[CDC_COM_INTERFACE];

  16.     if (transc->xfer_count == transc->max_len) {
  17.         usbd_ep_send(udev, EP_ID(ep_num), NULL, 0U);
  18.     } else {
  19.         cdc->packet_sent = 1U;
  20.         cdc->pre_packet_send = 1U;
  21.     }
  22. }
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
图片1.png
下载驱动并进行安装,之后将会在设备管理器中发现虚拟串口设备已经识别。
图片2.png
之后即可通过串口调试助手与MCU进行CDC通信,在串口调试助手中打开对应虚拟串口的端口,然后输入任意字符,进行发送,将会在接收窗口中看到MCU返回的接收数据,具体现象如下所示。
图片3.png
本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网,GD32MCU技术交流群:859440462


您需要登录后才可以回帖 登录 | 注册

本版积分规则

170

主题

190

帖子

13

粉丝
快速回复 在线客服 返回列表 返回顶部

170

主题

190

帖子

13

粉丝
快速回复 在线客服 返回列表 返回顶部