主机环境: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 UVC学习笔记1
下一篇:STM32 USB学习笔记8
推荐阅读最新更新时间:2024-03-16 16:25