打印

STM32 USB改双缓冲后,STM32的OUT接收速度到了1MB/S!

[复制链接]
32576|65
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
alien2006|  楼主 | 2008-1-12 20:49 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
前天测试自己编写的USB驱动程序时候发现从主机到STM32的OUT传输(主机到设备)速率竟然只有最高33KB/S,实在是晕死了。经过研究后发现是驱动程序中设置的PIPE MaxTransferSize参数的关系,原先设置64只能33KB/S,后参考其他USB设备驱动程序的值,设置成了65535,再测试USB OUT的速度,达到了500KB/S,终于解决了驱动程序的瓶颈。不过算下USB 2.0全速的通讯速率是12Mb/S,排除掉CRC、令牌、SOF等等开销怎么也应该不止最大500KB/S啊。到网上看了看,基本上应该能达到600KB/S~700KB/S以上,我现在的速度应该还有很大的提升才是。 
    看看程序,发现 
void EP3_OUT_Callback(void)//EP3 OUT的回调函数,当EP3接收到数据时候中断调用该函数 

  count_out = GetEPRxCount(ENDP3);//获得接收到的数据长度 
  PMAToUserBufferCopy(buffer_out, ENDP3_RXADDR, count_out);//将数据从USB EP3 RX的缓冲区拷贝到用户指定的数组中 
  SetEPRxValid(ENDP3); //完成拷贝后置有效状态,从而EP3发送ACK主机可以进行下一个数据包的发送 

    试着将PMAToUserBufferCopy这句注释掉(这样STM32就不处理接收到的数据了)后再测试速度,惊奇地发现速度竟然达到了997KB/S!晚上仔细想了想,数据肯定是要使用的,这个数据拷贝的过程的时间消费总是少不了的;由于通常情况下USB设备BULK数据接收的步骤就是:接收到数据,置NAK->将缓冲区数据拷贝到用户区(用户处理过程)->发ACK通知主机完成了完整的接收可以发送下一个->主机发送下一个,按照以上的步骤USB接收一步步的进行,只要STM32不完成数据处理,状态就一直是NAK,主机就会不停地发送该数据包,浪费了带宽,因此就会导致我上面最大速度500KB/S难以再增加的情况!不甘心啊~~ 
    昨天晚上又仔细研究了STM32的技术参考手册的USB章节内容,里面提到BULK可以采用双缓冲机制(PING-PONG)进行处理,正好可以解决上面的情况。双缓冲机制的原理就是分配2块接收缓冲,STM32的用户处理和USB接口可以分别交替占用2个缓冲区,当USB端点接收数据写其中一个缓冲区的时候,用户的应用程序可以同时处理另一个缓冲区,这样缓冲区依次交换占有者,只要用户处理程序在USB端点接收的时间片段内完成处理,就能够完全不影响USB的通讯速度! 
    程序部分修改 
一、EP3_OUT的设置修改, 
//ZYP:修改EP3为BULK双缓冲方式------------------------- 
  SetEPType(ENDP3, EP_BULK); 
  SetEPDoubleBuff(ENDP3); 
  SetEPDblBuffAddr(ENDP3, ENDP3_BUF0Addr, ENDP3_BUF1Addr); 
  SetEPDblBuffCount(ENDP3, EP_DBUF_OUT, VIRTUAL_COM_PORT_DATA_SIZE); 
  ClearDTOG_RX(ENDP3); 
  ClearDTOG_TX(ENDP3); 
  ToggleDTOG_TX(ENDP3); 
  SetEPRxStatus(ENDP3, EP_RX_VALID); 
  SetEPTxStatus(ENDP3, EP_TX_DIS); 
//------------------------------------------------------ 
二、EP3_OUT回调函数的修改 
void EP3_OUT_Callback(void) 

//ZYP:以下是修改成EP3双缓冲OUT后的处理函数 
  if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//先判断本次接收到的数据是放在哪块缓冲区的 
  { 
    FreeUserBuffer(ENDP3, EP_DBUF_OUT); //先释放用户对缓冲区的占有,这样的话USB的下一个接收过程可以立刻进行,同时用户并行进行下面处理 
    count_out = GetEPDblBuf0Count(ENDP3);//读取接收到的字节数 
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out); 
  } 
  else 
  { 
    FreeUserBuffer(ENDP3, EP_DBUF_OUT);  
    count_out = GetEPDblBuf1Count(ENDP3); 
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out); 
  } 

    经过上面的修改,终于解决了STM32在处理接收数据时导致主机等待的情况,用BUS HOUND软件测试了下 
 哈哈,这下终于爽了。 
