STM32 USB学习笔记9

发布者:keiss2018最新更新时间:2019-03-11 来源: eefocus关键字:STM32  USB 手机看文章 扫描二维码
随时随地手机看文章

主机环境:Windows 7 SP1


开发环境:MDK5.14


目标板:STM32F103C8T6


开发库:STM32F1Cube库和STM32_USB_Device_Library


现在我们来分析VCP例程的最后一个文件USB设备类的usbd_cdc文件,该文件跟CDC类紧密相关,看下其头文件的一些定义:



/** @defgroup usbd_cdc_Exported_Defines

  * @{

  */ 

#define CDC_IN_EP                                   0x81  /* EP1 for data IN */

#define CDC_OUT_EP                                  0x01  /* EP1 for data OUT */

#define CDC_CMD_EP                                  0x82  /* EP2 for CDC commands */

 

/* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */

#define CDC_DATA_HS_MAX_PACKET_SIZE                 512  /* Endpoint IN & OUT Packet size */

#define CDC_DATA_FS_MAX_PACKET_SIZE                 64  /* Endpoint IN & OUT Packet size */

#define CDC_CMD_PACKET_SIZE                         8  /* Control Endpoint Packet size */ 

 

#define USB_CDC_CONFIG_DESC_SIZ                     67

#define CDC_DATA_HS_IN_PACKET_SIZE                  CDC_DATA_HS_MAX_PACKET_SIZE

#define CDC_DATA_HS_OUT_PACKET_SIZE                 CDC_DATA_HS_MAX_PACKET_SIZE

 

#define CDC_DATA_FS_IN_PACKET_SIZE                  CDC_DATA_FS_MAX_PACKET_SIZE

#define CDC_DATA_FS_OUT_PACKET_SIZE                 CDC_DATA_FS_MAX_PACKET_SIZE

这些宏定义指明了CDC类使用的端点,以及最大包大小,对于STM32F103C8T6来说最大包大小是是64字节,其中命令控制端点最大包大小为8字节。在头文件中还有一个CDC类的结构声明,如下:


typedef struct

{

  uint32_t data[CDC_DATA_HS_MAX_PACKET_SIZE/4];      /* 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; 

该句柄管理着CDC类的数据信息,接着我们来查看usbd_cdc.c文件,该文件需要配合CDC1.20以及PSTN1.20协议文档来查看,在usbd_cdc,c中有我们CDC类的具体实现,如下:


/* 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,

};

其中EP0_TxSent、SOF、IsoINIncomplete、ISOOUTIncomplete为空。该文件中函数比较多,先从简单的分析,首先看下注册接口的函数,如下:


/**

* @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;

}

注册接口很简单,即把CDC接口指针链接到USB句柄中的UserData即可。接着是在CDC接口文件中提到的两个设置缓存区的函数,如下:


/**

  * @brief  USBD_CDC_SetTxBuffer

  * @param  pdev: device instance

  * @param  pbuff: Tx Buffer

  * @retval status

  */

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;  

}

 

 

/**

  * @brief  USBD_CDC_SetRxBuffer

  * @param  pdev: device instance

  * @param  pbuff: Rx Buffer

  * @retval status

  */

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;

}

给CDC句柄的相应字段赋值即可。再有两个是CDC接口文件中提到的数据发送和接收函数,如下:


/**

  * @brief  USBD_CDC_DataOut

  *         Data received on non-control Out endpoint

  * @param  pdev: device instance

  * @param  epnum: endpoint number

  * @retval status

  */

uint8_t  USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)

{      

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  

  if(pdev->pClassData != NULL)

  {

    if(hcdc->TxState == 0)

    {

      /* Tx Transfer in progress */

      hcdc->TxState = 1;

      

      /* Transmit next packet */

      USBD_LL_Transmit(pdev,

                       CDC_IN_EP,

                       hcdc->TxBuffer,

                       hcdc->TxLength);

      

      return USBD_OK;

    }

    else

    {

      return USBD_BUSY;

    }

  }

  else

  {

    return USBD_FAIL;

  }

}

 

 

/**

  * @brief  USBD_CDC_ReceivePacket

  *         prepare OUT Endpoint for reception

  * @param  pdev: device instance

  * @retval status

  */

