发新帖本帖赏金 60.00元(功能说明)我要提问
返回列表
打印
[APM32F1]

APM32USB双CDC配置_2

[复制链接]
1124|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 宫影空明人不往 于 2023-11-30 14:08 编辑

#申请原创#前言

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

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

1.USBD_FS_CDC_ItfInit

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


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

<blockquote>USBD_STA_T USBD_FS_CDC_ItfDeInit(void)

3.
USBD_FS_CDC_ItfCtrl

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

USBD_STA_T USBD_FS_CDC_ItfCtrl(uint8_t command, uint8_t *buffer, uint16_t length)
{
    USBD_STA_T usbStatus = USBD_OK;
    return usbStatus;
}

4.USBD_FS_CDC_ItfSend

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


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


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

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

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


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

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


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


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

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





使用特权

评论回复

打赏榜单

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

评论
21小跑堂 2023-12-8 18:59 回复TA
层层剖析,添加修改接口函数,在APM单片机实现USB双CDC配置。 
沙发
21小跑堂| | 2023-12-6 11:05 | 只看该作者
hi 大佬 申请原创记得@21小跑堂 哦~  这样跑堂可以更快到收到您的申请信息从而进入审核

使用特权

评论回复
评论
21小跑堂 2023-12-6 11:06 回复TA
有看到您说承接上一篇,可以编辑下上一篇@21小跑堂 审核原创哦~ 
发新帖 本帖赏金 60.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

9

主题

10

帖子

0

粉丝