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

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

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

* h# Y/ ]9 ^2 y" d1 前言# g' W) s& M& j- A1 t3 D/ g
    在进行USB开发的过程中,有多个客户反馈,USB传输数据时出现卡顿现象。本文将针对这一问题进行分析。
3 }, Q, q/ `( F, u5 K4 M% {) r  I. \* n3 Z
2 问题分析
( H+ @& w9 P2 ]. W8 f    这几个客户问题现象基本差不多,采用STM32作为Device设备,在与上位机或者PC端双向通讯一段时间后,从Device端到Host端的数据能够正常,而从Host端到Device端的数据异常,也就是说,STM32在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
1 `0 h* x, I2 e6 |* H& x# h    这几个客户,有用STM32F2的,也有用STM32F4的,有用CDC类的,也有用作HID设备的,但都使用了Cube库。5 Q6 t) }. D" I8 l: o: Z
    下面就具体问题以其中一个客户使用STM32F411的USB CDC类的案例来分析问题,现象如下USB通讯数据(CDC类):
! Z7 o: L7 O! V      
11.png
+ ~' p  d7 u# {9 oFigure 1 通信一段时间后Data Out数据异常  I  X+ C" C, |( R  u" `

0 Q9 ]" ]0 V) [: K展开Data Out数据:! Y1 c7 \6 e- @* Q8 t
12.jpg
. C  ^. N0 x9 W( f( JFigure 2 Data Out数据异常6 f1 ^6 h/ v- N; e& z* E! ]  n6 m

$ j7 D; E9 J* ~5 h9 f) v分析上图发现,并不是Host端没有向Device端发送Data Out数据,而是确实发送了,但被Device端NAK了。那么为什么会被NAK呢?
+ g& s4 j# _" i3 o# b, V: l通过在调试下查看寄存器,我们发现当出现问题时,Data OUT对应的端点1是处于关闭状态,那么为什么端点1会关闭?查看STM32端的接收代码:
/ E9 [" _* h" k- F4 f; l; x
" K7 ?7 j  s) l: K& _usbd_cdc_if.c:
  X7 G" D9 n  R
13.jpg - D# a9 O) }# P$ g
如上代码,在MCU端接收到赖在Host端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现Data Out端点关闭的问题,那么OUT端点到底是怎么关闭的呢?我们接下来看子函数:7 o+ |/ x- x) O3 s
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() ->HAL_PCD_EP_Receive() ->USB_EPStartXfer()
! J5 C' D5 H" A: w最后在USB_EPStartXfer函数中有发现再次使能OUT端点的代码:
3 c0 G" T) _6 P7 f6 x' F) R
14.jpg
; z$ u# W: X5 g) k% i8 o* E: b
15.jpg ' j( y' z* ~- H% P- H# P6 c
也就是说,在调用这个函数之前这个OUT端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是OUT端点一旦打开就一直开着的吗?带着这些疑问,我们查看STM32F411的参考手册,终于在22.17.6 Operational model一节中找到这么一幅图(由于CDC类数据传输采用的是BULK传输):( H& I0 y. D5 f& P$ o. {- n
16.jpg   V% y1 k" {& e) C, d
Figure 3 MCU对OUT数据传输的处理流程
! L: A' P) q# A' t
$ r7 |9 f  ~) ~, U* s0 i/ P如上图,MCU对BULK类型的OUT数据处理例程大体如下:" j0 r' E- |4 \4 Z* _: ?( g
1> Host端试图向一个端点发送OUT token;
3 M. n; W$ b- H& U# |8 \' l1 b2> 当Device端的USB外设接收到这么一个OUT token后,如果RXFIFO空间足够,它将数据包存储到RXFIFO中;
+ B0 m( r& j) S, E3> 在将数据包内容存储到RXFIFO后,USB外设将产生一个RXFLVL中断(OTG_FS_GINTSTS);
* C$ o8 b  }5 S; e4> 在接收到USB数据包的个数后(PKTCNT),USB核将内部自动将这个OUT端点的NAK为置1,以阻止接收更多数据包;& p6 N, F5 o( B; U/ c- D; ^8 v
5> 应用程序处理RXFLVL中断和从RXFIFO读取数据;4 X6 x, _$ K8 p, c) z1 y
6> 当应用读取完所有数据(等于XFRSIZ)后,USB核将产生一个XFRC中断(OTG_FS_DOEPINTx);
5 r/ m- @& ~5 x6 G7> 应用处理这个OTG_FS_DOEPINTx中断并通过OTG_FS_DOEPINTx 的中断为XFRC来判断传输完成;
7 r6 s( b1 r% y3 B1 W! i3 ~
; D( D/ i8 s* f0 A5 @( i5 Y从上面步骤中的第4步中可以看出,当USB核收到来自Host端的数据后会自动将OUT端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个OUT端点的原因。因此我们大体可以判断出在OUT数据传输的过程中,USB核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在USB核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
% D% k2 U  S8 Y9 [* V
17.png
0 v5 f9 }2 _1 }! m# ]& j ' C" C/ t6 t% I( {
之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁: 1 K9 q2 b0 W- f
19.jpg ' ~" p1 D5 d: f7 O( D% [- ^
1 p( z" k1 n  S% @& }
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到main等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;; c  y! F+ u" j/ n3 u1 h4 q
' g9 [' Y7 ~4 h4 s& [/ i
我们进一步查看__HAL_LOCK()宏定义:
3 f; a: U' w9 d5 `! k% R 20.png + p$ G8 e% n. i9 z
! v2 \! f5 M0 I2 R/ k
& u& }0 ~0 x# E+ F) g, J
若__HAL_LOCK(hpcd);失败则直接返回return HAL_BUSY的。为了验证在接收过程中是否__HAL_LOCK失败,我们引进全局变量Lock_Flag,在发送函数中若成功LOCK则设置Lock_Flag=1,UNLOCK后则复位为0:8 k! B" Q$ U% K
21.png
' d! X; y8 k8 d5 I; v
; r5 W- z! J% ]# B接下来在接收函数中对全局变量Lock_Flag值进行判断,若为1则锁死程序,因为在Lock_Flag=1时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回HAL_BUSY;这里通过锁死代码以便判断这种情况:
( i' o# F! J. V( J 22.jpg & z3 ]8 V$ j1 E5 b* @
通过调试,当出现问题时,程序果然被锁死在这个while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:( `4 D7 r! i2 u3 c. A5 a: D5 d
1> 在mian函数中发送数据CDC_Transmit_FS()
5 P7 n! f+ |6 N  B+ t* R2> USBD_CDC_TransmitPacket()  P6 O. W! H! M) a3 o
3> USBD_LL_Transmit()8 g; s7 K/ s, B% Z% a5 V. N2 b
4> HAL_PCD_EP_Transmit()
; B$ O1 q7 |" D5> __HAL_LOCK(hpcd); 此时成功获取互斥锁% n7 ~9 z. o% D/ g7 p" e! d
6> 恰好此时有一个接收中断,由于USB中断具有优先级,跳转到接收中断内执行;同时,USB核会自动关闭OUT端点;
% R1 V2 B( h6 A! R7 p7 B6 A7> HAL_PCD_DataOutStageCallback()
) G# I/ D2 Z) X$ q" D) f+ [8> USBD_CDC_DataOut()
9 u& D. {% z" }9 d+ r) ?8 d' _! l9> CDC_Receive_FS()' |% s! N! R& j8 q1 P# h: q
10> USBD_CDC_ReceivePacket(), Z" e7 t% ]# x- o
11> USBD_LL_PrepareReceive()
8 J, J2 h  c3 z; x12> HAL_PCD_EP_Receive()
2 d  U, E# M! Q13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在OUT端点没有再次打开就已经提前结束,导致接收循环无以为继。
1 `4 F, u" I+ L& \/ g5 `% C
- N- W: i0 A* c! k8 l+ i# _3 解决方案( b- h, L  }& T2 ?  c
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与USB接收中断相同的中断等级中去,例如可以利用USB的EOPF中断,在开启EOPF中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF每1ms触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。1 g- P$ T% z7 d) u) e; [, H4 r; D" ~
- v. m$ }% ^- _; s: _& @4 `
此外,其实此问题是出现在Cube库的低版本中,例如CubeF4 V1.5.0和CubeF2 V1.3.0中都存在,但是在最新本的CubeF4 V1.16.0,CubeF2 V1.6.0版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。

0 ~: a! K3 C/ l) Z  I$ s0 C7 `. \( A) j; x3 ^* u4 B9 p  \
5 l/ w1 r; [9 t* C* a8 I3 k( A) c1 L7 H4 V
9 H+ |) Y: J! y4 R0 N. ^

1 p" r0 Q% C3 D! c: E文档下载1>>         文档下载2>>       更多实战经验>>% l, ?9 _7 s) x6 @; ?- F6 l
收藏 3 评论7 发布时间:2017-9-28 15:55

举报

7个回答
斜阳 回答时间:2017-9-28 16:55:22
HAL的锁中断不安全到现在都没有解决吧
斜阳 回答时间:2017-9-28 17:04:38
TIM图片20170928170248.png 7 n2 M/ ~8 y* Z
st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了& y) Y$ _2 F; O1 O  z5 b. Y( c; ~

3 W# w" T* ^$ o( w, P* Q
小小向日葵 回答时间:2017-10-9 16:39:58
斜阳__ 发表于 2017-9-28 17:04
% I$ O% L* y; j3 ~st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了
, w" A8 F  Y' U; A) M2 v
% M/ B2 C3 C8 {& H6 ^& q  p( y8 m ...

( R. j; k' H$ W' w锁删除之后,还能工作吗?0 W7 J8 P7 z) z, P2 c: r* l
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 手机版