单片机
返回首页

STM32 USB如何配置多个CDC设备---5个CDC设备

2022-07-15 来源:csdn

1. 背景

由于项需求,在STM32F072中需实现5个CDC设备,折腾了很久分享出来,希望能帮助别人少踩一些坑.USB2.0全速,该款单片机支持8个输出和8个输入端点


2.USB CDC设备

2.1 它是什么?需要使用到什么?

它是USB设备类型的一种,如U盘插入电脑时,电脑会知道其是U盘,是一个存储设备,那么它就属于USB MCU存储设备。USB协议中对设备进行了分类.它在stm32上叫虚拟串口设备,当将其链接电脑后,可以把它当成串口使用,如使用windows的串口工具打开它,设置波特率等等,这是我的理解。


它需要两个接口,三个端点


InterFace 0:第一个接口,其包括一个端点,和Functional_Destriptcrs,该端点为控制接口(输入型端点(IN)),Functional_Destriptcrs则是一些描述符。先不管这个

InterFace 1:第二接口,其包括两个端点,endpoint In 和endpoint out端点,IN为stm32数据输出端点,OUT为stm32数据输入端点,随便提下,在STM32 HAL库中,USB的IN,OUT是相对于主机而言的,如:上诉的IN是指主机的输入,主机的输入(IN),对于设备来说就是OUT,数据的输出端点,IN OUT都是相对于主机而言

由此可见,正常情况下,一个CDC需要三个端点,其中两个IN端点,一个OUT端点


3.配置过程

3.1 配置流程

在STM32CubeIDE的帮助下,生成一个CDC的代码,这里没什么坑,点几下就成功了,注意的是可能需要将Heap设置大一点

修改设备描述符

分配端点和修改配置描述符

分配PMA端点缓存地址

修改HAL库关于CDC部分函数接口的参数,以适配多CDC设备的情况


3.2 使用STM32CubeIDE生成一个CDC程序的代码

根据下面的文件操作即可,文件会介绍USBCDC的知识,在PDF的最后会说怎么使用cubeIDE配置,很简单没什么坑(注意的是可能需要将Heap设置大一点) USB CDC类入门培训.pdf


3.3 分配端点和修改配置描述符

此时问题来了,CDC要求3个端点,其中2个输入1个输出,而stm32F072只支持8个输入端点,8个输出端点,2x5>8,正常情况下是不够的,这里就需要将IN端口中的控制端口省略,也就是第一幅图中的Interface 0中的endpoint IN端点,不分配有效端点给它,在本款单片机中IN端点的有效范围(0x80-0x87),OUT(0x00-0x08)

#define CDC_IN_EP                                   0x81U  /* CDC*/

#define CDC_OUT_EP                                  0x01U  

#define CDC_CMD_EP                                  0x88U  //无效端点


#define CDC_IN_EP1                                   0x82U   /* CDC1*/

#define CDC_OUT_EP1                                  0x02U  

#define CDC_CMD_EP1                                  0x89U  //无效端点


#define CDC_IN_EP2                                   0x83U   /* CDC2*/

#define CDC_OUT_EP2                                  0x03U  

#define CDC_CMD_EP2                                  0x8AU  //无效端点


#define CDC_IN_EP3                                   0x84U   /* CDC3*/

#define CDC_OUT_EP3                                  0x04U  

#define CDC_CMD_EP3                                  0x8CU  //无效端点


#define CDC_IN_EP4                                   0x85U   /* CDC4*/

#define CDC_OUT_EP4                                  0x05U

#define CDC_CMD_EP4                                  0x8BU  //无效端点


修改配置描述符号,这里面的内容就很多了,里面涉及到USB方方面面的属性和参数,其中USB_CDC_CONFIG_DESC_SIZ是该结构体的大小,记得修改,或者直接改为最大255。

配置描述符的结构如下:


配置描述符

