当前位置: 首页 > news >正文

STM32F4xx usb库源码详解 custom HID

STM32F4xx USB 库源码详解(custom HID)

首先我列出几个非常棒的参考文档:

Keil USB documentation

http://www.keil.com/pack/doc/mw/USB/html/index.html

USB_in_a_Nutshell
https://www.beyondlogic.org/usbnutshell/usb1.shtml

USB基础知识概论

https://www.crifan.com/files/doc/docbook/usb_basic/release/htmls/
http://www.crifan.com/files/doc/docbook/usb_basic/release/pdf/usb_basic.pdf.7z

在这里插入图片描述

在这里插入图片描述

一些常见的疑问

EndPoints到底有多少个

STM32F407/417/27/37/29/39等系列芯片,包括EP0在内的双向EP数目只有4个,而不是16个。这是要特别注意的地方,只不过程序为了其通用性,HAL_PCD_IRQHandler中对16个端点进行了轮询。

更具体详细的信息,建议参考《STM-AN4879》,

https://www.st.com/resource/en/application_note/dm00296349.pdf

传输方向的问题

这里特别列出来,是因为传输永远是相对HOST的,对于Device,IN是指输出到HOST(对HOST来说IN是输入,对DEVICE来说就是输出)OUT才是接收。

传输速率的问题

一般情况下,丢包是正常现像,比如你用windows发送数据,一次性发送1K,而实际传输只能是最大64BYTE,这时,stm32是无法这么快速度接收USB的数据的,可能正确接收到的还不到一半。当然你可以写一堆while什么的来验证是否接收完了上次的,但这种做法往往不值得推荐,你不知道那个while什么时候会卡死 。记住,除非你有确定的把握,在接收和发送中断里这么做是极为危险的。正确的做法是:在发送端要求接收端给出正确接收的应答,上一次成功接收到了,再发下一次的。当然,如果你的while判断不在中断中,那要容易处理得多。

进入正题

然后我们从初始化开始讲解,源码库函数直接到ST.com上去下载,其中有关于customHID的例程,也可以到这里下载,
https://github.com/ErakhtinB/USB_Custom_HID
我试了下,这位老兄的代码能直接跑通(只有DM,DP两根线的那种USB,没有SOF,ID, VBUS, st.com的反而要改的地方更多),各个例程大同小异。重要的是,要结合手册和规范理解这些东西。
另外,USB-CDC的传输原理和方案也写这个极为相似,源码可到这里下载,这个也能直接跑通,CDC_Receive_FS中加了一句CDC_Transmit_FS,把接收到的再发送回去的代码。
https://github.com/goog/stm32f407_cdc

在HID源码中,初始化的代码如下,

/**
  * Init USB device Library, add supported class and start the library
  * @retval None
  */
void MX_USB_DEVICE_Init(void){
  /* Init Device Library, add supported class and start the library. */
  USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
  USBD_RegisterClass(&hUsbDeviceFS, &USBD_CUSTOM_HID);
  USBD_CUSTOM_HID_RegisterInterface(&hUsbDeviceFS, &USBD_CustomHID_fops_FS);
  USBD_Start(&hUsbDeviceFS);
}

hUsbDeviceFS

该结构变量的类型为USBD_HandleTypeDef ,

/* USB Device Core handle declaration. */
USBD_HandleTypeDef hUsbDeviceFS;

注意这个是我们USB用到的全局变量,保存了USB生命周期的全部信息。

USBD_HandleTypeDef


/* 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[15];
  USBD_EndpointTypeDef    ep_out[15];  
  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;

每个端点(ep_in/ep_out)都有自己的配置,类型为USBD_EndpointTypeDef,

USBD_EndpointTypeDef(ep_in/ep_out)

/* USB Device handle structure */
typedef struct
{
  uint32_t                status;
  uint32_t                is_used;
  uint32_t                total_length;
  uint32_t                rem_length;
  uint32_t                maxpacket;
} USBD_EndpointTypeDef;

USBD_CUSTOM_HID

该结构变量的类型是USBD_ClassTypeDef ,其定义如下,

/** @defgroup USBD_CUSTOM_HID_Private_Variables
* @{
*/ 
USBD_ClassTypeDef  USBD_CUSTOM_HID = 
{
  USBD_CUSTOM_HID_Init,
  USBD_CUSTOM_HID_DeInit,
  USBD_CUSTOM_HID_Setup,
  NULL, /*EP0_TxSent*/  
  USBD_CUSTOM_HID_EP0_RxReady, /*EP0_RxReady*/ /* STATUS STAGE IN */
  USBD_CUSTOM_HID_DataIn, /*DataIn*/
  USBD_CUSTOM_HID_DataOut,
  NULL, /*SOF */
  NULL,
  NULL,      
  USBD_CUSTOM_HID_GetCfgDesc,
  USBD_CUSTOM_HID_GetCfgDesc, 
  USBD_CUSTOM_HID_GetCfgDesc,
  USBD_CUSTOM_HID_GetDeviceQualifierDesc,
};

注册路线

这个结构变量是通过
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CUSTOM_HID);
注册到全局变量
USBD_HandleTypeDef hUsbDeviceFS;
中的,也就是经常调用的
hUsbDeviceFS->pClass (pdev->pClass)

USBD_ClassTypeDef

结构类型的定义为USBD_ClassTypeDef:

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 == 1)
  uint8_t  *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev ,uint8_t index,  uint16_t *length);   
#endif  
  
} USBD_ClassTypeDef;

DataIn、DataOut – 数据传输函数

从这两个变量的定义可见,其输入输出函数对应起来就是

DataIn = USBD_CUSTOM_HID_DataIn

