USB描述符包括设备描述符(DeviceDescriptor)、配置描述符(ConfigDescriptor)、HID报告描述符(CustomHID_ReportDescriptor)、字符串描述符。
1.设备描述符--基本固定,不需要怎么修改。一般复合设备和非复合设备要修改bDeviceClass、bDeviceSubClass、bDeviceProtocol这3个的值
/* USB Standard Device Descriptor */
const uint8_t VCP_HID_DeviceDescriptor[] =
{
0x12, /* bLength */
USB_DEVICE_DESCRIPTOR_TYPE, /* bDescriptorType */
0x00,
0x02, /* bcdUSB = 2.00 */
0xEF, /* bDeviceClass: 复合设备 */
0x02, /* bDeviceSubClass */
0x01, /* bDeviceProtocol */
0x40, /* bMaxPacketSize0 */
0x83,
0x04, /* idVendor = 0x0483 */
0x40,
0x57, /* idProduct = 0x5740 */
0x00,
0x02, /* bcdDevice = 2.00 */
1, /* Index of string descriptor describing manufacturer */
2, /* Index of string descriptor describing product */
3, /* Index of string descriptor describing the device's serial number */
0x01 /* bNumConfigurations */
};
2.配置描述符--这里以复合设备(VCP+HID)为例。其结构一般如下所示:
注意:一个设备可以有多个配置,但同一时刻只能有一个配置有效,每个配置下可以有多个接口;同一端点号不能出现在同一个配置下的两个或多个不同的接口中;同一端点号可用在不同的配置中。
另外对于复合设备来说,在每个接口前需要添加IAD描述符。
如下为VCP+HID的配置描述符,可以根据具体需求增删数据,比如端点数量以及端点号之类的。
const uint8_t VCP_HID_ConfigDescriptor[] =
{
/*Configuration Descriptor*/
0x09, /* bLength: Configuration Descriptor size */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
VCP_HID_SIZ_CONFIG_DESC, /* wTotalLength:no of returned bytes */
0x00,
0x03, /* bNumInterfaces: 3 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: self powered */
0xFA, /* MaxPower 500 mA */
/* Interface Association Descriptor(IAD Descriptor) */
0x08, /* bLength */
0x0B, /* bDescriptorType*/
0x00, /* bFirstInterface*/
0x02, /* bInterfaceCount*/
0x02, /* bFunctionClass --CDC*/
0x02, /* bFunctionSubClass*/
0x01, /* bFunctionProtocoll*/
0x00, /* iFunction */
/************** Descriptor of virtual com port ****************/
/*Interface Descriptor*/
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x01, /* bNumEndpoints: One endpoints used */
0x02, /* bInterfaceClass: Communication Interface Class */
0x02, /* bInterfaceSubClass: Abstract Control Model */
0x01, /* bInterfaceProtocol: Common AT commands */
0x00, /* iInterface: */
/*Header Functional Descriptor*/
0x05, /* bLength: Endpoint Descriptor size */
0x24, /* bDescriptorType: CS_INTERFACE */
0x00, /* bDescriptorSubtype: Header Func Desc */
0x10, /* bcdCDC: spec release number */
0x01,
/*Call Management Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x01, /* bDescriptorSubtype: Call Management Func Desc */
0x00, /* bmCapabilities: D0+D1 */
0x01, /* bDataInterface: 1 */
/*ACM Functional Descriptor*/
0x04, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x02, /* bDescriptorSubtype: Abstract Control Management desc */
0x02, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface */
0x01, /* bSlaveInterface0: Data Class Interface */
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */
0x82, /* bEndpointAddress: (IN2) */
0x03, /* bmAttributes: Interrupt */
VCP_INT_SIZE, /* wMaxPacketSize: */
0x00,
0xFF, /* bInterval: */
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x01, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints: Two endpoints used */
0x0A, /* bInterfaceClass: CDC */
0x00, /* bInterfaceSubClass: */
0x00, /* bInterfaceProtocol: */
0x00, /* iInterface: */
/*Endpoint 3 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */
0x03, /* bEndpointAddress: (OUT3) */
0x02, /* bmAttributes: Bulk */
VCP_DATA_SIZE, /* wMaxPacketSize: */
0x00,
0x00, /* bInterval: ignore for Bulk transfer */
/*Endpoint 1 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */
0x81, /* bEndpointAddress: (IN1) */
0x02, /* bmAttributes: Bulk */
VCP_DATA_SIZE, /* wMaxPacketSize: */
0x00,
0x00, /* bInterval */
/*********************************IAD Descriptor*********************************/
0x08, //描述符大小
0x0B, //IAD描述符类型
0x02, //bFirstInterface
0x01, //bInferfaceCount
0x03, //bFunctionClass:HID
0x00, //bFunctionSubClass
0x00, //bFunctionProtocol
0x05, //iFunction
/************** Descriptor of HID interfaces****************/
0x09, /* bLength: Interface Descriptor size */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
0x02, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x01, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
0x01, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
0, /* iInterface: Index of string descriptor */
/*Descriptor of Custom HID HID */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x10, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
CUSTOMHID_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */
0x00,
/*Descriptor of Custom HID endpoints */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x84, /* bEndpointAddress: Endpoint Address (IN4) */
0x03, /* bmAttributes: Interrupt endpoint */
0x08, /* wMaxPacketSize: 8 Bytes max */
0x00,
0xF0, /* bInterval: Polling Interval (32 ms) */
/*Descriptor of Custom HID endpoints */
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint descriptor type*/
0x04, /* bEndpointAddress: Endpoint Address (OUT4) */
0x03, /* bmAttributes: Interrupt endpoint */
0x08, /* wMaxPacketSize: 8 Bytes max */
0x00,
0xF0 /* bInterval: Polling Interval (20 ms) */
};
3.报告描述符--对于HID设备来说需要有报告描述符,CDC设备以及存储设备不需要。报告描述符很复杂,详情可以参考《HID用途表.pdf》,里面详细说明了报告描述符的各个成员以及含义。以下直接给出键盘的报告描述符
const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
//第1字节
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x08, // REPORT_COUNT (8)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs) //1 byte
//第2字节
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs) //1 byte
//第3-8字节
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs) //6 bytes
0xc0 // END_COLLECTION
}; /* CustomHID_ReportDescriptor */
报告描述符可以参考一些网站资料比如:关于USBHID协议以及鼠标键盘描述符的解释、USB-HID鼠标、键盘通讯格式(转) 与本人实际测试结果。
USB HID设备是通过报告来给传送数据的,报告有输入报告和输出报告.
报告是一个数据包,里面包含的是索要传送的数据.输入报告是通过中断输入断点输入的,而输出报告有点区别,当没有中断输出断点时,可以通过控制输出断电0发送,当有中断输出断点时,通过中断输出断点发出。而报告描述符,是描述一个报告以及报告里面的数据是用来干什么的.通过它,USB Host可以解析出报告里面的数据所表示的含义.它通过控制输入断点0返回,Host使用获取报告描述符命令来获取报告描述符,注意这个请求是发送到Interface,而不是设备.一个报告描述符可以描述多个报告,不同的报告通过Report ID来区分,Reort ID在报告最前面,即第一个byte.而当报告描述符中没有规定Report ID时,报告中就没有ID字段,开始就是数据。
本例程中的键盘报高描述符不包含输出报告,因为不需要用指示灯指示大小写状态、数字开关灯信息。
主要修改XXX_Data_Setup()函数以及添加一些和HID相关的函数
RESULT VCP_HID_Data_Setup(uint8_t RequestNo)
{
uint8_t *(*CopyRoutine)(uint16_t);
CopyRoutine = NULL;
if (RequestNo == GET_LINE_CODING)
{
if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
{
CopyRoutine = VCP_GetLineCoding;
}
}
else if (RequestNo == SET_LINE_CODING)
{
if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
{
CopyRoutine = VCP_SetLineCoding;
}
Request = SET_LINE_CODING;
}
else if(RequestNo == GET_DESCRIPTOR) //请求HID描述符
{
if(Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))
{
if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)
{
CopyRoutine = CustomHID_GetReportDescriptor;
}
else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)
{
CopyRoutine = CustomHID_GetHIDDescriptor;
}
}
else if ( (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) )
{
switch( RequestNo )
{
case GET_PROTOCOL:
CopyRoutine = CustomHID_GetProtocolValue;
break;
case SET_REPORT:
CopyRoutine = CustomHID_SetReport_Feature;
Request = SET_REPORT;
break;
default:
break;
}
}
}
if (CopyRoutine == NULL)
{
return USB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
(*CopyRoutine)(0);
return USB_SUCCESS;
}
void test_hid_to_pc(void)
{
static u32 cnt = 0 ;
static u8 SendDatatoUSB[8] = {0x00,0x00,0x00} ; //发送的数据
static u8 i=4;
cnt ++ ;
if(((PrevXferComplete) && (bDeviceState == CONFIGURED))&& cnt >= 2000000)
{
*(SendDatatoUSB + 7 ) = i ;
UserToPMABufferCopy((u8*)(SendDatatoUSB), ENDP4_TXADDR, 8);
SetEPTxCount(ENDP4, 8);
SetEPTxValid(ENDP4);
while(GetEPTxStatus(ENDP4) != EP_TX_NAK); //等待发送完成
*(SendDatatoUSB + 7 ) = 0 ;
UserToPMABufferCopy((u8*)(SendDatatoUSB), ENDP4_TXADDR, 8);
SetEPTxCount(ENDP4, 8);
SetEPTxValid(ENDP4);
i++;
if(i>0x27)
i = 4;
cnt = 0 ;
PrevXferComplete = 0 ;
}
}
该函数在man.c中呗调用,循环发送a-z、1-0等按键值。
发送HID按键数据需要发送8个字节,其中byte0为控制按键(Ctrl、Shift、Alt等按键),byte1为保留字节,byte2-byte7共6个字节,为按键数据,每个字节对应一个按键,至于按键与数据对应关系参考《USB HID to PS/2 Scan Code Translation Table.pdf》。
这里需要注意的是,每次发送键值后,都要发送8字节的空数据到USB主机中,否则,我们举个例子,我按下A键,结果USB主机收到后就会在屏幕上输入'a',但是会一直不停得输入’a‘,这个问题当时也困扰我许久,最后才发现,原来USB主机会一直保持上次收到的数据进行操作,我们上次按下'a',则电脑就会一直打印'a'不停,所以在发送完按键值后,需要再发送空数据给USB主机。