STM32 USB学习笔记3

Wesley13
• 阅读 423

主机环境: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来获取设备所支持的语言。字符串描述的数据结构如下所示:

STM32 USB学习笔记3

第一个字节是该数据结构的字节长度,第二个字节是字符串描述符类型,后面的字节是字符串内容。有关字符串描述符类型值的定义在USB2.0规范的9.4章节的表9-5中,如下:

STM32 USB学习笔记3

可以看到字符串描述符类型的值为常量3,与之相符的在usbd_def.h文件中95行有如下定义:

  1. #define USB_DESC_TYPE_DEVICE 1

  2. #define USB_DESC_TYPE_CONFIGURATION 2

  3. #define USB_DESC_TYPE_STRING 3

  4. #define USB_DESC_TYPE_INTERFACE 4

  5. #define USB_DESC_TYPE_ENDPOINT 5

  6. #define USB_DESC_TYPE_DEVICE_QUALIFIER 6

  7. #define USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION 7

  8. #define USB_DESC_TYPE_BOS 0x0F

查看usbd_desc.h头文件,内容很简单

  1. __/**__

  2. _******************************************************************************_

  3. _* [@file](https://my.oschina.net/u/726396) USB_Device/CDC_Standalone/Inc/usbd_desc.h_

  4. _* [@author](https://my.oschina.net/arthor) MCD Application Team_

  5. _* [@version](https://my.oschina.net/u/931210) V1.2.0_

  6. _* [@date](https://my.oschina.net/u/2504391) 31-July-2015_

  7. _* [@brief](https://my.oschina.net/brief) Header for usbd_desc.c module_

  8. _******************************************************************************_

  9. _* @attention_

  10. _*_

  11. _* <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>_

  12. _*_

  13. _* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");_

  14. _* You may not use this file except in compliance with the License._

  15. _* You may obtain a copy of the License at:_

  16. _*_

  17. _* http://www.st.com/software_license_agreement_liberty_v2_

  18. _*_

  19. _* Unless required by applicable law or agreed to in writing, software_

  20. _* distributed under the License is distributed on an "AS IS" BASIS,_

  21. _* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied._

  22. _* See the License for the specific language governing permissions and_

  23. _* limitations under the License._

  24. _*_

  25. _******************************************************************************_

  26. _*/_

  27. _/* Define to prevent recursive inclusion -------------------------------------*/_

  28. #ifndef __USBD_DESC_H

  29. #define __USBD_DESC_H

  30. _/* Includes ------------------------------------------------------------------*/_

  31. #include "usbd_def.h"

  32. _/* Exported types ------------------------------------------------------------*/_

  33. _/* Exported constants --------------------------------------------------------*/_

  34. #define DEVICE_ID1 (0x1FFFF7E8)

  35. #define DEVICE_ID2 (0x1FFFF7EC)

  36. #define DEVICE_ID3 (0x1FFFF7F0)

  37. #define USB_SIZ_STRING_SERIAL 0x1A

  38. _/* Exported macro ------------------------------------------------------------*/_

  39. _/* Exported functions ------------------------------------------------------- */_

  40. extern USBD_DescriptorsTypeDef VCP_Desc;

  41. #endif _/* __USBD_DESC_H */_

  42. _/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/_

