返回列表 发新帖我要提问本帖赏金: 60.00元(功能说明)

[APM32F1] APM32USB双CDC配置_2

[复制链接]
2211|3
 楼主| 宫影空明人不往 发表于 2023-11-30 14:08 | 显示全部楼层 |阅读模式
本帖最后由 宫影空明人不往 于 2023-11-30 14:08 编辑

#申请原创#前言

承接上一篇帖子,正好将双CDC的设备描述符以及其端点和PMA分配好并且初始化,成功的能够被主机识别成两个CDC设备,但是对于第二个CDC的接口函数还没做处理,因此串口不能正常发送接收数据。
因此,此次将对第二个CDC的接口函数进行添加,使其能够正常发送接收数据。

思路
修改的步骤则是先从外层的接口函数(即用户接口函数)开始修改,再对USB内层使用到这些外部接口函数的地方进行修改。
  1. void USB_DeviceInit(void)
  2. {
  3.     /* USB CDC register interface handler */
  4.     USBD_CDC_RegisterItf(&gUsbDeviceFS, &USBD_CDC_INTERFACE_FS);
  5.     /* USB device and class init */
  6.     USBD_Init(&gUsbDeviceFS, USBD_SPEED_FS, &USBD_DESC_FS, &USBD_CDC_CLASS, USB_DevUserHandler);
  7. }
由上代码可以知道,接口函数的句柄是在设备初始化一开始传给了usb结构体。因为这次弄个的是相同class的复合设备,因此不增加相应的接口,而是修改之前的接口函数,使其能够区分两个CDC并做处理。
CDC接口函数如下:分别是初始化函数,非初始化函数,控制函数,发送函数以及接收函数。
  1. /* USB FS CDC interface handler */
  2. USBD_CDC_INTERFACE_T USBD_CDC_INTERFACE_FS =
  3. {
  4.     "CDC Interface FS",
  5.     USBD_FS_CDC_ItfInit,
  6.     USBD_FS_CDC_ItfDeInit,
  7.     USBD_FS_CDC_ItfCtrl,
  8.     USBD_FS_CDC_ItfSend,
  9.     USBD_FS_CDC_ItfSendEnd,
  10.     USBD_FS_CDC_ItfReceive,
  11. };

1.USBD_FS_CDC_ItfInit

初始化函数,就是对CDC的buffer进行初始化,具体函数如下。因此,我们也需要对CDC2的发送接收buf分配相应的数组地址。
  1. USBD_STA_T USBD_FS_CDC_ItfInit(void)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     USBD_CDC_ConfigRxBuffer(&gUsbDeviceFS, cdcRxBuffer);
  5.     USBD_CDC_ConfigTxBuffer(&gUsbDeviceFS, cdcTxBuffer, 0);
  6.     return usbStatus;
  7. }
具体就是在内部函数(即USBD_CDC_ConfigRxBuffer以及USBD_CDC_ConfigTxBuffer)做修改。


2.USBD_FS_CDC_ItfDeInit
去初始化函数没有对CDC做处理,因此这里也不对其做处理。

  1. <blockquote>USBD_STA_T USBD_FS_CDC_ItfDeInit(void)

3.
USBD_FS_CDC_ItfCtrl

控制函数也是同理,不对其做处理。

  1. USBD_STA_T USBD_FS_CDC_ItfCtrl(uint8_t command, uint8_t *buffer, uint16_t length)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     return usbStatus;
  5. }

4.USBD_FS_CDC_ItfSend

发送函数具体如下。实现逻辑就是先判断CDC发送口的state是否为空闲,然后将发送的数据的地址传给CDC的发送buf。最后使用USB的发送函数,将CDC发送buf的数据转移到USB的缓冲区中,再执行发送,将缓冲区的数据发送出去。
  1. USBD_STA_T USBD_FS_CDC_ItfSend(uint8_t *buffer, uint16_t length)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     USBD_CDC_INFO_T *usbDevCDC = (USBD_CDC_INFO_T*)gUsbDeviceFS.devClass[gUsbDeviceFS.classID]->classData;
  5.     if(usbDevCDC->cdcTx.state != USBD_CDC_XFER_IDLE)
  6.     {
  7.         return USBD_BUSY;
  8.     }
  9.     USBD_CDC_ConfigTxBuffer(&gUsbDeviceFS, buffer, length);
  10.     usbStatus = USBD_CDC_TxPacket(&gUsbDeviceFS);
  11.     return usbStatus;
  12. }