{

    配置描述符总概括(相当于预览:9字节)

    IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC

    {

        接口描述符1

        {

            其他描述符(ACM等)

            端点描述符(控制端点)

        }

        接口描述符2

        {

            端点描述符(IN)

            端点描述符(OUT)

        }

    }


    IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC1

    {

        接口描述符1

        {

            其他描述符(ACM等)

            端点描述符(控制端点)

        }

        接口描述符2

        {

            端点描述符(IN)

            端点描述符(OUT)

        }

    }


    IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC2

    {

        接口描述符1

        {

            其他描述符(ACM等)

            端点描述符(控制端点)

        }

        接口描述符2

        {

            端点描述符(IN)

            端点描述符(OUT)

        }

    }


    IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC3

    {

        接口描述符1

        {

            其他描述符(ACM等)

            端点描述符(控制端点)

        }

        接口描述符2

        {

            端点描述符(IN)

            端点描述符(OUT)

        }

    }


    IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC4

    {

        接口描述符1

        {

            其他描述符(ACM等)

            端点描述符(控制端点)

        }

        接口描述符2

        {

            端点描述符(IN)

            端点描述符(OUT)

        }

    }

}


配置描述符如下,下面有些字段需要修改的,都用casojie标记了,CTRL+F搜索casojie就可以方便显示;

__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =

