• 基于STM32实现USB组合设备CDC+MSC正确打开方式


    摘要:

    前一段时间对无刷电机的驱动有了兴趣,移植了odrive和simpleFOC代码,里面有关于stm32实现USB复合的实例,最近也有打算在electronbot里实现U盘+通讯来实现bootloader和语音文件的拷贝和管理。看了网上也有相关实现文章,比较HAL原代码框架,无论是odrive里,还是网上其它实现案例,都是通过ep_addr进行switch ,而原代码框架里有USBD_RegisterClassComposite函数,阅读HAL库USB相关代码后,决定以符合原代码框架的姿势打开USB组合设备CDC+MSC。


    目录

    摘要:

    编译环境

     一、基本工程建立

    二、描述符修改

    1.设备层

    2.配置描述符

    3.端点

    三.关键代码

     四.大功告成 

    总结


    编译环境

    编译环境使用了STM32CubeMX生成makefile工程,使用gcc编译,环境搭建材料如下,基本可参照odrive环境搭建。

    1、VSCodeUserSetup-x64-1.63.0.exe

    2、gcc-arm-none-eabi-10.3-2021.10-win32.exe

    3、openocd-20211118.zip

    4、xpack-windows-build-tools-4.2.1-2-win32-x64.zip

    可自行百度 Windows ODrive  编译环境搭建


     一、基本工程建立

    使用STM32CubeMX建立两个独立的工程,一个是CDC工程,一个是MSC工程。然后以一个工程为母版,本例程是以CDC为母版,将MSC工程路径Middlewares\ST\STM32_USB_Device_Library\Class下的MSC文件夹拷贝到CDC工程该路径下,如图

    二、描述符修改

    描述符修改基本遵循设备层,配置、接口、端点依次更改。

    1.设备层

    无论是CDC的还是MSC的设备描述符不符合要求了,并且看到代码里有USE_USBD_COMPOSITE宏判断:

     即可理解为如果定义了USE_USBD_COMPOSITE宏,即不再使用MSC和CDC里的设备描述符和相关配置。那么就应该在MSC和CDC两个关系之上,实现这一部分。于是新建两个文件分别是usbd_composite.c 和 usbd_composite.h,关键部分如下:

    上图下红框即是告诉主机,下行设备即是复合设备。

    1. USBD_ClassTypeDef USBD_CMPSIT=
    2. {
    3. NULL,
    4. NULL,
    5. NULL,
    6. NULL, /* EP0_TxSent */
    7. NULL,
    8. NULL,
    9. NULL,
    10. NULL,
    11. NULL,
    12. NULL,
    13. USBD_CMPSIT_GetHSCfgDesc,
    14. USBD_CMPSIT_GetFSCfgDesc,
    15. USBD_CMPSIT_GetOtherSpeedCfgDesc,
    16. USBD_CMPSIT_GetDeviceQualifierDescriptor
    17. };

    2.配置描述符

    配置主要信息内容:使用了几个接口,每个接口实现什么设备功能(CDC、HID、MSC...),使用了什么样的端点等,本例程主要用了三个接口,CDC使用了两个,index为0和1,MSC使用了一个,index为2。配置描述符如下: 

    1. __ALIGN_BEGIN static uint8_t USBD_CMPSIT_CfgDesc[USB_CMPSIT_CONFIG_DESC_SIZ] __ALIGN_END =
    2. {
    3. /* Configuration Descriptor */
    4. //0
    5. 0x09, /* bLength: Configuration Descriptor size */
    6. USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
    7. USB_CMPSIT_CONFIG_DESC_SIZ, /* wTotalLength */
    8. 0x00,
    9. 0x03, /* bNumInterfaces: 2 interfaces */
    10. 0x01, /* bConfigurationValue: Configuration value */
    11. 0x00, /* iConfiguration: Index of string descriptor
    12. describing the configuration */
    13. #if (USBD_SELF_POWERED == 1U)
    14. 0xC0, /* bmAttributes: Bus Powered according to user configuration */
    15. #else
    16. 0x80, /* bmAttributes: Bus Powered according to user configuration */
    17. #endif /* USBD_SELF_POWERED */
    18. USBD_MAX_POWER, /* MaxPower (mA) */
    19. /*---------------------------------------------------------------------------*/
    20. //9
    21. /* Interface Association Descriptor: CDC device (virtual com port) */
    22. 0x08, /* bLength: IAD size */
    23. 0x0B, /* bDescriptorType: Interface Association Descriptor */
    24. 0x00, /* bFirstInterface */
    25. 0x02, /* bInterfaceCount */
    26. 0x02, /* bFunctionClass: Communication Interface Class */
    27. 0x02, /* bFunctionSubClass: Abstract Control Model */
    28. 0x01, /* bFunctionProtocol: Common AT commands */
    29. 0x06, /* iFunction */
    30. //17
    31. /* Interface Descriptor */
    32. 0x09, /* bLength: Interface Descriptor size */
    33. USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
    34. /* Interface descriptor type */
    35. 0x00, /* bInterfaceNumber: Number of Interface */
    36. 0x00, /* bAlternateSetting: Alternate setting */
    37. 0x01, /* bNumEndpoints: One endpoint used */
    38. 0x02, /* bInterfaceClass: Communication Interface Class */
    39. 0x02, /* bInterfaceSubClass: Abstract Control Model */
    40. 0x01, /* bInterfaceProtocol: Common AT commands */
    41. 0x06, /* iInterface */
    42. //26
    43. /* Header Functional Descriptor */
    44. 0x05, /* bLength: Endpoint Descriptor size */
    45. 0x24, /* bDescriptorType: CS_INTERFACE */
    46. 0x00, /* bDescriptorSubtype: Header Func Desc */
    47. 0x10, /* bcdCDC: spec release number */
    48. 0x01,
    49. //31
    50. /* Call Management Functional Descriptor */
    51. 0x05, /* bFunctionLength */
    52. 0x24, /* bDescriptorType: CS_INTERFACE */
    53. 0x01, /* bDescriptorSubtype: Call Management Func Desc */
    54. 0x00, /* bmCapabilities: D0+D1 */
    55. 0x01, /* bDataInterface */
    56. //36
    57. /* ACM Functional Descriptor */
    58. 0x04, /* bFunctionLength */
    59. 0x24, /* bDescriptorType: CS_INTERFACE */
    60. 0x02, /* bDescriptorSubtype: Abstract Control Management desc */
    61. 0x02, /* bmCapabilities */
    62. //40
    63. /* Union Functional Descriptor */
    64. 0x05, /* bFunctionLength */
    65. 0x24, /* bDescriptorType: CS_INTERFACE */
    66. 0x06, /* bDescriptorSubtype: Union func desc */
    67. 0x00, /* bMasterInterface: Communication class interface */
    68. 0x01, /* bSlaveInterface0: Data Class Interface */
    69. //45
    70. /* Endpoint 2 Descriptor */
    71. 0x07, /* bLength: Endpoint Descriptor size */
    72. USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
    73. CDC_CMD_EP, /* bEndpointAddress */
    74. 0x03, /* bmAttributes: Interrupt */
    75. LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize */
    76. HIBYTE(CDC_CMD_PACKET_SIZE),
    77. CDC_FS_BINTERVAL, /* bInterval */
    78. /*---------------------------------------------------------------------------*/
    79. //52
    80. /* Data class interface descriptor */
    81. 0x09, /* bLength: Endpoint Descriptor size */
    82. USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
    83. 0x01, /* bInterfaceNumber: Number of Interface */
    84. 0x00, /* bAlternateSetting: Alternate setting */
    85. 0x02, /* bNumEndpoints: Two endpoints used */
    86. 0x0A, /* bInterfaceClass: CDC */
    87. 0x00, /* bInterfaceSubClass */
    88. 0x00, /* bInterfaceProtocol */
    89. 0x06, /* iInterface */
    90. //61
    91. /* Endpoint OUT Descriptor */
    92. 0x07, /* bLength: Endpoint Descriptor size */
    93. USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
    94. CDC_OUT_EP, /* bEndpointAddress */
    95. 0x02, /* bmAttributes: Bulk */
    96. LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize */
    97. HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
    98. 0x00, /* bInterval */
    99. //68
    100. /* Endpoint IN Descriptor */
    101. 0x07, /* bLength: Endpoint Descriptor size */
    102. USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
    103. CDC_IN_EP, /* bEndpointAddress */
    104. 0x02, /* bmAttributes: Bulk */
    105. LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize */
    106. HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
    107. 0x00, /* bInterval */
    108. //75
    109. /* Interface Association Descriptor: Mass Storage device */
    110. 0x08, /* bLength: IAD size */
    111. 0x0B, /* bDescriptorType: Interface Association Descriptor */
    112. 0x02, /* bFirstInterface */
    113. 0x01, /* bInterfaceCount */
    114. 0x08, /* bFunctionClass: */
    115. 0x06, /* bFunctionSubClass: */
    116. 0x50, /* bFunctionProtocol: */
    117. 0x07, /* iFunction */
    118. //83
    119. /******************** Mass Storage interface ********************/
    120. 0x09, /* bLength: Interface Descriptor size */
    121. 0x04, /* bDescriptorType: */
    122. 0x02, /* bInterfaceNumber: Number of Interface */
    123. 0x00, /* bAlternateSetting: Alternate setting */
    124. 0x02, /* bNumEndpoints */
    125. 0x08, /* bInterfaceClass: MSC Class */
    126. 0x06, /* bInterfaceSubClass : SCSI transparent*/
    127. 0x50, /* nInterfaceProtocol */
    128. 0x07, /* iInterface: */
    129. /******************** Mass Storage Endpoints ********************/
    130. //92
    131. 0x07, /* Endpoint descriptor length = 7 */
    132. 0x05, /* Endpoint descriptor type */
    133. MSC_EPIN_ADDR, /* Endpoint address (IN, address 1) */
    134. 0x02, /* Bulk endpoint type */
    135. LOBYTE(MSC_MAX_FS_PACKET),
    136. HIBYTE(MSC_MAX_FS_PACKET),
    137. 0x00, /* Polling interval in milliseconds */
    138. //99
    139. 0x07, /* Endpoint descriptor length = 7 */
    140. 0x05, /* Endpoint descriptor type */
    141. MSC_EPOUT_ADDR, /* Endpoint address (OUT, address 1) */
    142. 0x02, /* Bulk endpoint type */
    143. LOBYTE(MSC_MAX_FS_PACKET),
    144. HIBYTE(MSC_MAX_FS_PACKET),
    145. 0x00 /* Polling interval in milliseconds */
    146. //106
    147. };

     usbd_conf.h修改部分

    1. /*---------- -----------*/
    2. #define USBD_MAX_NUM_INTERFACES 3U
    3. /*---------- -----------*/
    4. #define USBD_MAX_NUM_CONFIGURATION 1U
    5. /*---------- -----------*/
    6. #define USBD_MAX_STR_DESC_SIZ 512U
    7. /*---------- -----------*/
    8. #define USBD_DEBUG_LEVEL 0U
    9. /*---------- -----------*/
    10. #define USBD_LPM_ENABLED 0U
    11. /*---------- -----------*/
    12. #define USBD_SELF_POWERED 1U
    13. #define MSC_MEDIA_PACKET 512U
    14. #define CDC_FS_BINTERVAL 0x20U
    15. #define USBD_SUPPORT_USER_STRING_DESC 1U

    3.端点

    本例程的端点分配规则:CDC使用了0x81、0x01、0x82,MSC使用了0x83、0x03

    usbd_cdc.h相关如下:

    1. #ifndef CDC_IN_EP
    2. #define CDC_IN_EP 0x81U /* EP1 for data IN */
    3. #endif /* CDC_IN_EP */
    4. #ifndef CDC_OUT_EP
    5. #define CDC_OUT_EP 0x01U /* EP1 for data OUT */
    6. #endif /* CDC_OUT_EP */
    7. #ifndef CDC_CMD_EP
    8. #define CDC_CMD_EP 0x82U /* EP2 for CDC commands */
    9. #endif /* CDC_CMD_EP */

    usbd_msc.h相关如下:

    1. #ifndef MSC_EPIN_ADDR
    2. #define MSC_EPIN_ADDR 0x83U
    3. #endif /* MSC_EPIN_ADDR */
    4. #ifndef MSC_EPOUT_ADDR
    5. #define MSC_EPOUT_ADDR 0x03U
    6. #endif /* MSC_EPOUT_ADDR */

    三.关键代码

     底层初始化部分修改,usbd_conf.c中USBD_LL_Init

     要使用USBD_RegisterClassComposite来复合 usb设备,即要对它的功能实现有所理解,然后函数内部还要实现一个USBD_CMPSIT_AddClass函数。阅读了该部分相关的代码,基本知道了它复合设备的思想了,即可逆推USBD_CMPSIT_AddClass要实现的功能。我实现该部分的代码如下:

    1. #ifdef USE_USBD_COMPOSITE
    2. void USBD_CMPSIT_AddClass(USBD_HandleTypeDef *pdev, USBD_ClassTypeDef *pclass, USBD_CompositeClassTypeDef classtype, uint8_t *EpAddr)
    3. {
    4. //printf("classId=%d : %d\r\n",pdev->classId,classtype);
    5. switch(classtype)
    6. {
    7. case CLASS_TYPE_CDC:{
    8. pdev->tclasslist[pdev->classId].ClassType = CLASS_TYPE_CDC;
    9. pdev->tclasslist[pdev->classId].Active = 1U;
    10. pdev->tclasslist[pdev->classId].NumEps = 3;
    11. pdev->tclasslist[pdev->classId].Eps[0].add = CDC_CMD_EP;
    12. pdev->tclasslist[pdev->classId].Eps[0].type = USBD_EP_TYPE_INTR;
    13. pdev->tclasslist[pdev->classId].Eps[0].size = CDC_CMD_PACKET_SIZE;
    14. pdev->tclasslist[pdev->classId].Eps[0].is_used = 1U;
    15. pdev->tclasslist[pdev->classId].Eps[1].add = CDC_OUT_EP;
    16. pdev->tclasslist[pdev->classId].Eps[1].type = USBD_EP_TYPE_BULK;
    17. pdev->tclasslist[pdev->classId].Eps[1].size = CDC_DATA_FS_MAX_PACKET_SIZE;
    18. pdev->tclasslist[pdev->classId].Eps[1].is_used = 1U;
    19. pdev->tclasslist[pdev->classId].Eps[2].add = CDC_IN_EP;
    20. pdev->tclasslist[pdev->classId].Eps[2].type = USBD_EP_TYPE_BULK;
    21. pdev->tclasslist[pdev->classId].Eps[2].size = CDC_DATA_FS_MAX_PACKET_SIZE;
    22. pdev->tclasslist[pdev->classId].Eps[2].is_used = 1U;
    23. pdev->tclasslist[pdev->classId].NumIf = 2;
    24. pdev->tclasslist[pdev->classId].Ifs[0] = 0;
    25. pdev->tclasslist[pdev->classId].Ifs[1] = 1;
    26. }break;
    27. case CLASS_TYPE_MSC:{
    28. pdev->tclasslist[pdev->classId].ClassType = CLASS_TYPE_MSC;
    29. pdev->tclasslist[pdev->classId].Active = 1U;
    30. pdev->tclasslist[pdev->classId].NumEps = 2;
    31. pdev->tclasslist[pdev->classId].Eps[0].add = MSC_EPIN_ADDR;
    32. pdev->tclasslist[pdev->classId].Eps[0].type = USBD_EP_TYPE_BULK;
    33. pdev->tclasslist[pdev->classId].Eps[0].size = MSC_MAX_FS_PACKET;
    34. pdev->tclasslist[pdev->classId].Eps[0].is_used = 1U;
    35. pdev->tclasslist[pdev->classId].Eps[1].add = MSC_EPOUT_ADDR;
    36. pdev->tclasslist[pdev->classId].Eps[1].type = USBD_EP_TYPE_BULK;
    37. pdev->tclasslist[pdev->classId].Eps[1].size = MSC_MAX_FS_PACKET;
    38. pdev->tclasslist[pdev->classId].Eps[1].is_used = 1U;
    39. pdev->tclasslist[pdev->classId].NumIf = 1;
    40. pdev->tclasslist[pdev->classId].Ifs[0] = 2;
    41. }break;
    42. default:break;
    43. }
    44. pdev->tclasslist[pdev->classId].CurrPcktSze = 0U;
    45. }
    46. #endif

    然后就在usbd_device.c里使用USBD_RegisterClassComposite函数来注册复合设备了,如下代码段:

    1. if(USBD_RegisterClassComposite(&hUsbDeviceFS, &USBD_CDC,CLASS_TYPE_CDC,0) != USBD_OK)
    2. {
    3. Error_Handler();
    4. }
    5. if(USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS) != USBD_OK)
    6. {
    7. Error_Handler();
    8. }
    9. if(USBD_RegisterClassComposite(&hUsbDeviceFS, &USBD_MSC,CLASS_TYPE_MSC,0) != USBD_OK)
    10. {
    11. Error_Handler();
    12. }
    13. if (USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS) != USBD_OK)
    14. {
    15. Error_Handler();
    16. }

     四.大功告成

     经过推理和设想,最后编译下载,功能完美实现。

     CDC实现数据传输:

     MSC部分由于没有使用flash实例化,即在电脑上发现U盘即为成功了:

    总结

    阅读原代码,想必作者USBD_RegisterClassComposite函数有实现该功能的最初设想,但STM32CubeMX没有例程来教导大家,实在遗憾,本人也是本着吹毛求疵的强迫症,尽量符合原框架不大改的思想去实现该功能。特与大家分享。

    原码已上传到github, branch 为fw-v1.0

    GitHub - wenyezhong/usb_composite

  • 相关阅读:
    02 C++STL之容器
    清理Linux操作系统buff/cache缓存
    C语言编程程序实现打分系统,输入十个分数(实型数据)去掉一个最高分,一个最低分,求出剩下分数的平均分输出(保留两位小数)
    vulnhub靶场之VIKINGS: 1
    [附源码]SSM计算机毕业设计健身健康规划系统JAVA
    【java基础】有参构造和无参构造详解
    7K325T 引脚功能详解
    不同的二叉搜索树【动态规划】
    04. JAVA注解机制
    人工智能内容生成元年—AI绘画原理解析
  • 原文地址:https://blog.csdn.net/brotherwyz/article/details/128070253