当前位置: 首页 > news >正文

【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         

通信接口

通信接口下拥有一个输入端点,用于报告主机一些状态。对于实现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,底层自发自收,测试结果如下

请添加图片描述

相关文章:

  • 用ARM进行汇编语言编程(3)逻辑移位和轮换,条件与分支
  • maltab datenum函数与正则表达式巧用:逐日数据转为逐月数据、日序转月序
  • PTA JAVA02 基础语法1
  • C++ 语言学习 day06 string , 异常
  • Linux命令`ll`的结果解析
  • 查题校园公众号查题系统
  • git分布式版本控制系统
  • C++ Color the ball
  • mysql的基础操作语句
  • Cookie/Session
  • java抽象类和接口(Comparator和Conparable的使用)
  • 百度首个江苏智算中心落地 携手盐城共建200P算力规模
  • 并发编程(四)---设计模式
  • GitHub:建立仓库,本地上传与更新内容
  • Linux EXPORT_SYMBOL宏详解
  • #Java异常处理
  • 《Java编程思想》读书笔记-对象导论
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • 08.Android之View事件问题
  • 4. 路由到控制器 - Laravel从零开始教程
  • CODING 缺陷管理功能正式开始公测
  • EOS是什么
  • HashMap剖析之内部结构
  • If…else
  • js中的正则表达式入门
  • mysql外键的使用
  • Web设计流程优化:网页效果图设计新思路
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 初探 Vue 生命周期和钩子函数
  • 观察者模式实现非直接耦合
  • 聊聊redis的数据结构的应用
  • 排序算法学习笔记
  • 与 ConTeXt MkIV 官方文档的接驳
  • 再次简单明了总结flex布局,一看就懂...
  • 智能合约Solidity教程-事件和日志(一)
  • UI设计初学者应该如何入门?
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • (1)SpringCloud 整合Python
  • (1)常见O(n^2)排序算法解析
  • (2)MFC+openGL单文档框架glFrame
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (Matlab)使用竞争神经网络实现数据聚类
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (二)丶RabbitMQ的六大核心
  • (分布式缓存)Redis分片集群
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (力扣)1314.矩阵区域和
  • (排序详解之 堆排序)