【USB设备设计】-- CDC 设备开发(虚拟串口设备)

7 篇文章 2 订阅

​在嵌入式系统中,串行异步通信接口(UART)使用很频繁的接口,跟主机建立通信往往会用到USB转串口的设备,本章将介绍如何将USB虚拟成串口设备。

前期准备

1.带USB 功能的MCU (笔者使用的NXP RT1062)

2.串口调试助手

虚拟串口为cdc类(Communication Device Class),通常CDC类设备由两个子类接口组成:1个通信接口类接口(Communication Interface Class)和 0到多个数据接口类接口(Data Interface Class)。通常来说CDC设备一般需要至少2个接口。废话不多说,先贴上描述符:

Device Descriptor:
------------------------------
0x12	bLength
0x01	bDescriptorType
0x0200	bcdUSB
0xEF	bDeviceClass      (Miscellaneous device)
0x02	bDeviceSubClass   
0x01	bDeviceProtocol   
0x40	bMaxPacketSize0   (64 bytes)
0x1FC9	idVendor
0x00A3	idProduct
0x0101	bcdDevice
0x01	iManufacturer   "L17"
0x02	iProduct        "USB VCP"
0x03	iSerialNumber   "0123456789ABCDEF"
0x01	bNumConfigurations

Configuration Descriptor:
------------------------------
0x09	bLength
0x02	bDescriptorType
0x004B	wTotalLength   (75 bytes)
0x02	bNumInterfaces
0x01	bConfigurationValue
0x00	iConfiguration
0xC0	bmAttributes   (Self-powered Device)
0x32	bMaxPower      (100 mA)

Interface Association Descriptor:
------------------------------
0x08	bLength
0x0B	bDescriptorType
0x00	bFirstInterface
0x02	bInterfaceCount
0x02	bFunctionClass      (Communication Device Class)
0x02	bFunctionSubClass   (Abstract Control Model - ACM)
0x00	bFunctionProtocol   
0x02	iFunction   "USB VCP"

Interface Descriptor:
------------------------------
0x09	bLength
0x04	bDescriptorType
0x00	bInterfaceNumber
0x00	bAlternateSetting
0x01	bNumEndPoints
0x02	bInterfaceClass      (Communication Device Class)
0x02	bInterfaceSubClass   (Abstract Control Model - ACM)
0x01	bInterfaceProtocol   (ITU-T V.250)
0x00	iInterface

CDC Header Functional Descriptor:
------------------------------
0x05	bFunctionalLength
0x24	bDescriptorType
0x00	bDescriptorSubtype
0x0110	bcdCDC

CDC Call Management Functional Descriptor:
------------------------------
0x05	bFunctionalLength
0x24	bDescriptorType
0x01	bDescriptorSubtype
0x01	bmCapabilities
0x01	bDataInterface

CDC Abstract Control Management Functional Descriptor:
------------------------------
0x04	bFunctionalLength
0x24	bDescriptorType
0x02	bDescriptorSubtype
0x06	bmCapabilities

CDC Union Functional Descriptor:
------------------------------
0x05	bFunctionalLength
0x24	bDescriptorType
0x06	bDescriptorSubtype
0x00	bControlInterface
0x01	bSubordinateInterface(0)

Endpoint Descriptor:
------------------------------
0x07	bLength
0x05	bDescriptorType
0x81	bEndpointAddress  (IN endpoint 1)
0x03	bmAttributes      (Transfer: Interrupt / Synch: None / Usage: Data)
0x0010	wMaxPacketSize    (1 x 16 bytes)
0x07	bInterval         (64 microframes)

Interface Descriptor:
------------------------------
0x09	bLength
0x04	bDescriptorType
0x01	bInterfaceNumber
0x00	bAlternateSetting
0x02	bNumEndPoints
0x0A	bInterfaceClass      (CDC Data)
0x00	bInterfaceSubClass   
0x00	bInterfaceProtocol   
0x00	iInterface

