移植DAPLink (一) USB部分

DAPLink是ARM推出的一款调试器方案,支持SWD接口的Cortex-M系列MCU,或JTAG接口的Cortex-A系列MPU,软件代码使用Apache2.0许可,因此可以自由地用在个人和商业项目上。
CMSIS-DAP项目主页:
https://www.keil.com/pack/doc/CMSIS/DAP/html/index.html
CMSIS-DAP Github主页:
https://github.com/ARMmbed/DAPLink

DAPLinkv1和v2区别

由于DAPLink目前支持的功能很多也比较复杂,比如使用USB-CDC虚拟串口,USB-MSC虚拟U盘,USB Audio Streaming音频输入/输出实现的ADC/DAC功能等,这里只说明调试器部分的区别。
DAPLinkv1调试器使用的是HID驱动,在win8.1/win10下可以做到免安装驱动程序,使用UsbTreeView软件可以查看到用于调试器的端点描述符和端点信息,下图中可以看到使用两个分别为IN/OUT方向的中断(interrupt)端点来完成数据传输,轮循时间间隔设置为1ms,由于这是个USB全速设备(12Mbps),最大包长是64字节。
image
在usb全速设备中,每个微帧(1500Bytes)的传输间隔是1ms,usb从机的一个中断端点在每个微帧只能安排一次传输事务。由于上面配置的轮询间隔是1ms,最大包长是64字节,那么中断传输的带宽 = 64 * 1000 / 1024 = 62.5KB/s,因此USB中断端点(HID模式)拿来做数据传输带宽还是比较低的。
image

在DAPLinkv2中,调试器部分使用了winusb驱动,设备端在USB描述符中报告自身支持winusb特性,并且提供上层应用程序访问的GUID,连接 WinUSB设备时,win8.1/win10主机系统会读取设备信息并自动加载Winusb.sys。
有了winusb驱动,就可以直接读写USB设备的端点了,它的功能实际上和libusb类似,不过libusb是将驱动上半部提供给用户空间完成的。那么在winusb驱动下,就可以不局限于中断端点,转而使用高速的批量传输(bulk)端点完成数据交互了。使用UsbTreeView查看DAPLinkv2设备的设备描述符和端点信息,相比与HID模式,WINUSB模式使用两个方向的批量传输端点替代了中断传输端点。
image

在一个微帧时间内,不会限制批量事务的传输数,总是利用剩余带宽安排传输。在全速设备中,USB数据线时钟为12MHz,一个微帧时间 = 1ms,理论上1ms能传输数据 = 12000000 / 8 / 1000 = 1500Bytes,批量传输事务每个包最大为64字节,协议开销13字节,因此一个微帧的最大传输数 = 1500 / (64 + 13) = 19,批量传输的带宽 = 64 * 19 * 1000 / 1024 = 1187.5KB/s = 1.159MB/s,比起全速设备的中断传输,速度有了很大的提升。
image

如果USB设备端芯片换成CH568这种支持高速USB(480Mbps)的MCU(也可以是F1C100S,AM1808,AM335X这类MPU),那么速度会更快一些,不过最终的编程速度还受到目标板Flash擦写速度的限制;如果是调试阶段直接载入RAM执行,那么选择支持高速USB的设备制作DAPLink就可以提高开发效率了。

本项目主要移植CMSIS-DAPv2 WinUSB部分,下面会给出GD32F303RCESP32-S2的USB移植示例;其中GD32F303RC使用GD提供的USB库,实时系统使用RTOS v2 (RTX5),ESP32-S2使用IDF框架提供的TinyUSB库,实时系统使用乐鑫魔改过的FreeRTOS

GD32F303RC芯片信息:
https://www.gigadevice.com/products/microcontrollers/gd32/arm-cortex-m4/mainstream-line/gd32f303-series/
ESP32S2芯片信息:
https://www.espressif.com.cn/en/products/socs/esp32-s2

