1 前言

此文延续之前相同文章的话题,是对上篇文章的补充,之所以会有此文,主要是之前发现问题是在STM32F4上,解决方案也是基于CubeF4,但是,当相同问题出现在STM32F0上时,使用之前的代码修改并不能适用,这也就是本文的目的所在。
注:需要读懂此文的内容,请先了解上篇文章的内容:
http://blog.csdn.net/flydream0/article/details/53205286

2 分析

2.1 问题的本质原因

在进行USB CDC类开发时,无法从设备端向主机端发送64整数倍数据,最本质的原因就是,当发送数据长度恰好是Data In端点的最大包长整数倍时,最后一包数据必须是零长度的数据包(ZLP)。这是由于在USB标准中,接收端并不是通过已经接收的数据长度来判断是否接收完成,且发送端也并没有给出将要发送多长的数据,因此,接收端在接收数据前,并不知道将要接收的数据是多少,那么,问题就来了,接收端又是如何判断当前的数据已经全部接收了呢?有两点:

  • 若接收到的数据包长不足最大包长时,则认为当前传输完成
  • 如接收到的数据包长为零时,则认为当前传输完成。

正式由于上述两种判断,当传输的数据刚好是端点的最大包长时,当发送完最后一包(比如64个字节)时,接收端无法判断是否传输结束,进而继续等待下一包数据。这个就是问题本质所在。

2.2 之前的解决方案

知道问题原因后,解决的方法也就变得简单了,总的原则就是,在发送完最后一包数据后,判断发送的包长是否为端点最大包长的整数倍,如是,则补发一个零长度的数据包(ZLP)。

现在来看看上篇文章的修改代码:
在usbd_cdc.c文件中:

static uint8_t  USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)
{USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;PCD_HandleTypeDef *hpcd =pdev->pData;USB_OTG_EPTypeDef *ep;ep = &hpcd->IN_ep[epnum];if(ep->xfer_len >0 &&ep->xfer_len%ep->maxpacket ==0)//判断当前发送包长是否为端点的最大包长整数倍{USBD_LL_Transmit (pdev,epnum,NULL,0);return USBD_OK;}else{if(pdev->pClassData != NULL){hcdc->TxState = 0;return USBD_OK;}else{return USBD_FAIL;}}
}

如上代码,程序使用if(ep->xfer_len >0 &&ep->xfer_len%ep->maxpacket ==0)来判断当前发送包长是否为端点的最大包长整数倍。这个放在CubeF4中是没有问题的,但是,如放在CubeF0中是有问题的:

  • CubeF0中是没有类型USB_OTG_EPTypeDef的端点,对应的,只有PCD_EPTypeDef类型的端点。
  • 在CubeF4中ep->xfer_le表示当前的传输长度,而在CubeF0中ep->xfer_le表示的是剩余需要发送的数据长度。

    因此,此方法并不能很好的兼容CubeF4和F1,究其原因,本质上还是,STM32F4采用的USB IP核为USB_OTG_FS,USB_OTG_HS两种IP核,而STM32F0上采用的是USB IP核(STM32F1也是)。因此,本来IP核就不一样,不兼容完全就是正常现象了。

    还有一点,上述代码修改使用了与底层相关的端点类型,这就局限了其适用范围,在切换成另一个USB IP核后就不一定再适用,且USB协议栈是属于中间件件层,原则上与底层要完全抽象分离出来,保持其硬件无关性。这也是我们对中间件代码进行修改的方向,只有这样,才能保证中间件层软件的模块化和通用性。

3 新的解决方法

下面我们就来做一个通用性的解决方法,虽然不一定是最佳方法,但碰到此类问题时不失为一种值得参考的方法。新的方法避免了使用底层数据,完全保持了原先从底层分离的原则。如下:

static uint8_t  USBD_CDC_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)
{USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;
#if 1USBD_EndpointTypeDef    *pep =&pdev->ep_in[epnum];if(hcdc !=NULL){if(pep->rem_length>0 && pep->total_length >0 && pep->total_length %pep->maxpacket ==0){pep->rem_length -=pep->total_length;USBD_LL_Transmit (pdev,epnum,NULL,0);   //send ZLPreturn USBD_OK;}else{if(pdev->pClassData != NULL){hcdc->TxState = 0;return USBD_OK;}else{return USBD_FAIL;}}}
#elseif(pdev->pClassData != NULL){hcdc->TxState = 0;return USBD_OK;}else{return USBD_FAIL;}
#endif
}

如上代码,代码使用了pdev->ep_in[epnum]的数据成员rem_length(剩余数据长度)和total_length(数据总长度)来判断是否需要再发送ZLP。其实不使用rem_length也是可以的。