Endpoint Descriptor:
------------------------------
0x07	bLength
0x05	bDescriptorType
0x82	bEndpointAddress  (IN endpoint 2)
0x02	bmAttributes      (Transfer: Bulk / Synch: None / Usage: Data)
0x0200	wMaxPacketSize    (512 bytes)
0x00	bInterval         

Endpoint Descriptor:
------------------------------
0x07	bLength
0x05	bDescriptorType
0x02	bEndpointAddress  (OUT endpoint 2)
0x02	bmAttributes      (Transfer: Bulk / Synch: None / Usage: Data)
0x0200	wMaxPacketSize    (512 bytes)
0x00	bInterval         
通信接口

通信接口下拥有一个输入端点,用于报告主机一些状态。对于实现USB虚拟串口功能,需要将USB通信接口类的类型设置为:Abstract Control Model子类和Common AT Commands传输协议

通信接口描述符后面会跟,功能描述符(Functional Descriptors),用于描述接口功能

描述符子类基本功能信息
00h功能描述符头
01hCall Management 功能描述符
02h抽象控制管理 功能描述符
03hDirect Line Management 功能描述符
04hTelephone Ringer 功能描述符
05hTelephone Call and Line State Reporting Capability 功能描述符
06hUnion 功能描述符
07hCountry Selection 功能描述符
08hTelephone Operation Mode 功能描述符
09hUSB Terminal 功能描述符
0AhNetwork Channel 功能描述符
0Bh协议单元功能描述符
0Ch扩展单元功能描述符
0Dh多通道管理功能描述符
0EhCAPI控制管理功能描述符
0Fh以太网网络功能描述符
10hATM功能描述符
11-FFh保留
数据接口

数据接口,没啥好说的,接口类定义为:0x0A 。端点描述符,注意传输类型为:Bulk 块传输

虚拟串口相关类请求

请添加图片描述

  • GET LINE CODING

主机获取当前串口属性请求,包括波特率、停止位、校验位及数据位的位数。CTL请求编码格式

请添加图片描述

返回7Byte参数,分别为: [0:3]波特率,[4]停止位,[5]校验位,[6]数据位。可知上图获取波特率为:115200

  • SET LINE CODING

类似GET LINE CODING,用于主机设置从机当前属性,可修改波特率、停止位、校验位及数据位

  • SET CTRL LINE STATE

该请求没有数据输出阶段,作用是设置设备的DTR和RTS引脚电平,D0位表示DTR,D1位表示RTS

请添加图片描述

类请求回调函数内容如下:

