请选择 进入手机版 | 继续访问电脑版

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

【实战经验】USB传输数据时出现卡顿现象

[复制链接]
zero99 发布时间:2017-9-28 15:55
USB传输数据时出现卡顿现象

2 U2 p& Q: I  W  m9 G1 前言! z! Y, @3 O5 h/ t2 r: a$ Q( f
    在进行USB开发的过程中,有多个客户反馈,USB传输数据时出现卡顿现象。本文将针对这一问题进行分析。9 b% l  q0 r- H9 J" ~( u0 ^# L
+ n0 y5 U; j2 f/ A
2 问题分析$ k* a9 m6 j8 Q0 `' v7 l
    这几个客户问题现象基本差不多,采用STM32作为Device设备,在与上位机或者PC端双向通讯一段时间后,从Device端到Host端的数据能够正常,而从Host端到Device端的数据异常,也就是说,STM32在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。8 x! s& R( x: B+ \8 G
    这几个客户,有用STM32F2的,也有用STM32F4的,有用CDC类的,也有用作HID设备的,但都使用了Cube库。
5 o: F6 L) N( Z4 Q& T7 @    下面就具体问题以其中一个客户使用STM32F411的USB CDC类的案例来分析问题,现象如下USB通讯数据(CDC类):# Q# x8 c' T& r  ?% n
      
11.png , O+ k. V1 Z  B+ F
Figure 1 通信一段时间后Data Out数据异常( t% E0 [6 [) ^9 P5 U
* A- i4 d( ?! W7 `8 s7 V
展开Data Out数据:
) k$ d$ _. F- ]2 }& r
12.jpg
% z3 X( z8 N: q0 pFigure 2 Data Out数据异常
- V9 O+ F5 o7 l. S
  ]* b6 g: |  v4 ?' w分析上图发现,并不是Host端没有向Device端发送Data Out数据,而是确实发送了,但被Device端NAK了。那么为什么会被NAK呢?" m0 N# j# g# [8 Y- E
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT对应的端点1是处于关闭状态,那么为什么端点1会关闭?查看STM32端的接收代码:
2 G, S2 p! l- N' D* D! j" G  ?$ n4 K3 S: u* ]3 w( N
usbd_cdc_if.c: : r, [, k4 K* l5 L$ u. {/ Z
13.jpg $ X. r" v( @) z5 r& s* R4 P" \. \
如上代码,在MCU端接收到赖在Host端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现Data Out端点关闭的问题,那么OUT端点到底是怎么关闭的呢?我们接下来看子函数:: b3 J+ K) ^$ u5 @$ ?  x
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() ->HAL_PCD_EP_Receive() ->USB_EPStartXfer()
' X0 t" G+ N8 }最后在USB_EPStartXfer函数中有发现再次使能OUT端点的代码:
, A. o) |% |2 _9 G
14.jpg
4 ^) E( m( h% L/ D2 }
15.jpg & k- y+ {) v2 ?+ m/ G! L( h/ j1 O
也就是说,在调用这个函数之前这个OUT端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是OUT端点一旦打开就一直开着的吗?带着这些疑问,我们查看STM32F411的参考手册,终于在22.17.6 Operational model一节中找到这么一幅图(由于CDC类数据传输采用的是BULK传输):# ?1 E* M8 o9 {( c2 G( ^' T9 _
16.jpg
; i1 k8 _6 m5 kFigure 3 MCU对OUT数据传输的处理流程! q# P5 I$ A7 i  y$ l
7 v# t6 j: s1 T+ A
如上图,MCU对BULK类型的OUT数据处理例程大体如下:3 y2 d( j  j: x' T
1> Host端试图向一个端点发送OUT token;
/ T! K- V+ k. y2 U! k2> 当Device端的USB外设接收到这么一个OUT token后,如果RXFIFO空间足够,它将数据包存储到RXFIFO中;
7 a; j5 _7 V. N0 J, c+ R3> 在将数据包内容存储到RXFIFO后,USB外设将产生一个RXFLVL中断(OTG_FS_GINTSTS);
& L+ y3 L1 j7 e4> 在接收到USB数据包的个数后(PKTCNT),USB核将内部自动将这个OUT端点的NAK为置1,以阻止接收更多数据包;
/ z+ j+ m1 A8 q, `! I7 y) _7 V5> 应用程序处理RXFLVL中断和从RXFIFO读取数据;
6 l$ ]6 t4 p, t$ A- |# `1 {9 A6> 当应用读取完所有数据(等于XFRSIZ)后,USB核将产生一个XFRC中断(OTG_FS_DOEPINTx);; o# \9 W9 y- ~/ n7 y
7> 应用处理这个OTG_FS_DOEPINTx中断并通过OTG_FS_DOEPINTx 的中断为XFRC来判断传输完成;
- F" `( c7 x+ V! l
! |8 g2 B$ S) G3 x4 K从上面步骤中的第4步中可以看出,当USB核收到来自Host端的数据后会自动将OUT端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个OUT端点的原因。因此我们大体可以判断出在OUT数据传输的过程中,USB核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在USB核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:, V" Q; w6 G. e/ u' X+ a
17.png
1 w* P# R$ N% A  q
& i# k+ B. G" I7 i0 T* \之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:
3 p1 O& G& ^# Q3 E/ q$ Y
19.jpg
1 w8 z3 ?! C( R4 u% {
9 `0 Z6 T5 k8 W! ^4 \) c* L( c接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到main等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;# v4 E& Y/ [; S4 z) H
. l% A5 ~3 U# n8 O4 G  Q
我们进一步查看__HAL_LOCK()宏定义:
% o: W; F% x5 \, h! d$ ^* b, l 20.png
, j& y9 L: O+ N; U, H. E
1 D% B+ [. y0 S: W0 I0 D2 ^- l- y1 }. [0 b9 V9 L4 v( y9 Z
若__HAL_LOCK(hpcd);失败则直接返回return HAL_BUSY的。为了验证在接收过程中是否__HAL_LOCK失败,我们引进全局变量Lock_Flag,在发送函数中若成功LOCK则设置Lock_Flag=1,UNLOCK后则复位为0:1 m: X- x* G( i
21.png
9 @% D$ h+ j3 `4 v% d* L) W
4 y& L! z7 j; d" D( a/ k3 c接下来在接收函数中对全局变量Lock_Flag值进行判断,若为1则锁死程序,因为在Lock_Flag=1时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回HAL_BUSY;这里通过锁死代码以便判断这种情况:
  [: m# ~" [; z- G# v$ ^# h) X; M 22.jpg
5 Q9 b- {  ]) }通过调试,当出现问题时,程序果然被锁死在这个while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:. Q3 C4 r2 n% }) _
1> 在mian函数中发送数据CDC_Transmit_FS()
1 X) d/ o1 {9 y2> USBD_CDC_TransmitPacket()
0 I+ _7 B" R3 `4 l9 k* \5 D& f3> USBD_LL_Transmit()
# n' C' L+ [: z4> HAL_PCD_EP_Transmit()6 K( ]$ y) `* ~7 G9 Z5 F2 b
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁
+ K3 |, j) {8 U1 r  }! K( }* F* y6> 恰好此时有一个接收中断,由于USB中断具有优先级,跳转到接收中断内执行;同时,USB核会自动关闭OUT端点;, P5 Y$ S& V+ B+ q- ~$ E; z- n
7> HAL_PCD_DataOutStageCallback()
. `0 T1 O1 ?1 k% ~8> USBD_CDC_DataOut()# _1 E" h" h; ^3 u* s
9> CDC_Receive_FS()
9 N. W+ Y/ l' p+ Y10> USBD_CDC_ReceivePacket()0 E2 F' Q5 `9 |( p' j; U
11> USBD_LL_PrepareReceive(), V8 [% ?4 @- t- V
12> HAL_PCD_EP_Receive()
* Z& p" l" L8 j; y. r( M13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在OUT端点没有再次打开就已经提前结束,导致接收循环无以为继。& u3 x: M6 f! W% M: r8 K8 \

  T1 }7 f+ ^: |2 `. D- F3 解决方案& t9 N5 ?) R3 D
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与USB接收中断相同的中断等级中去,例如可以利用USB的EOPF中断,在开启EOPF中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF每1ms触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
8 ?) q8 x& r7 t* Z) F+ O3 s4 G; o6 ?  A- b; V# Z4 }& }0 Q' C
此外,其实此问题是出现在Cube库的低版本中,例如CubeF4 V1.5.0和CubeF2 V1.3.0中都存在,但是在最新本的CubeF4 V1.16.0,CubeF2 V1.6.0版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
" g! G; I6 }2 d! l+ H0 K

1 ^. d; G" z. K/ T

# U7 K, {( f1 L
+ W9 B/ e6 p/ D

5 `+ j) ~% \* l) n4 d  C! C' O- [文档下载1>>         文档下载2>>       更多实战经验>>
" p6 f8 Y3 q" n# Y+ z8 T
收藏 3 评论7 发布时间:2017-9-28 15:55