USB描述符和枚举过程


关于usb2.0具体的枚举过程,可以查看usb中文网的相关资料,
USB设备的枚举过程分析
Windows和Linux不同主机下USB设备枚举过程中的差别
这里主要针对枚举过程中,设备端需要返回的描述符进行适配,下面给出实际调试过程中的USB枚举过程:
image
USB描述符的配置主要参考DAPLink项目DAPLink-main\source\usb文件夹的以下文件:
https://github.com/ARMmbed/DAPLink/tree/main/source/usb
usb_def.h,usb_lib.c,usbd_desc.h,usb_winusb.h
其中重点是usb_lib.c,包含了DAPLink所有的usb描述符配置信息。
推荐使用集成开发环境阅读usb_lib.c,并且需要先定义宏:
__USB_CONFIG__,USBD_ENABLE 1,USBD_WINUSB_ENABLE 1,USBD_BULK_ENABLE 1,USBD_BOS_ENABLE 1
虽然usb_lib.c内容很多,但这样借助集成开发环境的宏高亮功能,也能看的比较清楚了,下面是Eclipse的示例。
image

GD32F303RC示例

描述符相关

USB设备描述符:

设备描述符中唯一需要注意的是bcdUSB需要填0x02100x0201,这样在windows下主机才会发起请求二进制设备对象存储描述符(BOS Descriptor),如果填0x0200,0x0110我测试下来在win10中只会请求位置为0xEE的微软系统描述符。
顺带一提:通过0xEE返回MSFT100倒是可以制作自己的USB应用程序,而无需借助libusb驱动。
使用微软系统描述符1.0制作免驱动自定义USB设备

#define USB_DEV_QUALIFIER_DESC_LEN   0x0AU        /*!< USB device qualifier descriptor length */
#define USB_DEV_DESC_LEN             0x12U        /*!< USB device descriptor length */
#define USB_CFG_DESC_LEN             0x09U        /*!< USB configuration descriptor length */
#define USB_ITF_DESC_LEN             0x09U        /*!< USB interface descriptor length */
#define USB_EP_DESC_LEN              0x07U        /*!< USB endpoint descriptor length */
// 这里GD32提供的宏定义有问题,USB_BOS_DESC_LEN应该为5,参考cmsis rlusb框架
//#define USB_BOS_DESC_LEN             0x0CU        /*!< USB BOS descriptor length */
#define USB_BOS_DESC_LEN             0x05U        /*!< USB BOS descriptor length */

/* descriptor types of USB specifications */
enum _usb_desctype {
    USB_DESCTYPE_DEV              = 0x1U,         /*!< USB device descriptor type */
    USB_DESCTYPE_CONFIG           = 0x2U,         /*!< USB configuration descriptor type */
    USB_DESCTYPE_STR              = 0x3U,         /*!< USB string descriptor type */
    USB_DESCTYPE_ITF              = 0x4U,         /*!< USB interface descriptor type */
    USB_DESCTYPE_EP               = 0x5U,         /*!< USB endpoint descriptor type */
    USB_DESCTYPE_DEV_QUALIFIER    = 0x6U,         /*!< USB device qualifier descriptor type */
    USB_DESCTYPE_OTHER_SPD_CONFIG = 0x7U,         /*!< USB other speed configuration descriptor type */
    USB_DESCTYPE_ITF_POWER        = 0x8U,         /*!< USB interface power descriptor type */
    USB_DESCTYPE_BOS              = 0xFU          /*!< USB BOS descriptor type */
};

/* USB standard device descriptor */
#define USBD_BOS_ENABLE
#define USBD_EP0_MAX_SIZE 64
#define USB_CLASS_MISC 0xEF
#define USBD_VID    0xC251U
#define USBD_PID    0xF000U