DataOut = USBD_CUSTOM_HID_DataOut

主要调用路线:

HAL_PCD_IRQHandler–>HAL_PCD_DataInStageCallback–>USBD_LL_DataInStage --> DataIn

HAL_PCD_IRQHandler–>HAL_PCD_DataOutStageCallback --> USBD_LL_DataOutStage–>DataOut

也可看到,其发送和接收,都是在中断函数HAL_PCD_IRQHandler中进行处理的。

hpcd_USB_OTG_FS

该结构变量的类型是

PCD_HandleTypeDef hpcd_USB_OTG_FS;

其中PCD就是硬件控制器的意思:peripheral controller device/driver。

注册路线

这个结构变量的注册路线是:

USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
==>
USBD_LL_Init(pdev); 这里pdev就是hUsbDeviceFS
其中,
hpcd_USB_OTG_FS.pData = pdev;
pdev->pData = &hpcd_USB_OTG_FS;

所以,
hpcd_USB_OTG_FS.pData = &hUsbDeviceFS;
hUsbDeviceFS->pData = &hpcd_USB_OTG_FS;

PCD_HandleTypeDef

usb_conf.c中定义的全局变量hpcd_USB_OTG_FS,该变量在USBD_LL_Init中完成初始化

/** 
  * @brief  PCD Handle Structure definition  
  */ 
typedef struct
{
  PCD_TypeDef             *Instance;    /*!< Register base address              */
  PCD_InitTypeDef         Init;         /*!< PCD required parameters            */
  PCD_EPTypeDef           IN_ep[16U];   /*!< IN endpoint parameters             */
  PCD_EPTypeDef           OUT_ep[16U];  /*!< OUT endpoint parameters            */
  HAL_LockTypeDef         Lock;         /*!< PCD peripheral status              */
  __IO PCD_StateTypeDef   State;        /*!< PCD communication state            */
  uint32_t                Setup[12U];   /*!< Setup packet buffer                */
#ifdef USB_OTG_GLPMCFG_LPMEN
  PCD_LPM_StateTypeDef    LPM_State;    /*!< LPM State                          */
  uint32_t                BESL;
  uint32_t                lpm_active;   /*!< Enable or disable the Link Power Management .
                                        This parameter can be set to ENABLE or DISABLE */
#endif /* USB_OTG_GLPMCFG_LPMEN */
#ifdef USB_OTG_GCCFG_BCDEN
  uint32_t battery_charging_active;     /*!< Enable or disable Battery charging.
                                        This parameter can be set to ENABLE or DISABLE */
#endif /* USB_OTG_GCCFG_BCDEN */
  void                    *pData;       /*!< Pointer to upper stack Handler */
} PCD_HandleTypeDef;

相应地,每个端点IN_ep/OUT_ep也都有自己的配置,类型为PCD_EPTypeDef,

PCD_EPTypeDef (IN_ep/OUT_ep)

typedef USB_OTG_EPTypeDef      PCD_EPTypeDef ;

/** 
  * @brief  OTG End Point Initialization Structure definition 
  */
typedef struct
{
  uint8_t   num;            /*!< Endpoint number Min_Data = 1 and Max_Data = 15*/
  uint8_t   is_in;          /*!< Endpoint direction Min_Data = 0 and Max_Data = 1*/
  uint8_t   is_stall;       /*!< Endpoint stall condition Min_Data=0 and Max_Data=1*/
  uint8_t   type;           /*!< Endpoint type any value of @ref USB_EP_Type_*/
  uint8_t   data_pid_start; /*!< Initial data PID Min_Data = 0 and Max_Data = 1*/
  uint8_t   even_odd_frame; /*!< IFrame parity Min_Data = 0 and Max_Data = 1*/
  uint16_t  tx_fifo_num;    /*!< Transmission FIFO number Min_Data=1 and Max_Data=15*/
  uint32_t  maxpacket;      /*!< Endpoint Max packet size Min_Data=0 and Max_Data=64KB*/
  uint8_t   *xfer_buff;     /*!< Pointer to transfer buffer*/
  uint32_t  dma_addr;       /*!< 32 bits aligned transfer buffer address*/
  uint32_t  xfer_len;       /*!< Current transfer length*/
  uint32_t  xfer_count;     /*!< Partial transfer length in multi packet transfer*/
}USB_OTG_EPTypeDef;

USBx数据传输中的几个概念

库中大量用到的USBx是指FS和HS传输方式配置寄存器的基地址USB_OTG_FS和USB_OTG_HS。

注意,STM32F4xx的寄存器地址USB_OTG_FS和USB_OTG_HS具体可参考STM32F4xx datasheet Table 10 register boundary addresses ,实际物理地址定义如下,

#define USB_OTG_HS_PERIPH_BASE 0x40040000U
#define USB_OTG_FS_PERIPH_BASE 0x50000000U

#define USB_OTG_FS ((USB_OTG_GlobalTypeDef *) USB_OTG_FS_PERIPH_BASE)
#define USB_OTG_HS ((USB_OTG_GlobalTypeDef *) USB_OTG_HS_PERIPH_BASE)

选择EndPoint并进行数据传输

USBx_INEP(i)

这里都以USB_OTG_FS为例进行讲解,

#define USBx_DEVICE     ((USB_OTG_DeviceTypeDef *)((uint32_t )USBx + USB_OTG_DEVICE_BASE)) 
#define USBx_INEP(i)    ((USB_OTG_INEndpointTypeDef *)((uint32_t)USBx + USB_OTG_IN_ENDPOINT_BASE + (i)*USB_OTG_EP_REG_SIZE))
#define USBx_OUTEP(i)   ((USB_OTG_OUTEndpointTypeDef *)((uint32_t)USBx + USB_OTG_OUT_ENDPOINT_BASE + (i)*USB_OTG_EP_REG_SIZE))
#define USBx_DFIFO(i)   *(__IO uint32_t *)((uint32_t)USBx + USB_OTG_FIFO_BASE + (i) * USB_OTG_FIFO_SIZE)

