STM32 USB学习笔记3

Wesley13
• 阅读 862

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
2星期前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
2年前
HIVE 时间操作函数
日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
6个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这