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

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

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

3 o* s+ X: q* M! t( i+ n# V1 前言3 t0 y  H1 d1 O0 k1 a1 D* q  G
    在进行USB开发的过程中,有多个客户反馈,USB传输数据时出现卡顿现象。本文将针对这一问题进行分析。
/ s& F* P7 n/ j9 c" J$ G- A, x  |& w: v( j" P. L
2 问题分析5 p# y, j7 W4 g
    这几个客户问题现象基本差不多,采用STM32作为Device设备,在与上位机或者PC端双向通讯一段时间后,从Device端到Host端的数据能够正常,而从Host端到Device端的数据异常,也就是说,STM32在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
$ D5 g0 a) W$ V- B) k    这几个客户,有用STM32F2的,也有用STM32F4的,有用CDC类的,也有用作HID设备的,但都使用了Cube库。
. z! n9 v$ b; K4 r$ i: D6 A    下面就具体问题以其中一个客户使用STM32F411的USB CDC类的案例来分析问题,现象如下USB通讯数据(CDC类):" r( c# M8 E9 e/ p$ Y9 F
      
11.png ( E1 T) F; j" U  a8 W4 a
Figure 1 通信一段时间后Data Out数据异常6 N7 O( S0 f. f1 y

# X. c/ H/ G8 v9 U+ R展开Data Out数据:; v1 [! A! P( m: d) ~9 t6 \$ F7 L' ~
12.jpg
; z( h5 y: K* l( qFigure 2 Data Out数据异常8 j, T3 w0 n4 d6 d. T4 P

0 q9 p1 N5 B' A4 y分析上图发现,并不是Host端没有向Device端发送Data Out数据,而是确实发送了,但被Device端NAK了。那么为什么会被NAK呢?
4 b' g0 b0 {1 n通过在调试下查看寄存器,我们发现当出现问题时,Data OUT对应的端点1是处于关闭状态,那么为什么端点1会关闭?查看STM32端的接收代码:
) g4 Y/ e/ q0 s# i+ w& }7 Z( I. B2 ]) S" i0 J
usbd_cdc_if.c: # H* r7 B% o5 ?
13.jpg
/ z! Q5 @& ^  e% ^3 _* s如上代码,在MCU端接收到赖在Host端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现Data Out端点关闭的问题,那么OUT端点到底是怎么关闭的呢?我们接下来看子函数:4 b4 h* T6 d9 J, B
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() ->HAL_PCD_EP_Receive() ->USB_EPStartXfer()
" @0 |$ [. H) e1 J8 o5 X最后在USB_EPStartXfer函数中有发现再次使能OUT端点的代码:
8 h* ]9 z4 R% e, S; n- Z* i8 @# Y
14.jpg
9 Y7 z2 B2 l2 w! S
15.jpg - ~  j/ l3 [$ w: a% l. v( P1 i
也就是说,在调用这个函数之前这个OUT端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是OUT端点一旦打开就一直开着的吗?带着这些疑问,我们查看STM32F411的参考手册,终于在22.17.6 Operational model一节中找到这么一幅图(由于CDC类数据传输采用的是BULK传输):
5 }8 M. K! f) _, q2 s
16.jpg
3 u7 t( g- E$ ], J0 T: }Figure 3 MCU对OUT数据传输的处理流程3 o/ }$ T6 T& E, h" s
1 Q$ }  _7 m3 X6 p- S  o# |  ^
如上图,MCU对BULK类型的OUT数据处理例程大体如下:0 i" U0 E- \8 s
1> Host端试图向一个端点发送OUT token;' ?4 N% R+ s1 @0 K( j7 }! g$ v
2> 当Device端的USB外设接收到这么一个OUT token后,如果RXFIFO空间足够,它将数据包存储到RXFIFO中;3 k& h( J  O8 j! F, m
3> 在将数据包内容存储到RXFIFO后,USB外设将产生一个RXFLVL中断(OTG_FS_GINTSTS);
0 I/ z* g' b% k# K4> 在接收到USB数据包的个数后(PKTCNT),USB核将内部自动将这个OUT端点的NAK为置1,以阻止接收更多数据包;! H4 ]4 d5 Q2 Q9 |
5> 应用程序处理RXFLVL中断和从RXFIFO读取数据;; t" Z9 }, g' `5 L4 E0 m% s4 j. [& c
6> 当应用读取完所有数据(等于XFRSIZ)后,USB核将产生一个XFRC中断(OTG_FS_DOEPINTx);4 ]# j: T4 P* u& X! v
7> 应用处理这个OTG_FS_DOEPINTx中断并通过OTG_FS_DOEPINTx 的中断为XFRC来判断传输完成;
, a5 C( _  X; ?& K3 R+ _
$ A! p) i1 Q. c' u从上面步骤中的第4步中可以看出,当USB核收到来自Host端的数据后会自动将OUT端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个OUT端点的原因。因此我们大体可以判断出在OUT数据传输的过程中,USB核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在USB核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
) B5 p9 F: `3 a# R! h. P# J4 Z
17.png % {, U' W; H6 X2 c: Z5 k
* h' ?" P/ D: P9 x
之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁: 5 d$ A8 ?& d, `. e
19.jpg ( m9 n$ Y2 \; m

! N7 n, x4 y; R' j4 c接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到main等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;2 l# x0 Z5 ?4 r1 r
; T: b. r! C: Q  `$ k% ?
我们进一步查看__HAL_LOCK()宏定义:
! X) I7 n9 ?* B3 Z+ u 20.png
& z- Z! _2 o/ H " i% c  T6 Z7 G4 a1 Y! ]8 M2 a5 X

7 L0 t+ I- v4 ^1 v9 Y- R若__HAL_LOCK(hpcd);失败则直接返回return HAL_BUSY的。为了验证在接收过程中是否__HAL_LOCK失败,我们引进全局变量Lock_Flag,在发送函数中若成功LOCK则设置Lock_Flag=1,UNLOCK后则复位为0:
8 f, W  i" h" q" s9 b' q 21.png
0 g  L* C  \8 r* f
- m: J$ t: @* K" ~: J) n接下来在接收函数中对全局变量Lock_Flag值进行判断,若为1则锁死程序,因为在Lock_Flag=1时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回HAL_BUSY;这里通过锁死代码以便判断这种情况: 7 {9 @  H6 b6 D, M8 @
22.jpg - F" M; W0 k: W( J
通过调试,当出现问题时,程序果然被锁死在这个while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
4 K* G1 T) _8 F# ?1> 在mian函数中发送数据CDC_Transmit_FS()
/ d: |, ~4 N. V  P; t) C  ~0 o! a2> USBD_CDC_TransmitPacket()5 G: r# z( A$ _+ R
3> USBD_LL_Transmit()
) F2 q8 R- {" U4> HAL_PCD_EP_Transmit()$ b) o/ Y# {( E9 p" \8 C/ N
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁
) {# @1 l$ R0 x3 r- g6> 恰好此时有一个接收中断,由于USB中断具有优先级,跳转到接收中断内执行;同时,USB核会自动关闭OUT端点;
0 f' ?/ w  {6 d4 {+ p  U: Q' K7> HAL_PCD_DataOutStageCallback()
  j1 w4 j1 b7 B6 X0 ]8> USBD_CDC_DataOut()
8 \9 [& h" T, Q1 e, k9 P; h9 o9> CDC_Receive_FS()+ a" Y: w! W4 C" G& N
10> USBD_CDC_ReceivePacket()
  {# y& x  L( B) g3 O3 A11> USBD_LL_PrepareReceive()
4 R" S& M+ n+ y8 K7 S% t7 r" R% ]: g12> HAL_PCD_EP_Receive()
9 \8 `5 v% U+ B: O+ z. B& Z8 v13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在OUT端点没有再次打开就已经提前结束,导致接收循环无以为继。
+ |/ I# y5 S+ i  s
, \- \, o" q- g! z  w" T% z3 解决方案8 A/ d- y  g; j, R0 m# ?
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与USB接收中断相同的中断等级中去,例如可以利用USB的EOPF中断,在开启EOPF中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF每1ms触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。  Q! v4 j7 I+ e2 r: S' w5 Y' t$ H
, T( |/ I7 h% ~1 _9 C, G
此外,其实此问题是出现在Cube库的低版本中,例如CubeF4 V1.5.0和CubeF2 V1.3.0中都存在,但是在最新本的CubeF4 V1.16.0,CubeF2 V1.6.0版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。

+ Z$ N! X, e. V% B6 E- P; z. ~

4 u# Q% h5 s7 r, G" S! Z. H' b' Z% U* g3 g" V  }
( w" u" ]) h$ @2 D
文档下载1>>         文档下载2>>       更多实战经验>>
) V2 l" Q2 L" M& y
赞 收藏 3 评论7 发布时间:2017-9-28 15:55

举报

7个回答
斜阳 回答时间:2017-9-28 16:55:22
HAL的锁中断不安全到现在都没有解决吧
斜阳 回答时间:2017-9-28 17:04:38
TIM图片20170928170248.png
) B5 I" t& U0 K, I7 ^! T; R6 Yst的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了5 `/ E( o5 I$ d

: X0 i) f! i6 {; y, H
小小向日葵 回答时间:2017-10-9 16:39:58
斜阳__ 发表于 2017-9-28 17:041 z8 U2 O4 M. w2 f
st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了/ q: [7 n& j% g. b4 J: Z

) ~  q, R3 C+ ^4 x& n8 f8 P% h ...
6 v# _0 I3 p# n/ A3 ?8 X
锁删除之后,还能工作吗?( r* C, k# O% l9 T8 _, t3 r0 W- \/ e
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管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版