以USBx_INEP(i)为例,在FS模式下,USBx是指
#define USB_OTG_FS_PERIPH_BASE 0x50000000U

#define USB_OTG_IN_ENDPOINT_BASE 0x900U
#define USB_OTG_EP_REG_SIZE 0x20U

所以USBx_INEP(0)就是OTG_FS_DIEPCTL0,0x50000900

USBx_OUTEP(i)

同样,我们计算一下USBx_OUTEP到底是指哪一个,假设这里x=3,

#define USB_OTG_FS_PERIPH_BASE               0x50000000U  // 这个就是USBx  
#define USB_OTG_OUT_ENDPOINT_BASE            0xB00U   
#define USB_OTG_EP_REG_SIZE                  0x20U

#define USBx_OUTEP(i)   ((USB_OTG_OUTEndpointTypeDef *)((uint32_t)USBx + USB_OTG_OUT_ENDPOINT_BASE + (i)*USB_OTG_EP_REG_SIZE))

因此,USBx_OUTEP(15) = 0x50000B00U + 3*0x20

在接收发送中断函数HAL_PCD_IRQHandler中,先要通过

USB_ReadDevOutEPInterrupt(或USB_ReadDevInEPInterrupt)
确定中断到底来自哪一个EndPoint(共计16个),
USB_ReadDevOutEPInterrupt或USB_ReadDevInEPInterrupt进行确认的代码如下,

epint = USB_ReadDevOutEPInterrupt(hpcd->Instance, epnum);
epint = USB_ReadDevInEPInterrupt(hpcd->Instance, epnum);

具体实现代码如下,

/**
  * @brief  Returns Device OUT EP Interrupt register
  * @param  USBx  Selected device
  * @param  epnum  endpoint number
  *          This parameter can be a value from 0 to 15
  * @retval Device OUT EP Interrupt register
  */
uint32_t USB_ReadDevOutEPInterrupt (USB_OTG_GlobalTypeDef *USBx , uint8_t epnum)
{
  uint32_t v;
  v  = USBx_OUTEP(epnum)->DOEPINT;
  v &= USBx_DEVICE->DOEPMSK;
  return v;
}

/**
  * @brief  Returns Device IN EP Interrupt register
  * @param  USBx  Selected device
  * @param  epnum  endpoint number
  *          This parameter can be a value from 0 to 15
  * @retval Device IN EP Interrupt register
  */
uint32_t USB_ReadDevInEPInterrupt (USB_OTG_GlobalTypeDef *USBx , uint8_t epnum)
{
  uint32_t v, msk, emp;
  
  msk = USBx_DEVICE->DIEPMSK;
  emp = USBx_DEVICE->DIEPEMPMSK;
  msk |= ((emp >> epnum) & 0x1U) << 7U;
  v = USBx_INEP(epnum)->DIEPINT & msk;
  return v;
}

传输设定

调用路线

传输模式与数据量是在USB初始化的时候设定的,其函数调用路线如下,

OTG_FS_IRQHandler --> HAL_PCD_IRQHandler --> HAL_PCD_SetupStageCallback --> USBD_LL_SetupStage --> USBD_StdDevReq --> USBD_SetConfig --> USBD_SetClassConfig --> USBD_CUSTOM_HID_Init --> USBD_LL_OpenEP --> HAL_PCD_EP_Open