enum _str_index
{
    STR_IDX_LANGID                = 0x0U,        /* language ID string index */
    STR_IDX_MFC                   = 0x1U,        /* manufacturer string index */
    STR_IDX_PRODUCT               = 0x2U,        /* product string index */
    STR_IDX_SERIAL                = 0x3U,        /* serial string index */
    STR_IDX_CONFIG                = 0x4U,        /* configuration string index */
    STR_IDX_ITF                   = 0x5U,        /* interface string index */
    STR_IDX_MAX                   = 0x6U         /* string index max value */
};

const usb_desc_dev dap_dev_desc = {
    .header = {
        .bLength           = USB_DEV_DESC_LEN, 
        .bDescriptorType   = USB_DESCTYPE_DEV
    },

#if (USBD_BOS_ENABLE)
    // 通过二进制设备对象存储描述符(BOS Descriptor)向主机报告支持winusb
    .bcdUSB                = 0x0210U,
#else
    // 通过 操作系统字符串描述符->扩展兼容ID描述符->扩展属性描述符 向主机报告支持winusb
    // 暂未调试通过,windows未请求wIndex为5的主机扩展属性(Extend Properties),无法报告Keil-MDK使用DAPLink系统接口的GUID
    .bcdUSB                = 0x0200U,
#endif
    .bDeviceClass          = USB_CLASS_MISC,
    .bDeviceSubClass       = 0x02u,
    .bDeviceProtocol       = 0x01U,
    .bMaxPacketSize0       = USBD_EP0_MAX_SIZE,
    .idVendor              = USBD_VID,
    .idProduct             = USBD_PID,
    .bcdDevice             = 0x0110U,
    .iManufacturer         = STR_IDX_MFC,  // 1 厂商字符串位置 
    .iProduct              = STR_IDX_PRODUCT, // 2 产品字符串位置
    .iSerialNumber         = STR_IDX_SERIAL, // 3 序列号字符串位置
    .bNumberConfigurations = USBD_CFG_MAX_NUM // 1 配置描述符的数量
};

配置描述符:

需要注意的是Bulk OUT端点在前,Bulk IN端点在后。
image

/* bmAttributes in Configuration Descriptor */
#define USB_CONFIG_POWERED_MASK                0x40
#define USB_CONFIG_BUS_POWERED                 0x80
#define USB_CONFIG_SELF_POWERED                0xC0
#define USB_CONFIG_REMOTE_WAKEUP               0x20

/* USB device configuration descriptor */
const usb_desc_config_set dap_config_desc = {
    .config = {
        .header = {
            .bLength         = USB_CFG_DESC_LEN,
            .bDescriptorType = USB_DESCTYPE_CONFIG
        },
        // no vcom
        .wTotalLength        = (USB_CFG_DESC_LEN + USB_ITF_DESC_LEN + 2 * USB_EP_DESC_LEN),
        .bNumInterfaces      = 0x01U,
        .bConfigurationValue = 0x01U,
        .iConfiguration      = 0x00U,
        // bus powered, no remote wakeup
        .bmAttributes        = (USB_CONFIG_BUS_POWERED),
        // 2mA/LSB, 0xFA = 500mA
        .bMaxPower           = 0xFAu
    },

    // descriptor: winusb
    .dap_itf = {
        .header = {
            .bLength         = USB_ITF_DESC_LEN,
            .bDescriptorType = USB_DESCTYPE_ITF
        },
        .bInterfaceNumber    = 0x00U,
        .bAlternateSetting   = 0x00U,
        .bNumEndpoints       = 0x02U,
        .bInterfaceClass     = USB_CLASS_VEND_SPECIFIC,
        .bInterfaceSubClass  = 0x00,
        .bInterfaceProtocol  = 0x00,
        // The string index for this interface.
        .iInterface          = 0x04
    },

    // CMSIS-DAP v2 WinUSB 要求第一个端点是 Bulk OUT,第二个端点是 Bulk IN
    .dap_winusb_epout = {
        .header = {
            .bLength         = USB_EP_DESC_LEN,
            .bDescriptorType = USB_DESCTYPE_EP
        },
        .bEndpointAddress    = WINUSB_OUT_EP,
        .bmAttributes        = USB_EP_ATTR_BULK,
        .wMaxPacketSize      = WINUSB_PACKET_SIZE,
        // bInterval: ignore for Bulk transfer
        .bInterval           = 0x00U
    },

    .dap_winusb_epin = {
        .header = {
            .bLength         = USB_EP_DESC_LEN,
            .bDescriptorType = USB_DESCTYPE_EP
        },
        .bEndpointAddress    = WINUSB_IN_EP,
        .bmAttributes        = USB_EP_ATTR_BULK,
        .wMaxPacketSize      = WINUSB_PACKET_SIZE,
        // bInterval: ignore for Bulk transfer
        .bInterval           = 0x00U
    }
};