usb_status_t USB_DeviceCdcVcomCallback(class_handle_t handle, uint32_t event, void *param)
{
    uint32_t len;
    uint8_t *uartBitmap;
    usb_cdc_acm_info_t *acmInfo;
    usb_device_cdc_acm_request_param_struct_t *acmReqParam;
    usb_device_endpoint_callback_message_struct_t *epCbParam;
    volatile usb_cdc_vcom_struct_t *vcomInstance;
    usb_status_t error = kStatus_USB_InvalidRequest;
    acmReqParam = (usb_device_cdc_acm_request_param_struct_t *)param;
    epCbParam   = (usb_device_endpoint_callback_message_struct_t *)param;

    vcomInstance = &g_deviceComposite->cdcVcom;
    acmInfo      = vcomInstance->usbCdcAcmInfo;
    
    switch (event)
    {
        case kUSB_DeviceCdcEventSendResponse:
        {
            if ((epCbParam->length != 0) && (!(epCbParam->length % vcomInstance->bulkInEndpointMaxPacketSize)))
            {
                /* If the last packet is the size of endpoint, then send also zero-ended packet,
                 ** meaning that we want to inform the host that we do not have any additional
                 ** data, so it can flush the output.
                 */
                error = USB_DeviceCdcAcmSend(handle, vcomInstance->bulkInEndpoint, NULL, 0);
            }
            else if ((1 == vcomInstance->attach) && (1 == vcomInstance->startTransactions))
            {
                if ((epCbParam->buffer != NULL) || ((epCbParam->buffer == NULL) && (epCbParam->length == 0)))
                {
                    /* User: add your own code for send complete event */
                    /* Schedule buffer for next receive event */
                    error = USB_DeviceCdcAcmRecv(handle, vcomInstance->bulkOutEndpoint, vcomInstance->currRecvBuf,
                                                 vcomInstance->bulkOutEndpointMaxPacketSize);
                }
            }
            else
            {
            }
        }
        break;
        case kUSB_DeviceCdcEventRecvResponse:
        {
            if ((1 == vcomInstance->attach) && (1 == vcomInstance->startTransactions))
            {
                vcomInstance->recvSize = epCbParam->length;

                if (!vcomInstance->recvSize)
                {
                    /* Schedule buffer for next receive event */
                    error = USB_DeviceCdcAcmRecv(handle, vcomInstance->bulkOutEndpoint, vcomInstance->currRecvBuf,
                                                 vcomInstance->bulkOutEndpointMaxPacketSize);
                }
            }
        }
        break;
        case kUSB_DeviceCdcEventSerialStateNotif:
            ((usb_device_cdc_acm_struct_t *)handle)->hasSentState = 0;
            error                                                 = kStatus_USB_Success;
            break;
        case kUSB_DeviceCdcEventSendEncapsulatedCommand:
            break;
        case kUSB_DeviceCdcEventGetEncapsulatedResponse:
            break;
        case kUSB_DeviceCdcEventSetCommFeature:
            if (USB_DEVICE_CDC_FEATURE_ABSTRACT_STATE == acmReqParam->setupValue)
            {
                if (1 == acmReqParam->isSetup)
                {
                    *(acmReqParam->buffer) = vcomInstance->abstractState;
                    *(acmReqParam->length) = COMM_FEATURE_DATA_SIZE;
                }
                else
                {
                    /* no action, data phase, s_abstractState has been assigned */
                }
                error = kStatus_USB_Success;
            }
            else if (USB_DEVICE_CDC_FEATURE_COUNTRY_SETTING == acmReqParam->setupValue)
            {
                if (1 == acmReqParam->isSetup)
                {
                    *(acmReqParam->buffer) = vcomInstance->countryCode;
                    *(acmReqParam->length) = COMM_FEATURE_DATA_SIZE;
                }
                else
                {
                    /* no action, data phase, s_countryCode has been assigned */
                }
                error = kStatus_USB_Success;
            }
            else
            {
                /* no action, return kStatus_USB_InvalidRequest */
            }
            break;
        case kUSB_DeviceCdcEventGetCommFeature:
            if (USB_DEVICE_CDC_FEATURE_ABSTRACT_STATE == acmReqParam->setupValue)
            {
                *(acmReqParam->buffer) = vcomInstance->abstractState;
                *(acmReqParam->length) = COMM_FEATURE_DATA_SIZE;
                error                  = kStatus_USB_Success;
            }
            else if (USB_DEVICE_CDC_FEATURE_COUNTRY_SETTING == acmReqParam->setupValue)
            {
                *(acmReqParam->buffer) = vcomInstance->countryCode;
                *(acmReqParam->length) = COMM_FEATURE_DATA_SIZE;
                error                  = kStatus_USB_Success;
            }
            else
            {
                /* no action, return kStatus_USB_InvalidRequest */
            }
            break;
        case kUSB_DeviceCdcEventClearCommFeature:
            break;
        case kUSB_DeviceCdcEventGetLineCoding:
            *(acmReqParam->buffer) = vcomInstance->lineCoding;
            *(acmReqParam->length) = LINE_CODING_SIZE;
            error                  = kStatus_USB_Success;
            break;
        case kUSB_DeviceCdcEventSetLineCoding:
        {
            if (1 == acmReqParam->isSetup)
            {
                *(acmReqParam->buffer) = vcomInstance->lineCoding;
                *(acmReqParam->length) = LINE_CODING_SIZE;
            }
            else
            {
                /* no action, data phase, s_lineCoding has been assigned */
            }
            error = kStatus_USB_Success;
        }
        break;
        case kUSB_DeviceCdcEventSetControlLineState:
        {
            error                     = kStatus_USB_Success;
            vcomInstance->usbCdcAcmInfo->dteStatus = acmReqParam->setupValue;
            /* activate/deactivate Tx carrier */
            if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_CARRIER_ACTIVATION)
            {
                acmInfo->uartState |= USB_DEVICE_CDC_UART_STATE_TX_CARRIER;
            }
            else
            {
                acmInfo->uartState &= (uint16_t)~USB_DEVICE_CDC_UART_STATE_TX_CARRIER;
            }

            /* activate carrier and DTE. Com port of terminal tool running on PC is open now */
            if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE)
            {
                acmInfo->uartState |= USB_DEVICE_CDC_UART_STATE_RX_CARRIER;
            }
            /* Com port of terminal tool running on PC is closed now */
            else
            {
                acmInfo->uartState &= (uint16_t)~USB_DEVICE_CDC_UART_STATE_RX_CARRIER;
            }

            /* Indicates to DCE if DTE is present or not */
            acmInfo->dtePresent = (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE) ? true : false;

            /* Initialize the serial state buffer */
            acmInfo->serialStateBuf[0] = NOTIF_REQUEST_TYPE;                /* bmRequestType */
            acmInfo->serialStateBuf[1] = USB_DEVICE_CDC_NOTIF_SERIAL_STATE; /* bNotification */
            acmInfo->serialStateBuf[2] = 0x00;                              /* wValue */
            acmInfo->serialStateBuf[3] = 0x00;
            acmInfo->serialStateBuf[4] = 0x00; /* wIndex */
            acmInfo->serialStateBuf[5] = 0x00;
            acmInfo->serialStateBuf[6] = UART_BITMAP_SIZE; /* wLength */
            acmInfo->serialStateBuf[7] = 0x00;
            /* Notify to host the line state */
            acmInfo->serialStateBuf[4] = acmReqParam->interfaceIndex;
            /* Lower byte of UART BITMAP */
            uartBitmap    = (uint8_t *)&acmInfo->serialStateBuf[NOTIF_PACKET_SIZE + UART_BITMAP_SIZE - 2];
            uartBitmap[0] = acmInfo->uartState & 0xFFu;
            uartBitmap[1] = (acmInfo->uartState >> 8) & 0xFFu;
            len           = (uint32_t)(NOTIF_PACKET_SIZE + UART_BITMAP_SIZE);
            if (0 == ((usb_device_cdc_acm_struct_t *)handle)->hasSentState)
            {
                error = USB_DeviceCdcAcmSend(handle, vcomInstance->interruptEndpoint, acmInfo->serialStateBuf, len);
                if (kStatus_USB_Success != error)
                {
//                    usb_echo("kUSB_DeviceCdcEventSetControlLineState error!");
                }
                ((usb_device_cdc_acm_struct_t *)handle)->hasSentState = 1;
            }

            /* Update status */
            if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_CARRIER_ACTIVATION)
            {
                /*  To do: CARRIER_ACTIVATED */
            }
            else
            {
                /* To do: CARRIER_DEACTIVATED */
            }
            if (acmInfo->dteStatus & USB_DEVICE_CDC_CONTROL_SIG_BITMAP_DTE_PRESENCE)
            {
                /* DTE_ACTIVATED */
                if (1 == vcomInstance->attach)
                {
                    vcomInstance->startTransactions = 1;
                }
            }
            else
            {
                /* DTE_DEACTIVATED */
                if (1 == vcomInstance->attach)
                {
                    vcomInstance->startTransactions = 0;
                }
            }
        }
        break;
        case kUSB_DeviceCdcEventSendBreak:
            break;
        default:
            break;
    }

    return error;
}
测试自发自收功能

串口调试工具使用MobaXterm,底层自发自收,测试结果如下

请添加图片描述

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值