单片机
返回首页

STM32 USB学习笔记3

2019-03-11 来源:eefocus

主机环境: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    USB_Device/CDC_Standalone/Inc/usbd_desc.h

  * @author  MCD Application Team

  * @version V1.2.0

  * @date    31-July-2015

  * @brief   Header for usbd_desc.c module

  ******************************************************************************

  * @attention

  *

  *

© COPYRIGHT(c) 2015 STMicroelectronics

  *

  * 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文件分析完毕。主要为各个描述符的获取。


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • PIC单片机控制的遥控防盗报警器电路

  • 短波AM发射器电路设计图

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 如何构建一个触摸传感器电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章