- 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都能正常进行发送数据。