源文件程序下载地址:/download/longor1991/12290818
一.USB描述符修改
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, //bFirstInterface0x01, //bInferfaceCount0x03, //bFunctionClass:HID0x00, //bFunctionSubClass0x00, //bFunctionProtocol0x05, //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 bytes0xc0 // END_COLLECTION}; /* CustomHID_ReportDescriptor */
报告描述符可以参考一些网站资料比如:关于USBHID协议以及鼠标键盘描述符的解释、USB-HID鼠标、键盘通讯格式(转) 与本人实际测试结果。
USB HID设备是通过报告来给传送数据的,报告有输入报告和输出报告.
输入报告是USB设备发送数据给Host.PS:USB鼠标将鼠标移动和鼠标点击等信息返回给电脑,键盘将按键数据返回给电脑等.输出报告是Host发送数据给USB设备,PS:键盘上的数字键盘锁定灯和大写字母锁定灯等.
报告是一个数据包,里面包含的是索要传送的数据.输入报告是通过中断输入断点输入的,而输出报告有点区别,当没有中断输出断点时,可以通过控制输出断电0发送,当有中断输出断点时,通过中断输出断点发出。而报告描述符,是描述一个报告以及报告里面的数据是用来干什么的.通过它,USB Host可以解析出报告里面的数据所表示的含义.它通过控制输入断点0返回,Host使用获取报告描述符命令来获取报告描述符,注意这个请求是发送到Interface,而不是设备.一个报告描述符可以描述多个报告,不同的报告通过Report ID来区分,Reort ID在报告最前面,即第一个byte.而当报告描述符中没有规定Report ID时,报告中就没有ID字段,开始就是数据。
本例程中的键盘报高描述符不包含输出报告,因为不需要用指示灯指示大小写状态、数字开关灯信息。
二.usb_prop.c主要修改
主要修改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;}
三.应用Demo编写
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主机。