移植DAPLink (一) USB部分
关于DAPLink
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字节。
在usb全速设备中,每个微帧(1500Bytes)的传输间隔是1ms,usb从机的一个中断端点
在每个微帧只能安排一次传输事务。由于上面配置的轮询间隔是1ms,最大包长是64字节,那么中断传输的带宽 = 64 * 1000 / 1024 = 62.5KB/s
,因此USB中断端点(HID模式)拿来做数据传输带宽还是比较低的。
在DAPLinkv2中,调试器部分使用了winusb驱动,设备端在USB描述符中报告自身支持winusb特性,并且提供上层应用程序访问的GUID,连接 WinUSB设备时,win8.1/win10主机系统会读取设备信息并自动加载Winusb.sys。
有了winusb驱动,就可以直接读写USB设备的端点了,它的功能实际上和libusb类似,不过libusb是将驱动上半部提供给用户空间完成的。那么在winusb驱动下,就可以不局限于中断端点,转而使用高速的批量传输(bulk)端点完成数据交互了。使用UsbTreeView查看DAPLinkv2设备的设备描述符和端点信息,相比与HID模式,WINUSB模式使用两个方向的批量传输
端点替代了中断传输端点。
在一个微帧时间内,不会限制批量事务的传输数,总是利用剩余带宽安排传输。在全速设备中,USB数据线时钟为12MHz,一个微帧时间 = 1ms
,理论上1ms能传输数据 = 12000000 / 8 / 1000 = 1500Bytes
,批量传输事务每个包最大为64字节,协议开销13字节,因此一个微帧的最大传输数 = 1500 / (64 + 13) = 19
,批量传输的带宽 = 64 * 19 * 1000 / 1024 = 1187.5KB/s = 1.159MB/s
,比起全速设备的中断传输,速度有了很大的提升。
如果USB设备端芯片换成CH568这种支持高速USB(480Mbps)的MCU(也可以是F1C100S,AM1808,AM335X这类MPU),那么速度会更快一些,不过最终的编程速度还受到目标板Flash擦写速度的限制;如果是调试阶段直接载入RAM执行,那么选择支持高速USB的设备制作DAPLink就可以提高开发效率了。
本项目主要移植CMSIS-DAPv2 WinUSB
部分,下面会给出GD32F303RC
和ESP32-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枚举过程:
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的示例。
GD32F303RC示例
描述符相关
USB设备描述符:
设备描述符中唯一需要注意的是bcdUSB
需要填0x0210
或0x0201
,这样在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端点在后。
/* 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_string
是uint16_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编码示例:
产品字符串:
必须包含CMSIS-DAP
字符串
/* 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设备描述符
截取
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_CODE
,wIndex
会等于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等),那么只需要填一项即可。其中PropertyName
和PropertyData
必须是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信息
USB接口对接DAP
初始化部分:
在usb初始化函数中初始化使用的端点,并打开OUT端点的接收功能。这里我使用了rt_rbb_blk
2倍DAP_PACKET_SIZE
容量做了双缓冲,但实际上直接用单缓冲也足够了。因为DAPLink的USB收发部分是同步的,也就是设备收到数据,进行处理,返回结果给主机;在设备处理数据期间,主机是不会再有数据从USB发给设备的,可见这样效率是比较低的,主机必须等待设备处理完发回响应才发下一次的数据。
设备的数据接收
这里的dap_core_out
是中断执行环境,直接对收到的数据进行处理是不合适的,因此我将数据进行CPU拷贝
到缓冲区,调用dap_notify_data_arrives(xfer_count);
,将收到数据的长度通过消息队列发给dap_thread
。这里也可以用邮箱,但是我用的RTX RTOS
没有实现邮箱,因此使用消息队列替代。
数据处理与返回
这里还是比较简单的,将数据放入uint32_t DAP_ProcessCommand(const uint8_t *request, uint8_t *response)
函数处理,就可以得到响应数据和数据长度了。GD32的USB是RAM BASE的,调用usbd_ep_send
时就已经将待发给主机的数据拷贝到USB外设的专用RAM了,所以对主机方向dap_send_buffer
使用单缓冲即可。
ESP32-S2示例
开启USB功能
在Espressif-IDE工程文件列表中双击sdkconfig
,在右侧的功能列表中找到TinyUSB Stack
,勾选Use TinyUSB Stack
,其余的都保持默认,最后按Ctrl+S
保存。如果创建的工程文件列表中没有sdkconfig
文件,可以先编译一遍工程,就会自动生成该配置文件。
USB适配层概述
对于esp32-s2,需要对乐鑫官方的Tinyusb适配层做一些了解,参考它的代码实现用户App完全接管USB上层功能。
TinyUSB位于IDE安装路径下:Espressif\frameworks\esp-idf-v4.4.2\components\tinyusb
:
tinyusb
文件夹是tinyusb项目自身的代码。
additions
文件夹下是对esp32-s2的适配层,其中additions\src\portable
实现了esp32-s2的设备端控制器(Device Controller Driver),虽然esp32-s2支持OTG,但这里并没有实现主机端的功能。
additions\src
目录下的一些.c
文件实现了usb-cdcacm(虚拟串口),对接到vfs虚拟文件接口,并且创建了usb数据处理线程,这里我们并不直接使用乐鑫写好的代码(因为实现不了功能需求),而是根据这部分代码基本逻辑,编写自己的usb数据处理线程。
经过这样操作,USB设备就可以被主机成功枚举,由于我使用的是win10LTSC版本的系统,不能自动安装winusb驱动,因此出现代码28
表示驱动探测失败。
设备管理器也是感叹号
对于这种现象,只需要手动安装inf驱动描述文件就行,先关闭win10驱动签名验证,再到DAPLink网站下载inf文件,
USB Driver and *.inf file
直接复制即可,新建个记事本,粘贴,需要修改inf文件内的VID PID和USB设备一致,最后另存为.inf
文件,并放入单独的文件夹。
然后到设备管理器里,手动安装驱动。
usb部分到这里基本就完成了。
KEIL-DMK测试
经过上面的USB数据到DAP处理函数的对接,在keil-MDK就能识别到CMSIS DAPlink工具,只是由于当前还未移植DAPLink SWD接口IO操作部分,显示SWD/JTAG通信失败。