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

USB复合设备

  • 介绍
    • USB复合设备与组合设备区别
    • USB描述符修改
    • 修改CustomHID_Reset
    • 修改CustomHID_Data_Setup

介绍

本次使用的是Keil 5+STM32F103ZE(正点原子的板子)+STD标准库+USB的官方例程修改的。
因为新入职了一家公司,需要使用USB复合设备,不然每次使用都需要切换来切换去的很是麻烦,所以开始学习USB的复合设备相关知识。
本文章需要对事先了解一些USB的基础知识,不然在后面的修改中很容易卡住。
USB设备描述符介绍(转帖):https://www.cnblogs.com/lifexy/p/7634511.html?tdsourcetag=s_pctim_aiomsg
IAD描述符介绍(转帖):https://www.cnblogs.com/shangdawei/p/4712305.html?tdsourcetag=s_pctim_aiomsg
PS:这个IAD描述符是用来连接多个接口描述符用的,请务必认真阅读。

USB复合设备与组合设备区别

其实多个接口组合在一起有2种情况
第一种叫做USB复合设备(Compound Device):
就是此类设备内部会集成一个HUD,然后多个USB连接到此HUD上,然后每个USB都会有自己独立的PID,VID。
第二种叫做USB组合设备(Composite Device):
此类设备则内部只有一个PID,VID,通过定义多个接口描述符来实现多个功能的组合。

在USB官方例程中,例程名字就是叫做Composite_Example,其实这次的教程其实应该叫做组合设备的,只是大多数人认识叫做复合设备,所以我才写成复合设备,希望大家注意一下。
STM32 USB复合设备(VCP虚拟串口+HID键盘)详解_第1张图片

USB描述符修改

如果修改USB描述符成功,电脑能识别出来其实就已经成功了一半了

1.USB设备描述符

因为用到IAP描述符(上面的介绍IAD的地址)重点是bDeviceClass,bDeviceSubClass,bDeviceProtocol这3个值一定要改为如下图。
STM32 USB复合设备(VCP虚拟串口+HID键盘)详解_第2张图片
修改后的USB设备描述符如下所示:

/* USB Standard Device Descriptor */
const uint8_t Composite_DeviceDescriptor[Composite_DEVICE_DESC] =
  {

    0x12,   /* 设备描述符的长度bLength */
    USB_DEVICE_DESCRIPTOR_TYPE,     /* 此描述符类型,这里是USB设备描述符类型bDescriptorType */
    0x10,0x01,   /* bcdUSB = 2.00 */
    0xEF,   /* bDeviceClass 设备类 复合设备 */
    0x02,   /* bDeviceSubClass 设备子类*/
    0x01,   /* bDeviceProtocol 设备协议*/
    0x40,   /* bMaxPacketSize0 端点0对应包的的最大包的大小*/
    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.USB配置描述符

USB描述符主要有:USB设备描述符USB配置描述符接口描述符端点描述符报告描述符(在HID类中采用,在VCP虚拟串口中没有这个描述符)。

但是在USB代码中,将USB配置描述符接口描述符端点描述符这3个整为一个USB描述符,然后还是叫做USB配置描述符,关系如下图

/* USB Configuration Descriptor */
/* All Descriptors (配置描述符, 接口描述符, 端点描述符, 类描述符 */
const uint8_t Composite_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
  配置描述符   //Configuration Descriptor 只能有1个

  /*功能1——VCP虚拟串口接口*/
  IAD描述符    //复合设备才有 在单接口的设备这个可以不要
  接口1描述符   //Interface Descriptor
  类描述符    //Class Desdriptor
  端点描述符  //Endpoint Descriptor
  
  接口2描述符   //Interface Descriptor
  类描述符    //Class Desdriptor
  端点描述符  //Endpoint Descriptor
  端点描述符  //Endpoint Descriptor如果此接口有多个端点,可接着添加端点描述符

  /*如果有多个接口 下面还可以继续添加以下描述符*/
  /*功能2——HID键盘*/
  IAD描述符    //复合设备才有 在单接口的设备这个可以不要
  接口描述符   //Interface Descriptor
  类描述符    //Class Desdriptor
  端点描述符  //Endpoint Descriptor

  /*如果有多个接口 下面还可以继续添加以下描述符*/
  /*接口3 */
  ...
}