要注意:USBD_SetClassConfig中的这个pClass前面讲过,if(pdev->pClass->Init(pdev, cfgidx)调用的就是USBD_CUSTOM_HID_Init 。

数据传输大小

USBD_LL_OpenEP设定了端点号如CUSTOM_HID_EPIN_ADDR,传输模式如USBD_EP_TYPE_INTR数据传输量的大小如USB_FS_MAX_PACKET_SIZE(自定义)。另外注意,在VCP/CDC模式下,传输模式往往被设定为USBD_EP_TYPE_BULK。

USBD_CUSTOM_HID_Init

static uint8_t  USBD_CUSTOM_HID_Init (USBD_HandleTypeDef *pdev, 
                               uint8_t cfgidx)
{
  uint8_t ret = 0;
  USBD_CUSTOM_HID_HandleTypeDef     *hhid;
  /* Open EP IN */
  USBD_LL_OpenEP(pdev,
                 CUSTOM_HID_EPIN_ADDR,
                 USBD_EP_TYPE_INTR,
                 CUSTOM_HID_EPIN_SIZE);  //   USB_FS_MAX_PACKET_SIZE
  
  /* Open EP OUT */
  USBD_LL_OpenEP(pdev,
                 CUSTOM_HID_EPOUT_ADDR,
                 USBD_EP_TYPE_INTR,
                 CUSTOM_HID_EPOUT_SIZE);  //  USB_FS_MAX_PACKET_SIZE
  
  pdev->pClassData = USBD_malloc(sizeof (USBD_CUSTOM_HID_HandleTypeDef));
  
  if(pdev->pClassData == NULL)
  {
    ret = 1; 
  }
  else
  {
    hhid = (USBD_CUSTOM_HID_HandleTypeDef*) pdev->pClassData;
      
    hhid->state = CUSTOM_HID_IDLE;
    ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->Init();
          /* Prepare Out endpoint to receive 1st packet */ 
    USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf, 
                           USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
  }
    
  return ret;
}

这个USBD_LL_OpenEP函数中,有用的只有一句,

hal_status = HAL_PCD_EP_Open(pdev->pData, ep_addr, ep_mps, ep_type);

所以真正干活的只有HAL_PCD_EP_Open函数。

HAL_PCD_EP_Open

具体的设定由HAL_PCD_EP_Open负责,其中PCD_HandleTypeDef中的IN_ep/OUT_ep中的maxpacket就是最大允许传输量,mps就是max packet size 的意思,

注意:STM原来的例程中,ep->maxpacket = CUSTOM_HID_EPIN_SIZE = 2k; 也就是每个packet最大数据量为2k。

HAL_StatusTypeDef HAL_PCD_EP_Open(PCD_HandleTypeDef* hpcd, uint8_t ep_addr, uint16_t ep_mps, uint8_t ep_type)
{
    HAL_StatusTypeDef  ret = HAL_OK;
    USB_OTG_EPTypeDef* ep;

    if ((ep_addr & 0x80) == 0x80){
        ep = &hpcd->IN_ep[ep_addr & 0x7F];
    }
    else{
        ep = &hpcd->OUT_ep[ep_addr & 0x7F];
    }
    ep->num = ep_addr & 0x7F;

    ep->is_in = (0x80 & ep_addr) != 0;
    ep->maxpacket = ep_mps;
    ep->type = ep_type;
    if (ep->is_in){
        /* Assign a Tx FIFO */
        ep->tx_fifo_num = ep->num;
    }
    /* Set initial data PID. */
    if (ep_type == EP_TYPE_BULK){
        ep->data_pid_start = 0U;
    }

    __HAL_LOCK(hpcd);
    USB_ActivateEndpoint(hpcd->Instance, ep);
    __HAL_UNLOCK(hpcd);
    return ret;
}

传输函数

USBD_CUSTOM_HID_DataIn

这里再次提醒,IN相对于Device来说,就是发送的意思。前面讲到,发送函数是USBD_CUSTOM_HID_DataIn,

static uint8_t  USBD_CUSTOM_HID_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum){  
  /* Ensure that the FIFO is empty before a new transfer, this condition could 
  be caused by  a new transfer before the end of the previous transfer */
  ((USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData)->state = CUSTOM_HID_IDLE;
  return USBD_OK;
}

但是,注意这个USBD_CUSTOM_HID_DataIn貌似什么都没做,到这里再思考一下:那么数据是放在哪里,又是怎么发送出去了呢?

其实,这个函数是中断HAL_PCD_IRQHandler发送过程中调用的,我们后面会看到

真实的发送路线(准备数据阶段)

真实的发送路线如下,

USBD_CUSTOM_HID_SendReport -->USBD_LL_Transmit–>HAL_PCD_EP_Transmit -->USB_EPStartXfer

先看一下真正的发送函数(该函数是用户直接在有需要发送数据到上位机的地方调用的函数),

USBD_CUSTOM_HID_SendReport

uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef  *pdev, 
                                   uint8_t *report,
                                   uint16_t len)
{
    USBD_CUSTOM_HID_HandleTypeDef* hhid=(USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;
	if (pdev->dev_state == USBD_STATE_CONFIGURED ){
		if(hhid->state == CUSTOM_HID_IDLE){
			hhid->state = CUSTOM_HID_BUSY;
			USBD_LL_Transmit (pdev,  CUSTOM_HID_EPIN_ADDR, report, len);
		}
	}
	return USBD_OK;
}

其调用的这个USBD_LL_Transmit函数,有用的也只有一句

hal_status = HAL_PCD_EP_Transmit(pdev->pData, ep_addr, pbuf, size);

HAL_PCD_EP_Transmit

HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef* hpcd, uint8_t ep_addr, uint8_t* pBuf, uint32_t len){
    USB_OTG_EPTypeDef* ep;
    ep = &hpcd->IN_ep[ep_addr & 0x7F];

    /*setup and start the Xfer */
    ep->xfer_buff = pBuf;
    ep->xfer_len = len;
    ep->xfer_count = 0U;
    ep->is_in = 1U;
    ep->num = ep_addr & 0x7F;

    if (hpcd->Init.dma_enable == 1U){
        ep->dma_addr = (uint32_t)pBuf;
    }

    if ((ep_addr & 0x7F) == 0){
        USB_EP0StartXfer(hpcd->Instance, ep, hpcd->Init.dma_enable);
    }
    else{
        USB_EPStartXfer(hpcd->Instance, ep, hpcd->Init.dma_enable);
    }

    return HAL_OK;
}

USB_EPStartXfer

