HAL库 USB_CDC数据流收发函数分析

在使用ARM的HAL库实现USB接口通信,常常因为有多个库文件和函数的多层调用而觉得使用起来很复杂,而事实上通过合理的配置后,HAL库函数的使用非常简单,本文对利用STM32CubeMX生成的USB_CDC工程的库函数进行分析,从而通过简单的修改实现USB接口的通信。

自顶而下:

-----------------------------------------------------------------------------

设备接收数据流图:

HAL库 USB_CDC数据流收发函数分析_第1张图片

设备发送数据流图:

HAL库 USB_CDC数据流收发函数分析_第2张图片

 

-----------------------------------------------------------------------------

文件usbd_cdc_if.c,函数如下:

函数描述:USB输出端点接收的数据通过此函数发送给CDC接口。

注意事项:此函数将阻止USB端点上的所有OUT数据包接收,直到退出此函数。 如果在CDC接口上的传输完成之前(即使用DMA控制器)退出此函数,将导致接收更多数据,而先前的数据仍未发送。

参数说明:参数Buf: 待接收数据的缓冲区;

参数Len:   接收的数据数(以字节为单位)

返回:   USBD_OK(如果所有操作均正常),否则返回USBD_FAIL

-----------------------------------------------------------------------------

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{ 
  USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);//设置接收buf
  USBD_CDC_ReceivePacket(&hUsbDeviceHS);//接收数据包
  return (USBD_OK);
}

-----------------------------------------------------------------------------------

文件usbd_cdc.c,函数如下: 

函数描述:准备接收输出端点数据。

参数说明:参数pdev: USB设备结构体句柄;

返回:    USBD_OK(如果所有操作均正常),否则返回USBD_FAIL

-----------------------------------------------------------------------------

uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)
{
    (void)USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
                                 CDC_DATA_HS_OUT_PACKET_SIZE); 
  return (uint8_t)USBD_OK;
}

------------------------------------------------------------------------------------

文件usbd_conf.c,函数如下:

函数描述:准备一个端点接收数据。

参数说明:参数pdev:    USB设备结构体句柄;

                  参数ep_addr: 端点号;

                  参数pbuf:       指向要接收的数据的指针;

                  参数size:       数据大小;

                  返回:        USBD_OK(如果所有操作均正常),否则返回USBD_FAIL

------------------------------------------------------------------------------------

USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t *pbuf, uint32_t size)
{
  hal_status = HAL_PCD_EP_Receive(pdev->pData, ep_addr, pbuf, size);
  return usb_status; 
}

------------------------------------------------------------------------------------

文件stm32f4xx_hal_pcd.c,函数如下:

函数描述:接收大量数据。

参数说明:参数pdev:        PCD结构体句柄;

                  参数ep_addr:   端点号;

                 参数pbuf:          指向要接收的数据的指针;

                 参数len:             接收数据长度;

                 返回:              HAL_OK(如果所有操作均正常),否则返回HAL_FAIL     

-----------------------------------------------------------------------------------

HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
{
  /*设置和启动 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)//当DMA使能时,给DMA接收BUF的首地址。
  {
    ep->dma_addr = (uint32_t)pBuf;
  }
  if ((ep_addr & EP_ADDR_MSK) == 0U)//端点是EP0时,用USB_EP0StartXfer传输数据
  {
    (void)USB_EP0StartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
  }
  Else //端点不是EP0时,用USB_EPStartXfer传输数据
  {
    (void)USB_EPStartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
  }
  return HAL_OK;
}

------------------------------------------------------------------------------------

文件stm32f4xx_ll_usb.c(底层函数,重点分析)

函数描述:建立和启动一次EP的数据传输,该函数是USB收发中最底层的寄存器操作函数,数据的组包和解包均在此函数中进行。

  参数说明:  参数USBx: USB_OTG核心寄存器定义结构体句柄;

  参数ep:      USB_OTG端点寄存器定义结构体句柄;

  参数dma:  DMA使能,1有效,0关闭;

  返回:    HAL_OK(如果所有操作均正常),否则返回HAL_FAIL

-------------------------------------------------------------------------------------

HAL_StatusTypeDef USB_EPStartXfer(USB_OTG_GlobalTypeDef *USBx, USB_OTG_EPTypeDef *ep, uint8_t dma)
{  //定义变量
  uint32_t USBx_BASE = (uint32_t)USBx;
  uint32_t epnum = (uint32_t)ep->num;
  uint16_t pktcnt;
/* ***************************数据发送************************** */
  /* 数据发送:输入端点 */
  if (ep->is_in == 1U)    //判断端点方向:1-输入;0输出
  {
    /* ********************配置传输大小和包数****************** */
    if (ep->xfer_len == 0U)    //当传输长度为0
    {    //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
      USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT);
      USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (1U << 19));
      USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ);
    }
    else     //当传输长度不为0
    {     //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
      /* 配置传输大小和包数,例如:
	*xfersize = N * maxpacket+ short_packet pktcnt = N + (short_packet exist ? 1 : 0)
      */
      USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ);
      USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT);
      USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (((ep->xfer_len + ep->maxpacket - 1U) / ep->maxpacket) << 19));
      USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_XFRSIZ & ep->xfer_len);
 
      if (ep->type == EP_TYPE_ISOC) //判断端点的类型为同步端点
      {  //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
        USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_MULCNT);
        USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_MULCNT & (1U << 29));
      }
    }
	 /* ********************配置DMA****************** */
    if (dma == 1U)  //判断DMA使能
    {
      if ((uint32_t)ep->dma_addr != 0U)
      {    //配置IN端点DMA地址寄存器
        USBx_INEP(epnum)->DIEPDMA = (uint32_t)(ep->dma_addr);
      }
 
      if (ep->type == EP_TYPE_ISOC) //判断端点的类型为同步端点
      {
        if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)//判断USB设备状态寄存器
        {    //配置设备IN端点控制寄存器—设置为奇数帧
          USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SODDFRM;
        }
        else
        {//配置设备IN端点控制寄存器—设置为数据 PID
          USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
        }
      }
 
      /* EP使能,输入数据FIFO */
	//配置设备IN端点控制寄存器—设置为清除NAK和端点使能
  USBx_INEP(epnum)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);
    }
    else  //DMA关闭
    {
      /* EP使能,输入数据FIFO */
		//配置设备IN端点控制寄存器—设置为清除NAK和端点使能
  USBx_INEP(epnum)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);
      if (ep->type != EP_TYPE_ISOC) //判断端点的类型不为同步端点
      {
        /*为此EP启用Tx FIFO空中断 */
        if (ep->xfer_len > 0U)//且数据长度大于0
        {    //开USB设备对应的发送数据的端点的中断
          USBx_DEVICE->DIEPEMPMSK |= 1UL << (ep->num & EP_ADDR_MSK);
        }
      }
      else  //判断端点的类型为同步端点
      {  //判断USB设备状态寄存器
        if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)
        {//配置设备IN端点控制寄存器—设置为奇数帧
          USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SODDFRM;
        }
        else
        {//配置设备IN端点控制寄存器—设置为数据 PID
          USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
        }
      /* ********************写数据****************** */
     (void)USB_WritePacket(USBx, ep->xfer_buff, ep->num, (uint16_t)ep->xfer_len, dma);
      }
    }
  }
/* ***************************数据接收************************** */
	/* 数据接收:输出端点 */
  else /* OUT 端点*/
  {  /* ********************配置传输大小和包数****************** */
    /* 设置传输大小和包数,如下:
    * 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);
    }
  /* ********************配置DMA****************** */
    if (dma == 1U)
    {
      if ((uint32_t)ep->xfer_buff != 0U)
      {//设置设备OUT端点DMA地址
        USBx_OUTEP(epnum)->DOEPDMA = (uint32_t)(ep->xfer_buff);
      }
    }
 
    if (ep->type == EP_TYPE_ISOC)
    {
      if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)
      {
        USBx_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
      }
      else
      {
        USBx_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
      }
    }
    /* EP 使能*/
    USBx_OUTEP(epnum)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
  }
 
  return HAL_OK;
}

-------------------------------------------------------------------------------------

在HAL库USB接口CDC模式的函数中,收发功能函数到底层寄存器的操作中间嵌套了很多层。在实际使用过程中,我们并不需要关心中间的过程,仅需调用usbd_cdc_if.c文件中的相关函数就能实现数据的收发。

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)

      uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len)

例如:对接收函数稍作改变,就能实现将收到的数据通过USB发送回去,同时通过串口将数据打印。

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 11 */
	int i;
	uint8_t my_RxBuf[100];
        uint32_t my_RxLength;
	
	memcpy(my_RxBuf,Buf,*Len);
        my_RxLength=*Len;	
	CDC_Transmit_HS(my_RxBuf, my_RxLength);	
	for(i=0;i

结果如下:左为USB发送和接收,右为串口打印。

HAL库 USB_CDC数据流收发函数分析_第3张图片

 

你可能感兴趣的:(通信接口,USB)