接下来分析实际的USB配置描述符,这里配置了:
1.HID键盘,一个接口,两个端点4(IN)和端点4(OUT)用于PC接收USB设备发送的键值和USB接收PC发送的数据(比如亮大小写和Num Lock的灯)。
2.VCP虚拟串口,两个接口,三个端点,端点1(IN),端点2(IN),端点3(OUT)。

从设备发送给PC是IN,PC发送给从设备是OUT。

还有需要注意的是端点号不能重复,且端点0不能使用。

/* USB Configuration Descriptor */
/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
const uint8_t Composite_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =
{
     0x09, /* bLength: Configuration Descriptor size */
    USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
    CUSTOMHID_SIZ_CONFIG_DESC,
    /* wTotalLength: Bytes returned */
    0x00,
    0x03,         /* bNumInterfaces: 3 interface 共3个接口 CDC 2个 HID一个*/
    0x01,         /* bConfigurationValue: Configuration value */
    0x00,         /* iConfiguration: Index of string descriptor describing
                                 the configuration*/
    0xC0,         /* bmAttributes: Self powered */
    0x32,         /* MaxPower 100 mA: this current is used for detecting Vbus */

    /*************************************功能1 HID键盘**************************************/
    /*IAD描述符*/
    0x08,   //bLength:IAD描述符大小 
    0x0B,   //bDescriptorType:IAD描述符类型
    0x00,   //bFirstInterface:功能1 HID键盘的第一个接口描述符是在总的配置描述符中的第几个从0开始数
    0x01,   //bInferfaceCount:功能1 HID键盘有1个接口描述符
    0x03,   //bFunctionClass:同单HID功能时,设备符中的bDeviceClass
    0x00,   //bFunctionSubClass:同单HID功能时,设备符中的bDeviceSubClass
    0x01,   //bFunctionProtocol:同单HID功能时,设备符中的bDeviceProtocol
    0x00,   //iFunction:字符串描述中关于此设备的索引(个人理解是一个字符串描述符中有比如0~5是功能1的字符串,
            //6~10是功能2的字符串,如果是功能2的话,此值为6)
    
      
    /************** Descriptor of Custom HID interface ****************/
    /* 09 */
    0x09,         /* bLength: Interface Descriptor size */
    USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType: Interface descriptor type */
    0x00,         /* bInterfaceNumber: Number of Interface */ //<接口 0>
    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 ********************/
    /* 18 */
    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,
    //KEYBOARD_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */
    0x00,
    /******************** Descriptor of Custom HID endpoints ******************/
    /* 27 */
    0x07,          /* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
    0x84,          /* bEndpointAddress: Endpoint Address (IN) */
    0x03,          /* bmAttributes: Interrupt endpoint */
    0x08,          /* wMaxPacketSize: 8 Bytes max */
    0x00,
    0x20,          /* bInterval: Polling Interval (32 ms) */
    /* 34 */	
    0x07,	/* bLength: Endpoint Descriptor size */
    USB_ENDPOINT_DESCRIPTOR_TYPE,	/* bDescriptorType: */
    0x04,	/* bEndpointAddress:Endpoint Address (OUT) */
    0x03,	/* bmAttributes: Interrupt endpoint */
    0x01,	/* wMaxPacketSize: 1 Bytes max  */
    0x00,
    0x20,	/* bInterval: Polling Interval (20 ms) */
    /* 41 */
    /********************************功能2 VCP虚拟串口*****************************/
    /*IAD描述符*/
    /* Interface Association Descriptor(IAD Descriptor)  */ 
    0x08,   /*  bLength  */
    0x0B,   /*  bDescriptorType*/
    0x01,   /*  bFirstInterface*/
    0x02,   /*  bInterfaceCount*/
    0x02,   /*  bFunctionClass --CDC*/
    0x02,   /*  bFunctionSubClass*/
    0x01,   /*  bFunctionProtocoll*/
    0x00,   /*  iFunction */
    
     /**VCP虚拟串口**/
    /*Interface Descriptor接口描述符*/
    0x09,   /* bLength: Interface Descriptor size */
    USB_INTERFACE_DESCRIPTOR_TYPE,  /* bDescriptorType: Interface */
    /* Interface descriptor type */
    0x01,   /* bInterfaceNumber: Number of Interface */   //<接口 1>
    0x00,   /* bAlternateSetting: Alternate setting */
    0x01,   /* bNumEndpoints: One endpoints used 该接口非0端点数*/
    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 */
    VIRTUAL_COM_PORT_INT_SIZE,      /* wMaxPacketSize: */
    0x00,
    0xFF,   /* bInterval: */

    /*Data class interface descriptor类描述符*/
    0x09,   /* bLength: Endpoint Descriptor size */
    USB_INTERFACE_DESCRIPTOR_TYPE,  /* bDescriptorType: */
    0x02,   /* bInterfaceNumber: Number of Interface *///<接口 2>
    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 */
    VIRTUAL_COM_PORT_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 */
    VIRTUAL_COM_PORT_DATA_SIZE,             /* wMaxPacketSize: */
    0x00,
    0x00    /* bInterval */
  }; /* CustomHID_ConfigDescriptor */

