STM32配置组合设备(HID+CDC)

STM32配置组合设备(HID+CDC)

  • pass:其他组合设备也可依照同样的思路搭建
  • pass:本实验基于stm32f107+CubeMx+Keil 实现

本文只对HID和CDC组合设备生成做讲解,关于USB设备描述符等请大家参考本人之前的博客

1. CDC基础工程,HID基础工程生成

  • 首先使用stm32 cubemx配置生成CDC和HID工程,注意生成HID的时候生成的是HID的代码而不是Custom HID的代码。

CDC基础工程生成步骤
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


HID基础工程生成步骤
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


HID工程配置完成之后,修改主函数即可将数据发送出来

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t send_buf[]={0x00,0x00,0x00,0x00};
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    USBD_HID_SendReport(&hUsbDeviceFS,send_buf,sizeof(send_buf));
    HAL_Delay(100);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

主要是调用了USBD_HID_SendReport(&hUsbDeviceFS,send_buf,sizeof(send_buf));函数发送
需要注意的是此HID只配置了输入端点(也就是只能发数据),并没有配置输出端点(因此并不能通过HID接收数据)


CDC基础工程生成之后,修改主函数,

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

	uint8_t buf[]="abcd";
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		CDC_Transmit_FS(buf,sizeof(buf));
		HAL_Delay(100);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

主要是调用CDC_Transmit_FS(buf,sizeof(buf));函数发送数据出来,然后电脑端打开串口调试助手,打开串口,即可看到数据

至此HID和CDC的基础工程已经配置完成了,接下来就是使用这两个基础工程来搭建HID+CDC复合设备了!


2.USB工程熟悉

其实复合设备的编写大家还可以参考阅读此篇博客STM32 USB复合设备编写,然后需要大家认真去理解一下usb的程序了,需要大家自己去看下代码,对整个框架有个熟悉,大家也可以借助debug调试,看代码如何运行的。


2.1 USB初始化

/**
  * Init USB device Library, add supported class and start the library
  * @retval None
  */
void MX_USB_DEVICE_Init(void)
{
  /* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */
  
  /* USER CODE END USB_DEVICE_Init_PreTreatment */
  
  /* Init Device Library, add supported class and start the library. */
  if (USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS) != USBD_OK)
  {
    Error_Handler();
  }
  if (USBD_RegisterClass(&hUsbDeviceFS, &USBD_COMPOSITE) != USBD_OK)
  {
    Error_Handler();
  }
//  if (USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_CDC_Interface_fops_FS) != USBD_OK)
//  {
//    Error_Handler();
//  }
  if (USBD_Start(&hUsbDeviceFS) != USBD_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN USB_DEVICE_Init_PostTreatment */
  
  /* USER CODE END USB_DEVICE_Init_PostTreatment */
}
  • MX_USB_DEVICE_Init用来初始化USB,主要是注册相关函数
  • USBD_RegisterClass用来注册类,将指针指向对应指针函数
    • USBD_COMPOSITE具体函数为
    USBD_ClassTypeDef  USBD_COMPOSITE =
    {
      USBD_Composite_Init,
      USBD_Composite_DeInit,
      USBD_Composite_Setup,
      NULL, /*EP0_TxSent*/
      USBD_Composite_EP0_RxReady,  //add
      USBD_Composite_DataIn,
      USBD_Composite_DataOut,
      NULL,
      NULL,
      NULL,
      NULL,
      USBD_Composite_GetFSCfgDesc,
      NULL,
      USBD_Composite_GetDeviceQualifierDescriptor,
    };
    

主要用来实现usb复合设备的初始化,setup,数据输入输出处理等待

  • USBD_ClassTypeDef是一个函数指针结构体

     ```c
     typedef struct _Device_cb
     {
       uint8_t (*Init)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
       uint8_t (*DeInit)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
       /* Control Endpoints*/
       uint8_t (*Setup)(struct _USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef  *req);
       uint8_t (*EP0_TxSent)(struct _USBD_HandleTypeDef *pdev);
       uint8_t (*EP0_RxReady)(struct _USBD_HandleTypeDef *pdev);
       /* Class Specific Endpoints*/
       uint8_t (*DataIn)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
       uint8_t (*DataOut)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
       uint8_t (*SOF)(struct _USBD_HandleTypeDef *pdev);
       uint8_t (*IsoINIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
       uint8_t (*IsoOUTIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
     
       uint8_t  *(*GetHSConfigDescriptor)(uint16_t *length);
       uint8_t  *(*GetFSConfigDescriptor)(uint16_t *length);
       uint8_t  *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
       uint8_t  *(*GetDeviceQualifierDescriptor)(uint16_t *length);
     #if (USBD_SUPPORT_USER_STRING_DESC == 1U)
       uint8_t  *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev, uint8_t index,  uint16_t *length);
     #endif
     
     } USBD_ClassTypeDef;
     ```
    
  • USBD_CDC_RegisterInterface用来注册接口函数的,比如串口的发送接收函数等等

    USBD_CDC_ItfTypeDef USBD_CDC_Interface_fops_FS =
    {
      CDC_Init_FS,
      CDC_DeInit_FS,
      CDC_Control_FS,
      CDC_Receive_FS
    };
    

    这里注释是因为我们在后面的函数中将指针指过去了,也就是这一步我们在后面的步骤中已经实现了,所以这里不再需要


2.2USB中断

我们配置了USB之后,CubeMX就会自动配置中断,所有的USB通讯都通过中断完成(接收肯定是这样,发送是不是还没研究)
中断函数如下:

	/**
	  * @brief This function handles USB OTG FS global interrupt.
	  */
	void OTG_FS_IRQHandler(void)
	{
	  /* USER CODE BEGIN OTG_FS_IRQn 0 */
	
	  /* USER CODE END OTG_FS_IRQn 0 */
	  HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
	  /* USER CODE BEGIN OTG_FS_IRQn 1 */
	
	  /* USER CODE END OTG_FS_IRQn 1 */
	}

