USB虚拟串口+HID键盘复合设备开发详解(VCP+HID Composite)

源文件程序下载地址:https://download.csdn.net/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)为例。其结构一般如下所示:

USB虚拟串口+HID键盘复合设备开发详解(VCP+HID Composite)_第1张图片

注意:一个设备可以有多个配置,但同一时刻只能有一个配置有效,每个配置下可以有多个接口;同一端点号不能出现在同一个配置下的两个或多个不同的接口中;同一端点号可用在不同的配置中。

另外对于复合设备来说,在每个接口前需要添加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设备是通过报告来给传送数据的,报告有输入报告和输出报告.

  1. 输入报告是USB设备发送数据给Host.PS:USB鼠标将鼠标移动和鼠标点击等信息返回给电脑,键盘将按键数据返回给电脑等.
  2. 输出报告是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主机。

你可能感兴趣的:(单片机技术,windows)