HAL_StatusTypeDef USB_EPStartXfer(USB_OTG_GlobalTypeDef *USBx , USB_OTG_EPTypeDef *ep, uint8_t dma){
	uint16_t pktcnt = 0U;
	
	/* IN endpoint */
	if (ep->is_in == 1U){
		/* Zero Length Packet? */
		if (ep->xfer_len == 0U){
			USBx_INEP(ep->num)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT); 
			USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (1U << 19U)) ;
			USBx_INEP(ep->num)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ); 
		}
		else{
			/* Program the transfer size and packet count
			* as follows: xfersize = N * maxpacket +
			* short_packet pktcnt = N + (short_packet
			* exist ? 1 : 0)
			*/
			USBx_INEP(ep->num)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ);
			USBx_INEP(ep->num)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT); 
			USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (((ep->xfer_len + ep->maxpacket -1U)/ ep->maxpacket) << 19U)) ;
			USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_XFRSIZ & ep->xfer_len); 

			if (ep->type == EP_TYPE_ISOC)
			{
				USBx_INEP(ep->num)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_MULCNT); 
				USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_MULCNT & (1U << 29U)); 
			}       
		}

		if (dma == 1U){
			USBx_INEP(ep->num)->DIEPDMA = (uint32_t)(ep->dma_addr);
		}
		else{
			if (ep->type != EP_TYPE_ISOC){
				/* Enable the Tx FIFO Empty Interrupt for this EP */
				if (ep->xfer_len > 0U){
					USBx_DEVICE->DIEPEMPMSK |= 1U << ep->num;
				}
			}
		}

		if (ep->type == EP_TYPE_ISOC){
			if ((USBx_DEVICE->DSTS & ( 1U << 8U )) == 0U){
				USBx_INEP(ep->num)->DIEPCTL |= USB_OTG_DIEPCTL_SODDFRM;
			}
			else{
				USBx_INEP(ep->num)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
			}
		} 

		/* EP enable, IN data in FIFO */
		USBx_INEP(ep->num)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);

		if (ep->type == EP_TYPE_ISOC){
			USB_WritePacket(USBx, ep->xfer_buff, ep->num, ep->xfer_len, dma);   
		}
	}
	else {// OUT endpoint 
		/* Program the transfer size and packet count as follows:
		* pktcnt = N
		* xfersize = N * maxpacket
		*/  
		USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ); 
		USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT); 

		if (ep->xfer_len == 0U){
			USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
			USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U << 19U));
		}
		else{
			pktcnt = (ep->xfer_len + ep->maxpacket -1U)/ ep->maxpacket; 
			USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt << 19U));
			USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket * pktcnt));
		}

		if (dma == 1U){
			USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
		}

		if (ep->type == EP_TYPE_ISOC){
			if ((USBx_DEVICE->DSTS & ( 1U << 8U )) == 0U){
				USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
			}
			else{
				USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
			}
		}
		/* EP enable */
		USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
	}
	return HAL_OK;
}

该函数中,主要对USB_OTG_DOEPTSIZ_XFRSIZ和USB_OTG_DOEPTSIZ_PKTCNT进行了设置,

USBx_INEP(ep->num)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (((ep->xfer_len + ep->maxpacket -1U)/ ep->maxpacket) << 19U)) ;

以前面的例子,maxpacket = CUSTOM_HID_EPIN_SIZE = 2,表示可传输最大2K,例程中xfer_len = 5 = XFRSIZ 个字节,

PKCNT = (ep->xfer_len + ep->maxpacket -1U) / ep->maxpacket = 6/2 = 3

最后调用中断

USBx_INEP(ep->num)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);

将数据发送出去,需要理解的是,具体什么时候发送是用户控制不了的,用抓包工具就可以看出来,这是和主机的具体通信情况相关的。

发送传输过程(采用中断发送阶段)

Step-by-step

HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);

==>

HAL_PCD_DataInStageCallback(hpcd, epnum);
这里,hpcd就是hpcd_USB_OTG_FS,

==>

USBD_LL_DataInStage((USBD_HandleTypeDef*)hpcd->pData, epnum, hpcd->IN_ep[epnum].xfer_buff);

这相当于

USBD_LL_DataInStage(&hUsbDeviceFS, epnum, hpcd_USB_OTG_FS.IN_ep[epnum].xfer_buff);
其中有
pdev->pClass->DataIn(pdev, epnum);

==>

USBD_CUSTOM_HID_DataIn(&hUsbDeviceFS, epnum);

到此为止,一切准备就绪,然后,中断函数HAL_PCD_IRQHandler会调用PCD_WriteEmptyTxFifo进行数据发送,

if ((epint & USB_OTG_DIEPINT_TXFE) == USB_OTG_DIEPINT_TXFE){
PCD_WriteEmptyTxFifo(hpcd, epnum);
}

而其中PCD_WriteEmptyTxFifo最后调用的,是USB_WritePacket函数。

==>

最后,

USB_WritePacket(USBx, ep->xfer_buff, epnum, len, hpcd->Init.dma_enable);

把结果发送出去。

PCD_WriteEmptyTxFifo

特别注意,这里len32b是对32位对齐进行处理,在参考手册34.16 OTG_FS control and status registers中,对所有寄存器,包括DFIFO,都要求 32位对齐(32-bit block aligned)。

static HAL_StatusTypeDef PCD_WriteEmptyTxFifo(PCD_HandleTypeDef* hpcd, uint32_t epnum)
{
    USB_OTG_GlobalTypeDef* USBx = hpcd->Instance;
    USB_OTG_EPTypeDef* ep;
    int32_t len = 0U;
    uint32_t len32b;
    uint32_t fifoemptymsk = 0U;

    ep = &hpcd->IN_ep[epnum];
    //假设我有5个字节要发送,那么这里xfer_len=5,xfer_count=0表示还没有数据写到FIFO中,len = 5;
    len = ep->xfer_len - ep->xfer_count;   

    // 这里,我们已经设置了maxpacket=2
    if (len > ep->maxpacket){
        len = ep->maxpacket;
    }

    // 这个是发送的辅助变量,意思是32bit长的数据能有多少个。也就是对齐到32bit(4个BYTE)的意思。
    // 注意,为了避免负数,这里都用的是uint32_t类型
    // 参考:https://github.com/ARMmbed/mbed-os/pull/6609
    // 这里的计算结果是 len32b = (5+3)/4 = 2
    len32b = (len + 3U) / 4U; 

    // ep->xfer_count < ep->xfer_len 表示已经发送的字节数 < 全部需要发送的字节数
    while (((USBx_INEP(epnum)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) > len32b) &&
        (ep->xfer_count < ep->xfer_len) &&
        (ep->xfer_len != 0U))
    {
        /* Write the FIFO */
        len = ep->xfer_len - ep->xfer_count; // 还有多少个字节没有发送

        if (len > ep->maxpacket){   // maxpacket这里是2,或64
            len = ep->maxpacket;    // 所以这里len是2 (如果maxpacket=64,len就是5)
        }
        len32b = (len + 3U) / 4U;   // 剩下的还能凑成多少个4字节

        // 发送一个len长度的字节数
        USB_WritePacket(USBx, ep->xfer_buff, epnum, len, hpcd->Init.dma_enable);
        ep->xfer_buff += len;
        ep->xfer_count += len;
    }

    if (len <= 0U){ // 送来无意义的0(即没有字节要发送)所以屏蔽掉这个中断即可
        fifoemptymsk = 0x1U << epnum;
        USBx_DEVICE->DIEPEMPMSK &= ~fifoemptymsk;
    }

    return HAL_OK;
}