进入HAL_PCD_IRQHandler函数
我使用debug发现,主要处理在以下部分

	//截取其中一部分
	while (ep_intr != 0U)
      {
        if ((ep_intr & 0x1U) != 0U)
        {
          epint = USB_ReadDevOutEPInterrupt(hpcd->Instance, (uint8_t)epnum);

          if ((epint & USB_OTG_DOEPINT_XFRC) == USB_OTG_DOEPINT_XFRC)
          {
            CLEAR_OUT_EP_INTR(epnum, USB_OTG_DOEPINT_XFRC);
            (void)PCD_EP_OutXfrComplete_int(hpcd, epnum);
          }

          if ((epint & USB_OTG_DOEPINT_STUP) == USB_OTG_DOEPINT_STUP)
          {
            CLEAR_OUT_EP_INTR(epnum, USB_OTG_DOEPINT_STUP);
            /* Class B setup phase done for previous decoded setup */
            (void)PCD_EP_OutSetupPacket_int(hpcd, epnum);
          }

          if ((epint & USB_OTG_DOEPINT_OTEPDIS) == USB_OTG_DOEPINT_OTEPDIS)
          {
            CLEAR_OUT_EP_INTR(epnum, USB_OTG_DOEPINT_OTEPDIS);
          }

          /* Clear Status Phase Received interrupt */
          if ((epint & USB_OTG_DOEPINT_OTEPSPR) == USB_OTG_DOEPINT_OTEPSPR)
          {
            CLEAR_OUT_EP_INTR(epnum, USB_OTG_DOEPINT_OTEPSPR);
          }

          /* Clear OUT NAK interrupt */
          if ((epint & USB_OTG_DOEPINT_NAK) == USB_OTG_DOEPINT_NAK)
          {
            CLEAR_OUT_EP_INTR(epnum, USB_OTG_DOEPINT_NAK);
          }
        }
        epnum++;
        ep_intr >>= 1U;
      }
    }

主要的处理在(void)PCD_EP_OutXfrComplete_int(hpcd, epnum);(void)PCD_EP_OutSetupPacket_int(hpcd, epnum);内。

  • (void)PCD_EP_OutSetupPacket_int(hpcd, epnum);函数分析
    进入此函数,会调用HAL_PCD_SetupStageCallback(hpcd);
    之后调用USBD_LL_SetupStage((USBD_HandleTypeDef*)hpcd->pData, (uint8_t *)hpcd->Setup);,核心部分在这里面
	/**
	* @brief  USBD_SetupStage
	*         Handle the setup stage
	* @param  pdev: device instance
	* @retval status
	*/
	USBD_StatusTypeDef USBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup)
	{
	  USBD_ParseSetupRequest(&pdev->request, psetup);
	
	  pdev->ep0_state = USBD_EP0_SETUP;
	
	  pdev->ep0_data_len = pdev->request.wLength;
	
	  switch (pdev->request.bmRequest & 0x1FU)
	  {
	    case USB_REQ_RECIPIENT_DEVICE:
	      USBD_StdDevReq(pdev, &pdev->request);
	      break;
	
	    case USB_REQ_RECIPIENT_INTERFACE:
	      USBD_StdItfReq(pdev, &pdev->request);
	      break;
	
	    case USB_REQ_RECIPIENT_ENDPOINT:
	      USBD_StdEPReq(pdev, &pdev->request);
	      break;
	
	    default:
	      USBD_LL_StallEP(pdev, (pdev->request.bmRequest & 0x80U));
	      break;
	  }
	
	  return USBD_OK;
	}

主要在switch内调用不同的函数,实现设备枚举等内容,大家点进去之后就可以发现,他的调用过程都是使用指针指向对应的函数,而指向的内容在usb初始化的时候已经设置好。

  • (void)PCD_EP_OutXfrComplete_int(hpcd, epnum);函数分析
    进入此函数会调用HAL_PCD_DataOutStageCallback(hpcd, (uint8_t)epnum);
    进入之后由调用USBD_LL_DataOutStage((USBD_HandleTypeDef*)hpcd->pData, epnum, hpcd->OUT_ep[epnum].xfer_buff);,核心在这里面
		
	/**
	* @brief  USBD_DataOutStage
	*         Handle data OUT stage
	* @param  pdev: device instance
	* @param  epnum: endpoint index
	* @retval status
	*/
	USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev,
	                                        uint8_t epnum, uint8_t *pdata)
	{
	  USBD_EndpointTypeDef *pep;
	
	  if (epnum == 0U)
	  {
	    pep = &pdev->ep_out[0];
	
	    if (pdev->ep0_state == USBD_EP0_DATA_OUT)
	    {
	      if (pep->rem_length > pep->maxpacket)
	      {
	        pep->rem_length -= pep->maxpacket;
	
	        USBD_CtlContinueRx(pdev, pdata,
	                           (uint16_t)MIN(pep->rem_length, pep->maxpacket));
	      }
	      else
	      {
	        if ((pdev->pClass->EP0_RxReady != NULL) &&
	            (pdev->dev_state == USBD_STATE_CONFIGURED))
	        {
	          pdev->pClass->EP0_RxReady(pdev);
	        }
	        USBD_CtlSendStatus(pdev);
	      }
	    }
	    else
	    {
	      if (pdev->ep0_state == USBD_EP0_STATUS_OUT)
	      {
	        /*
	         * STATUS PHASE completed, update ep0_state to idle
	         */
	        pdev->ep0_state = USBD_EP0_IDLE;
	        USBD_LL_StallEP(pdev, 0U);
	      }
	    }
	  }
	  else if ((pdev->pClass->DataOut != NULL) &&
	           (pdev->dev_state == USBD_STATE_CONFIGURED))
	  {
	    pdev->pClass->DataOut(pdev, epnum);
	  }
	  else
	  {
	    /* should never be in this condition */
	    return USBD_FAIL;
	  }
	
	  return USBD_OK;
	}

在此函数内完成数据输出DataOut,EP0_RxReady,端点设置等等

2.3 相关结构体

usb全局结构体`USBD_HandleTypeDef hUsbDeviceFS;`
	/* USB Device handle structure */
	typedef struct _USBD_HandleTypeDef
	{
	  uint8_t                 id;
	  uint32_t                dev_config;
	  uint32_t                dev_default_config;
	  uint32_t                dev_config_status;
	  USBD_SpeedTypeDef       dev_speed;
	  USBD_EndpointTypeDef    ep_in[16];
	  USBD_EndpointTypeDef    ep_out[16];
	  uint32_t                ep0_state;
	  uint32_t                ep0_data_len;
	  uint8_t                 dev_state;
	  uint8_t                 dev_old_state;
	  uint8_t                 dev_address;
	  uint8_t                 dev_connection_status;
	  uint8_t                 dev_test_mode;
	  uint32_t                dev_remote_wakeup;
	
	  USBD_SetupReqTypedef    request;
	  USBD_DescriptorsTypeDef *pDesc;
	  USBD_ClassTypeDef       *pClass;
	  void                    *pClassData;
	  void                    *pUserData;
	  void                    *pData;
	} USBD_HandleTypeDef;

这个结构体特别重要,我们可以注意到在初始化usb的时候其实都是配置的这个结构体,对于此结构体我们重点理解后面几个指针参数,最重要!

	  USBD_DescriptorsTypeDef *pDesc;
	  USBD_ClassTypeDef       *pClass;
	  void                    *pClassData;
	  void                    *pUserData;
	  void                    *pData;