因此,需要为CDC2添加相应的处理函数,具体如下。主要是对分配buf地址给CDC函数以及发送函数增加一个cdcnum参数,这个参数用来区分是CDC1发送还是CDC2发送。
  1. USBD_STA_T USBD_FS_CDC_ItfSend(uint8_t *buffer, uint16_t length, uint8_t cdcnum)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     USBD_CDC_INFO_T *usbDevCDC = (USBD_CDC_INFO_T*)gUsbDeviceFS.devClass[gUsbDeviceFS.classID]->classData;
  5.         if(cdcnum == CDC1)
  6.         {
  7.                 if(usbDevCDC->cdcTx.state != USBD_CDC_XFER_IDLE)
  8.                 {
  9.                         return USBD_BUSY;
  10.                 }
  11.         }else{
  12.                 if(usbDevCDC->cdc2Tx.state != USBD_CDC_XFER_IDLE)
  13.                 {
  14.                         return USBD_BUSY;
  15.                 }
  16.         }
  17.     USBD_CDC_ConfigTxBuffer(&gUsbDeviceFS, buffer, length, cdcnum);
  18.     usbStatus = USBD_CDC_TxPacket(&gUsbDeviceFS, cdcnum);
  19.     return usbStatus;
  20. }
具体内部函数的修改这里不过太多阐述,如果想知道具体修改函数可以查看附件。不过,对于USB设备的发送有一个需要注意的点,就是发送的时候会对CDC的发送状态做判断,在执行发送的时候会把其状态改为USBD_CDC_XFER_BUSY,因此我们需要找到改为空闲的地方,将其新增对CDC2的处理。
通过KEIL的全局搜索工具,我们发送代码在USBD_CDC_DataInHandler函数里面对其设置成空闲状态。部分函数修改如下。
  1.   if((epNum&0x0F) == 0x1)
  2.                 {
  3.                         usbDevCDC->cdcTx.state = USBD_CDC_XFER_IDLE;
  4.         }
  5.                 else
  6.                 {
  7.                         usbDevCDC->cdc2Tx.state = USBD_CDC_XFER_IDLE;
  8.                 }
主要对端点信息判断是否为CDC1还是CDC2。


5.USBD_FS_CDC_ItfCtrl
发送结束函数也是不对其做处理,具体函数如下。
  1. USBD_STA_T USBD_FS_CDC_ItfSendEnd(uint8_t epNum, uint8_t *buffer, uint32_t *length)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     return usbStatus;
  5. }


6.
USBD_FS_CDC_ItfCtrl
接收函数具体如下。处理逻辑就是usb接收到相应数据并存放到缓冲区时,会触发中断并调用该接收函数,将缓冲区的数据转移到用户CDC的接收BUF中。
  1. USBD_STA_T USBD_FS_CDC_ItfReceive(uint8_t *buffer, uint32_t *length)
  2. {
  3.     USBD_STA_T usbStatus = USBD_OK;
  4.     uint16_t index;
  5.     USBD_CDC_ConfigRxBuffer(&gUsbDeviceFS, &buffer[0]);
  6.     USBD_CDC_RxPacket(&gUsbDeviceFS);
  7.     gUsbVCP.state = USBD_CDC_VCP_REV_UPDATE;
  8.     gUsbVCP.rxUpdateLen = *length;
  9.     for(index = 0; index < gUsbVCP.rxUpdateLen; index++)
  10.     {
  11.         cdcRxBufferTotal[gUsbVCP.rxTotalLen + index] = buffer[index];
  12.     }
  13.     gUsbVCP.rxTotalLen += gUsbVCP.rxUpdateLen;
  14.     return usbStatus;
  15. }