2.USB HID报告描述符
这里就不过多累赘了,报告描述符是用来描述HID类设备在收发数据的时候,该如何解释数据。比如有个报告描述符,描述了该键盘有8个字节的输入报告(该输入是针对于电脑的输入,设备是输出),发送给电脑,然后输出报告(这个不是必须的,可省略),用来发送给USB设备,控制CAPS LOCK和NUM LOCK灯的亮灭。鼠标只有1个字节的输入报告,用来描述鼠标的左键,右键,和鼠标的移动量。

const uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
  {       
//0x05:0000 01 01 这是个全局条目,用途页选择为普通桌面页
	0x05, 0x01, // USAGE_PAGE (Generic Desktop)
	//0x09:0000 10 01 这是个全局条目,用途选择为键盘
	0x09, 0x06, // USAGE (Keyboard)
	//0xa1:1010 00 01 这是个主条目,选择为应用集合,
	0xa1, 0x01, // COLLECTION (Application)
	//0x05:0000 01 11 这是个全局条目,用途页选择为键盘/按键
	0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)

	//0x19:0001 10 01 这是个局部条目,用途的最小值为0xe0,对应键盘上的左ctrl键
	0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
	//0x29:0010 10 01 这是个局部条目,用途的最大值为0xe7,对应键盘上的有GUI(WIN)键
	0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
	//0x15:0001 01 01 这是个全局条目,说明数据的逻辑值最小值为0
	0x15, 0x00, // LOGICAL_MINIMUM (0)
	//0x25:0010 01 01 这是个全局条目,说明数据的逻辑值最大值为1
	0x25, 0x01, // LOGICAL_MAXIMUM (1)

	//0x95:1001 01 01 这是个全局条目,数据域的数量为8个
	0x95, 0x08, // REPORT_COUNT (8)
	//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
	0x75, 0x01, // REPORT_SIZE (1)	   
	//0x81:1000 00 01 这是个主条目,有8*1bit数据域作为输入,属性为:Data,Var,Abs
	0x81, 0x02, // INPUT (Data,Var,Abs)

	//0x95:1001 01 01 这是个全局条目,数据域的数量为1个
	0x95, 0x01, // REPORT_COUNT (1)
	//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
	0x75, 0x08, // REPORT_SIZE (8)
	//0x81:1000 00 01 这是个主条目,有1*8bit数据域作为输入,属性为:Cnst,Var,Abs
	0x81, 0x03, // INPUT (Cnst,Var,Abs)

	//0x95:1001 01 01 这是个全局条目,数据域的数量为6个
	0x95, 0x06, // REPORT_COUNT (6)
	//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
	0x75, 0x08, // REPORT_SIZE (8)
	//0x25:0010 01 01 这是个全局条目,逻辑最大值为255
	0x25, 0xFF, // LOGICAL_MAXIMUM (255)
	//0x19:0001 10 01 这是个局部条目,用途的最小值为0
	0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
	//0x29:0010 10 01 这是个局部条目,用途的最大值为0x65
	0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
	//0x81:1000 00 01 这是个主条目,有6*8bit的数据域作为输入,属相为属性为:Data,Var,Abs
	0x81, 0x00, // INPUT (Data,Ary,Abs)

	//0x25:0010 01 01 这是个全局条目,逻辑的最大值为1
	0x25, 0x01, // LOGICAL_MAXIMUM (1)
	//0x95:1001 01 01 这是个全局条目,数据域的数量为2
	0x95, 0x02, // REPORT_COUNT (2)
	//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
	0x75, 0x01, // REPORT_SIZE (1)
	//0x05:0000 01 01 这是个全局条目,用途页选择为LED页
	0x05, 0x08, // USAGE_PAGE (LEDs)
	//0x19:0001 10 01 这是个局部条目,用途的最小值为0x01,对应键盘上的Num Lock
	0x19, 0x01, // USAGE_MINIMUM (Num Lock)
	//0x29:0010 10 01 这是个局部条目,用途的最大值为0x02,对应键盘上的Caps Lock
	0x29, 0x02, // USAGE_MAXIMUM (Caps Lock)
	//0x91:1001 00 01 这是个主条目,有2*1bit的数据域作为输出,属性为:Data,Var,Abs
	0x91, 0x02, // OUTPUT (Data,Var,Abs)

	//0x95:1001 01 01 这是个全局条目,数据域的数量为1个
	0x95, 0x01, // REPORT_COUNT (1)
	//0x75:0111 01 01 这是个全局条目,每个数据域的长度为6bit,正好与前面的2bit组成1字节
	0x75, 0x06, // REPORT_SIZE (6)
	//0x91:1001 00 01 这是个主条目,有1*6bit数据域最为输出,属性为:Cnst,Var,Abs
	0x91, 0x03, // OUTPUT (Cnst,Var,Abs)

	0xc0        // END_COLLECTION

  }; /* CustomHID_ReportDescriptor */

3.端点缓存地址修改
在usb_conf.h文件中有关于USB端点的配置,我们需要来修改一下。
首先是端点数量,这里是使用的总端点数,我们共使用了端点0~4,共5个,修改如下。

#define EP_NUM     (5)

接下来是关于端点的缓存区进行配置,配置每个端点使用的地址在内存中的偏移。在USB模块中有一个所有端点一起使用的RAM区(Packet Buffer)共512字节,其中有一个USB Description Table用于存储每个端点的在RAM区中的偏移和缓存大小。
STM32 USB复合设备(VCP虚拟串口+HID键盘)详解_第3张图片

每个端点都有2个方向(IN/OUT),共有8个端点,所有共有 8x2 = 16个方向,然后每个方向都有2个寄存器,每个寄存器有4字节,所以USB Description Table最大共占内存 16x4x2 = 128字节 = 0x80H(这个是最大值,有些实际未必会用到这么多端点)。
本例子中使用了:
EP0(IN)
EP0(OUT)

EP1(IN)
EP2(IN)
EP3(OUT)

EP4(IN)
EP4(OUT)
共7个端点,所以USB Description Table共占内存 7x4x2 = 56字节 = 0x38H,然后取个整数大于0x38即可,那就取0x40H。所以端点0的发送缓存地址起始地址0x40H。

/*-------------------------------------------------------------*/
/* --------------   Buffer Description Table  -----------------*/
/*-------------------------------------------------------------*/
/* buffer table base address */
#define BTABLE_ADDRESS      (0x00)

/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x40)
#define ENDP0_TXADDR        (0x80)

/* EP1 2 3  */
/* tx buffer base address */
#define ENDP1_TXADDR        (0xC0)
#define ENDP2_TXADDR        (0x100)
#define ENDP3_RXADDR        (0x110)

/* EP4  */
/* rx/tx buffer base address */
#define ENDP4_RXADDR        (0x150)
#define ENDP4_TXADDR        (0x190)