2.3.1 USBD_DescriptorsTypeDef *pDesc;指向的是设备描述符,
		/* USB Device descriptors structure */
		typedef struct
		{
		  uint8_t  *(*GetDeviceDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetLangIDStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetManufacturerStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetProductStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetSerialStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetConfigurationStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		  uint8_t  *(*GetInterfaceStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		#if (USBD_LPM_ENABLED == 1U)
		  uint8_t  *(*GetBOSDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
		#endif
		} USBD_DescriptorsTypeDef;

void MX_USB_DEVICE_Init(void)内调用

		  if (USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS) != USBD_OK)
		  {
		    Error_Handler();
		  }

FS_Desc为定义的结构体

		/** @defgroup USBD_DESC_Private_Variables USBD_DESC_Private_Variables
		  * @brief Private variables.
		  * @{
		  */
		
		USBD_DescriptorsTypeDef FS_Desc =
		{
		  USBD_FS_DeviceDescriptor
		, USBD_FS_LangIDStrDescriptor
		, USBD_FS_ManufacturerStrDescriptor
		, USBD_FS_ProductStrDescriptor
		, USBD_FS_SerialStrDescriptor
		, USBD_FS_ConfigStrDescriptor
		, USBD_FS_InterfaceStrDescriptor
		};
2.3.2 USBD_ClassTypeDef *pClass 指向一个类
		typedef struct _Device_cb
		{
		  uint8_t (*Init)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
		  uint8_t (*DeInit)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
		  /* Control Endpoints*/
		  uint8_t (*Setup)(struct _USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef  *req);
		  uint8_t (*EP0_TxSent)(struct _USBD_HandleTypeDef *pdev);
		  uint8_t (*EP0_RxReady)(struct _USBD_HandleTypeDef *pdev);
		  /* Class Specific Endpoints*/
		  uint8_t (*DataIn)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
		  uint8_t (*DataOut)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
		  uint8_t (*SOF)(struct _USBD_HandleTypeDef *pdev);
		  uint8_t (*IsoINIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
		  uint8_t (*IsoOUTIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
		
		  uint8_t  *(*GetHSConfigDescriptor)(uint16_t *length);
		  uint8_t  *(*GetFSConfigDescriptor)(uint16_t *length);
		  uint8_t  *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
		  uint8_t  *(*GetDeviceQualifierDescriptor)(uint16_t *length);
		#if (USBD_SUPPORT_USER_STRING_DESC == 1U)
		  uint8_t  *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev, uint8_t index,  uint16_t *length);
		#endif
		
		} USBD_ClassTypeDef;

对应的定义,下面是我们根据HID和CDC的修改的组合设备的类

			USBD_ClassTypeDef  USBD_COMPOSITE =
			{
			  USBD_Composite_Init,
			  USBD_Composite_DeInit,
			  USBD_Composite_Setup,
			  NULL, /*EP0_TxSent*/
			  USBD_Composite_EP0_RxReady,  //add
			  USBD_Composite_DataIn,
			  USBD_Composite_DataOut,
			  NULL,
			  NULL,
			  NULL,
			  NULL,
			  USBD_Composite_GetFSCfgDesc,
			  NULL,
			  USBD_Composite_GetDeviceQualifierDescriptor,
			};
2.3.3 void *pClassData; 特别重要,存放了CDC或者HID的相关句柄,搭建组合设备的时候需要重点区分此指针
		当是CDC的时候pClassData所指向的结构体
 		typedef struct
		{
		  uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE / 4U];      /* Force 32bits alignment */
		  uint8_t  CmdOpCode;
		  uint8_t  CmdLength;
		  uint8_t  *RxBuffer;
		  uint8_t  *TxBuffer;
		  uint32_t RxLength;
		  uint32_t TxLength;
		
		  __IO uint32_t TxState;
		  __IO uint32_t RxState;
		}
		USBD_CDC_HandleTypeDef;
	当是HID的时候指向的结构体
 		typedef struct
		{
		  uint32_t             Protocol;
		  uint32_t             IdleState;
		  uint32_t             AltSetting;
		  HID_StateTypeDef     state;
		}
		USBD_HID_HandleTypeDef;
2.3.4 void *pUserData;指向接口函数

我们看下uint8_t USBD_CDC_RegisterInterface(USBD_HandleTypeDef *pdev, USBD_CDC_ItfTypeDef *fops)函数内部就可以知道

 		/**
		* @brief  USBD_CDC_RegisterInterface
		  * @param  pdev: device instance
		  * @param  fops: CD  Interface callback
		  * @retval status
		  */
		uint8_t  USBD_CDC_RegisterInterface(USBD_HandleTypeDef   *pdev,
		                                    USBD_CDC_ItfTypeDef *fops)
		{
		  uint8_t  ret = USBD_FAIL;
		
		  if (fops != NULL)
		  {
		    pdev->pUserData = fops;
		    ret = USBD_OK;
		  }
		  return ret;
		}
2.3.5 void *pData;指向hpcd_USB_OTG_FShpcd_USB_OTG_FS在中断函数的时候会作为参数传入

USBD_LL_Init函数内pdev->pData = &hpcd_USB_OTG_FS;

/**
  * @brief  Initializes the low level portion of the device driver.
  * @param  pdev: Device handle
  * @retval USBD status
  */
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
  /* Init USB Ip. */
  if (pdev->id == DEVICE_FS) {
  /* Link the driver to the stack. */
  hpcd_USB_OTG_FS.pData = pdev;
  pdev->pData = &hpcd_USB_OTG_FS;
  //...略
}

pClassData和pUserData在USBD_HandleTypeDef中是指针形式,所以在调用不同类的时候,改变指针的指向,即可完成不同类的功能,此外复合设备配置修改之后修改pDesc和pClass指向即可。我们复合设备类的设计思想既是如此。


3. 配置组合设备

1.修改编译器优化等级

cubemx配置的工程默认优化等级都是2级优化,优化等级高了很容易出现离奇bug的,别问为什么,改低点就对了!
在这里插入图片描述

2. 以CDC工程为为基础,将HID工程添加进来

在这里插入图片描述

3. 将文件添加到对应工程中

在这里插入图片描述
在这里插入图片描述

4. 新建usbd_composite.cusbd_composite.h文件

5. 编写对应的usbd_composite.c文件

前面我们说过组合设备的核心,pClassData和pUserData在USBD_HandleTypeDef中是指针形式,所以在调用不同类的时候,改变指针的指向,即可完成不同类的功能,此外复合设备配置修改之后修改pDesc和pClass指向即可
根据上述思想,编写对应的usbd_composite.c文件

#include "usbd_composite.h"

USBD_CDC_HandleTypeDef *pCDCData;
USBD_HID_HandleTypeDef *pHIDData;

static uint8_t  USBD_Composite_Init (USBD_HandleTypeDef *pdev,
                            uint8_t cfgidx);
static uint8_t  USBD_Composite_DeInit (USBD_HandleTypeDef *pdev,
                              uint8_t cfgidx);
static uint8_t  USBD_Composite_Setup (USBD_HandleTypeDef *pdev,
                             USBD_SetupReqTypedef *req);
static uint8_t  USBD_Composite_EP0_RxReady(USBD_HandleTypeDef *pdev);
static uint8_t  USBD_Composite_DataIn (USBD_HandleTypeDef *pdev,
                              uint8_t epnum);
static uint8_t  USBD_Composite_DataOut (USBD_HandleTypeDef *pdev,
                               uint8_t epnum);
static uint8_t  *USBD_Composite_GetFSCfgDesc (uint16_t *length);
static uint8_t  *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length);

USBD_ClassTypeDef  USBD_COMPOSITE =
{
  USBD_Composite_Init,
  USBD_Composite_DeInit,
  USBD_Composite_Setup,
  NULL, /*EP0_TxSent*/
  USBD_Composite_EP0_RxReady,  //add
  USBD_Composite_DataIn,
  USBD_Composite_DataOut,
  NULL,
  NULL,
  NULL,
  NULL,
  USBD_Composite_GetFSCfgDesc,
  NULL,
  USBD_Composite_GetDeviceQualifierDescriptor,
};



/* USB composite device Configuration Descriptor */
/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
__ALIGN_BEGIN uint8_t USBD_Composite_CfgFSDesc[USBD_COMPOSITE_DESC_SIZE]  __ALIGN_END =
{
  /* 配置描述符 */
  0x09,   /* bLength: Configuation Descriptor size */
  USB_DESC_TYPE_CONFIGURATION,   /* bDescriptorType: Configuration */
  USBD_COMPOSITE_DESC_SIZE,  
  0x00,
  USBD_MAX_NUM_INTERFACES ,  /* bNumInterfaces: */
  0x01,   /* bConfigurationValue: 0 配置的值 */
  0x00,   /* iConfiguration: 00 字符串索引 */
  0x80,   /* bmAttributes:no-bus powered and Dissupport Remote Wake-up*/
  0x32,   /* MaxPower 100 mA  */


  /****************************HID************************************/
  /* Interface Association Descriptor */
  USBD_IAD_DESC_SIZE,                        // bLength IAD描述符大小
  USBD_IAD_DESCRIPTOR_TYPE,                  // bDescriptorType IAD描述符类型
  0x00,                                      // bFirstInterface 接口描述符是在总的配置描述符中的第几个从0开始数
  0x01,                                      // bInterfaceCount 接口描述符数量
  0x03,                                      // bFunctionClass  设备符中的bDeviceClass
  0x00,                                      // bFunctionSubClass  设备符中的bDeviceSubClass
  0x00,                                      // bInterfaceProtocol 设备符中的bDeviceProtocol
  0x00,

  /********************  HID interface ********************/
  /************** Descriptor of Custom HID interface ****************/
  /* 09 */
  0x09,                   /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  USBD_HID_INTERFACE,     /*bInterfaceNumber: Number of Interface 接口编号 0 */
  0x00,                   /*bAlternateSetting: Alternate setting  备用接口 */
  0x01,                   /*bNumEndpoints 使用的端点数 1 */
  0x03,                   /*bInterfaceClass: HID*/
  0x00,                   /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x00,                   /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  0,                      /*iInterface: Index of string descriptor*/
  
  /******************** Descriptor of Custom HID ********************/
  /* 18 */
  0x09,                   /*bLength: HID Descriptor size*/
  HID_DESCRIPTOR_TYPE,    /*bDescriptorType: HID*/
  0x00,                   /*bcdHID: HID Class Spec release number*/
  0x01,
  0x00,                   /*bCountryCode: Hardware target country*/
  0x01,                   /*bNumDescriptors: Number of HID class descriptors to follow*/
  0x22,                   /*bDescriptorType*/
  HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
  0x00,
  /******************** Descriptor of TouchScreen endpoint ********************/
  /* 27 */
  0x07,                   /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  HID_EPIN_ADDR,          /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,                   /*bmAttributes: Interrupt endpoint*/
  HID_EPIN_SIZE,          /*wMaxPacketSize: 16 Byte max */
  0x00,
  HID_FS_BINTERVAL,       /*bInterval: Polling Interval */
  /* 34 */
  
  /****************************CDC************************************/
  /* IAD描述符 */
  /* Interface Association Descriptor */
  USBD_IAD_DESC_SIZE,               // bLength
  USBD_IAD_DESCRIPTOR_TYPE,         // bDescriptorType
  0x01,                             // bFirstInterface 接口描述符是在总的配置描述符中的第几个从0开始数 1
  0x02,                             // bInterfaceCount 接口描述符数量 2
  0x02,                             // bFunctionClass     CDC Control
  0x02,                             // bFunctionSubClass  Abstract Control Model
  0x01,                             // bInterfaceProtocol  AT Commands: V.250 etc
  0x00,                             // iFunction
  
  /* CDC命令接口描述符 */
  /*Interface Descriptor */
  0x09,   /* bLength: Interface Descriptor size 长度 */
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface 接口编号0x04 */
  /* Interface descriptor type */
  USBD_CDC_CMD_INTERFACE,   /* bInterfaceNumber: Number of Interface 接口编号,第一个接口编号为1 */
  0x00,   /* bAlternateSetting: Alternate setting 接口备用编号 0 */
  0x01,   /* bNumEndpoints: One endpoints used 非0端点的数目 1 cdc接口只使用了一个中断输入端点 */
  0x02,   /* bInterfaceClass: Communication Interface Class 接口所使用的类0x02 */
  0x02,   /* bInterfaceSubClass: Abstract Control Model 接口所使用的子类0x02 */
  0x01,   /* bInterfaceProtocol: Common AT commands 使用AT命令协议 */
  0x00,   /* iInterface: 接口字符串索引值 0表示没有 */

  /* 类特殊接口描述符--功能描述符 用来描述接口的功能 */
  /*Header Functional Descriptor*/
  0x05,   /* bLength: Endpoint Descriptor size 描述符长度为5字节 */
  0x24,   /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
  0x00,   /* bDescriptorSubtype: Header Func Desc 子类为 Header Func Desc,编号0x00 */
  0x10,   /* bcdCDC: spec release number CDC版本 */
  0x01,

  /*Call Management Functional Descriptor*/
  0x05,   /* bFunctionLength */
  0x24,   /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
  0x01,   /* bDescriptorSubtype: Call Management Func Desc 子类为Call Management Func Desc 编号0x01*/
  0x00,   /* bmCapabilities: D0+D1 设备自己不管理call management */
  0x01,   /* bDataInterface: 1 有一个数据类接口用作call management */

  /*ACM Functional Descriptor*/
  0x04,   /* bFunctionLength */
  0x24,   /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE*/
  0x02,   /* bDescriptorSubtype: Abstract Control Management desc 子类为Abstract Control Management desc编号0x02*/
  0x02,   /* bmCapabilities 支持Set_Control_Line_State、Get_Line_Coding请求和Serial_State通知*/

  /*Union Functional Descriptor*/
  0x05,   /* bFunctionLength */
  0x24,   /* bDescriptorType: CS_INTERFACE 描述符类型为类特殊接口CS_INTERFACE */
  0x06,   /* bDescriptorSubtype: Union func desc 子类为Union func desc 编号0x06*/
  USBD_CDC_CMD_INTERFACE,    /* bMasterInterface: Communication class interface 编号为1的CDC接口 */
  USBD_CDC_DATA_INTERFACE,   /* bSlaveInterface0: Data Class Interface 编号为2的数据类接口 */

  /*Endpoint 2 Descriptor*/
  0x07,                           /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,   			/* bDescriptorType: Endpoint */
  CDC_CMD_EP,                     /* bEndpointAddress */
  0x03,                           /* bmAttributes: Interrupt */
  LOBYTE(CDC_CMD_PACKET_SIZE),    /* wMaxPacketSize: */
  HIBYTE(CDC_CMD_PACKET_SIZE),
  CDC_FS_BINTERVAL,                           /* bInterval: */
  /*---------------------------------------------------------------------------*/

	/* 数据类接口的接口描述符 */
  /*Data class interface descriptor*/
  0x09,   /* bLength: Endpoint Descriptor size 接口描述符长度9字节*/
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: 接口描述符的编号0x04*/
  USBD_CDC_DATA_INTERFACE,   /* bInterfaceNumber: Number of Interface 接口的编号为2*/
  0x00,   /* bAlternateSetting: Alternate setting 该接口的备用编号为0 */
  0x02,   /* bNumEndpoints: Two endpoints used 非0端点的数据 设备需要使用一对批量端点,设置为2*/
  0x0A,   /* bInterfaceClass: CDC 该接口所使用的类 数据类接口代码为0x0A */
  0x00,   /* bInterfaceSubClass: 接口所使用的子类为0*/
  0x00,   /* bInterfaceProtocol: 接口所使用的协议为0*/
  0x00,   /* iInterface:  接口的字符串索引值,0表示没有*/

	/* 输出端点的端点描述符 */
  /*Endpoint OUT Descriptor*/
  0x07,   /* bLength: Endpoint Descriptor size 端点描述符长度7字节 */
  USB_DESC_TYPE_ENDPOINT,               /* bDescriptorType: Endpoint 端点描述符编号为0x05 */
  CDC_OUT_EP,                           /* bEndpointAddress 端点的地址0x02 D7为方向*/
  0x02,                                 /* bmAttributes: Bulk 批量传输*/
  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  /* wMaxPacketSize: 端点的最大包长 512字节*/
  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
  0x00,                                 /* bInterval: ignore for Bulk transfer 端点查询时间,对批量端点无效 */

	/* 输入端点的端点描述符 */
  /*Endpoint IN Descriptor*/
  0x07,   /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT,               /* bDescriptorType: Endpoint 端点描述符编号为0x05*/
  CDC_IN_EP,                            /* bEndpointAddress 端点的地址0x82 D7为方向*/
  0x02,                                 /* bmAttributes: Bulk 批量传输*/
  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  /* wMaxPacketSize: 端点的最大包长 512字节*/
  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
  0x00                                  /* bInterval: ignore for Bulk transfer 端点查询时间,对批量端点无效*/
};

/* USB 设备限定符描述符 */
/* USB Standard Device Descriptor */ 
__ALIGN_BEGIN  uint8_t USBD_Composite_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC]  __ALIGN_END =
{
  USB_LEN_DEV_QUALIFIER_DESC,
  USB_DESC_TYPE_DEVICE_QUALIFIER,
  0x00,
  0x02,
  0x00,
  0x00,
  0x00,
  0x40,
  0x01,
  0x00,
};