{

  /*Configuration Descriptor*/

  0x09,   /* bLength: Configuration Descriptor size */

  USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */

  USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */

  0x00,

  0x0A,   /* bNumInterfaces: 10 interface *///总接口大小,5个CDC*2=10=0x0A casojie

  0x01,   /* bConfigurationValue: Configuration value */

  0x00,   /* iConfiguration: Index of string descriptor describing the configuration */

  0xC0,   /* bmAttributes: self powered */

  0x32,   /* MaxPower 0 mA */


  /*------------------------CDC0--------------------------------------------------8+9+5+5+7+9+7+7-*/

  // // // IAD  Interface Association Descriptor //IAD描述符需要添加 casojie

  // 0x08,  // bLength: Interface Descriptor size //标识IAD描述符的长度,基本都是8,casojie

  // 0x0B,      // bDescriptorType: IAD            //不用管

  // 0x00,  // bFirstInterface                      //第一个接口的序号

  // 0x02,  // bInterfaceCount                      //本IDA的接口数量

  // 0x02,  // bFunctionClass: CDC                  //表明该IAD是一个CDC设备

  // 0x02,  // bFunctionSubClass                    //不用管

  // 0x01,  // bFunctionProtocol                    //控制协议等其他我也不懂,默认就行

  // 0x02,  // iFunction    


 /*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x00,   /* bInterfaceNumber: Number of Interface */   //接口编号,从0开始 casojie

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */       //接口内有多少个端点被使用 1个 casojie

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */


  // /*Header Functional Descriptor*/

  // 0x05,   /* bLength: Endpoint Descriptor size */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x00,   /* bDescriptorSubtype: Header Func Desc */

  // 0x10,   /* bcdCDC: spec release number */

  // 0x01,


  // /*Call Management Functional Descriptor*/

  // 0x05,   /* bFunctionLength */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  // 0x00,   /* bmCapabilities: D0+D1 */

  // 0x01,   /* bDataInterface: 1 */


  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x0F,   /* bmCapabilities */


  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x00,   /* bMasterInterface: Communication class interface */ //联合描述符,与IAD功能类似,标识哪两个接口是属于一个设备的casojie

  0x01,   /* bSlaveInterface0: Data Class Interface */  //与上面一样,上面是0,这里是1,标识0号,1号接口是属于一个CDC设备的 casojie


  /*Endpoint 2 Descriptor*/

  0x07,                           /* bLength: Endpoint Descriptor size *///控制端点描述符,虽然端点号是无效的,但是这个描述符不可省略

  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */

  CDC_CMD_EP,                     /* bEndpointAddress */

  0x03,                           /* bmAttributes: Interrupt */

  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */

  HIBYTE(CDC_CMD_PACKET_SIZE),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/


  /*Data class interface descriptor*/   //第二个接口描述符 casojie

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x01,   /* bInterfaceNumber: Number of Interface */   //接口的序号,上一个接口是0,那这里就是1 casojie

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */       //标识此接口有两个端点(IN)(OUT) casojie

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */


  /*Endpoint OUT Descriptor*/                       //OUT端点

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */


  /*Endpoint IN Descriptor*/                        //IN端点

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                               /* bInterval: ignore for Bulk transfer */


  /*---------------------------------------------------------------------------*/

  // IAD  Interface Association Descriptor       //这里就是CDC1了,重复CDC0的配置过程,注意有些字段的序号 casojie

  // 0x08,  // bLength: Interface Descriptor size

  // 0x0B,      // bDescriptorType: IAD

  // 0x02,  // bFirstInterface                  //太多了,写不下去了,意思和第一个IDA一样的

  // 0x02,  // bInterfaceCount

  // 0x02,  // bFunctionClass: CDC

  // 0x02,  // bFunctionSubClass

  // 0x01,  // bFunctionProtocol 

  // 0x02,  // iFunction


/*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x02,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */


  // /*Header Functional Descriptor*/

  // 0x05,   /* bLength: Endpoint Descriptor size */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x00,   /* bDescriptorSubtype: Header Func Desc */

  // 0x10,   /* bcdCDC: spec release number */

  // 0x01,


  // /*Call Management Functional Descriptor*/

  // 0x05,   /* bFunctionLength */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  // 0x00,   /* bmCapabilities: D0+D1 */

  // 0x01,   /* bDataInterface: 1 */


  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x0F,   /* bmCapabilities */


  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x02,   /* bMasterInterface: Communication class interface */

  0x03,   /* bSlaveInterface0: Data Class Interface */


  // /*Endpoint 2 Descriptor*/

  0x07,                           /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */

  CDC_CMD_EP1,                     /* bEndpointAddress */

  0x03,                           /* bmAttributes: Interrupt */

  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */

  HIBYTE(CDC_CMD_PACKET_SIZE),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/


  /*Data class interface descriptor*/

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x03,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */


  /*Endpoint OUT Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP1,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */


  /*Endpoint IN Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP1,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                               /* bInterval: ignore for Bulk transfer */

  /*---------------------------------------------------------------------------*/

  // IAD  Interface Association Descriptor

  // 0x08,  // bLength: Interface Descriptor size

  // 0x0B,      // bDescriptorType: IAD

  // 0x04,  // bFirstInterface

  // 0x02,  // bInterfaceCount

  // 0x02,  // bFunctionClass: CDC

  // 0x02,  // bFunctionSubClass

  // 0x01,  // bFunctionProtocol 

  // 0x02,  // iFunction


/*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x04,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */


  // /*Header Functional Descriptor*/

  // 0x05,   /* bLength: Endpoint Descriptor size */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x00,   /* bDescriptorSubtype: Header Func Desc */

  // 0x10,   /* bcdCDC: spec release number */

  // 0x01,


  // /*Call Management Functional Descriptor*/

  // 0x05,   /* bFunctionLength */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  // 0x00,   /* bmCapabilities: D0+D1 */

  // 0x01,   /* bDataInterface: 1 */


  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x0F,   /* bmCapabilities */


  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x04,   /* bMasterInterface: Communication class interface */

  0x05,   /* bSlaveInterface0: Data Class Interface */


  // /*Endpoint 2 Descriptor*/

  0x07,                           /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */

  CDC_CMD_EP2,                     /* bEndpointAddress */

  0x03,                           /* bmAttributes: Interrupt */

  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */

  HIBYTE(CDC_CMD_PACKET_SIZE),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/


  /*Data class interface descriptor*/

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x05,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */


  /*Endpoint OUT Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP2,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */


  /*Endpoint IN Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP2,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                               /* bInterval: ignore for Bulk transfer */


  /*---------------------------------------------------------------------------*/

  // IAD  Interface Association Descriptor

  // 0x08,  // bLength: Interface Descriptor size

  // 0x0B,      // bDescriptorType: IAD

  // 0x06,  // bFirstInterface

  // 0x02,  // bInterfaceCount

  // 0x02,  // bFunctionClass: CDC

  // 0x02,  // bFunctionSubClass

  // 0x01,  // bFunctionProtocol 

  // 0x02,  // iFunction


/*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x06,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */


  // /*Header Functional Descriptor*/

  // 0x05,   /* bLength: Endpoint Descriptor size */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x00,   /* bDescriptorSubtype: Header Func Desc */

  // 0x10,   /* bcdCDC: spec release number */

  // 0x01,


  // /*Call Management Functional Descriptor*/

  // 0x05,   /* bFunctionLength */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  // 0x00,   /* bmCapabilities: D0+D1 */

  // 0x01,   /* bDataInterface: 1 */


  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x0F,   /* bmCapabilities */


  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x06,   /* bMasterInterface: Communication class interface */

  0x07,   /* bSlaveInterface0: Data Class Interface */


  /*Endpoint 2 Descriptor*/

  0x07,                           /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */

  CDC_CMD_EP3,                     /* bEndpointAddress */

  0x03,                           /* bmAttributes: Interrupt */

  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */

  HIBYTE(CDC_CMD_PACKET_SIZE),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/


  /*Data class interface descriptor*/

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x07,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */


  /*Endpoint OUT Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP3,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */


  /*Endpoint IN Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP3,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                               /* bInterval: ignore for Bulk transfer */


//   /*---------------------------------------------------------------------------*/

//   // IAD  Interface Association Descriptor

//   // 0x08,   // bLength: Interface Descriptor size

//   // 0x0B,       // bDescriptorType: IAD

//   // 0x08,   // bFirstInterface

//   // 0x02,   // bInterfaceCount

//   // 0x02,   // bFunctionClass: CDC

//   // 0x02,   // bFunctionSubClass

//   // 0x01,   // bFunctionProtocol 

//   // 0x02,   // iFunction


/*Interface Descriptor */

  0x09,   /* bLength: Interface Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */

  /* Interface descriptor type */

  0x08,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x01,   /* bNumEndpoints: One endpoints used */

  0x02,   /* bInterfaceClass: Communication Interface Class */

  0x02,   /* bInterfaceSubClass: Abstract Control Model */

  0x01,   /* bInterfaceProtocol: Common AT commands */

  0x00,   /* iInterface: */


  // /*Header Functional Descriptor*/

  // 0x05,   /* bLength: Endpoint Descriptor size */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x00,   /* bDescriptorSubtype: Header Func Desc */

  // 0x10,   /* bcdCDC: spec release number */

  // 0x01,


  // /*Call Management Functional Descriptor*/

  // 0x05,   /* bFunctionLength */

  // 0x24,   /* bDescriptorType: CS_INTERFACE */

  // 0x01,   /* bDescriptorSubtype: Call Management Func Desc */

  // 0x00,   /* bmCapabilities: D0+D1 */

  // 0x01,   /* bDataInterface: 1 */


  /*ACM Functional Descriptor*/

  0x04,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x02,   /* bDescriptorSubtype: Abstract Control Management desc */

  0x0F,   /* bmCapabilities */


  /*Union Functional Descriptor*/

  0x05,   /* bFunctionLength */

  0x24,   /* bDescriptorType: CS_INTERFACE */

  0x06,   /* bDescriptorSubtype: Union func desc */

  0x08,   /* bMasterInterface: Communication class interface */

  0x09,   /* bSlaveInterface0: Data Class Interface */


  /*Endpoint 2 Descriptor*/

  0x07,                           /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */

  CDC_CMD_EP4,                     /* bEndpointAddress */

  0x03,                           /* bmAttributes: Interrupt */

  LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */

  HIBYTE(CDC_CMD_PACKET_SIZE),

  0x10,                           /* bInterval: */ 

  /*---------------------------------------------------------------------------*/

  /*

  /*Data class interface descriptor*/

  0x09,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */

  0x09,   /* bInterfaceNumber: Number of Interface */

  0x00,   /* bAlternateSetting: Alternate setting */

  0x02,   /* bNumEndpoints: Two endpoints used */

  0x0A,   /* bInterfaceClass: CDC */

  0x00,   /* bInterfaceSubClass: */

  0x00,   /* bInterfaceProtocol: */

  0x00,   /* iInterface: */


  /*Endpoint OUT Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_OUT_EP4,                        /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00,                              /* bInterval: ignore for Bulk transfer */


  /*Endpoint IN Descriptor*/

  0x07,   /* bLength: Endpoint Descriptor size */

  USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */

  CDC_IN_EP4,                         /* bEndpointAddress */

  0x02,                              /* bmAttributes: Bulk */

  LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */

  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),

  0x00                               /* bInterval: ignore for Bulk transfer */ 

}