最后的发送 – USB_WritePacket

和前面的函数一样,这里也同样对32位对齐进行了处理,每次USBx_DFIFO发送4个BYTE的数据,后面不足4个Byte的也凑齐4个BYTE发送出去。从PCD_WriteEmptyTxFifo和USB_WritePacket这两个函数可以看出,maxpacket设置得越大,发送速度就越快,因为可以省掉很多数据大小检测的时间。

另外一点,一次发送的数据量和maxpacket没有关系(实际上能否正确发送与接收,与HID Report Descriptor的关系更大),maxpacket的作用仅仅是检测每次往DFIFO写入的数据大小,没发完的还是照样会一个劲地发,直到发完。至于写到FIFO里后,芯片什么时候发,发送多少,这些都和硬件的设计有关,也是用户控制不了的。

本质上,DFIFO中还能一次性写多少数据是由USB_OTG_DTXFSTS_INEPTFSAV决定的,所以程序中要不停地对其进行检测,while (((USBx_INEP(epnum)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) > len32b)

HAL_StatusTypeDef USB_WritePacket(USB_OTG_GlobalTypeDef *USBx, uint8_t *src, uint8_t ch_ep_num, uint16_t len, uint8_t dma)
{
  uint32_t count32b = 0U , i = 0U;
  
  if (dma == 0U){
    count32b =  (len + 3U) / 4U;
    for (i = 0U; i < count32b; i++, src += 4U){
      USBx_DFIFO(ch_ep_num) = *((__packed uint32_t *)src);
    }
  }
  return HAL_OK;
}

USBx_DFIFO(i) – 数据传输寄存器

同样,我们计算一下USBx_DFIFO到底是指哪一个,假设这里i=1,

#define USB_OTG_FS_PERIPH_BASE               0x50000000U  // 这个就是USBx  
#define USB_OTG_FIFO_BASE                    0x1000U
#define USB_OTG_FIFO_SIZE                    0x1000U

#define USBx_DFIFO(i)   *(__IO uint32_t *)((uint32_t)USBx + USB_OTG_FIFO_BASE + (i) * USB_OTG_FIFO_SIZE)

因此,USBx_DFIFO(1) = 0x50001000U + 1*0x1000

这个,正是手册34.6.1中讲到的数据传输寄存器Data FIFO (DFIFO) access register map。

接收函数

USBD_CUSTOM_HID_DataOut

接收相对简单一些,就不再细述。源码也很容易懂。

static uint8_t  USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum){
  USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0], 
                                                            hhid->Report_buf[1]);
  USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf, 
                         USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
 return USBD_OK;
}

理解配置字Descriptors

USBD_FS_DeviceDesc

<usbd_desc.c>

/** SPEC2.0 Tab.9-8 USB standard device descriptor.  */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType SPEC2.0 Tab.9-5 */
#if (USBD_LPM_ENABLED == 1)
  0x01,                       /*bcdUSB */ /* changed to USB version 2.01
                                             in order to support LPM L1 suspend
                                             resume test of USBCV3.0*/
#else
  0x00,                       /*bcdUSB bcd coded version number */
#endif /* (USBD_LPM_ENABLED == 1) */
  0x02,                       /* bcdUSB = 0x0200*/
  0x00,           /*bDeviceClass, ref. https://www.usb.org/defined-class-codes */
  0x00,                       /*bDeviceSubClass, as above*/
  0x00,                       /*bDeviceProtocol, as above*/
  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*/
};

CUSTOM_HID_ReportDesc_FS

report descriptor 的详解解释可参考:
https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/

官方USB-HID文档与解析工具可参考:
https://www.usb.org/hid
https://usb.org/sites/default/files/documents/hid1_11.pdf
https://www.usb.org/sites/default/files/documents/dt2_4.zip

注意事项:

  • 每个transaction中,对Full speed最大packet只有64字节,Low speed最大8个字节;每毫秒(per millisecond)发起一个transaction,所以每秒传输字节数,FullSpeed大约是64K, LowSpeed大约是8K。
  • 只有Input才可以由Interrupt In管道发送,Output和Feature则必须由Host通过控制管道发起。(HID1.11chapter5.6)
  • 如果在报告描述符中使用了 REPORT_ID 则 USB 发送数据缓冲区第一个字节必须为 REPORT_ID 以告知Windows/Linux系统该数据属于哪个 ID,否则系统无法正常接收数据;
  • 下位机在上传数据时要按照报告描述符中规定的字节个数进行传输。假如已经规定为64字节,下位机某次只有10个字节要发送给主机,那么实际发送的缓冲区中的字节数也应该是64个字节,不能只发10个字节。至于为了补全这64字节,后续到底是什么内容这些都不重要。只有这样上位机Readfile时才能正确收到数据。