static uint8_t  USBD_Composite_Init (USBD_HandleTypeDef *pdev,
                            uint8_t cfgidx)
{
  uint8_t res = 0;

  pdev->pUserData =  (void*)&USBD_CDC_Interface_fops_FS;
  res +=  USBD_CDC.Init(pdev,cfgidx);
  pCDCData = pdev->pClassData;
  /* TODO */
  pdev->pUserData = NULL;
  res +=  USBD_HID.Init(pdev,cfgidx);
  pHIDData = pdev->pClassData;
  return res;
}


static uint8_t  USBD_Composite_DeInit (USBD_HandleTypeDef *pdev,
                              uint8_t cfgidx)
{
  uint8_t res = 0;
	pdev->pClassData = pCDCData;
	pdev->pUserData = &USBD_CDC_Interface_fops_FS;
	res +=  USBD_CDC.DeInit(pdev,cfgidx);

	pdev->pClassData = pHIDData;
  /* TODO */
	pdev->pUserData = NULL;
	res +=  USBD_HID.DeInit(pdev,cfgidx);

	return res;
}


static uint8_t  USBD_Composite_EP0_RxReady(USBD_HandleTypeDef *pdev)
{
	pdev->pClassData = pCDCData;
	pdev->pUserData = &USBD_CDC_Interface_fops_FS;
  return USBD_CDC.EP0_RxReady(pdev);
}