uint8_t  USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)

{      

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  

  /* Suspend or Resume USB Out process */

  if(pdev->pClassData != NULL)

  {

    if(pdev->dev_speed == USBD_SPEED_HIGH  ) 

    {      

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             CDC_OUT_EP,

                             hcdc->RxBuffer,

                             CDC_DATA_HS_OUT_PACKET_SIZE);

    }

    else

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             CDC_OUT_EP,

                             hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);

    }

    return USBD_OK;

  }

  else

  {

    return USBD_FAIL;

  }

}

发送函数很简单,检测到发送状态处于空闲时则执行发送操作,并设置发送状态有BUSY,这里这里使用的发送端点是0x81,不是端点0。而且数据的发送和发送数据的缓存区设置二者是成对使用的。接收函数也比较简单只是区分了高速和全速模式下的最大包大小不同,这里使用的端点同样不是端点0而是端点0x001。CDC类接口使用的函数补充完毕,现在来看CDC类各个函数,依然从简单的开始几个描述符的获取,这个有四个描述符的获取,如下:


/**

  * @brief  USBD_CDC_GetFSCfgDesc 

  *         Return configuration descriptor

  * @param  speed : current device speed

  * @param  length : pointer data length

  * @retval pointer to descriptor buffer

  */

static uint8_t  *USBD_CDC_GetFSCfgDesc (uint16_t *length)

{

  *length = sizeof (USBD_CDC_CfgFSDesc);

  return USBD_CDC_CfgFSDesc;

}

 

/**

  * @brief  USBD_CDC_GetHSCfgDesc 

  *         Return configuration descriptor

  * @param  speed : current device speed

  * @param  length : pointer data length

  * @retval pointer to descriptor buffer

  */

static uint8_t  *USBD_CDC_GetHSCfgDesc (uint16_t *length)

{

  *length = sizeof (USBD_CDC_CfgHSDesc);

  return USBD_CDC_CfgHSDesc;

}

 

/**

  * @brief  USBD_CDC_GetCfgDesc 

  *         Return configuration descriptor

  * @param  speed : current device speed

  * @param  length : pointer data length

  * @retval pointer to descriptor buffer

  */

static uint8_t  *USBD_CDC_GetOtherSpeedCfgDesc (uint16_t *length)

{

  *length = sizeof (USBD_CDC_OtherSpeedCfgDesc);

  return USBD_CDC_OtherSpeedCfgDesc;

}

 

/**

* @brief  DeviceQualifierDescriptor 

*         return Device Qualifier descriptor

* @param  length : pointer data length

* @retval pointer to descriptor buffer

*/

uint8_t  *USBD_CDC_GetDeviceQualifierDescriptor (uint16_t *length)

{

  *length = sizeof (USBD_CDC_DeviceQualifierDesc);

  return USBD_CDC_DeviceQualifierDesc;

}

 

/* USB CDC device Configuration Descriptor */

__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =

{

  /*Configuration Descriptor*/

  0x09,   /* bLength: Configuration Descriptor size */

  USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */

  USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */

  0x00,

  0x02,   /* bNumInterfaces: 2 interface */

  0x01,   /* bConfigurationValue: Configuration value */

  0x00,   /* iConfiguration: Index of string descriptor describing the configuration */

  0xC0,   /* bmAttributes: self powered */

  0x32,   /* MaxPower 0 mA */

  

  /*---------------------------------------------------------------------------*/

  

  /*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x00,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */

  

  /*Header Functional Descriptor*/

  0x05,   /* bLength: Endpoint Descriptor size */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x00,   /* bDescriptorSubtype: Header Func Desc */

  0x10,   /* bcdCDC: spec release number */

  0x01,

  

  /*Call Management Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  0x00,   /* bmCapabilities: D0+D1 */

  0x01,   /* bDataInterface: 1 */

  

  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x02,   /* bmCapabilities */

  

  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x00,   /* bMasterInterface: Communication class interface */

  0x01,   /* bSlaveInterface0: Data Class Interface */

  

  /*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),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/

  

  /*Data class interface descriptor*/

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x01,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */

  

  /*Endpoint OUT Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */

  

  /*Endpoint IN Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00                               /* bInterval: ignore for Bulk transfer */

} ;