为什么上面有些描述符被注释掉了?

配置5个CDC如果描述符都写上了,这个结构体就超过了255个字节大小,超过了USB2.0最大配置描述符长度

在我的项目中,IAD和联合描述符(/Union Functional Descriptor/)功能一样,我这里注释IAD描述符,不会影响USB,但在windows平台下,它认的是IAD描述符,这个时候就可以注释联合描述符,打开IAD描述符,总之,省略一些非必要描述,减少长度

这里的坑很多很多,需要很耐心,配错一个字段,千奇百怪的问题就来了

3.4 配置PMA---USB硬件缓存

这里又有坑了,我把我知道的都说出

3.4.1 PMA是啥?

此部分感谢一个大佬的帖子,理解来源与它

大佬的PMA

这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。

3.4.2 5个CDC的PMA怎么配置?

啥也不说,先上代码

u_int16_t ep_addR=0x70; //起始分配地址: 这里是大坑 casojie

u_int8_t ep_addr_size=64;

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;   //此端点是用于USB枚举和基本的通信使用,默认就有,且不可省去

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;

/* USER CODE END EndPoint_Configuration */

//CDC0 每个端点都分配了64B

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;


//CDC1                                                                                                //+5

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP1, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;  //192 258

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP1 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;    //272 2A8