/**
* @brief  USBD_Composite_Setup
*         Handle the Composite requests
* @param  pdev: device instance
* @param  req: USB request
* @retval status
*/
static uint8_t  USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
  switch (req->bmRequest & USB_REQ_RECIPIENT_MASK)
  {
   case USB_REQ_RECIPIENT_INTERFACE:
     switch(req->wIndex)
      {
         case USBD_CDC_DATA_INTERFACE:
         case USBD_CDC_CMD_INTERFACE:
           pdev->pClassData = pCDCData;
           pdev->pUserData =  &USBD_CDC_Interface_fops_FS;
           return(USBD_CDC.Setup(pdev, req));

         case USBD_HID_INTERFACE:
           pdev->pClassData = pHIDData;
           /* TODO */
           pdev->pUserData =  NULL;
           return(USBD_HID.Setup (pdev, req));

         default:
            break;
     }
     break;

   case USB_REQ_RECIPIENT_ENDPOINT:
     switch(req->wIndex)
     {

         case CDC_IN_EP:
         case CDC_OUT_EP:
         case CDC_CMD_EP:
           pdev->pClassData = pCDCData;
           pdev->pUserData =  &USBD_CDC_Interface_fops_FS;
           return(USBD_CDC.Setup(pdev, req));

         case HID_EPIN_ADDR:
//         case HID_EPOUT_ADDR:
           pdev->pClassData = pHIDData;
           /* TODO */
           pdev->pUserData =  NULL;
           return(USBD_HID.Setup (pdev, req));

         default:
            break;
     }
     break;
  }
  return USBD_OK;
}

