STM32组合设备

  • STM32配置组合设备(HID+CDC)
    • 1. CDC基础工程,HID基础工程生成
    • 2.USB工程熟悉
      • 2.1 USB初始化
      • 2.2USB中断
      • 2.3 相关结构体
        • 2.3.1 ` USBD_DescriptorsTypeDef *pDesc;`指向的是设备描述符,
        • 2.3.2 `USBD_ClassTypeDef *pClass` 指向一个类
        • 2.3.3 `void *pClassData;` **特别重要**,存放了CDC或者HID的相关句柄,搭建组合设备的时候需要重点区分此指针
        • 2.3.4 `void *pUserData;`指向接口函数
        • 2.3.5 `void *pData;`指向`hpcd_USB_OTG_FS`,`hpcd_USB_OTG_FS`在中断函数的时候会作为参数传入
    • 3. 配置组合设备
      • 1.修改编译器优化等级
      • 2. 以CDC工程为为基础,将HID工程添加进来
      • 3. 将文件添加到对应工程中
      • 4. 新建`usbd_composite.c`和`usbd_composite.h`文件
      • 5. 编写对应的`usbd_composite.c`文件
      • 6. 修改设备描述符文件
      • 7. 修改usb初始化文件
      • 8. 修改HID和CDC的端点
      • 9. 修改支持的接口数
      • 10. 确认接口配置
      • 11. 修改内存申请函数
      • 12. 修改对应的应用函数内部指针调用
      • 13. 修改硬件层配置
      • 14. USB FIFO
    • 4. 补充说明:
    • 5. 结尾

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,  //addUSBD_Composite_DataIn,USBD_Composite_DataOut,NULL,NULL,NULL,NULL,USBD_Composite_GetFSCfgDesc,NULL,USBD_Composite_GetDeviceQualifierDescriptor,
    };
    

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

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

     ```ctypedef 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,  //addUSBD_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,  //addUSBD_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  设备符中的bDeviceClass0x00,                                      // bFunctionSubClass  设备符中的bDeviceSubClass0x00,                                      // bInterfaceProtocol 设备符中的bDeviceProtocol0x00,/********************  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,               // bLengthUSBD_IAD_DESCRIPTOR_TYPE,         // bDescriptorType0x01,                             // bFirstInterface 接口描述符是在总的配置描述符中的第几个从0开始数 10x02,                             // bInterfaceCount 接口描述符数量 20x02,                             // bFunctionClass     CDC Control0x02,                             // bFunctionSubClass  Abstract Control Model0x01,                             // bInterfaceProtocol  AT Commands: V.250 etc0x00,                             // 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 10x12,                       /*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 10x12,                       /*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组合设备,觉得文章可以的话,各位大佬可以打赏支持下哦!

STM32配置组合设备(HID+CDC)相关推荐

  1. STM32 USB组合设备HID+MIDI

    目的:完成一个HID + MIDI的组合设备 准备工作: ·用CUBE生成HID工程: ·复制一份工程修改为MIDI工程:(参考之前博客已完成这两个工程) ·新建USB_User文件夹,将USB相关配 ...

  2. STM32配置CH375B成HID Host模式读取自定义HID设备的数据 ——STM32配置CH375B接口函数

    接着上一篇上传,这个是STM32配置CH375B时用到的接口函数 头文件: #ifndef __BSP_CH375_H__ #define __BSP_CH375_H__#include " ...

  3. 使用stm32配置自定义的HID设备

    STM32USB设备设计步骤: 申明:文章为原创性文章,转载请申明!!! 本文不对USB协议进行讲述,对于usb协议,我建议大家静下心好好去看下对应的资料,USB协议不是一个简单的协议,不是一两天就能 ...

  4. STM32配置CH375B成HID Host模式读取自定义HID设备的数据 ——STM32端口初始化

    最近产品需要一个USB主机测试治具,所以需要做一个USB HOST去读取HID设备的数据,由于以前也没做过USB方面的项目,对这一块也不是很熟悉,因此遇到了很多困难,所幸的是经过两天半的努力,最终完成 ...

  5. stm32 USB HID+CDC 鼠标键盘串口 组合设备配置解析

    前言 查阅网上的博客与代码,很多都是关于USB的鼠标配置.USB的键盘配置.USB的虚拟串口配置,稍微深入一点的会将鼠标键盘合在一起,但移植起来就会报很多错误,要么是检测不到,要么是警告,这很正常,因 ...

  6. STM32CubeMX | 基于STM32使用HAL库实现USB组合设备之多路CDC

    STM32CubeMX | 基于STM32使用HAL库实现USB组合设备之多路CDC 本博客完整代码下载地址:https://download.csdn.net/download/qq15347150 ...

  7. USB Composite 组合设备之多路CDC实现

    USB Composite 组合设备之多路CDC实现 USB复合设备与组合设备区别 效果展示 修改相关配置 修改配置项 修改设备描述符 修改配置.接口.端点描述符 接口修改 FIFO配置 知识点 FI ...

  8. STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)

    STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL ) STM32的USB功能模块可以配置为虚拟串口(VCOM: Visual Port Com)或人机交互设备(HID: Hu ...

  9. 关于STM32的USB设备库DIY机械键盘

    前言 为什么想写这个呢,首先一方面是因为自己喜欢DIY一些小玩意,另一方面关于USB-HID的东西断断续续的学习了不少东西,想总结整理一下.其次就是网络上关于STM32制作USB-HID的案例很多,我 ...

最新文章

  1. 用aspnetpager实现datalist分页(绝对的简单实用)
  2. leetcode46. 全排列(回溯)
  3. linux 扩展挂载盘大小_Linux 挂载新添加磁盘LVM配置
  4. 条件编译宏定义_C语言学习- 预处理指令2 - 条件编译
  5. 莫烦python学习笔记之numpy.array,dtype,empty,zeros,ones,arrange,linspace
  6. POJ 2728 Desert King(最优比率生成树)
  7. python贪吃蛇源代码_python实现贪吃蛇游戏源码
  8. java彩票开奖程序_用java 实现彩票摇奖,猜拳程序
  9. linux命令提示符详解
  10. web测试中如何简单定位bug
  11. oracle 2的22次方,22的2次方(2的22的2次方等于多少)
  12. 服务器非80端口无法访问网页,使用非80网站访问服务器web提示需要备案呢?
  13. 8个接私活的网站,只要你有码,那“我”就有钱
  14. 360浏览器兼容性问题
  15. 计算机基础及photoshop应用试题,计算机基础及Photoshop应用选择题(计算机一级B考试卷).doc...
  16. Mac下载Bilibili视频
  17. 基于R语言的主成分和因子分析
  18. 浩辰编写lisp_CAD二次开发
  19. STM32F103学习笔记(3.0)——中断
  20. 三角形形态判断 循环计算e Python123题解

热门文章

  1. 最后一本书 第六章课后练习3,4
  2. 景安虚拟主机 Typecho设置伪静态教程 Apache 环境
  3. fusioncharts生成图表flash遮挡页面中元素的情况
  4. 9.FLINK Sink\API\自定义sink
  5. Pooling Revisited: Your Receptive Field is Suboptimal 论文解读和感想
  6. 企业微信怎么输入服务器id,微信企业号的agentid怎么查看?如何获取?
  7. 根据卡号查询所属银行
  8. 冒泡排序 python内置_除了冒泡排序,你知道Python内建的排序算法吗?
  9. 高等数学:第五章 定积分(2)换元积分法 分部积分法 广义积分
  10. html+dom+chm,HTML DOM getElementsByClassName() - JavaScript - 菜鸟学堂-脚本之家