打印
[信息]

【实战经验】在进行USB CDC类开发时,无法发送64整数倍的...

[复制链接]
4600|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
CD, USB, dc, ST, AC
本帖最后由 香水城 于 2017-8-11 14:33 编辑

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

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

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

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

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

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

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

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

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

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

同样在usbd_conf.c文件中:

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

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

如上,在发送时,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_TEST
更多实战经验请看:【ST MCU实战经验汇总贴】

沙发
王紫豪| | 2017-4-19 12:45 | 只看该作者
版主的**非常好,支持下

使用特权

评论回复
板凳
mmuuss586| | 2017-4-19 12:55 | 只看该作者

学习了

使用特权

评论回复
地板
aolin| | 2017-4-19 13:45 | 只看该作者
非常清晰,请问什么时候会有更新库发布?

使用特权

评论回复
5
lxl2wcj| | 2017-4-19 15:23 | 只看该作者
楼主,我用STM32F103照着改,发送64个字节没问题,可是发送65、66……(大于64个字节时),就会出现接收的数据缺失的情况,比如串口助手向MCU发送65个字节,MCU只返回64个字节(串口助手只显示64个字节)

使用特权

评论回复
6
ddllxxrr| | 2017-4-19 20:10 | 只看该作者
好**不得不顶

使用特权

评论回复
7
icecut| | 2017-4-19 21:07 | 只看该作者
有人问过我.....

使用特权

评论回复
8
wofei1314| | 2017-4-21 20:59 | 只看该作者
使用STM32F042单片机测试,果然好用,

使用特权

评论回复
9
dxfshsh| | 2017-5-7 20:00 | 只看该作者
质疑一下,香主,如果在一个64字节发送包后,发送一个长度为零的包,是可以将前面的那64个字节,顶出串口,但可能回带来下一个问题,PC端并没有认为通讯就此结束了,还再等待下面的数据,如果你此时关闭PC端串口,可能会出现等待,直到没有响应,串口司机。

使用特权

评论回复
10
dxfshsh| | 2017-5-7 20:15 | 只看该作者
解决该问题的最佳,方法就是拆包。

使用特权

评论回复
11
Dark_guan| | 2017-7-10 12:43 | 只看该作者
我仔细看了看新的Cube库里面都处理了这些了,关于最大buffer啥的

使用特权

评论回复
12
ZCShou| | 2018-7-31 09:52 | 只看该作者
本帖最后由 ZCShou 于 2018-7-31 09:53 编辑

贴一下使用标准外设库的驱动(STM32F0x2xx USB FS device library 我是在移植到070C6时),驱动库其实有处理发送数据正好为包大小,但是处理有问题,具体usbd_cdc_core.c 中的 uint8_t usbd_cdc_DataIn (void *pdev, uint8_t epnum)函数如下(见注释)
uint8_t  usbd_cdc_DataIn (void *pdev, uint8_t epnum)
{
  uint16_t USB_Tx_ptr;
  uint16_t USB_Tx_length;
  
  if (USB_Tx_State == 1)
  {
    if (APP_Rx_length == 0)
    {
      if (last_packet ==1)
      {
        last_packet =0;
        
        /*Send zero-length packet*/
        DCD_EP_Tx (pdev, CDC_IN_EP, 0, 0);
      }
      else
      {
        USB_Tx_State = 0;
      }
    }
    else
    {
      if (APP_Rx_length > CDC_DATA_IN_PACKET_SIZE){
        USB_Tx_ptr = APP_Rx_ptr_out;
        USB_Tx_length = CDC_DATA_IN_PACKET_SIZE;
        
        APP_Rx_ptr_out += CDC_DATA_IN_PACKET_SIZE;
        APP_Rx_length -= CDC_DATA_IN_PACKET_SIZE;   
      }
      else
      {
        USB_Tx_ptr = APP_Rx_ptr_out;
        USB_Tx_length = APP_Rx_length;
        
        APP_Rx_ptr_out += APP_Rx_length;
        /* 这里两句明显有问题吧,这两句顺序颠倒应该就可以了 */
        APP_Rx_length = 0;                       
        if (APP_Rx_length == CDC_DATA_IN_PACKET_SIZE) last_packet = 1;
      }
      
      /* Prepare the available data buffer to be sent on IN endpoint */
      DCD_EP_Tx (pdev,
                 CDC_IN_EP,
                 (uint8_t*)&APP_Rx_Buffer[USB_Tx_ptr],
                 USB_Tx_length);
    }
  }  

使用特权

评论回复
13
caoenq| | 2020-5-22 09:05 | 只看该作者
试了,还是不行。。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:意法半导体(中国)投资有限公司
简介:STM32技术专家

596

主题

17058

帖子

283

粉丝