你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】基于STM32使用HAL库实现USB组合设备之多路CDC

[复制链接]
STMCU小助手 发布时间:2022-4-9 23:12
工程环境:
STM32F103RC
STM32CubeIDE 1.5.1
第一步:基础工程生成
     首先先用STM32CUBEMX生成CDC工程,并测试通过没有问题后,就可以着手开始下一步的修改,如果你还不了解CDC虚拟串口,那么可以参考文章开头链接第一条说明里的博客内容。

第二步:USB设备描述符的修改
这一步很简单的,就是修改usbd_desc.c中的设备描述符数组USBD_FS_DeviceDesc,将设备类型改为组合设备类型:

_Y05WQZKXE)EC2KO[BK]8I3.png

第三步:修改PMA端点分布

}34}RD0%~S%TH9CW430~RZI.png

然后进入usbd_conf.c文件中,找到USBD_LL_Init函数,修改PMA端点初始化:

}P%Q31}O8~0HCQJ(NO{27PN.png

说一下PMA为什么这么改,目前我们用到的端点有:
0X80、0X00为USB所必须的端点
0X81、0X01为CDC1的输入输出端点
0X82、0X02为CDC2的输入输出端点
0X83为CDC1的命令控制端点(0X03端点未使用,但是占用空间)
0X84为CDC2的命令控制端点(0X04端点未使用,但是占用空间)

所以一共10个端点,10x8=80字节,十六进制为0X50,所以端点的缓存地址就是从PMA偏移0X50地址处开始。

输入输出端点最大可配置为64字节的缓存,所以是自增0X40。

命令端点的缓存数据大小是8字节缓存,自增16字节是因为光用到了输入端点,输出端点没用但是占用PMA空间,所以是自增0X10。

如下图指示是8字节:

20210427142334981.png

接下修改端点初始化,找到USBD_CDC_Init函数,添加CDC2的端点初始化操作:

  1. static uint8_t  USBD_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
  2. {
  3.   uint8_t ret = 0U;
  4.   USBD_CDC_HandleTypeDef   *hcdc;

  5.   if (pdev->dev_speed == USBD_SPEED_HIGH)
  6.   {
  7.     /* Open EP IN */
  8.     USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK,
  9.                    CDC_DATA_HS_IN_PACKET_SIZE);

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

  11.     /* Open EP OUT */
  12.     USBD_LL_OpenEP(pdev, CDC_OUT_EP, USBD_EP_TYPE_BULK,
  13.                    CDC_DATA_HS_OUT_PACKET_SIZE);

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

  15.   }
  16.   else
  17.   {
  18.     /* Open EP IN */
  19.     USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK,
  20.                    CDC_DATA_FS_IN_PACKET_SIZE);

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

  22.     /* Open EP OUT */
  23.     USBD_LL_OpenEP(pdev, CDC_OUT_EP, USBD_EP_TYPE_BULK,
  24.                    CDC_DATA_FS_OUT_PACKET_SIZE);

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

  26.     / +++lakun
  27.     /* Open EP IN */
  28.     USBD_LL_OpenEP(pdev, CDC2_IN_EP, USBD_EP_TYPE_BULK,
  29.                    CDC_DATA_FS_IN_PACKET_SIZE);

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

  31.     /* Open EP OUT */
  32.     USBD_LL_OpenEP(pdev, CDC2_OUT_EP, USBD_EP_TYPE_BULK,
  33.                    CDC_DATA_FS_OUT_PACKET_SIZE);

  34.     pdev->ep_out[CDC2_OUT_EP & 0xFU].is_used = 1U;
  35.   }
  36.   /* Open Command IN EP */
  37.   USBD_LL_OpenEP(pdev, CDC_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
  38.   pdev->ep_in[CDC_CMD_EP & 0xFU].is_used = 1U;

  39.   / +++lakun
  40.   /* Open Command IN EP */
  41.   USBD_LL_OpenEP(pdev, CDC2_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
  42.   pdev->ep_in[CDC2_CMD_EP & 0xFU].is_used = 1U;

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

  44.   if (pdev->pClassData == NULL)
  45.   {
  46.     ret = 1U;
  47.   }
  48.   else
  49.   {
  50.     hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

  51.     /* Init  physical Interface components */
  52.     ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();

  53.     /* Init Xfer states */
  54.     hcdc->TxState = 0U;
  55.     hcdc->RxState = 0U;

  56.     if (pdev->dev_speed == USBD_SPEED_HIGH)
  57.     {
  58.       /* Prepare Out endpoint to receive next packet */
  59.       USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
  60.                              CDC_DATA_HS_OUT_PACKET_SIZE);
  61.     }
  62.     else
  63.     {
  64.       /* Prepare Out endpoint to receive next packet */
  65.       USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
  66.                              CDC_DATA_FS_OUT_PACKET_SIZE);

  67.       / +++lakun
  68.       /* Prepare Out endpoint to receive next packet */
  69.       USBD_LL_PrepareReceive(pdev, CDC2_OUT_EP, hcdc->RxBuffer,
  70.                              CDC_DATA_FS_OUT_PACKET_SIZE);
  71.     }
  72.   }
  73.   return ret;
  74. }
