STM32F7xx基于HAL库的USB_CDC接收数据的函数调用

本文详细解析了STM32F7xx使用HAL库实现的USB通信中,数据接收的中断处理流程,包括OTG HS中断处理、HAL_PCD_IRQHandler函数、EP中断检查、DataOutStageCallback回调,以及USBD_CDC接收数据的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在STM32F7xx的HAL库实现的USB通信中,里面存在着多个库文件和函数的调用,这一章节主要对USB接收数据的函数调用流程来进行分析,USB的数据发送部分相对来说比较容易分析。

在usb通信中,STM32F7xx作为从设备,当USB接收到数据时,从而产生中断;

/**
 * @File: stm32f7xx_it.c
 * @brief This function handles USB On the Go Hs global interrupt.
 */
extern PCD_HandleTypeDef hpcd_USB_OTG_HS;
void OTG_HS_IRQHandler(void)
{
  HAL_PCD_IRQHandler(&hpcd_USB_OTG_HS);
}

进入HAL_PCD_IRQHandler(&hpcd_USB_OTG_HS)函数,部分函数代码如下;

/**
 * @File: stm32f7xx_hal_pcd.c
 * @brief This function handles USB On the Go Hs global interrupt.
 */
void HAL_PCD_IRQHandler(PCD_HandleTypeDef *hpcd)
{
    //...
     /* Read in the device interrupt bits */
      ep_intr = USB_ReadDevAllOutEpInterrupt(hpcd->Instance);
      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);
          }
            //...
        }
          //...
      }
    //...
}

读取device out endpoint interrupt寄存器后判断是否数据传输完成,然后清除传输完成中断标志位并进入函数PCD_EP_OutXfrComplete_int(hpcd, epnum),其部分函数代码如下;

/**
 * @File: stm32f7xx_hal_pcd.c
 * @brief process EP OUT transfer complete interrupt.
 */
static HAL_StatusTypeDef PCD_EP_OutXfrComplete_int(PCD_HandleTypeDef *hpcd, uint32_t epnum)
{
    //...
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
        hpcd->DataOutStageCallback(hpcd, (uint8_t)epnum);
#else
        HAL_PCD_DataOutStageCallback(hpcd, (uint8_t)epnum);
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
    //...
}

然后会进入数据处理回调函数HAL_PCD_DataOutStageCallback,主要是调用USB的底层函数USBD_LL_DataOutStage;

/**
 * @File: usbd_conf.c
 * @brief Data Out stage callback.
 */
void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
  USBD_LL_DataOutStage((USBD_HandleTypeDef*)hpcd->pData, epnum, 
                       hpcd->OUT_ep[epnum].xfer_buff);
}

其函数USBD_LL_DataOutStage()的部分代码如下;

/**
 * @File: usbd_core.c
 * @brief USBD_LL_DataOutStage.
 */
USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev,
                                        uint8_t epnum, uint8_t *pdata)
{
    USBD_EndpointTypeDef *pep;
    USBD_StatusTypeDef ret;
    if(epnum == 0U)
    {
        //...
    }
   else if ((pdev->pClass->DataOut != NULL) &&
            (pdev->dev_state == USBD_STATE_CONFIGURED))
  {
    ret = (USBD_StatusTypeDef)pdev->pClass->DataOut(pdev, epnum);
    if (ret != USBD_OK)
    {
      return ret;
    }
  }
    //...
}

里面主要是通过pdev->pClass->DataOut(pdev, epnum)来调用函数USBD_CDC_DataOut;

通过对结构体USBD_HandleTypeDef的分析,在usbd_conf.c文件中定义了两个结构体USBD_HandleTypeDef和USBD_ClassTypeDef:

/**
 * @File: usbd_conf.c
 *
 */