//CDC2

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP2 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //0x270

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP2 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;//0x2C0


//CDC3

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP3, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;  //192 258 0x1E0

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP3 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;


//CDC4

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP4, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;  

HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP4 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;

PMA图(我理解的)



在PMA的开头部分,是用于记录信息的,这些信息就是:端点缓存区的起始地址:说这简单点,里面写了端点0的缓存区的起始地址是X,端点1的缓冲区起始地址是Y等等

由此可见,当使用的端点数量比较多时,信息记录区索要的空间就比较大,反之,就小,需要动态调整

由大佬的帖子可知,记录一个端点的起始地址信息需要8字节,那么我们五个CDC,则5x2(IN,OUT)=10,再加上0x80 ,0x00端点,一共12个.则12*8=76字节

因此,端点(0x00)分配的地址需要往后偏移,默认的不行,如这次需要76字节的大小,换成16进制,0x4C,我设置了0x70>0x4C,默认的太小了,会导致部分信息记录区被端点0的数据覆盖,而导致部分端点无法使用.

4.修改USB初始化代码,新增对新端点的初始化

static uint8_t  USBD_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)

{

  uint8_t ret = 0U;

  USBD_CDC_HandleTypeDef   *hcdc;


  if (pdev->dev_speed == USBD_SPEED_HIGH)

  {

    /* Open EP IN */

    USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK,

                   CDC_DATA_HS_IN_PACKET_SIZE);


    pdev->ep_in[CDC_IN_EP & 0xFU].is_used = 1U;


    /* Open EP OUT */

    USBD_LL_OpenEP(pdev, CDC_OUT_EP, USBD_EP_TYPE_BULK,

                   CDC_DATA_HS_OUT_PACKET_SIZE);


    pdev->ep_out[CDC_OUT_EP & 0xFU].is_used = 1U;


  }

  else

  {

    /* Open EP IN */

     USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);


     pdev->ep_in[CDC_IN_EP & 0xFU].is_used = 1U;


    /* Open EP OUT */

     USBD_LL_OpenEP(pdev, CDC_OUT_EP, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);


     pdev->ep_out[CDC_OUT_EP & 0xFU].is_used = 1U;



        /* Open EP IN */

     USBD_LL_OpenEP(pdev, CDC_IN_EP1, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);


     pdev->ep_in[CDC_IN_EP1 & 0xFU].is_used = 1U;


    /* Open EP OUT */

     USBD_LL_OpenEP(pdev, CDC_OUT_EP1, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);


     pdev->ep_out[CDC_OUT_EP1 & 0xFU].is_used = 1U;