源码,

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
	// 7BYTES
	0x06, 0x00, 0xFF,  // Usage Page (Vendor Defined 0xFF00)
	0x09, 0x01,        // Usage (0x01)
	0xA1, 0x01,        // Collection (Application)
		// LED1 20BYTES 
		0x85, 0x01,        //   Report ID (1)
		0x09, 0x01,        //   Usage (0x01)
		0x15, 0x00,        //   Logical Minimum (0)
		0x25, 0x01,        //   Logical Maximum (1)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x01,        //   Report Count (1)
		0xB1, 0x82,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		0x85, 0x01,        //   Report ID (1)
		0x09, 0x01,        //   Usage (0x01)
		0x91, 0x82,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		// LED2 20BYTES
		0x85, 0x02,        //   Report ID (2)
		0x09, 0x02,        //   Usage (0x02)
		0x15, 0x00,        //   Logical Minimum (0)
		0x25, 0x01,        //   Logical Maximum (1)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x01,        //   Report Count (1)
		0xB1, 0x82,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		0x85, 0x02,        //   Report ID (2)
		0x09, 0x02,        //   Usage (0x02)
		0x91, 0x82,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		// LED3 20BYTES
		0x85, 0x03,        //   Report ID (3)
		0x09, 0x03,        //   Usage (0x03)
		0x15, 0x00,        //   Logical Minimum (0)
		0x25, 0x01,        //   Logical Maximum (1)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x01,        //   Report Count (1)
		0xB1, 0x82,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		0x85, 0x03,        //   Report ID (3)
		0x09, 0x03,        //   Usage (0x03)
		0x91, 0x82,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		// KEY1 20BYTES
		0x85, 0x04,        //   Report ID (4)
		0x09, 0x03,        //   Usage (0x04)
		0x15, 0x00,        //   Logical Minimum (0)
		0x25, 0x01,        //   Logical Maximum (1)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x01,        //   Report Count (1)
		0xB1, 0x82,        //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		0x85, 0x04,        //   Report ID (4)
		0x09, 0x03,        //   Usage (0x04)
		0x81, 0x82,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
		// 15BYTES		
		0x85, 0x05,        //   Report ID (5)
		0x09, 0x05,        //   Usage (0x05)
		0x15, 0x00,        //   Logical Minimum (0)
		0x26, 0xFF, 0x00,  //   Logical Maximum (255)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x3F,        //   Report Count (63)
		0x91, 0x82,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile)
		// 15BYTES		
		0x85, 0x06,        //   Report ID (6)
		0x09, 0x05,        //   Usage (0x06)
		0x15, 0x00,        //   Logical Minimum (0)
		0x26, 0xFF, 0x00,  //   Logical Maximum (255)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x3F,        //   Report Count (63)
		0x81, 0x82,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
		// 15BYTES
		0x85, 0x07,        //   Report ID (7)
		0x09, 0x06,        //   Usage (0x07)
		0x15, 0x00,        //   Logical Minimum (0)
		0x26, 0xFF, 0x00,  //   Logical Maximum (255)
		0x75, 0x08,        //   Report Size (8)
		0x95, 0x3F,        //   Report Count (63)
		0x81, 0x82,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
	// 1BYTES
	0xC0,              // End Collection

	// 133 bytes
};

在上面这个源码中,我们配置了3个LED,即通过上位机(USB HID Demonstrator, ST官网提供)控制,另外定义了一个开发板上按键,然后定义了3个63(包括report_ID,实际有64个)字节的通信通道。

在Windows中是通过下面这个结构获取这些信息的,比如有多少个通道(不知道怎么翻译好,英文是capabilities),最大通信字节数等。