这里只分析全速模式下的配置描述符,因为STM32F103C8T6只提供了全速USB模块,且另外三个描述符只在高速模式下才会使用,这里就不分析了,有精力的话可以看看。当获取配置描述符时其所关联的接口描述符、端点描述符都将返回,该函数很简单,但其返回的内容比较多,比较杂,还是放到后面分析吧。。。

先看下CDC类初始化和重新初始化函数,如下:



/**

  * @brief  USBD_CDC_Init

  *         Initialize the CDC interface

  * @param  pdev: device instance

  * @param  cfgidx: Configuration index

  * @retval status

  */

static uint8_t  USBD_CDC_Init (USBD_HandleTypeDef *pdev, 

                               uint8_t cfgidx)

{

  uint8_t ret = 0;

  USBD_CDC_HandleTypeDef   *hcdc;

  

  if(pdev->dev_speed == USBD_SPEED_HIGH  ) 

  {  

    /* Open EP IN */

    USBD_LL_OpenEP(pdev,

                   CDC_IN_EP,

                   USBD_EP_TYPE_BULK,

                   CDC_DATA_HS_IN_PACKET_SIZE);

    

    /* Open EP OUT */

    USBD_LL_OpenEP(pdev,

                   CDC_OUT_EP,

                   USBD_EP_TYPE_BULK,

                   CDC_DATA_HS_OUT_PACKET_SIZE);

    

  }

  else

  {

    /* Open EP IN */

    USBD_LL_OpenEP(pdev,

                   CDC_IN_EP,

                   USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);

    

    /* Open EP OUT */

    USBD_LL_OpenEP(pdev,

                   CDC_OUT_EP,

                   USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);

  }

  /* Open Command IN EP */

  USBD_LL_OpenEP(pdev,

                 CDC_CMD_EP,

                 USBD_EP_TYPE_INTR,

                 CDC_CMD_PACKET_SIZE);

  

    

  pdev->pClassData = USBD_malloc(sizeof (USBD_CDC_HandleTypeDef));

  

  if(pdev->pClassData == NULL)

  {

    ret = 1; 

  }

  else

  {

    hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

    

    /* Init  physical Interface components */

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();

    

    /* Init Xfer states */

    hcdc->TxState =0;

    hcdc->RxState =0;

       

    if(pdev->dev_speed == USBD_SPEED_HIGH  ) 

    {      

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             CDC_OUT_EP,

                             hcdc->RxBuffer,

                             CDC_DATA_HS_OUT_PACKET_SIZE);

    }

    else

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             CDC_OUT_EP,

                             hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);

    }

    

    

  }

  return ret;

}

 

/**

  * @brief  USBD_CDC_Init

  *         DeInitialize the CDC layer

  * @param  pdev: device instance

  * @param  cfgidx: Configuration index

  * @retval status

  */

static uint8_t  USBD_CDC_DeInit (USBD_HandleTypeDef *pdev, 

                                 uint8_t cfgidx)

{

  uint8_t ret = 0;

  

  /* Open EP IN */

  USBD_LL_CloseEP(pdev,

              CDC_IN_EP);

  

  /* Open EP OUT */

  USBD_LL_CloseEP(pdev,

              CDC_OUT_EP);

  

  /* Open Command IN EP */

  USBD_LL_CloseEP(pdev,

              CDC_CMD_EP);

  

  

  /* DeInit  physical Interface components */

  if(pdev->pClassData != NULL)

  {

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->DeInit();

    USBD_free(pdev->pClassData);

    pdev->pClassData = NULL;

  }

  

  return ret;

}

在初始化函数中首先是打开三个所用到的端点,两个传输数据的端点是批量端点,一个命令端点是中断端点,之后为CDC句柄分配空间并链接到USB句柄的pClassData指针上,并调用CDC类接口文件中的物理接口资源的初始化函数,发送和接口状态置为空闲,并开启数据接收。而在重新初始化函数中,关闭三个端点,调用CDC类接口文件的重新初始化函数,释放CDC句柄空间。接着查看Setup函数,如下:


/**

  * @brief  USBD_CDC_Setup

  *         Handle the CDC specific requests

  * @param  pdev: instance

  * @param  req: usb requests

  * @retval status

  */

static uint8_t  USBD_CDC_Setup (USBD_HandleTypeDef *pdev, 

                                USBD_SetupReqTypedef *req)

{

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  static uint8_t ifalt = 0;

    

  switch (req->bmRequest & USB_REQ_TYPE_MASK)

  {

  case USB_REQ_TYPE_CLASS :

    if (req->wLength)

    {

      if (req->bmRequest & 0x80)

      {

        ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,

                                                          (uint8_t *)hcdc->data,

                                                          req->wLength);

          USBD_CtlSendData (pdev, 

                            (uint8_t *)hcdc->data,

                            req->wLength);

      }

      else

      {

        hcdc->CmdOpCode = req->bRequest;

        hcdc->CmdLength = req->wLength;

        

        USBD_CtlPrepareRx (pdev, 

                           (uint8_t *)hcdc->data,

                           req->wLength);

      }

      

    }

    else

    {

      ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,

                                                        (uint8_t*)req,

                                                        0);

    }

    break;

 

  case USB_REQ_TYPE_STANDARD:

    switch (req->bRequest)

    {      

    case USB_REQ_GET_INTERFACE :

      USBD_CtlSendData (pdev,

                        &ifalt,

                        1);

      break;

      

    case USB_REQ_SET_INTERFACE :

      break;

    }

 

  default: 

    break;

  }

  return USBD_OK;

}

该函数管理CDC类的具体请求,这里对请求类型进行检测,遇到类请求,且wLength为0时,直接传递给Control()函数进行处理,而当wLength不为0时表明有数据阶段,bmRequest的最高位为1则是USB设备发送数据到USB主机上,因此执行完Control()后,通过控制传输把数据发往USB主机;而如果bmRequest的最高位为0则是USB设备需要接收USB主机发来的数据,因此需要调用USBD_CtlPrepareRx(),这里还记录了请求代码以及请求的数据长度信息在后面会使用到,而对于标准请求来说,这里只处理了GET_INTERFACE请求,返回0给USB主机,信息如下:

接着来看DataIn函数,如下:



/**

  * @brief  USBD_CDC_DataIn

  *         Data sent on non-control IN endpoint

  * @param  pdev: device instance

  * @param  epnum: endpoint number

  * @retval status

  */

static uint8_t  USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)

{

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  

  if(pdev->pClassData != NULL)

  {

    

    hcdc->TxState = 0;

 

    return USBD_OK;

  }

  else

  {

    return USBD_FAIL;

  }

}

该函数基本为空,因为在USB中断函数中最后调用该函数时表明需要发送的数据已经发送完毕了,因此这里只把发送状态置为空闲即可。DataOut函数跟DataIn函数正好相反,如下:


/**

  * @brief  USBD_CDC_DataOut

  *         Data received on non-control Out endpoint

  * @param  pdev: device instance

  * @param  epnum: endpoint number

  * @retval status

  */

static uint8_t  USBD_CDC_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)

{      

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  

  /* 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 */

  if(pdev->pClassData != NULL)

  {

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength);

 

    return USBD_OK;

  }

  else

  {

    return USBD_FAIL;

  }

}

该函数执行时表明已经收到了数据,使用USBD_LL_GetRxDataSize()获取接收到的数据大小,接着调用Receive()函数对数据进行处理即可。接着还剩下一个EP0_RxReady,如下:


/**

  * @brief  USBD_CDC_DataOut

  *         Data received on non-control Out endpoint

  * @param  pdev: device instance

  * @param  epnum: endpoint number

  * @retval status

  */

static uint8_t  USBD_CDC_EP0_RxReady (USBD_HandleTypeDef *pdev)

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;

  

  if((pdev->pUserData != NULL) && (hcdc->CmdOpCode != 0xFF))

  {

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(hcdc->CmdOpCode,

                                                      (uint8_t *)hcdc->data,

                                                      hcdc->CmdLength);

      hcdc->CmdOpCode = 0xFF; 

      

  }

  return USBD_OK;

}

