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

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

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

[复制链接]
zero99 发布时间:2017-9-28 15:55
USB传输数据时出现卡顿现象
. _% ]+ A0 y. f. [# ^7 c4 L; g
1 前言
- k7 R8 f  Z5 e1 d6 P* J    在进行USB开发的过程中,有多个客户反馈,USB传输数据时出现卡顿现象。本文将针对这一问题进行分析。
( u& l; g/ V7 X4 n
% ]2 y9 Q1 ~) V3 l" S& h, F' Q2 问题分析
0 R0 S' j; U% n* a    这几个客户问题现象基本差不多,采用STM32作为Device设备,在与上位机或者PC端双向通讯一段时间后,从Device端到Host端的数据能够正常,而从Host端到Device端的数据异常,也就是说,STM32在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。2 q" e( r" p0 y, b* Z% i
    这几个客户,有用STM32F2的,也有用STM32F4的,有用CDC类的,也有用作HID设备的,但都使用了Cube库。
/ c( U) c2 V9 C* T2 {& F    下面就具体问题以其中一个客户使用STM32F411的USB CDC类的案例来分析问题,现象如下USB通讯数据(CDC类):
1 m. O4 N; f7 @) q5 Q      
11.png . m8 h% G. o  c5 p: A3 N
Figure 1 通信一段时间后Data Out数据异常
% H5 {" W% y3 Z- K1 }( Q' B1 w! q8 x8 I  T! q+ o
展开Data Out数据:
5 T7 N+ q9 L5 W; O* H
12.jpg   x% Y. }1 \: |6 `! L
Figure 2 Data Out数据异常: m- E0 n; y/ V9 T  ?; N
$ B3 E3 X4 u! z7 x$ e( j/ H
分析上图发现,并不是Host端没有向Device端发送Data Out数据,而是确实发送了,但被Device端NAK了。那么为什么会被NAK呢?1 f  X0 b8 {5 b9 c2 ?8 W, ]1 i- f
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT对应的端点1是处于关闭状态,那么为什么端点1会关闭?查看STM32端的接收代码:
4 Q! w/ s( F, C1 O. y/ f
8 M& A+ V: T# e& u% G0 ]usbd_cdc_if.c: : c& o) t! l6 p4 _% M' e
13.jpg % d! d& m' ?- y' ?4 |8 r4 z/ |
如上代码,在MCU端接收到赖在Host端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现Data Out端点关闭的问题,那么OUT端点到底是怎么关闭的呢?我们接下来看子函数:4 I7 Q$ ~4 @$ P0 Q* o
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() ->HAL_PCD_EP_Receive() ->USB_EPStartXfer()
$ l9 s! x: A* U* N' y最后在USB_EPStartXfer函数中有发现再次使能OUT端点的代码:
$ R# o" ~: b$ G2 \) F1 G$ l
14.jpg ( b# c- G3 p6 y0 J( {
15.jpg ! W* i. `4 ]' S
也就是说,在调用这个函数之前这个OUT端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是OUT端点一旦打开就一直开着的吗?带着这些疑问,我们查看STM32F411的参考手册,终于在22.17.6 Operational model一节中找到这么一幅图(由于CDC类数据传输采用的是BULK传输):
( \7 O9 R: ?, l- {9 w8 @: h& b, C4 P
16.jpg 7 H# S! a9 l8 x3 \" K
Figure 3 MCU对OUT数据传输的处理流程; c) L+ H- ~* U1 L. v2 H0 c

0 `1 o6 r7 a9 m. f; V! A如上图,MCU对BULK类型的OUT数据处理例程大体如下:9 B- g4 H5 y2 C' B  d: B# B
1> Host端试图向一个端点发送OUT token;
4 H" k* B8 J# T" G$ t) @( a2> 当Device端的USB外设接收到这么一个OUT token后,如果RXFIFO空间足够,它将数据包存储到RXFIFO中;
# L4 C/ g+ |, w+ }, l3 x/ a+ E3> 在将数据包内容存储到RXFIFO后,USB外设将产生一个RXFLVL中断(OTG_FS_GINTSTS);
7 ^' t& f1 M, G0 S3 o: @4> 在接收到USB数据包的个数后(PKTCNT),USB核将内部自动将这个OUT端点的NAK为置1,以阻止接收更多数据包;" \6 {% u5 O+ y4 Z& M; h
5> 应用程序处理RXFLVL中断和从RXFIFO读取数据;( M& a4 {" M: n8 b
6> 当应用读取完所有数据(等于XFRSIZ)后,USB核将产生一个XFRC中断(OTG_FS_DOEPINTx);$ |  f, }& h/ z  Q/ h6 m9 G3 i# s
7> 应用处理这个OTG_FS_DOEPINTx中断并通过OTG_FS_DOEPINTx 的中断为XFRC来判断传输完成;
0 l' O+ d4 ~5 t  R8 g2 o/ Q
" K7 }# Z+ D! g2 H/ q# n, u从上面步骤中的第4步中可以看出,当USB核收到来自Host端的数据后会自动将OUT端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个OUT端点的原因。因此我们大体可以判断出在OUT数据传输的过程中,USB核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在USB核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
$ M* B: o. G* r  C& l
17.png & s- j3 h7 ^8 j6 ?( p+ n3 t# i

* L3 D% e# T# O  I6 R$ F之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁: : d, x) s3 Y4 x) a6 X& X
19.jpg
$ L6 F" |. H3 z  q/ ?* {
" [# w4 O. S% S- H$ l, N) u接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到main等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;- _- A) o! K/ j5 x6 W9 [0 f& v
' \1 L4 b3 ~. d" u. Y5 n3 H6 ]
我们进一步查看__HAL_LOCK()宏定义:* O# _; h7 y/ \/ F
20.png
" j0 q: S+ D  i5 i  g
4 o& {# y7 a' B! j. u# {
3 c4 E/ i5 Y- n, n7 t- m若__HAL_LOCK(hpcd);失败则直接返回return HAL_BUSY的。为了验证在接收过程中是否__HAL_LOCK失败,我们引进全局变量Lock_Flag,在发送函数中若成功LOCK则设置Lock_Flag=1,UNLOCK后则复位为0:
" W1 T5 k5 P( U. V3 K. M: G1 b 21.png
9 L( r- S/ n. r, M# P3 _" C, K+ f - ^! r% b( o; A4 n1 h, r
接下来在接收函数中对全局变量Lock_Flag值进行判断,若为1则锁死程序,因为在Lock_Flag=1时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回HAL_BUSY;这里通过锁死代码以便判断这种情况: , U9 D# k8 a) d% ^! d, Y
22.jpg 8 c3 a$ ]- T) J  h! p" \
通过调试,当出现问题时,程序果然被锁死在这个while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
" F% G( a- R) H  J1> 在mian函数中发送数据CDC_Transmit_FS()
; c8 T8 T/ |% T( z2> USBD_CDC_TransmitPacket()
! K( d+ a; C2 l# A6 p3> USBD_LL_Transmit()# T0 J  ^' S+ X/ |$ A8 u4 }
4> HAL_PCD_EP_Transmit()5 c: J3 V1 Y6 A5 m3 n0 O, M
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁
0 @9 ?2 V0 y3 l& V, Y3 c/ A9 \0 e6> 恰好此时有一个接收中断,由于USB中断具有优先级,跳转到接收中断内执行;同时,USB核会自动关闭OUT端点;
" l5 l; I4 b! t# B2 z; ]7> HAL_PCD_DataOutStageCallback()
# a" H8 T+ i9 Y3 M$ b0 n- F8> USBD_CDC_DataOut()
4 V6 I/ [# F9 r# _, A9> CDC_Receive_FS()
& c% U: y8 \" R" `10> USBD_CDC_ReceivePacket()
, g3 w* U! h# i7 x11> USBD_LL_PrepareReceive()
8 b7 @1 [% ^1 B12> HAL_PCD_EP_Receive()
9 ?8 l; E3 C6 z. w* b% J13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在OUT端点没有再次打开就已经提前结束,导致接收循环无以为继。8 j) k  J% Y2 w- y
* R, ^# ~) [7 E: M2 I6 W/ I9 X
3 解决方案
; }8 e9 t6 J$ h  H4 a$ Z知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与USB接收中断相同的中断等级中去,例如可以利用USB的EOPF中断,在开启EOPF中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF每1ms触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
9 W- c* Q/ u, P3 N1 E9 Z
# \& o1 o" x6 h1 H" Q, R& h1 W0 L* ^此外,其实此问题是出现在Cube库的低版本中,例如CubeF4 V1.5.0和CubeF2 V1.3.0中都存在,但是在最新本的CubeF4 V1.16.0,CubeF2 V1.6.0版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
$ y0 b$ Y, e6 z$ J& X1 t. f# U' K
& O4 J0 o8 s  C, S2 G
5 u$ J) y/ N$ A

% J& k- i, k  l5 [

5 X- Z, p8 Q1 n文档下载1>>         文档下载2>>       更多实战经验>>
* c" e$ y( Z; G0 R, l: d
收藏 3 评论7 发布时间:2017-9-28 15:55

举报

7个回答
斜阳 回答时间:2017-9-28 16:55:22
HAL的锁中断不安全到现在都没有解决吧
斜阳 回答时间:2017-9-28 17:04:38
TIM图片20170928170248.png ) g! h  d; x+ E. S3 l
st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了
8 l) F& t7 ?3 T8 I7 v" y( b0 z% P# ]" `* o
小小向日葵 回答时间:2017-10-9 16:39:58
斜阳__ 发表于 2017-9-28 17:041 a5 L# `7 O  n
st的工程师就是这样修bug的??由于设计的锁中断不安全,直接把锁删除了; _" l! I  i) ^( g6 m

$ f3 ~" T: l5 \/ Y; n+ l ...
1 ^$ E* _% b" k
锁删除之后,还能工作吗?' e7 D, m0 v8 m
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 手机版