接下来,我们就需要保证rem_length和total_length的准确性即可。在这里,我们模仿端点0中对应值的设置,原则上只修改到usbd_conf.c和usbd_cdc.c文件,并未修改usb核的三个源码文件(usbd_core.c,usbd_ioreq.c,usbd_ctlreq.c)。从而保证其影响范围控制在USB CDC类的范围内。
Total_length是在usb reset的回调函数中设置:
//usbd_conf.c usbd_conf.c是用户文件

void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
{
USBD_HandleTypeDef *pdev =(USBD_HandleTypeDef*)hpcd->pData;USBD_SpeedTypeDef speed = USBD_SPEED_FULL;/*Set USB Current Speed*/switch (hpcd->Init.speed){case PCD_SPEED_FULL:speed = USBD_SPEED_FULL;break;default:speed = USBD_SPEED_FULL;break;}USBD_LL_SetSpeed((USBD_HandleTypeDef*)hpcd->pData, speed);
//对pdev->ep_in[xx]的最大包长赋值
pdev->ep_in[CDC_IN_EP &0x7FU].maxpacket =USB_FS_MAX_PACKET_SIZE;
pdev->ep_out[CDC_OUT_EP &0x7FU].maxpacket =USB_FS_MAX_PACKET_SIZE;
pdev->ep_in[CDC_CMD_EP &0x7FU].maxpacket =CDC_CMD_PACKET_SIZE;/*Reset Device*/USBD_LL_Reset((USBD_HandleTypeDef*)hpcd->pData);
}

如上,在USB复位中断回调函数中实现了对非0端点的其他端点的最大包长赋值,并存储在dev->ep_in[xx]中,注意这里是dev的成员ep_in[xx]中,并不是PCD中的端点中。

同样在usbd_conf.c文件中:

USBD_StatusTypeDef  USBD_LL_Transmit (USBD_HandleTypeDef *pdev,uint8_t  ep_addr,uint8_t  *pbuf,uint16_t  size)
{HAL_StatusTypeDef hal_status = HAL_OK;USBD_StatusTypeDef usb_status = USBD_OK;pdev->ep_in[ep_addr &0x7FU].total_length =size;hal_status = HAL_PCD_EP_Transmit(pdev->pData, ep_addr, pbuf, size);
//…

如上代码,在USBD_LL_Transmit函数中,实现了对pev->ep_in[xx]端点的发送长度赋值。

最后就差rem_length赋值了:
在usbd_cdc.c文件中:

uint8_t  USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
{USBD_CDC_HandleTypeDef   *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData;if(pdev->pClassData != NULL){if(hcdc->TxState == 0){/* Tx Transfer in progress */hcdc->TxState = 1;pdev->ep_in[CDC_IN_EP &0x7FU].total_length =hcdc->TxLength;pdev->ep_in[CDC_IN_EP &0x7FU].rem_length =hcdc->TxLength;/* Transmit next packet */USBD_LL_Transmit(pdev,CDC_IN_EP,hcdc->TxBuffer,hcdc->TxLength);return USBD_OK;}else{return USBD_BUSY;}}else{return USBD_FAIL;}
}

如上,在发送时,dev->ep_in[xx]端点的rem_length剩余长度会初始为发送总长度,在发送完成中断中,rem_length会即使更新,见之前的USBD_CDC_DataIn函数,这样就基本修改完成了。

4 测试验证

修改的代码是与底层分离的,因此原则上使用与STM32全系列带USB的MCU,但这里我们只验证了STM32F0与STM32F4,本文给出的示例代码分别对应了STM32F072B-Discovery和STM32F4-Discovery(STM32F407)板,且在device与host端双向无限发送数据的情况还均能稳定,因此测试结果是通过的。

5 注意事项

  • 发送完成中断是指将所有要发送的数据都发送完后产生的中断,USB 外设并不会自动根据包长情况决定是否发送ZLP;且这个中断一般是用作通知(Notification)的,可以在此回调中执行少数工作,比如状态更新等等,但原则上不要做其他大量繁重工作,避免影响通讯的稳定性能。
  • 而接收中断却不相同,不管有没有达到最大包长,只要接收到一包数据就会产生一次中断,进而回调到USBD_CDC_DataOut回调函数。这个是与Data_In不一样的地方。
  • 若Host端向Device端发送64个字节,按标准USB,host端也应该发送ZLP,但实际上Host可能并没有发送ZLP,在这种情况下,STM32依然可以正常接收,这是由于不管有没有等到ZLP,MCU端依然会产生接收完成中断,最终回调到DataOut函数中。
  • 无限从device向Host端发送数据时,需要在Host端打开串口并接收,不然在发送若干条Data_In数据后,Host端会NACK拒绝.这个由于Host端的接收缓存有限,在缓存满了后无法再接收,因此只能NACK拒绝。在打开出口后,缓存中的数据被移走,腾出新的接收空间后才能继续接收数据。

最后附上完整工程源码,下载地址: http://download.csdn.net/detail/flydream0/9833821

在进行USB CDC类开发时,无法发送64整数倍的数据(续)相关推荐

  1. 在进行USB CDC类开发时,无法发送64整数倍的数据

    1 前言 本文将基于STM32F4DISCOVERY板,介绍如何使用USB的CDC类进行开发,以及在开发过程中碰到发送64整数倍数据时会失败的问题分析及解决方案. 2 硬件介绍 在创建工程之前,我们首 ...

  2. 如何让CDC类USB设备批量接收64字节以上数据

    很多STM32开发者在实现CDC类虚拟串口与PC主机通信过程中,有时会遇到点麻烦而不得其解.那就是当主机端单次发送的数据不超过64字节时,接收正常.一旦发送数据量大于64字节时就接收失败,总是出现丢包 ...

  3. STM32 USB使用记录:使用CDC类虚拟串口(VCP)进行通讯

    文章目录 目的 基础说明 使用STM32CubeIDE配置生成代码 用户代码分析 回环测试 串口参数设置 USB HS使用与演示 通讯速率测试 测试代码 USB FS测试 USB HS测试 影响速度的 ...

  4. USB CDC从理论到实践

    本文摘自ST官网的"USB CDC类入门培训".整理的内容是我能够看得懂的,认为比较实用的,记录下来,以便以后查阅,同时也把原文档中的笔误给更正了一下.若要看更详细的可以去ST技术 ...

  5. WinCE平台USB摄像头驱动开发

    (转载)http://tech.e800.com.cn/articles/2009/116/1257487620781_1.html 由于良好的性能.低廉的价格和灵活方便的特性,USB 摄像头正被广泛 ...

  6. STM32 USB CDC 虚拟多串口

    转自: http://www.stmcu.org.cn/module/forum/thread-613510-1-1.html 楼主  发表于 2017-9-28 22:30:04 | 只看该作者 | ...

  7. [原创] STM32 USB CDC 虚拟多串口

        creep 该用户从未签到 61 主题 1995 帖子 26 蝴蝶豆 版主 最后登录 2018-10-19 发消息 电梯直达 楼主  发表于 2017-9-28 22:30:04 | 只看该作 ...

  8. 浅谈WinCE平台USB摄像头驱动开发流程

    转自http://tech.e800.com.cn/articles/2009/116/1257487620781_1.html 由于良好的性能.低廉的价格和灵活方便的特性,USB 摄像头正被广泛的集 ...

  9. ESP32-S2应用开发——USB通信(CDC类)

    ESP32S2应用开发--USB通信(CDC类) 目录 ESP32S2应用开发--USB通信(CDC类) 前言 1 硬件介绍 1.1 硬件连接 2 软件开发 2.1 安装开发板 2.2 安装库 2.3 ...

最新文章

  1. scp 命令 路径_基于SSH的文件传输:scp命令
  2. three.js写的游戏
  3. 【小o地图Excel插件版】不止能做图表,还能抓58、大众点评网页数据...
  4. python 3.5 3.6 3.7_选择 Python3.6 还是 Python 3.7
  5. 强制apt使用ipv4来更新
  6. 使用扩展技术对SAP Fiori应用进行端到端的增强,一个实际案例介绍
  7. 应用新的JDK 11字符串方法
  8. tar 解压到指定目录、去除前导目录
  9. php中这个向右的箭头怎么理解呢?$db-query
  10. Mac上的IDEA安装配置maven
  11. 微信小程序中使用setData修改变量数组或对象中的参数方法
  12. Oracle日期函数总结
  13. Eclipse更改字体大小
  14. 2022年湖北劳务资质如何办理?劳务资质不分等级
  15. 动手学深度学习 PyTorch版-Day3
  16. 鸿蒙os手机评测视频,鸿蒙OS正式发布:荣耀智慧屏首发,一起来体验鸿蒙OS吧
  17. 【Beta】Scrum Meeting 7 与助教谈话
  18. 7-4 sdut-C语言实验-分割整数
  19. 关于matlab兼容性win10系统,详解win10系统运行不了matlab10软件的办法
  20. HTTP请求偶尔失败(21秒后超时) - 问题排查

热门文章

  1. 我的第一个QT程序——QT开发环境的配置
  2. Timer/Counter
  3. LeetCode 27 合并两个排序的链表
  4. 百度蜘蛛与百度快照的关系
  5. flutter 状态管理 flutter_bloc 的使用以及总结
  6. 5本经典著作描绘学习路线图带你飞过C++
  7. 笔记本玩cf如何调全屏 笔记本玩cf屏幕两边有黑边怎么办
  8. 使用 Python 将 MP4视频 转换为GIF动画
  9. Emily Dickinson 《Not In Vain》
  10. 基尔霍夫电压电流定律