STM32配置组合设备(HID+CDC)
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_FS
,hpcd_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.c
和usbd_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 上电复位为最大值 有一个问题
- 所有OUT端点共用 大小由GRXFSIZ设置,起始地址为0
- 发送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)相关推荐
- STM32 USB组合设备HID+MIDI
目的:完成一个HID + MIDI的组合设备 准备工作: ·用CUBE生成HID工程: ·复制一份工程修改为MIDI工程:(参考之前博客已完成这两个工程) ·新建USB_User文件夹,将USB相关配 ...
- STM32配置CH375B成HID Host模式读取自定义HID设备的数据 ——STM32配置CH375B接口函数
接着上一篇上传,这个是STM32配置CH375B时用到的接口函数 头文件: #ifndef __BSP_CH375_H__ #define __BSP_CH375_H__#include " ...
- 使用stm32配置自定义的HID设备
STM32USB设备设计步骤: 申明:文章为原创性文章,转载请申明!!! 本文不对USB协议进行讲述,对于usb协议,我建议大家静下心好好去看下对应的资料,USB协议不是一个简单的协议,不是一两天就能 ...
- STM32配置CH375B成HID Host模式读取自定义HID设备的数据 ——STM32端口初始化
最近产品需要一个USB主机测试治具,所以需要做一个USB HOST去读取HID设备的数据,由于以前也没做过USB方面的项目,对这一块也不是很熟悉,因此遇到了很多困难,所幸的是经过两天半的努力,最终完成 ...
- stm32 USB HID+CDC 鼠标键盘串口 组合设备配置解析
前言 查阅网上的博客与代码,很多都是关于USB的鼠标配置.USB的键盘配置.USB的虚拟串口配置,稍微深入一点的会将鼠标键盘合在一起,但移植起来就会报很多错误,要么是检测不到,要么是警告,这很正常,因 ...
- STM32CubeMX | 基于STM32使用HAL库实现USB组合设备之多路CDC
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备之多路CDC 本博客完整代码下载地址:https://download.csdn.net/download/qq15347150 ...
- USB Composite 组合设备之多路CDC实现
USB Composite 组合设备之多路CDC实现 USB复合设备与组合设备区别 效果展示 修改相关配置 修改配置项 修改设备描述符 修改配置.接口.端点描述符 接口修改 FIFO配置 知识点 FI ...
- STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)
STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL ) STM32的USB功能模块可以配置为虚拟串口(VCOM: Visual Port Com)或人机交互设备(HID: Hu ...
- 关于STM32的USB设备库DIY机械键盘
前言 为什么想写这个呢,首先一方面是因为自己喜欢DIY一些小玩意,另一方面关于USB-HID的东西断断续续的学习了不少东西,想总结整理一下.其次就是网络上关于STM32制作USB-HID的案例很多,我 ...
最新文章
- 用aspnetpager实现datalist分页(绝对的简单实用)
- leetcode46. 全排列(回溯)
- linux 扩展挂载盘大小_Linux 挂载新添加磁盘LVM配置
- 条件编译宏定义_C语言学习- 预处理指令2 - 条件编译
- 莫烦python学习笔记之numpy.array,dtype,empty,zeros,ones,arrange,linspace
- POJ 2728 Desert King(最优比率生成树)
- python贪吃蛇源代码_python实现贪吃蛇游戏源码
- java彩票开奖程序_用java 实现彩票摇奖,猜拳程序
- linux命令提示符详解
- web测试中如何简单定位bug
- oracle 2的22次方,22的2次方(2的22的2次方等于多少)
- 服务器非80端口无法访问网页,使用非80网站访问服务器web提示需要备案呢?
- 8个接私活的网站,只要你有码,那“我”就有钱
- 360浏览器兼容性问题
- 计算机基础及photoshop应用试题,计算机基础及Photoshop应用选择题(计算机一级B考试卷).doc...
- Mac下载Bilibili视频
- 基于R语言的主成分和因子分析
- 浩辰编写lisp_CAD二次开发
- STM32F103学习笔记(3.0)——中断
- 三角形形态判断 循环计算e Python123题解
热门文章
- 最后一本书 第六章课后练习3,4
- 景安虚拟主机 Typecho设置伪静态教程 Apache 环境
- fusioncharts生成图表flash遮挡页面中元素的情况
- 9.FLINK Sink\API\自定义sink
- Pooling Revisited: Your Receptive Field is Suboptimal 论文解读和感想
- 企业微信怎么输入服务器id,微信企业号的agentid怎么查看?如何获取?
- 根据卡号查询所属银行
- 冒泡排序 python内置_除了冒泡排序,你知道Python内建的排序算法吗?
- 高等数学:第五章 定积分(2)换元积分法 分部积分法 广义积分
- html+dom+chm,HTML DOM getElementsByClassName() - JavaScript - 菜鸟学堂-脚本之家