该函数执行时表明已经收到了USB主机发往端点0的数据,这里指的是Setup阶段之后发送的数据,在本例中即为设置串口属性的LineCoding结构数据,这里就用到了之前存储的CmdOpCode以及CmdLength信息。完整地分析下来发现第三个端点没用到。。。是多余的了,现在我们来看哈配置描述符的数据,首先看一下配置描述符结构,如下:


这个截图有些不全,可以参看USB2.0协议的第9章节找到该结构信息,对应本例中各个字段的值如下:



/*Configuration Descriptor*/

  0x09,   /* bLength: Configuration Descriptor size */

  USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */

  USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */

  0x00,

  0x02,   /* bNumInterfaces: 2 interface */

  0x01,   /* bConfigurationValue: Configuration value */

  0x00,   /* iConfiguration: Index of string descriptor describing the configuration */

  0xC0,   /* bmAttributes: self powered */

  0x32,   /* MaxPower 0 mA */

其中USB_CDC_CONFIG_DESC_SIZ的值为67,说明该配置描述符返回的数据总长度为67个字节,这里指示接口数目为2,但之前在usbd_conf.h文件中有一个最大接口数目的宏定义,其值是为1的,二者有些冲突,bConfigurationValue指明了该配置的值为1,当USB主机发来SetConfiguration请求设置该配置时就需要传递参数值为bConfigurationValue的值即1,后面iConfiguration这里赋的值为0,感觉有些问题,该值的含义是在字符串描述符中有关配置的字符串描述符的索引,在usbd_desc文件中有关配置的字符串描述符的索引号是0x04,二者又冲突了,bmAttributes字段表明该设备支持自供电不支持远程唤醒,最后一项,该配置下设备从USB总线上最多可以获取100mA的电流,配置描述符完毕后,其后面应该紧接着第一个接口描述符,有关接口描述符的结构如下:


接口是一组端点的集合并提供给外部功能,本例中接口描述符的各个字段值如下:



 /*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x00,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */

对照USB2.0协议可知该接口不支持交替设置,即接口只有一个功能,该接口使用了一个端点(除默认端点0之外),接口类为0x02,接口子类为0x02,接口协议为0x01,描述该接口的字符串描述符索引为0,这又跟usbd_desc.c文件中定义的不同,在usbd_desc.c中描述接口的字符串描述符索引号为0x05,这里有三个字段的值的含义在USB2.0协议文档找不到对应:bInterfaceClass、bInterfaceSubClass、bInterfaceProtocol。这些字段的值需要在另外一个文档CDC1.20以及PSTN1.20协议文档中找到,查看CDC1.20协议文档可知,该文档为通信设备定义了三个类:

1. 通信设备类


2. 通信接口类


3. 数据接口类


每个类各有其不同的作用,如下

所有的通信设备都有一个使用通信类管理设备的接口,各个类代码如下:


在4.2章节可知通信类接口代码为0x02,Abstract Control Model子类代码为0x02,跟示例中代码一致。通信类接口的协议代码如下:


此处选择的是0x01--AT Commands,而通信设备类代码是可以用在我们的设备描述符中的bDeviceClass字段中,在本例中该字段是置0的,因此是没有使用通信设备类代码的,标准的接口描述符就分析完毕,而由于第三方类信息是作用于接口之上的,因此多出了一个功能描述符用于描述接口上的具体类信息,因此,我们可以看到示例中有很多功能描述符在接口描述符的后面,功能描述符都以一个通用的信头描述符作为起始,这样可以方便USB主机解析具体类信息,一个功能描述符的通用格式如下:



有关bDescriptorType、bDescriptorSubType的取值,如下:



bDescriptorType取值有两种:CS_INTERFACE、CSENDPOINT。bDescriptorSubType的取值很多,这些描述符主要用于通信接口类中,首先看下Header Functional Descriptor结构,如下:


在本例中信头功能描述符的代码如下:



/*Header Functional Descriptor*/

  0x05,   /* bLength: Endpoint Descriptor size */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x00,   /* bDescriptorSubtype: Header Func Desc */

  0x10,   /* bcdCDC: spec release number */

  0x01,

