打印
[研电赛技术支持]

【GD32F303红枫派使用手册】第二十八讲 USB-虚拟串口实验

[复制链接]
130|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

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()将接收到的数据发送给主机,主机再回显到串口调试助手的接收显示界面中。
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);
        }
    }
}
下面为大家介绍下虚拟串口设备所使用的设备及配置描述符。
设备描述符如下所示,其中bDevcieClass0x02,表明当前设备为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教程,关注聚沃科技官网,GD32MCU技术交流群:859440462


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

88

主题

108

帖子

2

粉丝