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


    ​在嵌入式系统中,串行异步通信接口(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         
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    通信接口

    通信接口下拥有一个输入端点,用于报告主机一些状态。对于实现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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    测试自发自收功能

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

    请添加图片描述

  • 相关阅读:
    解决electron-builder制作的安装包在安装过程中出现“安装中止”的问题
    HDMI接口含义
    【每日一题】餐厅过滤器
    datagrid获得选中行的id
    pandas|Task03索引
    FPGA计数器边界问题解析
    【数据分享】2008-2022年全国范围逐年NO2栅格数据(免费获取)
    链动2+1模式 一秒教您扩充你自己的私域流量池
    python学习--字符串的常用操作
    冯喜运:6.5黄金原油今日行情趋势分析及操作策略
  • 原文地址:https://blog.csdn.net/weixin_38426553/article/details/126662684