复制代码

注:这一步骤的修改都非常挂关键,好多人组合设备修改出错的原因就是在修改PMA这里出的问题!在本文章起始处的蓝色链接《STM32 USB知识扫盲》文中有对PAM的详细讲解,一定要仔细看一下并理解!

第四步:修改配置描述符
贴一下我修改好的两路CDC配置,修改过的地方都有+++lakun样的标记:

  1. /* USB CDC device Configuration Descriptor */
  2. __ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
  3. {
  4.   /*Configuration Descriptor*/
  5.   0x09,   /* bLength: Configuration Descriptor size */
  6.   USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */
  7.   USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */
  8.   0x00,
  9.   0x04,   /* bNumInterfaces: 4 interface */  /* +++lakun:一个CDC用到了连个接口,两个CDC就是4个接口 */
  10.   0x01,   /* bConfigurationValue: Configuration value */
  11.   0x00,   /* iConfiguration: Index of string descriptor describing the configuration */
  12.   0xC0,   /* bmAttributes: self powered */
  13.   0x32,   /* MaxPower 0 mA */

  14.   /*---------------------------------------------------------------------------*/

  15.   //
  16.   // +++lakun: IAD(Interface Association Descriptor)
  17.   //
  18.   0X08,  // bLength: Interface Descriptor size,固定值
  19.   0X0B,  // bDescriptorType: IAD,固定值
  20.   0X00,  // bFirstInterface,第一个接口的起始序号
  21.   0X02,  // bInterfaceCount,本IAD下的接口数量
  22.   0X02,  // bFunctionClass: CDC,表明该IAD是一个CDC类型的设备
  23.   0X02,  // bFunctionSubClass:子类型,默认即可
  24.   0X01,  // bFunctionProtocol:控制协议,默认即可
  25.   0X02,  // iFunction

  26.   /*Interface Descriptor */
  27.   0x09,   /* bLength: Interface Descriptor size */
  28.   USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */
  29.   /* Interface descriptor type */
  30.   0x00,   /* bInterfaceNumber: Number of Interface */                   /* +++lakun:接口编号,从0开始 */
  31.   0x00,   /* bAlternateSetting: Alternate setting */
  32.   0x01,   /* bNumEndpoints: One endpoints used */
  33.   0x02,   /* bInterfaceClass: Communication Interface Class */
  34.   0x02,   /* bInterfaceSubClass: Abstract Control Model */
  35.   0x01,   /* bInterfaceProtocol: Common AT commands */
  36.   0x00,   /* iInterface: */

  37.   /*Header Functional Descriptor*/
  38.   0x05,   /* bLength: Endpoint Descriptor size */
  39.   0x24,   /* bDescriptorType: CS_INTERFACE */
  40.   0x00,   /* bDescriptorSubtype: Header Func Desc */
  41.   0x10,   /* bcdCDC: spec release number */
  42.   0x01,

  43.   /*Call Management Functional Descriptor*/
  44.   0x05,   /* bFunctionLength */
  45.   0x24,   /* bDescriptorType: CS_INTERFACE */
  46.   0x01,   /* bDescriptorSubtype: Call Management Func Desc */
  47.   0x00,   /* bmCapabilities: D0+D1 */
  48.   0x01,   /* bDataInterface: 1 */

  49.   /*ACM Functional Descriptor*/
  50.   0x04,   /* bFunctionLength */
  51.   0x24,   /* bDescriptorType: CS_INTERFACE */
  52.   0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
  53.   0x02,   /* bmCapabilities */

  54.   /*Union Functional Descriptor*/
  55.   0x05,   /* bFunctionLength */
  56.   0x24,   /* bDescriptorType: CS_INTERFACE */
  57.   0x06,   /* bDescriptorSubtype: Union func desc */
  58.   0x00,   /* bMasterInterface: Communication class interface */                 /* +++lakun:这里指示的是本CDC的通信接口编号 */
  59.   0x01,   /* bSlaveInterface0: Data Class Interface */                          /* +++lakun:这里指示的是本CDC的数据接口编号 */

  60.   /*Endpoint 2 Descriptor*/
  61.   0x07,                           /* bLength: Endpoint Descriptor size */
  62.   USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */
  63.   CDC_CMD_EP,                     /* bEndpointAddress */
  64.   0x03,                           /* bmAttributes: Interrupt */
  65.   LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */
  66.   HIBYTE(CDC_CMD_PACKET_SIZE),
  67.   CDC_FS_BINTERVAL,                           /* bInterval: */
  68.   /*---------------------------------------------------------------------------*/

  69.   /*Data class interface descriptor*/
  70.   0x09,   /* bLength: Endpoint Descriptor size */
  71.   USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */
  72.   0x01,   /* bInterfaceNumber: Number of Interface */                          /* +++lakun:CDC1的数据接口编号 */
  73.   0x00,   /* bAlternateSetting: Alternate setting */
  74.   0x02,   /* bNumEndpoints: Two endpoints used */
  75.   0x0A,   /* bInterfaceClass: CDC */
  76.   0x00,   /* bInterfaceSubClass: */
  77.   0x00,   /* bInterfaceProtocol: */
  78.   0x00,   /* iInterface: */

  79.   /*Endpoint OUT Descriptor*/
  80.   0x07,   /* bLength: Endpoint Descriptor size */
  81.   USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  82.   CDC_OUT_EP,                        /* bEndpointAddress */
  83.   0x02,                              /* bmAttributes: Bulk */
  84.   LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  85.   HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  86.   0x00,                              /* bInterval: ignore for Bulk transfer */

  87.   /*Endpoint IN Descriptor*/
  88.   0x07,   /* bLength: Endpoint Descriptor size */
  89.   USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  90.   CDC_IN_EP,                         /* bEndpointAddress */
  91.   0x02,                              /* bmAttributes: Bulk */
  92.   LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  93.   HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  94.   0x00,                               /* bInterval: ignore for Bulk transfer */


  95.   //
  96.   // +++lakun: IAD(Interface Association Descriptor)
  97.   //
  98.   0X08,  // bLength: Interface Descriptor size,固定值
  99.   0X0B,  // bDescriptorType: IAD,固定值
  100.   0X02,  // bFirstInterface,第一个接口的起始序号(第0、1编号的接口用于CDC1,现在是第二个CDC了,所以从2开始)
  101.   0X02,  // bInterfaceCount,本IAD下的接口数量
  102.   0X02,  // bFunctionClass: CDC,表明该IAD是一个CDC类型的设备
  103.   0X02,  // bFunctionSubClass:子类型,默认即可
  104.   0X01,  // bFunctionProtocol:控制协议,默认即可
  105.   0X02,  // iFunction

  106.   /*Interface Descriptor */
  107.   0x09,   /* bLength: Interface Descriptor size */
  108.   USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */
  109.   /* Interface descriptor type */
  110.   0x02,   /* bInterfaceNumber: Number of Interface */ /* +++lakun:这里就是第二个CDC了,第0、1编号的接口给CDC1使用了,所以是2开始的 */
  111.   0x00,   /* bAlternateSetting: Alternate setting */
  112.   0x01,   /* bNumEndpoints: One endpoints used */
  113.   0x02,   /* bInterfaceClass: Communication Interface Class */
  114.   0x02,   /* bInterfaceSubClass: Abstract Control Model */
  115.   0x01,   /* bInterfaceProtocol: Common AT commands */
  116.   0x00,   /* iInterface: */

  117.   /*Header Functional Descriptor*/
  118.   0x05,   /* bLength: Endpoint Descriptor size */
  119.   0x24,   /* bDescriptorType: CS_INTERFACE */
  120.   0x00,   /* bDescriptorSubtype: Header Func Desc */
  121.   0x10,   /* bcdCDC: spec release number */
  122.   0x01,

  123.   /*Call Management Functional Descriptor*/
  124.   0x05,   /* bFunctionLength */
  125.   0x24,   /* bDescriptorType: CS_INTERFACE */
  126.   0x01,   /* bDescriptorSubtype: Call Management Func Desc */
  127.   0x00,   /* bmCapabilities: D0+D1 */
  128.   0x01,   /* bDataInterface: 1 */

  129.   /*ACM Functional Descriptor*/
  130.   0x04,   /* bFunctionLength */
  131.   0x24,   /* bDescriptorType: CS_INTERFACE */
  132.   0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
  133.   0x02,   /* bmCapabilities */

  134.   /*Union Functional Descriptor*/
  135.   0x05,   /* bFunctionLength */
  136.   0x24,   /* bDescriptorType: CS_INTERFACE */
  137.   0x06,   /* bDescriptorSubtype: Union func desc */
  138.   0x02,   /* bMasterInterface: Communication class interface */                    /* +++lakun:这里指示的是本CDC的通信接口编号 */
  139.   0x03,   /* bSlaveInterface0: Data Class Interface */                             /* +++lakun:这里指示的是本CDC的数据接口编号 */

  140.   /*Endpoint 2 Descriptor*/
  141.   0x07,                           /* bLength: Endpoint Descriptor size */
  142.   USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */
  143.   CDC2_CMD_EP,                     /* bEndpointAddress */
  144.   0x03,                           /* bmAttributes: Interrupt */
  145.   LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */
  146.   HIBYTE(CDC_CMD_PACKET_SIZE),
  147.   CDC_FS_BINTERVAL,                           /* bInterval: */
  148.   /*---------------------------------------------------------------------------*/

  149.   /*Data class interface descriptor*/
  150.   0x09,   /* bLength: Endpoint Descriptor size */
  151.   USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */
  152.   0x03,   /* bInterfaceNumber: Number of Interface */ /* +++lakun:CDC2的数据接口编号 */
  153.   0x00,   /* bAlternateSetting: Alternate setting */
  154.   0x02,   /* bNumEndpoints: Two endpoints used */
  155.   0x0A,   /* bInterfaceClass: CDC */
  156.   0x00,   /* bInterfaceSubClass: */
  157.   0x00,   /* bInterfaceProtocol: */
  158.   0x00,   /* iInterface: */

  159.   /*Endpoint OUT Descriptor*/
  160.   0x07,   /* bLength: Endpoint Descriptor size */
  161.   USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  162.   CDC2_OUT_EP,                        /* bEndpointAddress */
  163.   0x02,                              /* bmAttributes: Bulk */
  164.   LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  165.   HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  166.   0x00,                              /* bInterval: ignore for Bulk transfer */

  167.   /*Endpoint IN Descriptor*/
  168.   0x07,   /* bLength: Endpoint Descriptor size */
  169.   USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
  170.   CDC2_IN_EP,                         /* bEndpointAddress */
  171.   0x02,                              /* bmAttributes: Bulk */
  172.   LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  173.   HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  174.   0x00,                               /* bInterval: ignore for Bulk transfer */

  175. } ;