PS:上面的FreeUserBuffer(ENDP3, EP_DBUF_OUT); 这句话的上下位置是关键,如果放到函数的后面,则仍旧会有主机等待STM32处理数据的情况,速度仍然是500KB/S! 
    把这句话放在拷贝函数的前面的话就真正把双缓冲PING-PONG机制用起来了。大致算了下PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);这句话当count_out为最大值64的时候STM32执行需要302个周期,72MHZ情况下约4.2微秒执行时间,而USB传输按照12Mb/s的线速度传输64字节的数据至少也得40微秒,因此只要PMAToUserBufferCopy的时间不超过40微秒,就不会导致缓冲区竞争的情况。 



沙发
hotpower| | 2008-1-12 21:00 | 只看该作者

沙发~~~那天楼主搞个STM32的USB专集~~~

使用特权

评论回复
板凳
computer00| | 2008-1-12 21:01 | 只看该作者

哈哈,恭喜了。双缓冲是个好东西~~~D12里面也有。

如果两个缓冲区同时都满了,怎么去区分哪个先满,哪个后满的呢?

使用特权

评论回复
地板
alien2006|  楼主 | 2008-1-12 21:12 | 只看该作者

另外,由于手头没有双缓冲BULK的使用范例

    也不知道上面的的修改是否合理,只是匆匆测试了下,数据接收似乎没啥问题。
   考虑之下,也许在极端的情况下,比方说MCU还在拷贝数据缓冲BUFFER0这300多个周期中,这时候产生了较长时间的中断,如果这时候下一个包也已经由USB接口写入到BUFFER1中,由于用户占用的缓冲区BUFFER0在拷贝前已经通知释放,这样USB接口会继续将第三个包写入BUFFER0,而实际上BUFFER0的用户拷贝过程仍未完成,会导致缓冲区BUFFER0内的原先第一个包的数据被破坏,破坏数据完整性。
   应该还需要再完善一下,还请大家再出出主意

使用特权

评论回复
5
alien2006|  楼主 | 2008-1-12 21:19 | 只看该作者

圈圈大侠说得是啊

上面的例子是没考虑判断的,知道您是USB的专家,接下来我该怎么做呢?


使用特权

评论回复
6
hotpower| | 2008-1-13 10:48 | 只看该作者

敢劳驾楼主和00给俺发些USB的扫盲资料???跪谢了~~~

哈哈~~~

汽油桶: HotPower@126.com 

使用特权

评论回复
7
ijk| | 2008-1-13 15:56 | 只看该作者

楼主真是高人

  没几天就把USB传输搞定了,是学习的榜样!

使用特权

评论回复
8
computer00| | 2008-1-13 16:34 | 只看该作者

晕...这个我也不知道呀...要仔细研究下这个数据手册...

看有没有相关标志了。如果没有,再去找找看能不能获取到是DATA0包还是DATA1包,它们是交替出现的。

使用特权

评论回复
9
hotpower| | 2008-1-13 16:42 | 只看该作者

哈哈~~~一切技术障碍都将成为坦途,胡作非为的时候就要到了~

使用特权

评论回复
10
香水城| | 2008-1-13 16:48 | 只看该作者

STM32的USB模块当然有DATA0/DATA1的标志,而且每个端点都有

请看《STM32技术参考手册》的第17.6.2节,DTOG_RX用于指示接收,DTOG_TX用于指示发送。

使用特权

评论回复
11
alien2006|  楼主 | 2008-1-13 21:24 | 只看该作者

汗~~ 说实话自己现在对USB还是云里雾里呢

    东一榔头西锤的,USB固件还是拿现成的例子改来改去的,心里也没底,也想能系统的学习需诶,期待圈圈和版主能给大家上上USB的课:)
    好在现在对USB驱动程序的编写稍微知道了点,现在利用DriverStudio来写驱动程序倒是还算顺利。毕竟WINDOWS上面的东西大部分都是微软给你写好了的。不像固件的编写,要对协议和硬件等都非常了解,那可才是真功夫啊。
    
    

使用特权

评论回复
12
McuPlayer| | 2008-1-13 23:42 | 只看该作者

等Alien的新研究结果

使用特权

评论回复
13
hotpower| | 2008-1-14 00:15 | 只看该作者

哈哈~~~看来有空一定扫扫USB的盲区才是~~~

使用特权

评论回复
14
aleyn| | 2008-1-14 15:16 | 只看该作者

为alien2006和USB加油!

为alien2006和USB加油!

使用特权

评论回复
15
alien2006|  楼主 | 2008-1-14 15:38 | 只看该作者

呵呵,谢谢大家