//--------------------------------------------------------------------

       /* Open EP IN */

     USBD_LL_OpenEP(pdev, CDC_IN_EP2, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);


    pdev->ep_in[CDC_IN_EP2 & 0xFU].is_used = 1U;


    /* Open EP OUT */

    USBD_LL_OpenEP(pdev, CDC_OUT_EP2, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);


    pdev->ep_out[CDC_OUT_EP2 & 0xFU].is_used = 1U;



   //-------------------------5---------------------

           /* Open EP IN */

    USBD_LL_OpenEP(pdev, CDC_IN_EP3, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);


    pdev->ep_in[CDC_IN_EP3 & 0xFU].is_used = 1U;


    /* Open EP OUT */

    USBD_LL_OpenEP(pdev, CDC_OUT_EP3, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);


    pdev->ep_out[CDC_OUT_EP3 & 0xFU].is_used = 1U;


    //-------------------------5---------------------


           /* Open EP IN */

    USBD_LL_OpenEP(pdev, CDC_IN_EP4, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_IN_PACKET_SIZE);


    pdev->ep_in[CDC_IN_EP4 & 0xFU].is_used = 1U;


    /* Open EP OUT */

    USBD_LL_OpenEP(pdev, CDC_OUT_EP4, USBD_EP_TYPE_BULK,

                   CDC_DATA_FS_OUT_PACKET_SIZE);


    pdev->ep_out[CDC_OUT_EP4 & 0xFU].is_used = 1U;


  }

  /* Open Command IN EP *///控制端口无效,不要取消注释

//  USBD_LL_OpenEP(pdev, CDC_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);

//  pdev->ep_in[CDC_CMD_EP & 0xFU].is_used = 1U;



//   USBD_LL_OpenEP(pdev, CDC_CMD_EP1, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);

//   pdev->ep_in[CDC_CMD_EP1 & 0xFU].is_used = 1U;



//     USBD_LL_OpenEP(pdev, CDC_CMD_EP2, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);

//   pdev->ep_in[CDC_CMD_EP2 & 0xFU].is_used = 1U;



  //   USBD_LL_OpenEP(pdev, CDC_CMD_EP3, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);

  // pdev->ep_in[CDC_CMD_EP3 & 0xFU].is_used = 1U;


  pdev->pClassData = USBD_malloc(sizeof(USBD_CDC_HandleTypeDef));


  if (pdev->pClassData == NULL)

  {

    ret = 1U;

  }

  else

  {

    hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;


    /* Init  physical Interface components */

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();


    /* Init Xfer states */

    hcdc->TxState = 0U;

    hcdc->RxState = 0U;


    if (pdev->dev_speed == USBD_SPEED_HIGH)

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,

                             CDC_DATA_HS_OUT_PACKET_SIZE);

    }

    else

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);


                                   /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP1, hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);


                                   /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP2, hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);


                                   /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP3, hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);


                                                             /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP4, hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);


      USBD_LL_PrepareReceive(pdev, CDC_OUT_EP5, hcdc->RxBuffer,

                            CDC_DATA_FS_OUT_PACKET_SIZE);

    }

  }

  return ret;

}

5.修改HAL默认USB CDC相关接口函数

现在是多CDC的状况了,但默认的HAL库的函数均只是针对一个CDC的情况,我们需要将端口的参数引出来

USBD_CDC_DataOut:USB接收函数回调,修改提供端口参数

CDC_Receive_FS(uint8_t *Buf, uint32_t *Len):USB CDC接收函数

CDC_Transmit_FS(uint8_t* Buf, uint16_t Len):USB CDC发送函数

USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)

USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)

先说说这几个函数的关系:CDC_Transmit_FS---调用--->USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)

USBD_CDC_DataOut(数据接收回调)---调用--->CDC_Receive_FS

CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)修改为CDC_Receive_FS(uint8_t* Buf, uint32_t *Len,uint8_t epnum): 此后所有OUT端点来的数据都将进入此函数,可通过参数epnum区分


static uint8_t  USBD_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum)

{

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;


  /* Get the received data length */

  hcdc->RxLength = USBD_LL_GetRxDataSize(pdev, epnum);

  /* USB data will be immediately processed, this allow next USB traffic being

  NAKed till the end of the application Xfer */

  // printf('USBD_CDC_DataOut.1294:Len:%d :EP:%02Xn',hcdc->RxLength,epnum);

  if (pdev->pClassData != NULL)

  {

    ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength,epnum);//此处修改,加入端点,相关的函数声明也跟这这样改

    return USBD_OK;

  }


  else

  {

    return USBD_FAIL;

  }

}

CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)该为CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum):只是增加一个参数,函数体不用动

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum)

{

  uint8_t result = USBD_OK;

  /* USER CODE BEGIN 7 */

  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS_CDC.pClassData;

  if (hcdc->TxState != 0){

    return USBD_BUSY;

  }

  USBD_CDC_SetTxBuffer(&hUsbDeviceFS_CDC, Buf, Len);

  result = USBD_CDC_TransmitPacket(&hUsbDeviceFS_CDC,epnum);    //将epnum的端点传到这个函数,这个函数也需要改

  /* USER CODE END 7 */

  return result;

}

USBD_CDC_TransmitPacket的修改

uint8_t  USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev,uint8_t epnum)

{

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;


  if (pdev->pClassData != NULL)

  {

    if (hcdc->TxState == 0U)

    {

      /* Tx Transfer in progress */

      hcdc->TxState = 1U;


      /* Update the packet total length */

      pdev->ep_in[epnum & 0xFU].total_length = hcdc->TxLength;//将原本的CDC_IN_EP更改为我们传入的端点

      /* Transmit next packet */

      USBD_LL_Transmit(pdev, epnum, hcdc->TxBuffer,

                       (uint16_t)hcdc->TxLength);//此处也是


      return USBD_OK;

    }

    else

    {

      return USBD_BUSY;

    }

  }

  else

  {

    return USBD_FAIL;

  }

}


该函数的作用:当端口接收一包数据后,需要执行此函数来接收下一包数据,简单的说:告诉主机我可以接收下一包数据了


uint8_t  USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev,uint8_t epnum)

{

  USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;


  /* Suspend or Resume USB Out process */

  if (pdev->pClassData != NULL)

  {

    if (pdev->dev_speed == USBD_SPEED_HIGH)

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             CDC_OUT_EP,

                             hcdc->RxBuffer,

                             CDC_DATA_HS_OUT_PACKET_SIZE);

    }

    else

    {

      /* Prepare Out endpoint to receive next packet */

      USBD_LL_PrepareReceive(pdev,

                             epnum,

                             hcdc->RxBuffer,

                             CDC_DATA_FS_OUT_PACKET_SIZE);

    }

    return USBD_OK;

  }

  else

  {

    return USBD_FAIL;

  }

}


对以后而言,我们就使用CDC_Receive_FS(uint8_t* Buf, uint32_t *Len,uint8_t epnum)和CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum) 向CDCx发送数据,就填写CDCx的IN端口 * 接收CDCx数据时,CDC_Receive_FS函数会传入该数据来源(OUT)端口,就可知道哪个CDCx了


6.后续

此时就完成了,不过不排除有些深层的问题。

如:当五个CDC同时在Read时,其中一个close,有可能会导致其他四个都无法再Read出数据,需要重新Open,但在电脑的深度depin系统上并没有这个问题,在项目中的板子上就有,希望大佬们提供下线索。感激不尽


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

  • SOC系统级芯片设计实验

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

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

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

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

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

  • 红外线探测报警器

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

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

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

    相关电子头条文章