前言
工作项目中,某些项目对数据吞吐量有很高要求(比如需要5M/s左右的带宽),于是笔者设计了高速HID,端点大小:1024Byte设备,但是公司之前产品使用的是端点大小:64Byte的全速设备,为了保证USB设备的兼容性,于是笔者设计成两个接口的HID设备(Interface0:64Byte HID、Interface1:1024Byte HID)。好了,接下来介绍双HID复合设备是如何开发的。
前期准备
1.带USB 2.0高速功能的MCU (笔者使用的NXP RT1052)
2.libusb 1.x 版本(开发上位机可使用该库)
3.VS2015 keil (IDE环境选择很多,因人而异)
HID描述符基本结构
- 配置描述符:主要设置接口数量为:2
- 接口描述符:接口0,端点描述符设置为:64Byte;接口1,端点描述符设置为:1024Byte
-------------------------
Configuration Descriptor:
-------------------------
0x09 bLength
0x02 bDescriptorType
0x0049 wTotalLength (73 bytes)
0x02 bNumInterfaces
0x01 bConfigurationValue
0x00 iConfiguration
0xC0 bmAttributes (Self-powered Device)
0x32 bMaxPower (100 mA)
Interface Descriptor:
------------------------------
0x09 bLength
0x04 bDescriptorType
0x00 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface
HID Descriptor:
------------------------------
0x09 bLength
0x21 bDescriptorType
0x0110 bcdHID
0x21 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x001B bDescriptorLength
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x81 bEndpointAddress (IN endpoint 1)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (1 x 64 bytes)
0x01 bInterval (1 microframes)
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x02 bEndpointAddress (OUT endpoint 2)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (1 x 64 bytes)
0x01 bInterval (1 microframes)
Interface Descriptor:
------------------------------
0x09 bLength
0x04 bDescriptorType
0x01 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x03 bInterfaceClass (Human Interface Device Class)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface
HID Descriptor:
------------------------------
0x09 bLength
0x21 bDescriptorType
0x0110 bcdHID
0x21 bCountryCode
0x01 bNumDescriptors
0x22 bDescriptorType (Report descriptor)
0x001B bDescriptorLength
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x83 bEndpointAddress (IN endpoint 3)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0400 wMaxPacketSize (1 x 1024 bytes)
0x01 bInterval (1 microframes)
Endpoint Descriptor:
------------------------------
0x07 bLength
0x05 bDescriptorType
0x03 bEndpointAddress (OUT endpoint 3)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0400 wMaxPacketSize (1 x 1024 bytes)
0x01 bInterval (1 microframes)
接口0,HID报告描述符 64byte
uint8_t g_UsbDeviceHid64ByteReportDescriptor[] =
{
0x05, 0x01,
0x09, 0x00,
0xa1, 0x01,
0x15, 0x00,
0x25, 0xff,
0x19, 0x01,
0x29, 0x08,
0x95, 0x40,
0x75, 0x08,
0x81, 0x02,
0x19, 0x01,
0x29, 0x08,
0x91, 0x02,
0xc0
};
接口1:HID报告描述符 1024byte
uint8_t g_UsbDeviceHid1024ByteReportDescriptor[] =
{
0x05, 0x01,
0x09, 0x00,
0xa1, 0x01,
0x15, 0x00,
0x25, 0xff,
0x19, 0x01,
0x29, 0x08,
0x95, 0x80,
0x75, 0x40,
0x81, 0x02,
0x19, 0x01,
0x29, 0x08,
0x91, 0x02,
0xc0
};
实现USB标准请求
USB标准请求,无非就是:总线复位,设置接口,获取接口,获取设备描述符,获取配置描述符,获取字符串描述符等等。反正主机请求啥,设备提供啥就行。
usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param)
{
usb_status_t error = kStatus_USB_Success;
uint8_t *temp8 = (uint8_t *)param;
uint16_t *temp16 = (uint16_t *)param;
switch (event)
{
case kUSB_DeviceEventBusReset:
{
/* USB bus reset signal detected */
g_UsbDeviceHidGeneric.attach = 0U;
if (kStatus_USB_Success == USB_DeviceClassGetSpeed(CONTROLLER_ID, &g_UsbDeviceHidGeneric.speed))
{
USB_DeviceSetSpeed(handle, g_UsbDeviceHidGeneric.speed);
}
}
break;
case kUSB_DeviceEventSetConfiguration:
if(param)
{
/* Set device configuration request */
g_UsbDeviceHidGeneric.attach = 1U;
g_UsbDeviceHidGeneric.currentConfiguration = *temp8;
if (USB_HID_GENERIC_CONFIGURE_INDEX == (*temp8))
{
error = USB_DeviceHidRecv(
g_UsbDeviceHidGeneric.hidHandle, USB_HID_GENERIC_ENDPOINT_OUT,
(uint8_t *)g_UsbDeviceHidGeneric.buffer,
USB_HID_GENERIC_OUT_BUFFER_LENGTH);
error = USB_DeviceHidRecv(
g_UsbDeviceHidGeneric.hid1024ByteHandle, USB_HID_1024BYTE_ENDPOINT_OUT,
(uint8_t *)g_UsbDeviceHidGeneric.buffer1024byte,
USB_HID_1024BYTE_OUT_BUFFER_LENGTH);
}
}
break;
case kUSB_DeviceEventSetInterface:
if (g_UsbDeviceHidGeneric.attach)
{
/* Set device interface request */
uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
uint8_t alternateSetting = (uint8_t)(*temp16 & 0x00FFU);
if (interface == USB_HID_64BYTE_INTERFACE_INDEX)
{
g_UsbDeviceHidGeneric.currentInterfaceAlternateSetting[interface] = alternateSetting;
if (alternateSetting == 0U)
{
error = USB_DeviceHidRecv(
g_UsbDeviceHidGeneric.hidHandle, USB_HID_GENERIC_ENDPOINT_OUT,
(uint8_t *)g_UsbDeviceHidGeneric.buffer,
USB_HID_GENERIC_OUT_BUFFER_LENGTH);
}
}
else if (interface == USB_HID_1024BYTE_INTERFACE_INDEX)
{
g_UsbDeviceHidGeneric.currentInterfaceAlternateSetting[interface] = alternateSetting;
if (alternateSetting == 0U)
{
error = USB_DeviceHidRecv(
g_UsbDeviceHidGeneric.hid1024ByteHandle, USB_HID_1024BYTE_ENDPOINT_OUT,
(uint8_t *)g_UsbDeviceHidGeneric.buffer1024byte,
USB_HID_1024BYTE_OUT_BUFFER_LENGTH);
}
}
}
break;
case kUSB_DeviceEventGetConfiguration:
if (param)
{
/* Get current configuration request */
*temp8 = g_UsbDeviceHidGeneric.currentConfiguration;
error = kStatus_USB_Success;
}
break;
case kUSB_DeviceEventGetInterface:
if (param)
{
/* Get current alternate setting of the interface request */
uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
if (interface < USB_DEVICE_INTERFACE_COUNT)
{
*temp16 = (*temp16 & 0xFF00U) | g_UsbDeviceHidGeneric.currentInterfaceAlternateSetting[interface];
error = kStatus_USB_Success;
}
else
{
error = kStatus_USB_InvalidRequest;
}
}
break;
case kUSB_DeviceEventGetDeviceDescriptor:
if (param)
{
/* Get device descriptor request */
error = USB_DeviceGetDeviceDescriptor(handle, (usb_device_get_device_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetConfigurationDescriptor:
if (param)
{
/* Get device configuration descriptor request */
error = USB_DeviceGetConfigurationDescriptor(handle,
(usb_device_get_configuration_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetStringDescriptor:
if (param)
{
/* Get device string descriptor request */
error = USB_DeviceGetStringDescriptor(handle, (usb_device_get_string_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetHidDescriptor:
if (param)
{
/* Get hid descriptor request */
error = USB_DeviceGetHidDescriptor(handle, (usb_device_get_hid_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetHidReportDescriptor:
if (param)
{
/* Get hid report descriptor request */
error =
USB_DeviceGetHidReportDescriptor(handle, (usb_device_get_hid_report_descriptor_struct_t *)param);
}
break;
case kUSB_DeviceEventGetHidPhysicalDescriptor:
if (param)
{
/* Get hid physical descriptor request */
error = USB_DeviceGetHidPhysicalDescriptor(handle,
(usb_device_get_hid_physical_descriptor_struct_t *)param);
}
break;
default:
break;
}
return error;
}
HID报告描述符请求函数修改:
usb_status_t USB_DeviceGetHidReportDescriptor(usb_device_handle handle,
usb_device_get_hid_report_descriptor_struct_t *hidReportDescriptor)
{
if (USB_HID_64BYTE_INTERFACE_INDEX== hidReportDescriptor->interfaceNumber)
{
hidReportDescriptor->buffer = g_UsbDeviceHidGenericReportDescriptor;
hidReportDescriptor->length = USB_DESCRIPTOR_LENGTH_HID_GENERIC_REPORT;
}
else if(USB_HID_1024BYTE_INTERFACE_INDEX == hidReportDescriptor->interfaceNumber)
{
hidReportDescriptor->buffer = g_UsbDeviceHid1024ByteReportDescriptor;
hidReportDescriptor->length = USB_DESCRIPTOR_LENGTH_HID_GENERIC_REPORT;
}
else
{
return kStatus_USB_InvalidRequest;
}
return kStatus_USB_Success;
}
将设备接上主机看枚举状况
枚举过程
通过Bus Hound可以抓包看到枚举过程,如下图:
上位机设计思路
1.遍历Windows端口所有usb设备,获取设备数量
libusb_get_device_list
2.遍历所有设备的描述符信息,通过PID VID等信息找到我们指定的设备
libusb_get_device_descriptor
3.打开指定设备
libusb_open
4.查询接口0,查询接口1
获取该接口端点IN OUT
5.端点上通信(hid 是中断传输,所以调用中断传输sdk)
libusb_interrupt_transfer
实例化成功USB HID设备后,如下识别2个Interface和对应的四个端点地址
可参考笔者之前的文章:HID高速设备1024byte------上下位机搭建
收发测试
端点1024Byte占用窗口比较大,这里就部分展示下