这里有三个宏定义DEVICE_ID1、DEVICE_ID2、DEVICE_ID3,这三个值是STM32芯片产品唯一身份标识,可以用作USB字符序列号(96位),该寄存器是只读的。详情参考STM32参考手册的设备电子签名章节。此外,该文件还引用了一个数据结构USBD_DescriptorsTypeDef,该结构是一个函数指针集合。

  1. _/* USB Device descriptors structure */_

  2. typedef struct

  3. {

  4. uint8_t *(*GetDeviceDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  5. uint8_t *(*GetLangIDStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  6. uint8_t *(*GetManufacturerStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  7. uint8_t *(*GetProductStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  8. uint8_t *(*GetSerialStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  9. uint8_t *(*GetConfigurationStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  10. uint8_t *(*GetInterfaceStrDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  11. #if (USBD_LPM_ENABLED == 1)

  12. uint8_t *(*GetBOSDescriptor)( USBD_SpeedTypeDef speed , uint16_t *length);

  13. #endif

  14. } USBD_DescriptorsTypeDef;

用于获取各种描述符,主要是获取字符串描述符,与之对应的下标在usbd_def.h的66行

  1. #define USBD_IDX_LANGID_STR 0x00

  2. #define USBD_IDX_MFC_STR 0x01

  3. #define USBD_IDX_PRODUCT_STR 0x02

  4. #define USBD_IDX_SERIAL_STR 0x03

  5. #define USBD_IDX_CONFIG_STR 0x04

  6. #define USBD_IDX_INTERFACE_STR 0x05

注意USBD_DescripotrsTypeDef结构中的GetDeviceDescriptor不是获取字符串描述符,同时前面提到使用索引号0来获取设备所支持的语言,因此这里定义USBD_IDX_LANGID_STR为0,至于其他索引号值是否有标准定义,暂时为找到出处。该结构实体定义在usbd_desc.c文件中如下

  1. USBD_DescriptorsTypeDef VCP_Desc = {

  2. USBD_VCP_DeviceDescriptor,

  3. USBD_VCP_LangIDStrDescriptor,

  4. USBD_VCP_ManufacturerStrDescriptor,

  5. USBD_VCP_ProductStrDescriptor,

  6. USBD_VCP_SerialStrDescriptor,

  7. USBD_VCP_ConfigStrDescriptor,

  8. USBD_VCP_InterfaceStrDescriptor,

  9. };

稍后来看各个函数,在usbd_desc.c文件的前面有如下定义

  1. #define USBD_VID 0x0483

  2. #define USBD_PID 0x5740

  3. #define USBD_LANGID_STRING 0x409

  4. #define USBD_MANUFACTURER_STRING "STMicroelectronics"

  5. #define USBD_PRODUCT_FS_STRING "STM32 Virtual ComPort in FS Mode"

  6. #define USBD_CONFIGURATION_FS_STRING "VCP Config"

  7. #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完整列表如下:

STM32 USB学习笔记3

语言ID0x409指的是English(United States),该值可以在USB_LANGIDs.pdf文档中找到,如下:

STM32 USB学习笔记3

现在来分析获取描述符函数,首先从获取语言ID开始,将代码整合一下,方便查看,如下:

  1. #define USB_LEN_LANGID_STR_DESC 0x04

  2. _/* USB Standard Device Descriptor */_

  3. const uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC]=

  4. {

  5. USB_LEN_LANGID_STR_DESC,

  6. USB_DESC_TYPE_STRING,

  7. LOBYTE(USBD_LANGID_STRING),

  8. HIBYTE(USBD_LANGID_STRING),

  9. };

  10. __/**__

  11. _* @brief Returns the LangID string descriptor._

  12. _* @param speed: Current device speed_

  13. _* @param length: Pointer to data length variable_

  14. _* @retval Pointer to descriptor buffer_

  15. _*/_

  16. uint8_t *USBD_VCP_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)

  17. {

  18. *length = sizeof(USBD_LangIDDesc);

  19. return (uint8_t*)USBD_LangIDDesc;

  20. }

USBD_VCP_LangIDStrDescriptor函数最终是要返回一个语言ID数组结构,且以UNICODE编码,如下:

STM32 USB学习笔记3

由于这里只支持英文,因此这里bLength为4,bDescriptorType为3,LANGID为0x409。产品、厂商、配置、接口字符串描述符所对应的函数是同一种结构,说明一个即可

  1. __/**__

  2. _* @brief Returns the manufacturer string descriptor._

  3. _* @param speed: Current device speed_

  4. _* @param length: Pointer to data length variable_

  5. _* @retval Pointer to descriptor buffer_

  6. _*/_

  7. uint8_t *USBD_VCP_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)

  8. {

  9. USBD_GetString((uint8_t *)USBD_MANUFACTURER_STRING, USBD_StrDesc, length);

  10. return USBD_StrDesc;

  11. }

这里是将ASCII编码的字符串转成UNICODE编码的字符串,同时UNICODE编码的字符串不是以NULL作为结束。USBD_GetString()方法定义在usbd_ctlreq.c文件中,如下:

  1. __/**__

  2. _* @brief USBD_GetString_

  3. _* Convert Ascii string into unicode one_

  4. _* @param desc : descriptor buffer_

  5. _* @param unicode : Formatted string buffer (unicode)_

  6. _* @param len : descriptor length_

  7. _* @retval None_

  8. _*/_

  9. void USBD_GetString(uint8_t *desc, uint8_t *unicode, uint16_t *len)

  10. {

  11. uint8_t idx = 0;

  12. if (desc != NULL)

  13. {

  14. *len = USBD_GetLen(desc) * 2 + 2;

  15. unicode[idx++] = *len;

  16. unicode[idx++] = USB_DESC_TYPE_STRING;

  17. while (*desc != '\0')

  18. {

  19. unicode[idx++] = *desc++;

  20. unicode[idx++] = 0x00;

  21. }

  22. }

  23. }

  24. __/**__

  25. _* @brief USBD_GetLen_

  26. _* return the string length_

  27. _* @param buf : pointer to the ascii string buffer_

  28. _* @retval string length_

  29. _*/_

  30. static uint8_t USBD_GetLen(uint8_t *buf)

  31. {

  32. uint8_t len = 0;

  33. while (*buf != '\0')

  34. {

  35. len++;

  36. buf++;

  37. }

  38. return len;

  39. }

接着分析获取序列号字符串描述符方法,整理如下:

  1. __/**__

  2. _* @brief Returns the serial number string descriptor._

  3. _* @param speed: Current device speed_

  4. _* @param length: Pointer to data length variable_

  5. _* @retval Pointer to descriptor buffer_

  6. _*/_

  7. uint8_t *USBD_VCP_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)

  8. {

  9. *length = USB_SIZ_STRING_SERIAL;

  10. _/* Update the serial number string descriptor with the data from the unique ID*/_

  11. Get_SerialNum();

  12. return USBD_StringSerial;

  13. }

  14. __/**__

  15. _* @brief Create the serial number string descriptor_

  16. _* @param None_

  17. _* @retval None_

  18. _*/_

  19. static void Get_SerialNum(void)

  20. {

  21. uint32_t deviceserial0, deviceserial1, deviceserial2;

  22. deviceserial0 = *(uint32_t*)DEVICE_ID1;

  23. deviceserial1 = *(uint32_t*)DEVICE_ID2;

  24. deviceserial2 = *(uint32_t*)DEVICE_ID3;

  25. deviceserial0 += deviceserial2;

  26. if (deviceserial0 != 0)

  27. {

  28. IntToUnicode (deviceserial0, &USBD_StringSerial[2] ,8);

  29. IntToUnicode (deviceserial1, &USBD_StringSerial[18] ,4);

  30. }

  31. }

  32. __/**__

  33. _* @brief Convert Hex 32Bits value into char_

  34. _* @param value: value to convert_

  35. _* @param pbuf: pointer to the buffer_

  36. _* @param len: buffer length_

  37. _* @retval None_

  38. _*/_

  39. static void IntToUnicode (uint32_t value , uint8_t *pbuf , uint8_t len)

  40. {

  41. uint8_t idx = 0;

  42. for( idx = 0 ; idx < len ; idx ++)

  43. {

  44. if( ((value >> 28)) < 0xA )

  45. {

  46. pbuf[2* idx] = (value >> 28) + '0';

  47. }

  48. else

  49. {

  50. pbuf[2* idx] = (value >> 28) + 'A' - 10;

  51. }

  52. value = value << 4;

  53. pbuf[2* idx + 1] = 0;

  54. }

  55. }

这里取序列号只取了48位,取了deviceserial0的32位和deviceserial1的高16位,注意IntToUnicode()方法即可,以16进制进行转换,从高位开始,序列号字符串描述符字节长度为0x1A=12*2(序列号内容)+2(前面两个字节)。至此,字符串描述符的获取分析完毕,还剩下一个设备描述符的获取,如下:

  1. _/* USB Standard Device Descriptor */_

  2. const uint8_t hUSBDDeviceDesc[USB_LEN_DEV_DESC]= {

  3. 0x12, _/* bLength */_

  4. USB_DESC_TYPE_DEVICE, _/* bDescriptorType */_

  5. 0x00, _/* bcdUSB */_

  6. 0x02, _/* USB2.0 spec*/_

  7. 0x00, _/* bDeviceClass */_

  8. 0x00, _/* bDeviceSubClass */_

  9. 0x00, _/* bDeviceProtocol */_

  10. USB_MAX_EP0_SIZE, _/* bMaxPacketSize */_

  11. LOBYTE(USBD_VID), _/* idVendor */_

  12. HIBYTE(USBD_VID), _/* idVendor */_

  13. LOBYTE(USBD_PID), _/* idVendor */_

  14. HIBYTE(USBD_PID), _/* idVendor */_

  15. 0x00, _/* bcdDevice rel. 2.00 */_

  16. 0x02,

  17. USBD_IDX_MFC_STR, _/* Index of manufacturer string */_

  18. USBD_IDX_PRODUCT_STR, _/* Index of product string */_

  19. USBD_IDX_SERIAL_STR, _/* Index of serial number string */_

  20. USBD_MAX_NUM_CONFIGURATION _/* bNumConfigurations */_

  21. }; _/* USB_DeviceDescriptor */_

  22. __/**__

  23. _* @brief Returns the device descriptor._

  24. _* @param speed: Current device speed_

  25. _* @param length: Pointer to data length variable_

  26. _* @retval Pointer to descriptor buffer_

  27. _*/_

  28. uint8_t *USBD_VCP_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)

  29. {

  30. *length = sizeof(hUSBDDeviceDesc);

  31. return (uint8_t*)hUSBDDeviceDesc;

  32. }

设备描述符的获取主要看hUSBDDeviceDesc数组结构,该数组长度为18个字节,与之对应的数据结构在USB2.0规范的9.6章节有说明,一个设备只有一个设备描述符,设备描述符数据结构如下:

STM32 USB学习笔记3

STM32 USB学习笔记3

根据此结构来分析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文件分析完毕。主要为各个描述符的获取。

点赞
收藏
评论区
推荐文章
技术小男生 技术小男生
4个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
4个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
4个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
小森森 小森森
4个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
4个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
3个月前
快速入门|使用MemFire Cloud构建React Native应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
helloworld_34035044 helloworld_34035044
6个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
helloworld_28799839 helloworld_28799839
4个月前
常用知识整理
Javascript判断对象是否为空jsObject.keys(myObject).length0经常使用的三元运算我们经常遇到处理表格列状态字段如status的时候可以用到vue