信头功能描述符很简单,包含的内容不多,因为其只是帮助USB主机解析后面的功能描述符。功能描述符的子类有很多但不是全部都需要,下面说下联合功能描述符(Union Functional Descriptor),该功能描述符是由CDC1.20协议规范定义的,协议中只规定了信头功能描述符一定是最前面的,但没有规定剩下的功能描述符的顺序,这里先把CDC1.20协议规范定义的功能描述符分析完毕,再分析PSTN1.20协议规范定义的其他功能描述符,联合功能描述符描述了可以组成一个功能单元的一组接口之间的关系,该组接口中的一个被设计成一个主机(master)或控制(controlling)接口,可以用于接收某些具体类信息或发送一些通知,其结构如下:


本例中联合功能描述符代码如下:



 /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x00,   /* bMasterInterface: Communication class interface */

  0x01,   /* bSlaveInterface0: Data Class Interface */

可知,主接口号为0(控制接口),从接口号为1(数据接口)。在CDC1.20协议规范中有一个通信类接口描述符的示例,供我们参考,下面分析PSTN1.20协议规范描述的新的功能描述符:呼叫管理功能描述符(Call Management Functional Descriptor)、抽象控制管理功能描述符(Abstract Control Management Functional Descriptor)。首先看下呼叫管理功能描述符结构:

本例中呼叫管理功能描述符代码如下:



/*Call Management Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  0x00,   /* bmCapabilities: D0+D1 */

  0x01,   /* bDataInterface: 1 */

可知设备本身不处理呼叫管理,并指明数据类接口号为1,抽象控制管理功能描述符结构如下:

本例中抽象控制管理功能描述符代码如下:



 /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x02,   /* bmCapabilities */

可知设备只支持了Set_Line_Coding、Set_Control_Line_State、Get_Line_Coding请求以及Serial_State通知。至此第一个接口描述符分析完毕,跟在接口描述符后面的是其关联的端点描述符,结构如下:




内容有些长,本例中端点描述符代码如下:



 /*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),

  0x10,                           /* bInterval: */ 

可知端点是IN端点,地址为0x02,该端点是作为控制接口中的端点,但在本例实际中是没有用到的。该端点为中断端点,最大包大小为8个字节,轮询该端点的数据传输间隔是16。后面为数据接口以及跟其相关的端点描述符,数据接口中是没有功能描述符的,在此就不分析该接口了。至此,VCP例程代码分析完毕,不得不说USB通信博大精深,要想深入理解还需时日,这个只能作为入门例程帮助我们理解USB通信,同时USB器件库提供了扩展模板以便开发者开发新的设备类,想要学好USB通信,首先需要知道跟其相关联的协议规范,并知晓各个描述符的,难点就在于如何把协议规范中的理论转换成实际可操作的代码,一起努力吧!

关键字:STM32  USB 引用地址:STM32 USB学习笔记9

上一篇:STM32 UVC学习笔记1
下一篇:STM32 USB学习笔记8

推荐阅读最新更新时间:2024-03-16 16:25

USB3.0的物理层发送端测试方案介绍
  USB简介   USB(Universal Serial Bus)即通用串行总线,用于把键盘、鼠标、打印机、扫描仪、数码相机、MP3、U盘等外围设备连接到计算机,它使计算机与周边设备的接口标准化,从 2000年以后,支持USB2.0版本的计算机和设备已被广泛使用,USB2.0包括了三种速率:高速480Mbps、全速12Mbps、低速 1.5Mbps。目前除了键盘和鼠标为低速设备外,大多数设备都是速率达480M的高速设备。   尽管USB2.0的速度已经相当快,对于目前高清视频和动辄GByte的数据传输还是有些慢,在2008年11月,HP、Intel、微软、NEC、ST-NXP、TI联合起来正式发布了 USB3.0的V1.0