/**
* @brief  USBD_Composite_DataIn
*         handle data IN Stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
static uint8_t  USBD_Composite_DataIn (USBD_HandleTypeDef *pdev,
                              uint8_t epnum)
{
  switch(epnum)
  {
      case CDC_INDATA_NUM:
         pdev->pUserData =  &USBD_CDC_Interface_fops_FS;
         pdev->pClassData = pCDCData;
         return(USBD_CDC.DataIn(pdev,epnum));
      case HID_INDATA_NUM:
         /* TODO */
         pdev->pUserData = NULL;
         pdev->pClassData = pHIDData;
         return(USBD_HID.DataIn(pdev,epnum));
      default:
         break;
  }
  return USBD_FAIL;
}

/**
* @brief  USBD_Composite_DataOut
*         handle data OUT Stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
uint8_t  USBD_Composite_DataOut (USBD_HandleTypeDef *pdev,
                               uint8_t epnum)
{
  switch(epnum)
  {
      case CDC_OUTDATA_NUM:
      case CDC_OUTCMD_NUM:
        pdev->pClassData = pCDCData;
        pdev->pUserData =  &USBD_CDC_Interface_fops_FS;
        return(USBD_CDC.DataOut(pdev,epnum));

      default:
         break;
  }
  return USBD_FAIL;
}

/**
* @brief  USBD_Composite_GetHSCfgDesc
*         return configuration descriptor
* @param  length : pointer data length
* @retval pointer to descriptor buffer
*/
uint8_t  *USBD_Composite_GetFSCfgDesc (uint16_t *length)
{
   *length = sizeof (USBD_Composite_CfgFSDesc);
   return USBD_Composite_CfgFSDesc;
}


/**
* @brief  DeviceQualifierDescriptor
*         return Device Qualifier descriptor
* @param  length : pointer data length
* @retval pointer to descriptor buffer
*/
uint8_t  *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length)
{
  *length = sizeof (USBD_Composite_DeviceQualifierDesc);
  return USBD_Composite_DeviceQualifierDesc;
}

编写对应的usbd_composite.h文件

#ifndef __USBD_COMPOSITE_H_
#define __USBD_COMPOSITE_H_

#include "usbd_hid.h"
#include "usbd_cdc.h"
#include  "usbd_cdc_if.h"


#define USBD_COMPOSITE_DESC_SIZE    (108)

#define USBD_IAD_DESC_SIZE           0x08
#define USBD_IAD_DESCRIPTOR_TYPE     0x0B

#define USBD_HID_INTERFACE           0  //HID接口索引值
#define USBD_CDC_CMD_INTERFACE       1  //CDC CMD接口索引值
#define USBD_CDC_DATA_INTERFACE      2  //CDC Data接口索引值

#define HID_INDATA_NUM              (HID_EPIN_ADDR & 0x0F)
#define CDC_INDATA_NUM              (CDC_IN_EP & 0x0F)
#define CDC_OUTDATA_NUM             (CDC_OUT_EP & 0x0F)
#define CDC_OUTCMD_NUM              (CDC_CMD_EP & 0x0F)

extern USBD_CDC_HandleTypeDef *pCDCData;
extern USBD_HID_HandleTypeDef *pHIDData;

extern USBD_ClassTypeDef    USBD_COMPOSITE;
#endif

6. 修改设备描述符文件

/** USB standard device descriptor. */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
#if 1
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType 描述符编号0x01 */
  0x00,                       /*bcdUSB 版本2.0 */
  0x02,
  0xEF,                       /*bDeviceClass 综合设备 */
  0x02,                       /*bDeviceSubClass*/
  0x01,                       /*bDeviceProtocol*/
  USB_MAX_EP0_SIZE,           /*bMaxPacketSize 端点0大小为64字节 */
  LOBYTE(USBD_VID),           /*idVendor  厂家ID */
  HIBYTE(USBD_VID),           /*idVendor*/
  LOBYTE(USBD_PID_FS),        /*idProduct 产品ID */
  HIBYTE(USBD_PID_FS),        /*idProduct*/
  0x00,                       /*bcdDevice rel. 2.00版本*/
  0x02,
  USBD_IDX_MFC_STR,           /*Index of manufacturer string 厂商字符串索引值 0x01 */
  USBD_IDX_PRODUCT_STR,       /*Index of product string 产品字符串索引值 0x02 */
  USBD_IDX_SERIAL_STR,        /*Index of serial number string 设备序列号字符串索引值 0x03 */
  USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations 该设备所具有的配置数 0x01 */
#elif 1
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  0x00,                       /*bcdUSB */
  0x02,
  0x02,                       /*bDeviceClass*/
  0x02,                       /*bDeviceSubClass*/
  0x00,                       /*bDeviceProtocol*/
  USB_MAX_EP0_SIZE,           /*bMaxPacketSize*/
  LOBYTE(USBD_VID),           /*idVendor*/
  HIBYTE(USBD_VID),           /*idVendor*/
  LOBYTE(USBD_PID_FS),        /*idProduct*/
  HIBYTE(USBD_PID_FS),        /*idProduct*/
  0x00,                       /*bcdDevice rel. 2.00*/
  0x02,
  USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/  
#endif
};

7. 修改usb初始化文件

/**
  * Init USB device Library, add supported class and start the library
  * @retval None
  */
void MX_USB_DEVICE_Init(void)
{
  /* USER CODE BEGIN USB_DEVICE_Init_PreTreatment */
  
  /* USER CODE END USB_DEVICE_Init_PreTreatment */
  
  /* Init Device Library, add supported class and start the library. */
  if (USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS) != USBD_OK)
  {
    Error_Handler();
  }
  if (USBD_RegisterClass(&hUsbDeviceFS, &USBD_COMPOSITE) != USBD_OK)
  {
    Error_Handler();
  }
//  if (USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_CDC_Interface_fops_FS) != USBD_OK)
//  {
//    Error_Handler();
//  }
  if (USBD_Start(&hUsbDeviceFS) != USBD_OK)
  {
    Error_Handler();
  }

  /* USER CODE BEGIN USB_DEVICE_Init_PostTreatment */
  
  /* USER CODE END USB_DEVICE_Init_PostTreatment */
}

8. 修改HID和CDC的端点

修改端点的时候注意一定不要冲突,其次,端点的配置也在设备描述符类体现,需要大家注意
usbd_cdc.h文件内

#define CDC_IN_EP                                   0x81U  /* EP1 for data IN */
#define CDC_OUT_EP                                  0x01U  /* EP1 for data OUT */
#define CDC_CMD_EP                                  0x82U  /* EP2 for CDC commands */

usbd_hid文件内

#define HID_EPIN_ADDR                 0x83U
#define HID_EPIN_SIZE                 0x04U

9. 修改支持的接口数

修改usbd_conf.h文件内配置