typedef struct _Device_cb
{
  uint8_t (*Init)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
  uint8_t (*DeInit)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
  /* Control Endpoints*/
  uint8_t (*Setup)(struct _USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef  *req);
  uint8_t (*EP0_TxSent)(struct _USBD_HandleTypeDef *pdev);
  uint8_t (*EP0_RxReady)(struct _USBD_HandleTypeDef *pdev);
  /* Class Specific Endpoints*/
  uint8_t (*DataIn)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
  uint8_t (*DataOut)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
  uint8_t (*SOF)(struct _USBD_HandleTypeDef *pdev);
  uint8_t (*IsoINIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
  uint8_t (*IsoOUTIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
  uint8_t  *(*GetHSConfigDescriptor)(uint16_t *length);
  uint8_t  *(*GetFSConfigDescriptor)(uint16_t *length);
  uint8_t  *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
  uint8_t  *(*GetDeviceQualifierDescriptor)(uint16_t *length);
} USBD_ClassTypeDef;
/* 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;
  uint8_t                 ConfIdx;
  USBD_SetupReqTypedef    request;
  USBD_DescriptorsTypeDef *pDesc;
  USBD_ClassTypeDef       *pClass;
  void                    *pClassData;
  void                    *pUserData;
  void                    *pData;
  void                    *pBosDesc;
  void                    *pConfDesc;
} USBD_HandleTypeDef;

并对结构体USBD_ClassTypeDef进行了实例化:

/**
 * @File: usbd_conf.c
 *@brief: CDC interface class callbacks structure.
 */
USBD_ClassTypeDef  USBD_CDC =
{
  USBD_CDC_Init,
  USBD_CDC_DeInit,
  USBD_CDC_Setup,
  NULL,                 /* EP0_TxSent, */
  USBD_CDC_EP0_RxReady,
  USBD_CDC_DataIn,
  USBD_CDC_DataOut,
  NULL,
  NULL,
  NULL,
  USBD_CDC_GetHSCfgDesc,
  USBD_CDC_GetFSCfgDesc,
  USBD_CDC_GetOtherSpeedCfgDesc,
  USBD_CDC_GetDeviceQualifierDescriptor,
};

而在USB进行设备初始化时,就会来调用函数USBD_RegisterClass(&hUsbDeviceHS, &USBD_CDC)来进行初始化,其函数USBD_RegisterClass()的部分代码如下;

/**
 * @File: usbd_conf.c
 *@brief: Link class driver to Device Core.
 */
USBD_StatusTypeDef USBD_RegisterClass(USBD_HandleTypeDef *pdev, 
                                      USBD_ClassTypeDef *pclass)
{
    //...
    /* link the class to the USB Device handle */
    pdev->pClass = pclass;
    //...
}

所以在函数USBD_LL_DataOutStage()中的pdev->pClass->DataOut(pdev, epnum)就会来调用上面提到的函数USBD_CDC_DataOut,在文件usbd_conf.c中进行了定义;

/**
 * @File: usbd_conf.c
 * @brief Data received on non-control Out endpoint
 */
static uint8_t USBD_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
    USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)pdev->pClassData;
​
  if (pdev->pClassData == NULL)
  {
    return (uint8_t)USBD_FAIL;
  }
  /* Get the received data length */
  hcdc->RxLength = USBD_LL_GetRxDataSize(pdev, epnum);
  /* USB data will be immediately processed, this allow next USB traffic being
  NAKed till the end of the application Xfer */
  ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength);
    
  return (uint8_t)USBD_OK;
}

函数中的((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength)就会调用到函数CDC_Receive_HS。

同样,在文件usbd_conf.c中定义了结构体USBD_CDC_ItfTypeDef并进行了实例化;

/**
 * @File: usbd_conf.c
 *
 */
typedef struct _USBD_CDC_Itf
{
  int8_t (* Init)(void);
  int8_t (* DeInit)(void);
  int8_t (* Control)(uint8_t cmd, uint8_t *pbuf, uint16_t length);
  int8_t (* Receive)(uint8_t *Buf, uint32_t *Len);
  int8_t (* TransmitCplt)(uint8_t *Buf, uint32_t *Len, uint8_t epnum);
} USBD_CDC_ItfTypeDef;
/*
*CD  Interface callback
*/
USBD_CDC_ItfTypeDef USBD_Interface_fops_HS =
{
  CDC_Init_HS,
  CDC_DeInit_HS,
  CDC_Control_HS,
  CDC_Receive_HS,
  CDC_TransmitCplt_HS
};

并在usb的设备初始化中调用函数USBD_CDC_RegisterInterface(&hUsbDeviceHS, &USBD_Interface_fops_HS)进行了初始化,其USBD_CDC_RegisterInterface()函数代码如下;

/**
 * @File: usbd_conf.c
 *@brief: USBD_CDC_RegisterInterface.
 */
uint8_t USBD_CDC_RegisterInterface(USBD_HandleTypeDef *pdev,USBD_CDC_ItfTypeDef *fops)
{
  if (fops == NULL)
  {
    return (uint8_t)USBD_FAIL;
  }
  pdev->pUserData = fops;
  return (uint8_t)USBD_OK;
}

所以((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength)就会调用到函数CDC_Receive_HS,在文件usbd_cdc_if.c中进行了定义;

/**
 * @File: usbd_cdc_if.c
 * @brief Data received over USB OUT endpoint are sent over CDC interface
 *         through this function.
 * @param  Buf: Buffer of data to be received
 * @param  Len: Number of data received (in bytes)
 */
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 11 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceHS);
  return (USBD_OK);
  /* USER CODE END 11 */
}