同理,需要对USBD_CDC_ConfigRxBuffer以及USBD_CDC_RxPacket加入cdcnum的变量,使其能够对CDC做判断处理。同时,需要知道是哪一个CDC接收数据,需要从源头进行修改。对其接口全局搜索找到其调用函数,并对其做修改。具体如下。
  1. static USBD_STA_T USBD_CDC_DataOutHandler(USBD_INFO_T* usbInfo, uint8_t epNum)
  2. {
  3.     USBD_STA_T  usbStatus = USBD_OK;
  4.     USBD_CDC_INFO_T* usbDevCDC = (USBD_CDC_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
  5.     if (usbDevCDC == NULL)
  6.     {
  7.         return USBD_FAIL;
  8.     }
  9.         if(epNum == 0x1)
  10.         {
  11.     usbDevCDC->cdcRx.length = USBD_EP_ReadRxDataLenCallback(usbInfo, epNum);
  12.     ((USBD_CDC_INTERFACE_T *)usbInfo->devClassUserData[usbInfo->classID])->ItfReceive(usbDevCDC->cdcRx.buffer, \
  13.                                                                                       &usbDevCDC->cdcRx.length, CDC1);
  14.         }
  15.         else
  16.         {
  17.     usbDevCDC->cdcRx.length = USBD_EP_ReadRxDataLenCallback(usbInfo, epNum);
  18.     ((USBD_CDC_INTERFACE_T *)usbInfo->devClassUserData[usbInfo->classID])->ItfReceive(usbDevCDC->cdcRx.buffer, \
  19.                                                                                       &usbDevCDC->cdcRx.length, CDC2);
  20.         }
  21.     return usbStatus;
  22. }
那么,对于CDC2的接口处理添加就完毕了。最后就是对一些修改了函数参数的定义进行修改。

7.main
当然,还需要对main函数进行修改,不然怎么知道CDC2有没有正常传输数据。这里因为对于CDC2的接收数组没有再做进一步的处理,因此只看CDC2的发送。修改如下:
  1. i++;
  2.                 sprintf(str,"Hello %d\r\n",i);
  3.                 USBD_FS_CDC_ItfSend((uint8_t *)str, strlen(str),CDC1);
  4.                 i++;
  5.                 sprintf(str,"Hello %d\r\n",i);
  6.                 USBD_FS_CDC_ItfSend((uint8_t *)str, strlen(str),CDC2);

测试
打开串口工具,可以发现有两个串行设备,COM8以及COM12。
1.png

然后打开COM8的时候发现无法连接到系统上。 2.png

通过usb分析仪发现打开COM8串口的时候,主机获取CDC请求的时候设备端没有进行反应。
3.png

因此,怀疑是代码哪一处地方漏掉做处理导致CDC2没办法正常打开串口。
最后发现在USBD_SetupStage函数中,会对接口信息进行判断,在进去类的setup处理函数中。具体如下:
  1. if (usbInfo->reqSetup.DATA_FIELD.wIndex[0] <= USBD_SUP_INTERFACE_MAX_NUM)
  2.                                 {
  3.                                     /* Add multi class support */
  4.                                     classIndex = 0;
  5.                                     if ((classIndex != 0xFF) && (classIndex < USBD_SUP_CLASS_MAX_NUM))
  6.                                     {
  7.                                         usbInfo->classID = classIndex;
  8.                                        
  9.                                         if (usbInfo->devClass[classIndex]->ClassSetup != NULL)
  10.                                         {
  11.                                             usbStatus = usbInfo->devClass[classIndex]->ClassSetup(usbInfo, &usbInfo->reqSetup);
  12.                                         }
因为主机对于CDC2的CDC请求会带接口信息,为2,。因此,需要将USBD_SUP_INTERFACE_MAX_NUM该宏定义改为2。
这样子,COM8(CDC2)就能够成功进行打印输出了。
4.png

但是发现其打印的数据是个乱码,正常来说应该递增的数据。通过DEBUG调试知道数据是能够正常的传到CDC2的一个发送数组并且正确的迁移到合适的缓冲区空间里面,但是最后打印出来的数据不是缓冲区所存储的数据。
通过查阅资料发现缓冲区它是有一个表头的东西存在,给到USB查找相应端点缓冲区地址进行发送。这个表的大小就根据端点0的地址来计算的。
  1. #define USBD_EP0_OUT_ADDR                       0x00
  2. #define USBD_EP0_OUT_SIZE                       0x18
  3. #define USBD_EP0_IN_ADDR                        0x80
  4. #define USBD_EP0_IN_SIZE                        0x58
通过查看端点0的地址,我们可以知道表的大小只有0x18,只能容纳3个端点。而CDC2使用的发送端点为端点3,刚好超出了表所包含的端点。因此,这里将其改为0x20和0x60。

结果
最终,两路CDC都能正常进行发送数据。
5.png




打赏榜单

21小跑堂 打赏了 60.00 元 2023-12-08
理由:恭喜通过原创审核!期待您更多的原创作品~

评论

层层剖析,添加修改接口函数,在APM单片机实现USB双CDC配置。  发表于 2023-12-8 18:59
21小跑堂 发表于 2023-12-6 11:05 | 显示全部楼层
hi 大佬 申请原创记得@21小跑堂 哦~  这样跑堂可以更快到收到您的申请信息从而进入审核

评论

有看到您说承接上一篇,可以编辑下上一篇@21小跑堂 审核原创哦~  发表于 2023-12-6 11:06
您需要登录后才可以回帖 登录 | 注册

本版积分规则

12

主题

14

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部