举报

7个回答
斜阳 回答时间:2017-9-28 16:55:22
HAL的锁中断不安全到现在都没有解决吧
斜阳 回答时间:2017-9-28 17:04:38
TIM图片20170928170248.png
  z; f: V& O% G% y) _% y: E! qst的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了7 G: h2 G- [5 R3 t% g
" X# R$ _* J$ F% ~! l9 [/ C# H) n
小小向日葵 回答时间:2017-10-9 16:39:58
斜阳__ 发表于 2017-9-28 17:04; q1 U; O) j; B  g# }, D
st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了0 T7 t$ g0 _  ^7 a2 s

2 |3 z+ Z- N" z3 w4 [4 L ...
( u3 }  b6 T% }; b1 K; S
锁删除之后,还能工作吗?0 Q! q& G0 K; G! {8 H2 W4 t
2023从心出发 回答时间:2017-10-27 11:38:38
MARK  
anobodykey 回答时间:2017-10-27 15:00:10
cube库还是问题多多呀
wylew 回答时间:2018-1-19 21:13:19
你好,我想问下,那个计算非0-OUT端点传输大小和包个数的公式是怎么算来的?就是当ep->xfer_len为0和不为0时计算传输大小和包个数的公式,谢谢了
agmdr 回答时间:2018-1-20 07:56:26
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版