前面提到的USB初始化函数,在文件usb_device.c中进行了定义,主要是通过函数MX_USB_DEVICE_Init()来对USB设备进行初始化;

/**
 * @File: usb_device.c
 *@brief: Init USB device Library, add supported class and start the library.
 */
void MX_USB_DEVICE_Init(void)
{
  /* Init Device Library, add supported class and start the library. */
  if (USBD_Init(&hUsbDeviceHS, &HS_Desc, DEVICE_HS) != USBD_OK)
  {
    Error_Handler();
  }
  if (USBD_RegisterClass(&hUsbDeviceHS, &USBD_CDC) != USBD_OK)
  {
    Error_Handler();
  }
  if (USBD_CDC_RegisterInterface(&hUsbDeviceHS, &USBD_Interface_fops_HS) != USBD_OK)
  {
    Error_Handler();
  }
  if (USBD_Start(&hUsbDeviceHS) != USBD_OK)
  {
    Error_Handler();
  }
}
​文件usbd_cdc_if.c中的函数CDC_Receive_HS()就可以在里面进行对USB主设备发来的数据进行接收,指针uint8_t* Buf指向数据内容,指针uint32_t *Len指向数据的长度。

接下来在函数CDC_Receive_HS()中就会调用文件usbd_conf.c中的函数USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)和USBD_LL_PrepareReceive()函数;

/**
 * @File: usbd_cdc.c
 *@brief: prepare OUT Endpoint for reception.
 */
uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)
{
    //...
    if (pdev->dev_speed == USBD_SPEED_HIGH)
    {
            /* Prepare Out endpoint to receive next packet */
           (void)USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
                                 CDC_DATA_HS_OUT_PACKET_SIZE);
    }
    //...
}
/**
 * @brief  Prepares an endpoint for reception.
 */
USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t *pbuf, uint32_t size)
{
  HAL_StatusTypeDef hal_status = HAL_OK;
  USBD_StatusTypeDef usb_status = USBD_OK;
​
  hal_status = HAL_PCD_EP_Receive(pdev->pData, ep_addr, pbuf, size);
​
  usb_status =  USBD_Get_USB_Status(hal_status);
​
  return usb_status;
}

文件stm32f7xx_hal_pcd.c中的函数HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len);

/**
 * @File: stm32f7xx_hal_pcd.c
 *@brief: Receive an amount of data.
 */
HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
{
  PCD_EPTypeDef *ep;
  ep = &hpcd->OUT_ep[ep_addr & EP_ADDR_MSK];
  /*setup and start the Xfer */
  ep->xfer_buff = pBuf;
  ep->xfer_len = len;
  ep->xfer_count = 0U;
  ep->is_in = 0U;
  ep->num = ep_addr & EP_ADDR_MSK;
  if (hpcd->Init.dma_enable == 1U)
  {
    ep->dma_addr = (uint32_t)pBuf;
  }
  if ((ep_addr & EP_ADDR_MSK) == 0U)
  {
    (void)USB_EP0StartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
  }
  else
  {
    (void)USB_EPStartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
  }
  return HAL_OK;
}

接着就会调用文件stm32f7xx_ll_usb.c中的函数USB_EPStartXfer(USB_OTG_GlobalTypeDef *USBx, USB_OTG_EPTypeDef *ep, uint8_t dma);

/**
 * @File: stm32f7xx_ll_usb.c
 *@brief: setup and starts a transfer over an EP.
 */
HAL_StatusTypeDef USB_EPStartXfer(USB_OTG_GlobalTypeDef *USBx, USB_OTG_EPTypeDef *ep, uint8_t dma)
{
   //...
      /* IN endpoint */
  if (ep->is_in == 1U)
  {
      //...
  }
  else /* OUT endpoint */
  {
    /* Program the transfer size and packet count as follows:
    * pktcnt = N
    * xfersize = N * maxpacket
    */
    USBx_OUTEP(epnum)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
    USBx_OUTEP(epnum)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);
    if (ep->xfer_len == 0U)
    {
      USBx_OUTEP(epnum)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
      USBx_OUTEP(epnum)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U << 19));
    }
    else
    {
      pktcnt = (uint16_t)((ep->xfer_len + ep->maxpacket - 1U) / ep->maxpacket);
      USBx_OUTEP(epnum)->DOEPTSIZ |= USB_OTG_DOEPTSIZ_PKTCNT & ((uint32_t)pktcnt << 19);
      USBx_OUTEP(epnum)->DOEPTSIZ |= USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket * pktcnt);
    }
    //...
    /* EP enable */
    USBx_OUTEP(epnum)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
  }
​
  return HAL_OK;
}

该函数主要是对USB接收中最底层的寄存器来进行配置。至此,这就是整个USB数据接收的函数调用流程。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值