typedef struct _HIDP_CAPS {
  USAGE  UsagePage;
  USAGE  Usage;
  USHORT InputReportByteLength;
  USHORT OutputReportByteLength;
  USHORT FeatureReportByteLength;
  USHORT Reserved[17];
  USHORT NumberLinkCollectionNodes;
  USHORT NumberInputButtonCaps;
  USHORT NumberInputValueCaps;
  USHORT NumberInputDataIndices;
  USHORT NumberOutputButtonCaps;
  USHORT NumberOutputValueCaps;
  USHORT NumberOutputDataIndices;
  USHORT NumberFeatureButtonCaps;
  USHORT NumberFeatureValueCaps;
  USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;

后面的基本上没有难度,估且贴一下源码吧!

USBD_CUSTOM_HID_CfgDesc

<usbd_customhid.h>

#define USB_CUSTOM_HID_CONFIG_DESC_SIZ       41
#define USB_CUSTOM_HID_DESC_SIZ              9
#define CUSTOM_HID_DESCRIPTOR_TYPE           0x21
#define CUSTOM_HID_REPORT_DESC               0x22

SPEC2.0 Tab9-5

// spec Tab.9-5
#define  USB_DESC_TYPE_DEVICE                              1
#define  USB_DESC_TYPE_CONFIGURATION                       2
#define  USB_DESC_TYPE_STRING                              3
#define  USB_DESC_TYPE_INTERFACE                           4
#define  USB_DESC_TYPE_ENDPOINT                            5
#define  USB_DESC_TYPE_DEVICE_QUALIFIER                    6
#define  USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION           7
#define  USB_DESC_TYPE_BOS                                 0x0F

<usbd_customhid.c>

/* USB CUSTOM_HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
	0x09, /* bLength: Configuration Descriptor size */
	USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration SPEC2.0 Tab.9-5 */
	USB_CUSTOM_HID_CONFIG_DESC_SIZ,	/* wTotalLength: Bytes returned */
	0x00,
	0x01,         /*bNumInterfaces: 1 interface*/
	0x01,         /*bConfigurationValue: Configuration value*/
	0x00,    /*iConfiguration: Index of string descriptor describing the configuration*/
	0xC0,         /*bmAttributes: bus powered */
	0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/

	/************** Descriptor of CUSTOM HID interface ****************/
	/* 09 SPEC2.0 Tab9-12, interface descriptor */
	0x09,         /*bLength: Interface Descriptor size*/
	USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type */
	0x00,         /*bInterfaceNumber: Number of Interface*/
	0x00,         /*bAlternateSetting: Alternate setting*/
	0x02,         /*bNumEndpoints*/
	0x03,         /*bInterfaceClass: CUSTOM_HID*/
                  //re. https://www.usb.org/defined-class-codes, INTERFACE CLASS = 03
	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: CUSTOM_HID Descriptor size*/
	CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/
	0x11,         /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/
	0x01,
	0x00,         /*bCountryCode: Hardware target country*/
	0x01,         /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/
	0x22,         /*bDescriptorType*/
	USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
	0x00,
	/******************** Descriptor of Custom HID endpoints ********************/
	/* 27 */
	0x07,          /*bLength: Endpoint Descriptor size*/
	USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

	CUSTOM_HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
	0x03,          /*bmAttributes: Interrupt endpoint*/
	CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */
	0x00,
	0x20,          /*bInterval: Polling Interval (20 ms)*/
	/* 34 */

	0x07,	         /* bLength: Endpoint Descriptor size */
	USB_DESC_TYPE_ENDPOINT,	/* bDescriptorType: */
	CUSTOM_HID_EPOUT_ADDR,  /*bEndpointAddress: Endpoint Address (OUT)*/
	0x03,	/* bmAttributes: Interrupt endpoint */
	CUSTOM_HID_EPOUT_SIZE,	/* wMaxPacketSize: 2 Bytes max  */
	0x00,
	0x20,	/* bInterval: Polling Interval (20 ms) */
	/* 41 */
} ;

其回调函数为,

static uint8_t  *USBD_CUSTOM_HID_GetCfgDesc (uint16_t *length)
{
  *length = sizeof (USBD_CUSTOM_HID_CfgDesc);
  return USBD_CUSTOM_HID_CfgDesc;
}

USBD_CUSTOM_HID_Desc

<usbd_customhid.c>

/* USB CUSTOM_HID device Configuration Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_Desc[USB_CUSTOM_HID_DESC_SIZ] __ALIGN_END =
{
	/* 18 */
	0x09,         /*bLength: CUSTOM_HID Descriptor size*/
	CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/
	0x11,         /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/
	0x01,
	0x00,         /*bCountryCode: Hardware target country*/
	0x01,         /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/
	0x22,         /*bDescriptorType*/
	USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
	0x00,
};

USBD_CUSTOM_HID_DeviceQualifierDesc

<usbd_customhid.c>

/* USB Standard Device Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_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,
};

相关文章:

  • STM32F4xx usb库源码详解:HAL_PCDEx_SetRxFiFo 和 HAL_PCDEx_SetTxFiFo
  • Libuv 1.34.2 源码详解 ---- 以uvCat为例讲解
  • 步进电机的细分驱动中1-2相, W1-2相, 2W1-2相, 4W1-2相 表示什么意思?
  • MCU_关于STM32Fxxx中断EXTI产生时多次(两次)进入中断的原因
  • MCU_通过windows串口API控制RTS和DTR
  • MCU_STM32的HAL库中的宏DMA_FLAG_TCIF0_4/DMA_FLAG_TCIF1_5/DMA_FLAG_TCIF2_6/DMA_FLAG_TCIF3_7
  • LWIP_TCP如何理解数据发送,何时使用tcp_recved函数
  • MCU_使用STM32CUBEMX配置STM32F107/407 RMII-ETHERNET要注意的细节:PHY Address和MCO时钟
  • MCU_STM32CUBEMX v5.5.0的一个BUG:ethernetif_input引起进入HardFault_Handler
  • MCU_STM32CUBEMX配置生成CAN2的初始化代码的修改
  • MCU_STM32F4xx使用CCM RAM
  • MCU_C语言中 数组型指针 的应用 -- char (*stringp)[]
  • Anaconda 安装yaml
  • pip(easy_install)anaconda本地安装文件包(附gluon-cv的升级过程)
  • vscode如何安装官方提供的Microsoft.python.language server包?
  • [译]Python中的类属性与实例属性的区别
  • es6要点
  • javascript 总结(常用工具类的封装)
  • Rancher-k8s加速安装文档
  • Selenium实战教程系列(二)---元素定位
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 基于游标的分页接口实现
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 人脸识别最新开发经验demo
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 责任链模式的两种实现
  • Android开发者必备:推荐一款助力开发的开源APP
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • #控制台大学课堂点名问题_课堂随机点名
  • $.ajax,axios,fetch三种ajax请求的区别
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (二)WCF的Binding模型
  • (分类)KNN算法- 参数调优
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (原)本想说脏话,奈何已放下
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)编辑寄语:因为爱心,所以美丽
  • (转)我也是一只IT小小鸟
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • ****Linux下Mysql的安装和配置
  • .axf 转化 .bin文件 的方法
  • .net core webapi Startup 注入ConfigurePrimaryHttpMessageHandler
  • .NET 使用配置文件
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .net反编译的九款神器
  • .net流程开发平台的一些难点(1)
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • /proc/vmstat 详解
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • [@Controller]4 详解@ModelAttribute