修改CustomHID_Reset

端口的初始化是放在CustomHID_Reset中的,需要添加新加的端口初始化,我是基于HID的例程修改的,所以只需要添加虚拟串口的端点,并且将HID的端口号改为4。

void CustomHID_Reset(void)
{
  /* Set CustomHID_DEVICE as not configured */
  pInformation->Current_Configuration = 0;
  pInformation->Current_Interface = 0;/*the default Interface*/
  
  /* Current Feature initialization */
  pInformation->Current_Feature = CustomHID_ConfigDescriptor[7];
 
  SetBTABLE(BTABLE_ADDRESS);

  /* Initialize Endpoint 0 */
  SetEPType(ENDP0, EP_CONTROL);
  SetEPTxStatus(ENDP0, EP_TX_STALL);
  SetEPRxAddr(ENDP0, ENDP0_RXADDR);
  SetEPTxAddr(ENDP0, ENDP0_TXADDR);
  Clear_Status_Out(ENDP0);
  SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
  SetEPRxValid(ENDP0);

  /* Initialize Endpoint 1 */
  SetEPType(ENDP1, EP_BULK);
  SetEPTxAddr(ENDP1, ENDP1_TXADDR);
  SetEPTxStatus(ENDP1, EP_TX_NAK);
  SetEPRxStatus(ENDP1, EP_RX_DIS);

  /* Initialize Endpoint 2 */
  SetEPType(ENDP2, EP_INTERRUPT);
  SetEPTxAddr(ENDP2, ENDP2_TXADDR);
  SetEPRxStatus(ENDP2, EP_RX_DIS);
  SetEPTxStatus(ENDP2, EP_TX_NAK);

  /* Initialize Endpoint 3 */
  SetEPType(ENDP3, EP_BULK);
  SetEPRxAddr(ENDP3, ENDP3_RXADDR);
  SetEPRxCount(ENDP3, VIRTUAL_COM_PORT_DATA_SIZE);
  SetEPRxStatus(ENDP3, EP_RX_VALID);
  SetEPTxStatus(ENDP3, EP_TX_DIS);
  
  /* Initialize Endpoint 4 */
  SetEPType(ENDP4, EP_INTERRUPT);
  SetEPTxAddr(ENDP4, ENDP4_TXADDR);
  SetEPRxAddr(ENDP4, ENDP4_RXADDR);
  SetEPTxCount(ENDP4, 8);
  SetEPRxCount(ENDP4, 1);
  SetEPRxStatus(ENDP4, EP_RX_VALID);
  SetEPTxStatus(ENDP4, EP_TX_NAK);

  /* Set this device to response on default address */
  SetDeviceAddress(0);
  bDeviceState = ATTACHED;
}

修改CustomHID_Data_Setup

只是将VCP例程中的Data_Setup和HID的Data_Setup整合在一起。

RESULT CustomHID_Data_Setup(uint8_t RequestNo)
{
  uint8_t *(*CopyRoutine)(uint16_t);
  
//  if (pInformation->USBwIndex != 0) ???????????????????????????????????????????????????
//    return USB_UNSUPPORT;    
  
  CopyRoutine = NULL;

  if (RequestNo == GET_LINE_CODING)
  {
    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
    {
      CopyRoutine = Virtual_Com_Port_GetLineCoding;
    }
  }
  else if (RequestNo == SET_LINE_CODING)
  {
    if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT))
    {
      CopyRoutine = Virtual_Com_Port_SetLineCoding;
    }
    Request = SET_LINE_CODING;
  }
  
  else if (RequestNo == GET_DESCRIPTOR)
  {
      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;
        }
      }
  }
    
  
  /*** GET_PROTOCOL, GET_REPORT, SET_REPORT ***/
  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;
}

其实此时电脑就已经能够同时识别出HID和CDC了,接下来就是发送和接收了,这部分有点USB基础的应该都能做出来,在这里就不过多累赘了。

以下是代码下载地址
USB复合设备代码
https://download.csdn.net/download/juqi_/11220896

你可能感兴趣的:(STM32 USB复合设备(VCP虚拟串口+HID键盘)详解)