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

【实战经验】进行USB CDC类开发时,无法发送64整数倍数据(续)

[复制链接]
zero99 发布时间:2017-5-5 09:39
在进行 USB CDC类开发时,
无法发送64整数倍的数据(续)

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

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

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

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

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

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

如上代码,程序使用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 新的解决方法
下面我们就来做一个通用性的解决方法,虽然不一定是最佳方法,但碰到此类问题时不失为一种值得参考的方法。新的方法避免了使用底层数据,完全保持了原先从底层分离的原则。如下:
13.jpg
14.jpg

如上代码,代码使用了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是用户文件
15.jpg

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

同样在usbd_conf.c文件中:
16.png

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

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

如上,在发送时,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拒绝。在打开出口后,缓存中的数据被移走,腾出新的接收空间后才能继续接收数据。


在进行USB CDC类开发时,无法发送64整数倍的数据(续).pdf (371.01 KB, 下载次数: 99)
1 收藏 3 评论5 发布时间:2017-5-5 09:39

举报

5个回答
原来是他 回答时间:2017-6-28 08:40:12
谢谢楼主分享~
valetang-126879 回答时间:2017-6-28 09:19:55
谢谢楼主分享
wofei1314 回答时间:2017-8-27 10:31:42
好贴~

学习来了,谢谢分享~
shuichengwen 回答时间:2018-7-27 10:20:31
多谢分享
tankyhai 回答时间:2020-11-24 10:29:29
多谢分享

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

官网相关资源

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