/*---------- -----------*/
#define USBD_MAX_NUM_INTERFACES     3
/*---------- -----------*/
#define USBD_MAX_NUM_CONFIGURATION     1
/*---------- -----------*/
#define USBD_MAX_STR_DESC_SIZ     512
/*---------- -----------*/
#define USBD_DEBUG_LEVEL     0
/*---------- -----------*/
#define USBD_SELF_POWERED     1
/*---------- -----------*/
#define MAX_STATIC_ALLOC_SIZE     512

/****************************************/
/* #define for FS and HS identification */
#define DEVICE_FS 		0

10. 确认接口配置

在usbd_composite.c文件内的USBD_Composite_CfgFSDesc[]描述符中

11. 修改内存申请函数

pdev->pClassData函数在初始化的时候都是需要申请内存的,函数内部调用的USBD_malloc申请,但是这里有个很大的坑,默认函数内部是定义了一个静态数组,然后将数组指针返回,但是在定义数组的时候特别坑,默认是下面这样子

void *USBD_static_malloc(uint32_t size)
{
  static uint32_t mem[(sizeof(USBD_CDC_HandleTypeDef)/4)+1];/* On 32-bit boundary */
  return mem;
}

如果加入HID的,那申请的内存大小也会是(sizeof(USBD_CDC_HandleTypeDef)/4)+1大小!!!
因此进行修改,分别定义

/**
  * @brief  Static single allocation.
  * @param  size: Size of allocated memory
  * @retval None
  */
void *USBD_static_CDC_malloc(uint32_t size)
{
  static uint32_t mem[(sizeof(USBD_CDC_HandleTypeDef)/4)+1];/* On 32-bit boundary */
  return mem;
}
/**
  * @brief  Static single allocation.
  * @param  size: Size of allocated memory
  * @retval None
  */
void *USBD_static_HID_malloc(uint32_t size)
{
  static uint32_t mem[(sizeof(USBD_HID_HandleTypeDef)/4)+1];/* On 32-bit boundary */
  return mem;
}

或者修改宏,使用malloc动态申请内存

12. 修改对应的应用函数内部指针调用

之前的单独的CDC或者HID调用的时候直接使用pdev->pClassData;就可以了,但是组合设备不一样,因为pdev->pClassData是在不断切换的,你不知道此时pdev->pClassData指向的是CDC还是HID,所以在调用对应的发送函数的时候必须修改。

  • 如:CDC_Transmit_FS()
/**
  * @brief  CDC_Transmit_FS
  *         Data to send over USB IN endpoint are sent over CDC interface
  *         through this function.
  *         @note
  *
  *
  * @param  Buf: Buffer of data to be sent
  * @param  Len: Number of data to be sent (in bytes)
  * @retval USBD_OK if all operations are OK else USBD_FAIL or USBD_BUSY
  */
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
  uint8_t result = USBD_OK;
  /* USER CODE BEGIN 7 */
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)pCDCData;//(USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
  if (hcdc->TxState != 0){
    return USBD_BUSY;
  }
  hUsbDeviceFS.pClassData=pCDCData;
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
  result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
  /* USER CODE END 7 */
  return result;
}

这里内部调用的USBD_CDC_SetTxBuffer()USBD_CDC_SetRxBuffer()指向不能随便修改,在补充说明里面详细解释为什么没有改USBD_CDC_TransmitPacket()内部hcdc指向也没有修改,因为USBD_CDC_TransmitPacket()只有CDC_Transmit_FS()调用过,而我们修改了CDC_Transmit_FS()内部
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)pCDCData;//(USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
因此pdev->pClassData指向已经修改过来了,所以没必要改了

uint8_t  USBD_CDC_SetTxBuffer(USBD_HandleTypeDef   *pdev,
                              uint8_t  *pbuff,
                              uint16_t length)
{
  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  hcdc->TxBuffer = pbuff;
  hcdc->TxLength = length;

  return USBD_OK;
}

uint8_t  USBD_CDC_SetRxBuffer(USBD_HandleTypeDef   *pdev,
                              uint8_t  *pbuff)
{
  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  hcdc->RxBuffer = pbuff;

  return USBD_OK;
}

uint8_t  USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
{
  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  if (pdev->pClassData != NULL)
  {
    if (hcdc->TxState == 0U)
    {
      /* Tx Transfer in progress */
      hcdc->TxState = 1U;

      /* Update the packet total length */
      pdev->ep_in[CDC_IN_EP & 0xFU].total_length = hcdc->TxLength;

      /* Transmit next packet */
      USBD_LL_Transmit(pdev, CDC_IN_EP, hcdc->TxBuffer,
                       (uint16_t)hcdc->TxLength);

      return USBD_OK;
    }
    else
    {
      return USBD_BUSY;
    }
  }
  else
  {
    return USBD_FAIL;
  }
}

注意修改的时候一定不要只改了上层,要看下函数内部调用的函数

  • USBD_HID_SendReport函数
uint8_t USBD_HID_SendReport(USBD_HandleTypeDef  *pdev,
                            uint8_t *report,
                            uint16_t len)
{
  USBD_HID_HandleTypeDef     *hhid = (USBD_HID_HandleTypeDef*)pHIDData;//(USBD_HID_HandleTypeDef *)pdev->pClassData;

  if (pdev->dev_state == USBD_STATE_CONFIGURED)
  {
    if (hhid->state == HID_IDLE)
    {
      hhid->state = HID_BUSY;
      USBD_LL_Transmit(pdev,
                       HID_EPIN_ADDR,
                       report,
                       len);
    }
  }
  return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_Transmit(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t *pbuf, uint16_t size)
{
  HAL_StatusTypeDef hal_status = HAL_OK;
  USBD_StatusTypeDef usb_status = USBD_OK;

  hal_status = HAL_PCD_EP_Transmit(pdev->pData, ep_addr, pbuf, size);
     
  usb_status =  USBD_Get_USB_Status(hal_status);
  
  return usb_status;    
}

13. 修改硬件层配置

增加端点肯定需要硬件的支持,修改USBD_LL_Init函数
首先是hpcd_USB_OTG_FS.Init.dev_endpoints = 8;
然后是HAL_PCDEx_SetTxFiFo()配置端点
但是HAL_PCDEx_SetTxFiFo()配置的时候很坑,配置的大小要求特别严格,此函数第二个参数表示的是端点,第三个参数表示的是大小,默认生成的是这样子(设置这个最好清楚USB的FIFO构造,在14点有描述)

  HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x80);