语言类型描述符:

推荐用默认的英语,这决定了USB主机对后面一些字符串描述符的解码方式。

/* USB language ID descriptor */
const usb_desc_LANGID usbd_language_id_desc =  {
    .header = {
         .bLength            = sizeof(usb_desc_LANGID), 
         .bDescriptorType    = USB_DESCTYPE_STR
     },
    .wLANGID                 = ENG_LANGID
};

厂商字符串:

关于字符串描述符,字符串使用的是小端(低字节在前)Unicode码(UCS2),因此是2个字节表示一个英文,数字或符号,在GD32的USB框架的字符串结构体中unicode_stringuint16_t双字节类型,因此使用大括号{}批量赋值时,'C', 'M', 'S', 'I', 'S'... 中的每个字符位置实际上占2字节空间;由于'C'=0x43 'M'=0x4D 这样的ASCII码只有1字节有数据,转换到2字节的uint16_t时高位自动填0,变成'C'=0x0043 'M'=0x004D...这样就巧妙的完成了ASCII码到UCS2码的转换。所以.unicode_string = {'C', 'M', 'S', 'I', 'S',这种看起来奇怪的字符赋值方式主要是为了进行编码转换。

typedef struct _usb_desc_str {
    usb_desc_header header;                       /*!< descriptor header, including type and size. */
    uint16_t unicode_string[64];                  /*!< unicode string data */
} usb_desc_str;

这个可以自行定义,不影响功能。

/* USB manufacture string */
static const usb_desc_str manufacturer_string = {
    .header = {
         .bLength         = USB_STRING_LEN(20U),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
    .unicode_string = {'C', 'M', 'S', 'I', 'S', '-', 'D', 'A', 'P','L','I','N','K', ' ','b','y',' ', 'A','R','M'}
};

如果你使用的USB框架字符串数据类型是uint8_t,那需要自行在每个字符后面填个0,见下图手动拼接UCS2编码示例:
image

产品字符串:

必须包含CMSIS-DAP字符串
image

/* USB product string */
static const usb_desc_str product_string = {
    .header = {
         .bLength         = USB_STRING_LEN(14U),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
     .unicode_string = {'C', 'M', 'S', 'I', 'S', '-', 'D', 'A', 'P', ' ','v','2','.','1'}
};

序列号字符串:

这个可以自行定义,不影响功能。

/* USBD serial string */
static usb_desc_str serial_string = {
    .header = {
         .bLength         = USB_STRING_LEN(12U), 
         .bDescriptorType = USB_DESCTYPE_STR,
     },
    .unicode_string = {'9','7','8','9','A','0','0','0','0','0','0','1'}
};

接口字符串:

对于组合设备(DAPLink + USBCDC虚拟串口),必须包含CMSIS-DAP字符串

static const usb_desc_str dap_itf_string = {
    .header = {
         .bLength         = USB_STRING_LEN(17U),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
    .unicode_string = {'C', 'M', 'S', 'I', 'S', '-', 'D', 'A', 'P',' ','v','2','.','1','-', 'G','D'}
};

字符串描述符集合:

/* string descriptor index enumeration */
enum _str_index
{
    STR_IDX_LANGID                = 0x0U,        /* language ID string index */
    STR_IDX_MFC                   = 0x1U,        /* manufacturer string index */
    STR_IDX_PRODUCT               = 0x2U,        /* product string index */
    STR_IDX_SERIAL                = 0x3U,        /* serial string index */
    STR_IDX_CONFIG                = 0x4U,        /* configuration string index */
    STR_IDX_ITF                   = 0x5U,        /* interface string index */
    STR_IDX_MAX                   = 0x6U         /* string index max value */
};

/* USB string descriptor */
const uint8_t* usbd_dap_strings[] = {
    [STR_IDX_LANGID]  = (uint8_t *)&usbd_language_id_desc,
    // VendorStringDesc
	// 这里的位置索引和USB设备描述符填写的要一致
    [STR_IDX_MFC]     = (uint8_t *)&manufacturer_string,
    [STR_IDX_PRODUCT] = (uint8_t *)&product_string,
    [STR_IDX_SERIAL]  = (uint8_t *)&serial_string,
    // 这里需要改成4 InterfaceStringDesc
    [4]  = (uint8_t *)&dap_itf_string
};

上文USB设备描述符截取
image

BOS描述符:

上文的字符串描述符可以由MCU自带的USB框架完成上报,但大多数情况BOS描述符需要自行编写传输逻辑,这里给出在GD32实现的代码示例,不同的USB框架也存在类似的vendor_req厂商请求回调函数(vendor_req中只需要对SETUP包回复,DATA和ACK包不做响应),按照下面的逻辑实现这个函数就行了。

static uint8_t dap_vendor_req (usb_dev *udev, usb_req *req) {
    uint16_t packet, len;
	
    if(req->bmRequestType & 0x80) {
        // Device to host
        if(req->bRequest == WINUSB_VENDOR_CODE) {
            if(req->wIndex == 4) {
                // Windows获取兼容描述符,先请求了头16个字节的数据,然后再根据实际长度请求了完整的数据
                packet = winusb_extended_compatid_descritpor[0];
                len = (req->wLength < packet) ? req->wLength : packet;

                udev->transc_in[0].xfer_buf = (uint8_t *)winusb_extended_compatid_descritpor;
                udev->transc_in[0].xfer_len = len;

            }else if(req->wIndex == 5) {
                // nothing to do...
                udev->transc_in[0].xfer_buf = NULL;
                udev->transc_in[0].xfer_len = 0;

            }else if(req->wIndex == 7) {
                packet = USBD_WINUSB_DESC_SET_LEN;
                len = (req->wLength < packet) ? req->wLength : packet;

                udev->transc_in[0].xfer_buf = (uint8_t *)WinUSBDescriptorSetDescriptor;
                udev->transc_in[0].xfer_len = len;
            }
        }
        return (uint8_t)REQ_SUPP;
    }
    // Host to device
    return (uint8_t)REQ_NOTSUPP;
}

平台兼容GUID必须是以下内容:

0xDF, 0x60, 0xDD, 0xD8,
0x89, 0x45, 0xC7, 0x4C,
0x9C, 0xD2, 0x65, 0x9D,
0x9E, 0x64, 0x8A, 0x9F,

USBD_WINUSB_DESC_SET_LEN是下一步返回WinUSBDescriptorSetDescriptor的总长度(字节长度),WINUSB_VENDOR_CODE是厂商代码,这个可以自定义,在下一步usb主机(windows)发起厂商特定请求vendor_request时,bRequest会等于用户定义的WINUSB_VENDOR_CODEwIndex会等于7,只要在这个请求中返回WinUSBDescriptorSetDescriptor即可。

#define WBVAL(x)                          (x & 0xFF),((x >> 8) & 0xFF)

#define USBD_NUM_DEV_CAPABILITIES         1

#define USBD_WINUSB_DESC_LEN              28
/*
// WinUSB Microsoft OS 2.0 descriptor Platform Capability Descriptor
typedef __PACKED_STRUCT _WINUSB_PLATFORM_CAPABILITY_DESCRIPTOR {
    U8  bLength;
    U8  bDescriptorType;
    U8  bDevCapabilityType;
    U8  bReserved;
    U8  platformCapabilityUUID[16];
    U32 dwWindowsVersion;
    U16 wDescriptorSetTotalLength;
    U8  bVendorCode;
    U8  bAltEnumCode;
} WINUSB_PLATFORM_CAPABILITY_DESCRIPTOR;
*/

#define USBD_BOS_WTOTALLENGTH             (USB_BOS_DESC_LEN + USBD_WINUSB_DESC_LEN)

/* WinUSB Microsoft OS 2.0 Descriptor Types */
#define WINUSB_SET_HEADER_DESCRIPTOR_TYPE           0x00
#define WINUSB_SUBSET_HEADER_CONFIGURATION_TYPE     0x01
#define WINUSB_SUBSET_HEADER_FUNCTION_TYPE          0x02
#define WINUSB_FEATURE_COMPATIBLE_ID_TYPE           0x03
#define WINUSB_FEATURE_REG_PROPERTY_TYPE            0x04
#define WINUSB_FEATURE_MIN_RESUME_TIME_TYPE         0x05
#define WINUSB_FEATURE_MODEL_ID_TYPE                0x06
#define WINUSB_FEATURE_CCGP_DEVICE_TYPE             0x07

#define WINUSB_PROP_DATA_TYPE_REG_SZ                0x01
#define WINUSB_PROP_DATA_TYPE_REG_MULTI_SZ          0x07

/* WinUSB Microsoft OS 2.0 descriptor request codes */
#define WINUSB_REQUEST_GET_DESCRIPTOR_SET          0x07
#define WINUSB_REQUEST_SET_ALT_ENUM                0x08

/* WinUSB Microsoft OS 2.0 descriptor sizes */
#define WINUSB_DESCRIPTOR_SET_HEADER_SIZE          10
#define WINUSB_FUNCTION_SUBSET_HEADER_SIZE         8
#define WINUSB_FEATURE_COMPATIBLE_ID_SIZE          20

#define FUNCTION_SUBSET_LEN                 160
#define DEVICE_INTERFACE_GUIDS_FEATURE_LEN  132

#define USBD_WINUSB_DESC_SET_LEN    (WINUSB_DESCRIPTOR_SET_HEADER_SIZE + FUNCTION_SUBSET_LEN)

const uint8_t BinaryObjectStoreDescriptor[] = {
    USB_BOS_DESC_LEN, // bLength
    USB_BINARY_OBJECT_STORE_DESCRIPTOR_TYPE, // bDescriptorType
    WBVAL(USBD_BOS_WTOTALLENGTH), // wTotalLength
    USBD_NUM_DEV_CAPABILITIES,                          // bNumDeviceCaps

    /*** MS OS 2.0 descriptor platform capability descriptor ***/
    USBD_WINUSB_DESC_LEN, // bLength
    USB_DEVICE_CAPABILITY_DESCRIPTOR_TYPE, // bDescriptorType
    USB_DEVICE_CAPABILITY_PLATFORM, // bDevCapabilityType: PLATFORM (05H)
    0x00, // bReserved
    0xDF, 0x60, 0xDD, 0xD8,     // PlatformCapabilityUUID
    0x89, 0x45, 0xC7, 0x4C,
    0x9C, 0xD2, 0x65, 0x9D,
    0x9E, 0x64, 0x8A, 0x9F,

    0x00, 0x00, 0x03, 0x06,        // dwWindowsVersion: 0x06030000 for Windows 8.1
    WBVAL(USBD_WINUSB_DESC_SET_LEN), // wDescriptorSetTotalLength: size of MS OS 2.0 descriptor set
    WINUSB_VENDOR_CODE,         // bMS_VendorCode
    0x00,                       // bAltEnumCmd
};

WINUSB描述符集合描述符:用来描述WINUSB特性集合的描述符,这里只支持BULK Device(不支持WEBUSB等),那么只需要填一项即可。其中PropertyNamePropertyData必须是DAPLink规定的,因为DAPLink的驱动程序使用这些GUID来查找主机中支持WINUSB特性的设备。

const uint8_t WinUSBDescriptorSetDescriptor[] = {
    /*** Microsoft OS 2.0 Descriptor Set Header ***/
    WBVAL(WINUSB_DESCRIPTOR_SET_HEADER_SIZE), // wLength
    WBVAL(WINUSB_SET_HEADER_DESCRIPTOR_TYPE),   //wDescriptorType

    0x00, 0x00, 0x03, 0x06,     // dwWindowsVersion: 0x06030000 for Windows 8.1

    WBVAL(USBD_WINUSB_DESC_SET_LEN),          // wDescriptorSetTotalLength

    /*** Microsoft OS 2.0 function subset header ***/
    WBVAL(WINUSB_FUNCTION_SUBSET_HEADER_SIZE),/* wLength */
    WBVAL(WINUSB_SUBSET_HEADER_FUNCTION_TYPE),/* wDescriptorType */
    0,                                        /* bFirstInterface USBD_BULK_IF_NUM*/
    0,                                        /* bReserved */
    WBVAL(FUNCTION_SUBSET_LEN),               /* wSubsetLength */

    /*** Microsoft OS 2.0 compatible ID descriptor ***/
    WBVAL(WINUSB_FEATURE_COMPATIBLE_ID_SIZE), /* wLength */
    WBVAL(WINUSB_FEATURE_COMPATIBLE_ID_TYPE), /* wDescriptorType */
    'W', 'I', 'N', 'U', 'S', 'B', 0, 0,       /* CompatibleId*/
    0, 0, 0, 0, 0, 0, 0, 0,                   /* SubCompatibleId*/

    /*** MS OS 2.0 registry property descriptor ***/
    WBVAL(DEVICE_INTERFACE_GUIDS_FEATURE_LEN),/* wLength */
    WBVAL(WINUSB_FEATURE_REG_PROPERTY_TYPE),  /* wDescriptorType */
    WBVAL(WINUSB_PROP_DATA_TYPE_REG_MULTI_SZ), /* wPropertyDataType */

    // PropertyName: "DeviceInterfaceGUID"
    WBVAL(42), /* wPropertyNameLength */
    'D',0,'e',0,'v',0,'i',0,'c',0,'e',0,
    'I',0,'n',0,'t',0,'e',0,'r',0,'f',0,'a',0,'c',0,'e',0,
    'G',0,'U',0,'I',0,'D',0,'s',0,0,0,

    // PropertyData: "{CDB3B5AD-293B-4663-AA36-1AAE46463776}"
    WBVAL(80), /* wPropertyDataLength */
    '{',0,
    'C',0,'D',0,'B',0,'3',0,'B',0,'5',0,'A',0,'D',0,'-',0,
    '2',0,'9',0,'3',0,'B',0,'-',0,
    '4',0,'6',0,'6',0,'3',0,'-',0,
    'A',0,'A',0,'3',0,'6',0,'-',
    0,'1',0,'A',0,'A',0,'E',0,'4',0,'6',0,'4',0,'6',0,'3',0,'7',0,'7',0,'6',0,
    '}',0,0,0,0,0,
};

微软系统描述符:当主机请求字符串描述符,且位置索引是0xEE时,应当返回以下内容。
.bcdUSB = 0x0210时主机不会请求os_string_descritpor,该描述符可以省略。

// "MSFT100" : index : 0xEE : langId : 0x0000
const uint8_t os_string_descritpor[] = {
        0x12, 0x03, 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, WINUSB_VENDOR_CODE, 0
};

完整的USBTreeView信息

image

USB接口对接DAP

初始化部分:

在usb初始化函数中初始化使用的端点,并打开OUT端点的接收功能。这里我使用了rt_rbb_blk2倍DAP_PACKET_SIZE容量做了双缓冲,但实际上直接用单缓冲也足够了。因为DAPLink的USB收发部分是同步的,也就是设备收到数据,进行处理,返回结果给主机;在设备处理数据期间,主机是不会再有数据从USB发给设备的,可见这样效率是比较低的,主机必须等待设备处理完发回响应才发下一次的数据。
image

设备的数据接收

这里的dap_core_out是中断执行环境,直接对收到的数据进行处理是不合适的,因此我将数据进行CPU拷贝到缓冲区,调用dap_notify_data_arrives(xfer_count);,将收到数据的长度通过消息队列发给dap_thread。这里也可以用邮箱,但是我用的RTX RTOS没有实现邮箱,因此使用消息队列替代。
image
image

数据处理与返回

这里还是比较简单的,将数据放入uint32_t DAP_ProcessCommand(const uint8_t *request, uint8_t *response)函数处理,就可以得到响应数据和数据长度了。GD32的USB是RAM BASE的,调用usbd_ep_send时就已经将待发给主机的数据拷贝到USB外设的专用RAM了,所以对主机方向dap_send_buffer使用单缓冲即可。
image
image

ESP32-S2示例

开启USB功能

在Espressif-IDE工程文件列表中双击sdkconfig,在右侧的功能列表中找到TinyUSB Stack,勾选Use TinyUSB Stack,其余的都保持默认,最后按Ctrl+S保存。如果创建的工程文件列表中没有sdkconfig文件,可以先编译一遍工程,就会自动生成该配置文件。
image

USB适配层概述

对于esp32-s2,需要对乐鑫官方的Tinyusb适配层做一些了解,参考它的代码实现用户App完全接管USB上层功能。
TinyUSB位于IDE安装路径下:Espressif\frameworks\esp-idf-v4.4.2\components\tinyusb
image
tinyusb文件夹是tinyusb项目自身的代码。
additions文件夹下是对esp32-s2的适配层,其中additions\src\portable实现了esp32-s2的设备端控制器(Device Controller Driver),虽然esp32-s2支持OTG,但这里并没有实现主机端的功能。
additions\src目录下的一些.c文件实现了usb-cdcacm(虚拟串口),对接到vfs虚拟文件接口,并且创建了usb数据处理线程,这里我们并不直接使用乐鑫写好的代码(因为实现不了功能需求),而是根据这部分代码基本逻辑,编写自己的usb数据处理线程。
image

经过这样操作,USB设备就可以被主机成功枚举,由于我使用的是win10LTSC版本的系统,不能自动安装winusb驱动,因此出现代码28表示驱动探测失败。
image

设备管理器也是感叹号

对于这种现象,只需要手动安装inf驱动描述文件就行,先关闭win10驱动签名验证,再到DAPLink网站下载inf文件,
USB Driver and *.inf file
直接复制即可,新建个记事本,粘贴,需要修改inf文件内的VID PID和USB设备一致,最后另存为.inf文件,并放入单独的文件夹。
image
然后到设备管理器里,手动安装驱动。





usb部分到这里基本就完成了。

KEIL-DMK测试

经过上面的USB数据到DAP处理函数的对接,在keil-MDK就能识别到CMSIS DAPlink工具,只是由于当前还未移植DAPLink SWD接口IO操作部分,显示SWD/JTAG通信失败。
image

posted @ 2022-09-24 20:45  Yanye  阅读(6767)  评论(17编辑  收藏  举报