复制代码

第五步:修改函数接口
默认的HAL库函数是只针对一路CDC的情况,所以我们需要修改成多路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_CDC_DataOut->
                         CDC_Receive_FS

USBD_CDC_DataOut函数有一个参数是epnum,这个是端点号,我们就可以使用该参数来区分是哪一路CDC!

所以USBD_CDC_DataOut函数添加上epnum参数,改为:

87FFT4J4O6N~RMKNZ8YR89U.png

USBD_CDC_TransmitPacket函数改为:

GUMS[2B0{]XJ3_K82PE(5.png

USBD_CDC_ReceivePacket函数改为:

`HOL3{QSVG](2{Y(QC0`WJ6.png

CDC_Transmit_FS函数改为:

@69X$D8YZ_@V7T1V17)84.png

CDC_Receive_FS函数改为:

@IXY_]7N2PN{CNTIF7PSQ.png

到此就已经修改完毕了,将USB接入电脑后,设备管理区会多出现一个USB复合设备和两个端口,这样就是成功了:

20210427151851625.png

下面进行测试,使用串口助手同时对两路CDC进行收发测试效果如下:

20210427145204370.gif

互不影响,USB两路CDC成功!

注意事项及问题
如果你移植的时候将USB接入电脑后,设备管理器里面只会出现1路串口,那么可能是因为你先用的CDC单独的工程测试的,电脑已经默认枚举为了VCP,那么此时有两种办法解决:

1.设备管理器卸载掉出现的虚拟串口,重新拔插USB
2.修改程序里的USBD_VID,换个其他编号

{`H`JIQ5FA@PY~5VH9YRM.png

我是用第二个办法解决的!

补充:3路CDC实现
实现3路CDC修改步骤跟实现2路一样,首先是PMA端点的配置:

J8%P41)T]b9Q%C)$T$HER.png

_E5WLU7L13%Y]XC(2ZQOZIY.png


为什么要把CDC_DATA_FS_MAX_PACKET_SIZE改成32呢?

现在我们算一下使用到的端点:
0X80、0X00用于USB必须的
0X81、0X01用于CDC1输入输出端点
0X82、0X02用于CDC2输入输出端点
0X83、0X03用于CDC3输入输出端点
0X84用于CDC1命令控制端点、0X04未用但占用空间
0X85用于CDC2命令控制端点、0X05未用但占用空间
0X86用于CDC3命令控制端点、0X06未用但占用空间


可以看到我们用到的端点很多,光算输入输出端点一共8个,每个端点最大占用64,那么就是512字节,而PMA一共才512字节,所以如果将端点缓冲设置为最大64字节的话,3路CDC串口缓存空间不够用!不改成32的话你会发现第1和2路串口收发没问题,而第三路串口返回的都是0!

如下图:

2021042715193621.gif

改成32后,效果如下:






三路虚拟串口收发数据互不影响,到这里就成功了!



20210427145004468.png
20210427151703661.png
收藏 评论0 发布时间:2022-4-9 23:12

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版