[测试测量]
<font color='red'>USB</font>3.0的物理层发送端测试方案介绍
STM32-串口超时判断方式接收未知长度数据
usart.c串口中断处理函数: view plain copy void USART1_IRQHandler(void) { u8 res; if(USART1- SR&(1 5))//接收到数据 { res=USART1- DR; if(USART1_Recv_Len USART1_MAX_RECV_LEN) //还可以接收数据 { TIM3- CNT=0; //计数器清空 if(Rec_Over_Flag==0)TIM3_Set(1); //使能定
[单片机]
STM32的USART中RTS、CTS的作用和意义
Ⅰ、写在前面 我们都知道USART中RX和TX这两个引脚的功能,这两个引脚是USART串行通信最常见和必不可少的两个引脚。但我们在手册中会发现关于USART的其他引脚:USART_CK、USART_RTS、USART_CTS,如下图: 但我们大部分都没怎么使用过USART_RTS和USART_CTS这两个引脚。下面将给大家简单讲述一下关于USART串口拓展的知识。 Ⅱ、关于DB9串口接头 我们都听说过RS232,说232就知道DB9这个串口接头。 DB9个引脚的功能: 1 CD ← Carrier Detect 载波检测 2 RXD ← Receive Data 接收数据 3 TXD → Transmit Da
[单片机]
<font color='red'>STM32</font>的USART中RTS、CTS的作用和意义
STM32的SPI外设片选只有一个,怎么破?
之前用STM32的SPI需要控制很多外部芯片,可是一个SPI的外设只有一个片选,要实现独立片选一主多从,怎么实现呢? SPI总线拓扑 一般地,SPI总线按照下图方式进行连接,一主多从。 如上图: 每个从设备都有独立的片选引脚,主机同一时间段内,与一个从设备进行通信,也即选中一个从设备。 MOSI/MISO/SCLK并联在一起 MISO须是三态门,当从设备未选中时,该脚须设置为高阻态,而不能是输出态,否则会影响总线 ! 对于MOSI/SCLK,虽然并联在一起,但是由于仅一个输出,多输入。 但是你看STM32的SPI外设,一个SPI仅有一个NSS信号,以STM32F407的SPI2为例: 那么要实现前面说的一主多从
[单片机]
STM32使用LWIP库新建tcp_sever
main函数 区域1是lwip的初始化 void LwIP_Init(void) { struct ip_addr ipaddr; struct ip_addr netmask; struct ip_addr gw; mem_init();//内存堆初始化 memp_init();//内存池初始化 IP4_ADDR(&ipaddr, 192, 168, 16, 211); IP4_ADDR(&netmask, 255, 255 , 255, 0); IP4_ADDR(&gw, 192, 168, 16, 1); netif_add(&netif, &ipaddr, &netmask,
[单片机]
一种STM32的串口控制台的实现
一.背景 曾经玩Linux时非常喜欢这种基于出串口的控制台, 通过简单的串口TX和RX能实现嵌入式硬件的人机交互,非常实用, 那么STM32能否实现通过超级终端与用户互动的构想呢? 答案是肯定的,由于这个UART控制平台就像应用程序套上一层可访问的外科(Shell)故而我将这种基于UART的控制平台简称Shell,构架和效果如下图: 这张图箭头指向的是输入的指令,其余是STM32串口输出的信息,, 可以看到通过这些简单的指令输入我们通过Shell可以做很多事情: 1. 现场设备发生故障,可以通过Shell可以查看设备的故障状态统计信息 2. 能实现串口程序升级(需要Shell+IAP驱动程序支持) 3.
[单片机]
一种<font color='red'>STM32</font>的串口控制台的实现
STM32 GPIO 简单操作函数
STM32库函数太多,而且不能识别大小写,经常记错,GPIO简单记忆。 ---第一步--模式配置 void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //对应GPIO所在的总线时钟必须打开 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //哪个GPIO口 GPIO_InitStructu
[单片机]
使用VSCode搭建STM32开发环境
首先附上一张VS Code图一直都喜欢这种,黑色主题感觉高大上。 一、需要的软件和工具。 下载最新版VS Code: 安装好插件,具有良好的代码补全与调试功能。 “VS Code下载地址:https://code.visualstudio.com/” 下载 LLVM:用于代码补全,其实可以理解为 Clang。因为VS Code 中“C/C++”插件的自动补全功能不太好用。STM32中好多库函数都补全不出来。记得按照好之后,将路径添加到环境变量里。 “LLVM下载地址:http://releases.llvm.org/download.html” 下载安装 Git for Windows: 提供Git支持和MINGW64指令终端
[单片机]
使用VSCode搭建<font color='red'>STM32</font>开发环境
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved