主机环境:Windows 7 SP1
开发环境:MDK5.14
目标板:STM32F103C8T6
开发库:STM32F1Cube库和STM32_USB_Device_Library
现在开始分析VCP示例代码,从最简单的usbd_desc开始。USB设备使用描述符来报告其功能特性,描述符是一个已知格式的数据结构,USB规范中定义了以下几种描述符:Device(设备)、Device_Qualifier(设备限定)、Configuration(配置)、Other_Speed_Configuration(其他速度配置)、Interface(接口)、Endpoint(端点)、String(字符串)。usbd_desc文件主要提供USB字符串描述符,字符串描述符对于设备来说是可选的,是对其他描述符的文字说明,且字符串描述符使用UNICODE编码,同时字符串描述符支持多国语言,当请求字符串描述符时需要使用16禁止的LANGID来指定所期望的语言。使用字符串索引0来获取设备所支持的语言。字符串描述的数据结构如下所示:
第一个字节是该数据结构的字节长度,第二个字节是字符串描述符类型,后面的字节是字符串内容。有关字符串描述符类型值的定义在USB2.0规范的9.4章节的表9-5中,如下:
可以看到字符串描述符类型的值为常量3,与之相符的在usbd_def.h文件中95行有如下定义:
#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_desc.h头文件,内容很简单
__/**__
_******************************************************************************_
_* [@file](https://my.oschina.net/u/726396) USB_Device/CDC_Standalone/Inc/usbd_desc.h_
_* [@author](https://my.oschina.net/arthor) MCD Application Team_
_* [@version](https://my.oschina.net/u/931210) V1.2.0_
_* [@date](https://my.oschina.net/u/2504391) 31-July-2015_
_* [@brief](https://my.oschina.net/brief) Header for usbd_desc.c module_
_******************************************************************************_
_* @attention_
_*_
_* <h2><center>© COPYRIGHT(c) 2015 STMicroelectronics</center></h2>_
_*_
_* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");_
_* You may not use this file except in compliance with the License._
_* You may obtain a copy of the License at:_
_*_
_* http://www.st.com/software_license_agreement_liberty_v2_
_*_
_* Unless required by applicable law or agreed to in writing, software_
_* distributed under the License is distributed on an "AS IS" BASIS,_
_* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied._
_* See the License for the specific language governing permissions and_
_* limitations under the License._
_*_
_******************************************************************************_
_*/_
_/* Define to prevent recursive inclusion -------------------------------------*/_
#ifndef __USBD_DESC_H
#define __USBD_DESC_H
_/* Includes ------------------------------------------------------------------*/_
#include "usbd_def.h"
_/* Exported types ------------------------------------------------------------*/_
_/* Exported constants --------------------------------------------------------*/_
#define DEVICE_ID1 (0x1FFFF7E8)
#define DEVICE_ID2 (0x1FFFF7EC)
#define DEVICE_ID3 (0x1FFFF7F0)
#define USB_SIZ_STRING_SERIAL 0x1A
_/* Exported macro ------------------------------------------------------------*/_
_/* Exported functions ------------------------------------------------------- */_
extern USBD_DescriptorsTypeDef VCP_Desc;
#endif _/* __USBD_DESC_H */_
_/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/_
这里有三个宏定义DEVICE_ID1、DEVICE_ID2、DEVICE_ID3,这三个值是STM32芯片产品唯一身份标识,可以用作USB字符序列号(96位),该寄存器是只读的。详情参考STM32参考手册的设备电子签名章节。此外,该文件还引用了一个数据结构USBD_DescriptorsTypeDef,该结构是一个函数指针集合。
_/* USB Device descriptors structure */_
typedef struct
{
uint8_t *(*GetDeviceDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetLangIDStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetManufacturerStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetProductStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetSerialStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetConfigurationStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
uint8_t *(*GetInterfaceStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
#if (USBD_LPM_ENABLED == 1)
uint8_t *(*GetBOSDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);
#endif
} USBD_DescriptorsTypeDef;
用于获取各种描述符,主要是获取字符串描述符,与之对应的下标在usbd_def.h的66行
#define USBD_IDX_LANGID_STR 0x00
#define USBD_IDX_MFC_STR 0x01
#define USBD_IDX_PRODUCT_STR 0x02
#define USBD_IDX_SERIAL_STR 0x03
#define USBD_IDX_CONFIG_STR 0x04
#define USBD_IDX_INTERFACE_STR 0x05
注意USBD_DescripotrsTypeDef结构中的GetDeviceDescriptor不是获取字符串描述符,同时前面提到使用索引号0来获取设备所支持的语言,因此这里定义USBD_IDX_LANGID_STR为0,至于其他索引号值是否有标准定义,暂时为找到出处。该结构实体定义在usbd_desc.c文件中如下
USBD_DescriptorsTypeDef VCP_Desc = {
USBD_VCP_DeviceDescriptor,
USBD_VCP_LangIDStrDescriptor,
USBD_VCP_ManufacturerStrDescriptor,
USBD_VCP_ProductStrDescriptor,
USBD_VCP_SerialStrDescriptor,
USBD_VCP_ConfigStrDescriptor,
USBD_VCP_InterfaceStrDescriptor,
};
稍后来看各个函数,在usbd_desc.c文件的前面有如下定义
#define USBD_VID 0x0483
#define USBD_PID 0x5740
#define USBD_LANGID_STRING 0x409
#define USBD_MANUFACTURER_STRING "STMicroelectronics"
#define USBD_PRODUCT_FS_STRING "STM32 Virtual ComPort in FS Mode"
#define USBD_CONFIGURATION_FS_STRING "VCP Config"
#define USBD_INTERFACE_FS_STRING "VCP Interface"
USBD_VID和USBD_PID分别是厂商ID、产品ID,这两个ID需要向USB组织申请,不是免费的,当前该ID列表可以在以下网址查看 http://www.linux-usb.org/usb.ids,通过查看可知0x0483是STMicroelectronics申请的,0x5740对应的产品是STM32F407,ST完整列表如下:
语言ID0x409指的是English(United States),该值可以在USB_LANGIDs.pdf文档中找到,如下:
现在来分析获取描述符函数,首先从获取语言ID开始,将代码整合一下,方便查看,如下:
#define USB_LEN_LANGID_STR_DESC 0x04
_/* USB Standard Device Descriptor */_
const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC]=
{
USB_LEN_LANGID_STR_DESC,
USB_DESC_TYPE_STRING,
LOBYTE(USBD_LANGID_STRING),
HIBYTE(USBD_LANGID_STRING),
};
__/**__
_* @brief Returns the LangID string descriptor._
_* @param speed: Current device speed_
_* @param length: Pointer to data length variable_
_* @retval Pointer to descriptor buffer_
_*/_
uint8_t *USBD_VCP_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
*length = sizeof(USBD_LangIDDesc);
return (uint8_t*)USBD_LangIDDesc;
}
USBD_VCP_LangIDStrDescriptor函数最终是要返回一个语言ID数组结构,且以UNICODE编码,如下:
由于这里只支持英文,因此这里bLength为4,bDescriptorType为3,LANGID为0x409。产品、厂商、配置、接口字符串描述符所对应的函数是同一种结构,说明一个即可
__/**__
_* @brief Returns the manufacturer string descriptor._
_* @param speed: Current device speed_
_* @param length: Pointer to data length variable_
_* @retval Pointer to descriptor buffer_
_*/_
uint8_t *USBD_VCP_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
USBD_GetString((uint8_t *)USBD_MANUFACTURER_STRING, USBD_StrDesc, length);
return USBD_StrDesc;
}
这里是将ASCII编码的字符串转成UNICODE编码的字符串,同时UNICODE编码的字符串不是以NULL作为结束。USBD_GetString()方法定义在usbd_ctlreq.c文件中,如下:
__/**__
_* @brief USBD_GetString_
_* Convert Ascii string into unicode one_
_* @param desc : descriptor buffer_
_* @param unicode : Formatted string buffer (unicode)_
_* @param len : descriptor length_
_* @retval None_
_*/_
void USBD_GetString(uint8_t *desc, uint8_t *unicode, uint16_t *len)
{
uint8_t idx = 0;
if (desc != NULL)
{
*len = USBD_GetLen(desc) * 2 + 2;
unicode[idx++] = *len;
unicode[idx++] = USB_DESC_TYPE_STRING;
while (*desc != '\0')
{
unicode[idx++] = *desc++;
unicode[idx++] = 0x00;
}
}
}
__/**__
_* @brief USBD_GetLen_
_* return the string length_
_* @param buf : pointer to the ascii string buffer_
_* @retval string length_
_*/_
static uint8_t USBD_GetLen(uint8_t *buf)
{
uint8_t len = 0;
while (*buf != '\0')
{
len++;
buf++;
}
return len;
}
接着分析获取序列号字符串描述符方法,整理如下:
__/**__
_* @brief Returns the serial number string descriptor._
_* @param speed: Current device speed_
_* @param length: Pointer to data length variable_
_* @retval Pointer to descriptor buffer_
_*/_
uint8_t *USBD_VCP_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
*length = USB_SIZ_STRING_SERIAL;
_/* Update the serial number string descriptor with the data from the unique ID*/_
Get_SerialNum();
return USBD_StringSerial;
}
__/**__
_* @brief Create the serial number string descriptor_
_* @param None_
_* @retval None_
_*/_
static void Get_SerialNum(void)
{
uint32_t deviceserial0, deviceserial1, deviceserial2;
deviceserial0 = *(uint32_t*)DEVICE_ID1;
deviceserial1 = *(uint32_t*)DEVICE_ID2;
deviceserial2 = *(uint32_t*)DEVICE_ID3;
deviceserial0 += deviceserial2;
if (deviceserial0 != 0)
{
IntToUnicode (deviceserial0, &USBD_StringSerial[2] ,8);
IntToUnicode (deviceserial1, &USBD_StringSerial[18] ,4);
}
}
__/**__
_* @brief Convert Hex 32Bits value into char_
_* @param value: value to convert_
_* @param pbuf: pointer to the buffer_
_* @param len: buffer length_
_* @retval None_
_*/_
static void IntToUnicode (uint32_t value , uint8_t *pbuf , uint8_t len)
{
uint8_t idx = 0;
for( idx = 0 ; idx < len ; idx ++)
{
if( ((value >> 28)) < 0xA )
{
pbuf[2* idx] = (value >> 28) + '0';
}
else
{
pbuf[2* idx] = (value >> 28) + 'A' - 10;
}
value = value << 4;
pbuf[2* idx + 1] = 0;
}
}
这里取序列号只取了48位,取了deviceserial0的32位和deviceserial1的高16位,注意IntToUnicode()方法即可,以16进制进行转换,从高位开始,序列号字符串描述符字节长度为0x1A=12*2(序列号内容)+2(前面两个字节)。至此,字符串描述符的获取分析完毕,还剩下一个设备描述符的获取,如下:
_/* USB Standard Device Descriptor */_
const uint8_t hUSBDDeviceDesc[USB_LEN_DEV_DESC]= {
0x12, _/* bLength */_
USB_DESC_TYPE_DEVICE, _/* bDescriptorType */_
0x00, _/* bcdUSB */_
0x02, _/* USB2.0 spec*/_
0x00, _/* bDeviceClass */_
0x00, _/* bDeviceSubClass */_
0x00, _/* bDeviceProtocol */_
USB_MAX_EP0_SIZE, _/* bMaxPacketSize */_
LOBYTE(USBD_VID), _/* idVendor */_
HIBYTE(USBD_VID), _/* idVendor */_
LOBYTE(USBD_PID), _/* idVendor */_
HIBYTE(USBD_PID), _/* idVendor */_
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 */_
}; _/* USB_DeviceDescriptor */_
__/**__
_* @brief Returns the device descriptor._
_* @param speed: Current device speed_
_* @param length: Pointer to data length variable_
_* @retval Pointer to descriptor buffer_
_*/_
uint8_t *USBD_VCP_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
*length = sizeof(hUSBDDeviceDesc);
return (uint8_t*)hUSBDDeviceDesc;
}
设备描述符的获取主要看hUSBDDeviceDesc数组结构,该数组长度为18个字节,与之对应的数据结构在USB2.0规范的9.6章节有说明,一个设备只有一个设备描述符,设备描述符数据结构如下:
根据此结构来分析hUSBDDeviceDesc内容的含义,符合USB2.0规范,bDeviceClass置0,表明一个配置里每个接口指定其各自的类信息同时不同的接口独立地操作。由于bDeviceClass字段置0,因此bDeviceSubClass字段同样置0。bDeviceProtocol置0,设备不使用特定类协议。端点0最大包大小为64字节,接着填充厂商ID和产品ID,bcdDevice标记设备稳定版本,这里为2.0版本,当然可以修改该值。iManufacturer、iProduct、iSerialNumber分别是在字符串描述符中各自的索引号,前面有提到。最后标记配置数为1。USB设备可以有一个或多个配置,每个配置有一个或多个接口,每个接口有一个或多个端点。至此,usbd_desc文件分析完毕。主要为各个描述符的获取。