另外再预告一下,昨天晚上解决了按照我一楼的程序可能存在缓冲区竞争导致数据被误写的问题,提供了完全可靠的处理,同时又不会影响速度,可以保证几乎1MB/S的接收速度,哈哈
   先卖个关子,现在没时间,等晚上有空再详细写来

使用特权

评论回复
16
alien2006|  楼主 | 2008-1-14 21:22 | 只看该作者

正常可靠的程序应该如下

void EP3_OUT_Callback(void) 

  if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//判断接收到的数据在哪个缓冲区 
  { 
    count_out = GetEPDblBuf0Count(ENDP3);//读取接收到的字节数 
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out); //用户拷贝出数据
  } 
  else 
  { 
    count_out = GetEPDblBuf1Count(ENDP3); 
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out); 
  }
  FreeUserBuffer(ENDP3, EP_DBUF_OUT); //翻转标志,释放用户缓冲区 

    STM32的USB端点双缓冲制有2个标志位来保障PING-PONG,只要用户不释放对缓冲区的占用,2个标志位相同它就会发NAK,主机会重发数据;只有MPU完成了拷贝工作释放缓冲区,状态位翻转,才会发ACK,通知主机进行下一个传输,因此上面这段程序利用了它的PING-PONG机制可以确保数据不会冲突。
    但是有个问题很奇怪,就是上面这段程序执行这400个不到的周期却会对USB传输速度有很大影响,从单缓冲的最大505KB/S到双缓冲566KB/S,提升实在有限!

使用特权

评论回复
17
alien2006|  楼主 | 2008-1-14 21:32 | 只看该作者

如何能兼顾最高的USB速度和保证数据不会冲突

做到鱼和熊掌兼得呢,昨天晚上意外地发现竟然很简单!
void EP3_OUT_Callback(void) 

  if (_GetENDPOINT(ENDP3) & EP_DTOG_TX)//判断接收到的数据在哪个缓冲区,读取接收到的字节数,从原来用函数方式的18个周期提高到3个周期  
  { 
    count_out = _GetEPDblBuf0Count(ENDP3//原来26个周期现在3个周期
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out); //用户拷贝出数据
  } 
  else 
  { 
    count_out = _GetEPDblBuf1Count(ENDP3); 
    PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out); 
  }
  _ToggleDTOG_TX(ENDP2);//原来29个周期现在14个周期

    真是有点想不通,把3处函数替换成宏之后,大约总共减少不过52个周期,可是USB的速度可是翻倍了,又达到了1MB/S!(单缓冲即使修改成宏,最高也不过550KB/S,先天不足)
    个中缘由哪位大侠能解释解释?


 

使用特权

评论回复
18
香水城| | 2008-1-14 21:52 | 只看该作者

尝试解释17楼所说的现象

你的试验是Bulk OUT,试想USB总线上的数据包如下:
  +-----+-------+--------------+ +-----+       +-----+-------+--------------+ +-----+
  | OUT | DATA0 | 64 Byte Data | | ACK | Delay | OUT | DATA1 | 64 Byte Data | | ??? |
  +-----+-------+--------------+ +-----+       +-----+-------+--------------+ +-----+

这是连续2个OUT数据包,当你收到第1个数据包后,如果Firmware来不及接受第2个数据包,则第2个数据包就会被NAK,这时PC将再次发送第2个数据包。我们可以看到,只要收到第1个数据包后,Firmware不能在第2个数据包到来之前(不是结束之前)设置好Endpoint,就要浪费整整一个数据包的传送时间;如果Firmware能按时设置好Endpoint的状态,哪怕是1us,则可能赢得了一个数据包的传送时间。

估计楼主碰到的正是这种情况

使用特权

评论回复
19
computer00| | 2008-1-14 22:58 | 只看该作者

哈哈~~~USB1.1就存在这么一个问题...2.0里面增加了ping包,

可以减少这个浪费(好象只能在高速模式下使用ping?记不清楚拉,晕)。

使用特权

评论回复
20
alien2006|  楼主 | 2008-1-14 23:15 | 只看该作者

香版分析得很透彻,我明白了

自己还是对USB的协议理解不够深啊。香版主的功力深厚,非常感谢!
    现在BULK OUT采用双缓冲终于没问题了,刚才编了个小程序,PC的程序不停地向STM32中的4K字节的数组写入4K个随机数随后再从该数组读出进行比较累计出错的次数,试了N多次,都没有出错,这个OUT双缓冲看来是很可靠的。
    等下再把BULK IN也改成双缓冲,呵呵,我是速度偏执狂啊,哈哈!!

使用特权

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

本版积分规则

16

主题

136

帖子

6

粉丝