那么我需要增加端点设置的话就必须要减少之前的大小,否则usblyzer抓包会看到内部复位错误

  HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x40);
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
  /* Init USB Ip. */
  if (pdev->id == DEVICE_FS) {
  /* Link the driver to the stack. */
  hpcd_USB_OTG_FS.pData = pdev;
  pdev->pData = &hpcd_USB_OTG_FS;
  
  hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
  hpcd_USB_OTG_FS.Init.dev_endpoints = 8;
  hpcd_USB_OTG_FS.Init.speed = PCD_SPEED_FULL;
  hpcd_USB_OTG_FS.Init.Sof_enable = DISABLE;
  hpcd_USB_OTG_FS.Init.low_power_enable = DISABLE;
  hpcd_USB_OTG_FS.Init.vbus_sensing_enable = DISABLE;
  if (HAL_PCD_Init(&hpcd_USB_OTG_FS) != HAL_OK)
  {
    Error_Handler( );
  }

#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
  /* Register USB PCD CallBacks */
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SOF_CB_ID, PCD_SOFCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SETUPSTAGE_CB_ID, PCD_SetupStageCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_RESET_CB_ID, PCD_ResetCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SUSPEND_CB_ID, PCD_SuspendCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_RESUME_CB_ID, PCD_ResumeCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_CONNECT_CB_ID, PCD_ConnectCallback);
  HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_DISCONNECT_CB_ID, PCD_DisconnectCallback);

  HAL_PCD_RegisterDataOutStageCallback(&hpcd_USB_OTG_FS, PCD_DataOutStageCallback);
  HAL_PCD_RegisterDataInStageCallback(&hpcd_USB_OTG_FS, PCD_DataInStageCallback);
  HAL_PCD_RegisterIsoOutIncpltCallback(&hpcd_USB_OTG_FS, PCD_ISOOUTIncompleteCallback);
  HAL_PCD_RegisterIsoInIncpltCallback(&hpcd_USB_OTG_FS, PCD_ISOINIncompleteCallback);
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
  HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x40);
//  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 4, 0x20);
//  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 5, 0x20);
  }
  return USBD_OK;
}

14. USB FIFO

互联型产品USB为USB OTG,其实这部分可以详细看下手册这章的关于FIFO的说明,手册上讲的很清楚,下面是我对FIFO的总结

  • 提供1.25K字节的专用RAM和高级的FIFO管理
  • 接收FIFO
    • 所有OUT端点共用 大小由GRXFSIZ设置,起始地址为0
      GRXFSIZ 偏移地址0x024
      单位:32位的字
      最小16 最大256 上电复位为最大值 有一个问题
  • 发送FIFO
    • 每个IN端点配备一个专用FIFO IN0的FIFO长度由GNPTXFSIZ配置 INx的FIFO大小由DIEPTXFx来配置 在这里插入图片描述
  • 设备模式功能
    OTG_FS控制器接口:
    ● 提供1个双向的控制端点0
    ● 提供3个IN端点,支持大容量、中断或同步传输
    ● 提供3个OUT端点,支持大容量、中断或同步传输
    ● 为有效地使用USB的数据RAM区,管理一个共享的接收FIFO,和一个发送OUT FIFO
    ● 管理多达4个专用的发送IN FIFO(为每个IN端点配置一个FIFO),以便减少应用程序的负荷
    ● 支持软件的断开连接功能

在这里插入图片描述
从上图我们可以知道FIFO是如何设置大小的,但是需要特别注意的是设置FIFO深度的单位,是32位的字
然后我们回到上一点(13.修改硬件层配置)所描述的修改

  HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 2, 0x20);
  HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x40);

现在来解释下为什么设置fifo的时候我们改大了就会报错
我们来计算一下现在的FIFO大小
0x80+0x40+0x20+0x20+0x40=0x140=320
又由于单位是32位的字,所以设置的整个FIFO大小位320*4=1280字节
1280/1024=1.25k刚好是支持的最大FIFO大小
这就是我们之前设置FIFO的时候不能随便乱加的原因了!

4. 补充说明:

  • req->wIndex:对应接口的索引,在设备描述符中设置,之后通过参数返回,所以需要重点理解设备描述符,这里给大家推荐一个网站USB描述符,大家可以参考网站上的,但是看的时候还是需要注意,我发现他的部分注释有错误,但是在下方的详细描述内是正确的。
  • 为什么第3点中的12小点说USBD_CDC_SetTxBuffer()USBD_CDC_SetRxBuffer()函数内部的
    USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
    不能修改为
    USBD_CDC_HandleTypeDef *hcdc = pCDCData;
    分析: 我们在初始化usb的时候,会首先进入USBD_Composite_Init()函数中,
static uint8_t  USBD_Composite_Init (USBD_HandleTypeDef *pdev,
                            uint8_t cfgidx)
{
  uint8_t res = 0;

  pdev->pUserData =  (void*)&USBD_CDC_Interface_fops_FS;
  res +=  USBD_CDC.Init(pdev,cfgidx);
  pCDCData = pdev->pClassData;
  /* TODO */
  pdev->pUserData = NULL;
  res +=  USBD_HID.Init(pdev,cfgidx);
  pHIDData = pdev->pClassData;
  return res;
}

该函数调用 res += USBD_CDC.Init(pdev,cfgidx);进行cdc的初始化,此时pCDCData还是一个空指针,
只有在res += USBD_CDC.Init(pdev,cfgidx);执行完成之后,才会执行pCDCData = pdev->pClassData;pCDCData指针赋值。
USBD_CDC.Init(pdev,cfgidx)也就是USBD_CDC_Init()函数,初始化CDC,并在这里面给pdev->pClassData申请内存,

pdev->pClassData=USBD_CDC_malloc(sizeof(USBD_CDC_HandleTypeDef));

之后进入usbd_cdc_if.c文件内的

 /* Init  physical Interface components */
    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();

也就是

static int8_t CDC_Init_FS(void)
{
  /* USER CODE BEGIN 3 */
  /* Set Application Buffers */
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);
  return (USBD_OK);
  /* USER CODE END 3 */
}

这里面调用USBD_CDC_SetTxBuffer()USBD_CDC_SetRxBuffer()

uint8_t  USBD_CDC_SetTxBuffer(USBD_HandleTypeDef   *pdev,
                              uint8_t  *pbuff,
                              uint16_t length)
{
  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  hcdc->TxBuffer = pbuff;
  hcdc->TxLength = length;

  return USBD_OK;
}

uint8_t  USBD_CDC_SetRxBuffer(USBD_HandleTypeDef   *pdev,
                              uint8_t  *pbuff)
{
  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  hcdc->RxBuffer = pbuff;

  return USBD_OK;
}

如果我们之前简单的修改*hcdc = (USBD_CDC_HandleTypeDef *) pCDCData;那么此时pCDCData还是一个NULL的空指针,对空指针操作会直接进入硬件错误中断,因此我们不能这么修改!!!

5. 结尾

至此,USB组合设备已经配置完成了,搞了个把月了,终于算是看到希望了,但是还有很多需要完善的地方,关于usb,总体框架还是很重要,大家一定要沉下去,仔细去理解代码,借助debug看看程序怎么运行的,然后结合我给大家的分享去拿下他,一定要有信心,征服它!
完整工程附上USB HID+CDC组合设备,觉得文章可以的话,各位大佬可以打赏支持下哦!

  • 24
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱出名的狗腿子

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值