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

【经验分享】USB 传输数据时出现卡顿现象

[复制链接]
STMCU小助手 发布时间:2022-2-15 19:59
1 前言5 W' O; f- |8 F: @$ w
在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。) q& A+ ]7 T5 d1 [9 v5 `  I8 B
9 U" ]/ G7 ^8 p3 S+ P; C- e, V
2 问题分析
& g. r1 k* C+ \6 q3 ]0 S3 i这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
2 s9 d. _2 Z' X) d' u+ T* t: v这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。5 m. q$ e7 K' W6 ^) e) t; c
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):
$ K$ W8 v" A. E6 w; M& i
# R( C! L; C  i) q9 o! ` RA%Y@]HA1K@X1)`5GR3T`UB.png
5 ~3 p# N+ D2 V( q) s; I* m: D) P, x+ Q) c; B
展开 Data Out 数据:
! \; S* ]2 y2 W
  e9 D# h; l  s) M Q~PHG_]1`BYIBE0$[(HE9.png
7 W* v3 O1 z( S* @) a7 @& S3 A1 i
3 |$ q, \4 H, v5 d/ U' Y分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?& W+ g, }! i/ W4 F
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:& x5 Z8 x8 u: ], w( X
usbd_cdc_if.c:& u$ x5 |5 \; l- j9 U
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
    " B- e1 r) G7 R- ~2 M
  2. {
    . x! K% r. n# S: z4 M" Z
  3. /* USER CODE BEGIN 6 */5 T/ @" r( u, d2 _
  4. //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
    - n, e. {+ k% F# N
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
    3 `! E: l" ~* K
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    5 C4 c  I2 Q4 N% n* W* B9 B1 X) A
  7. return (USBD_OK);
    % f- D/ B  g; y/ L: P
  8. /* USER CODE END 6 */
    - y, O/ }8 o% V: `9 `' k
  9. }
复制代码

7 d' g1 p4 G: `9 |如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:1 \3 E# x, L( X
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -
: |* m5 r$ z1 |> HAL_PCD_EP_Receive() ->USB_EPStartXfer()- L4 [2 k9 x' T: d" C: Q
> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
+ q/ V# q4 O8 Z  ^  m* I7 n! Y
  1. //…, ?7 x. u3 X& a1 s
  2. else /* OUT endpoint */7 ?( p2 w  I5 c8 O
  3. {
    8 p1 }/ V7 P% F5 H) A; E' V2 O
  4. /* Program the transfer size and packet count as follows:( O( v0 D% h) Q4 z

  5. ; J6 g: D* q/ A- m* }. {+ }
  6. * pktcnt = N
    ; a/ `% J, ]% d2 p* Q
  7. * xfersize = N * maxpacket
    3 H/ @( U* q( b: }6 @
  8.   */! I" ~; S, o' }8 z& G1 Q
  9.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
      p+ H1 E: o' Q; p4 X) F/ u4 E! K
  10.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);
    ! T! x1 Y7 t, s  a* B9 k3 R8 |. ~
  11.   if (ep->xfer_len == 0U)
    6 P. E0 h( }, U" i
  12.   {
    - N& G6 q3 H/ v. z0 o5 f
  13.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
    / s3 W" L5 S% _0 U) h' n! t" g
  14.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;2 n. H& ^* j) b$ d; j- ]1 h
  15.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *0 i/ @/ M/ ]+ m& {; [% _
  16.   pktcnt));
    ) f/ e$ F% A* B( r
  17.   }
      W" T: R% e$ G/ B1 n7 @
  18.   if (dma == 1U)
    , U, L, N" s! p4 Z4 X1 l5 }& p9 t
  19.   {- R( u9 [% S# i" H  b9 m- K
  20.   USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
    $ v  c" b8 C" I1 ]
  21.   }0 _0 a8 \8 e& X7 P

  22. 8 \7 H8 @- G4 \/ Y; H
  23. if (ep->type == EP_TYPE_ISOC)4 U* G7 R; v/ D. a
  24. {
    0 O( g  a$ y1 ]
  25. if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
    3 Z# p! w$ G* k9 j9 p
  26. }% b# ^; q$ A+ h' o4 v3 f* w
  27. else4 ^% [5 D- C+ I6 p8 }% M7 z
  28. {
    ! c' s- Q& q' S. |
  29. USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
    0 T6 y3 R$ n2 g  u
  30. }
    2 N. c3 l- p% }1 q$ [
  31. }) s6 K- J' w# E3 ]
  32. /* EP enable */; g* K9 B( T9 V- \/ G2 p+ s% P* B
  33. USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
    6 x& V5 }2 j  _, y
  34. }
复制代码

$ {) U7 f2 D3 T: v也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
+ H0 N3 J$ ?7 o9 f
. A) m- \& F7 Z* D3 E. j DJ$@890HN0K]FT5U7JRG[6H.png
" [2 z4 L+ P. T! u4 k( x$ S
" i7 }. k0 W6 K" ?0 n5 ^, D' h2 K% Y如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
1 L( C* a3 y  Y1> Host 端试图向一个端点发送 OUT token;& Y, E# r2 L% V% b9 s
2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;, d- o1 p7 X/ P- H. J9 j4 t( m
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);! S2 s# c* B' U* {, H
4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;8 Q6 Q, b/ R1 m+ n0 n( d/ s
5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
6 H4 o. y( b. [; u6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);8 ?. T9 t- p6 ]2 F. T
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
+ @( _2 G: j# E+ y9 h
" a& F& G' V- R, L从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:, g& B% b1 G6 C! v! D
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    6 J# N: R2 n& w7 x8 H. [
  2. *pBuf, uint32_t len)3 ?# S' }  ]: N- o4 `- @; c+ @# a, o8 z
  3. {; D% H% J/ [" Y( O- W  i
  4. USB_OTG_EPTypeDef *ep;3 ^0 w% u" _/ M2 g$ l9 I

  5. , B1 n' k% c2 z9 s! L9 w3 l4 M
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
    5 b5 ~" O: o2 C& Q) V
  7. 3 {) O9 x* X  p& G7 d* e
  8. /*setup and start the Xfer */
    / V) P7 A' w9 L8 P& h/ `
  9. ep->xfer_buff = pBuf;
    / H% V9 \" M2 }% L/ r' O. l
  10. ep->xfer_len = len;
    8 U6 ?* F( V+ h  T% |+ [- H, w7 D
  11. ep->xfer_count = 0U;
    3 q6 v* h; O! P) U7 g: p
  12. ep->is_in = 0U;) ^5 u; w) `* ]% l
  13. ep->num = ep_addr & 0x7FU;% m- s, O8 w% [( ?: c
  14. 9 W6 K% o# O% W% b9 v
  15. if (hpcd->Init.dma_enable == 1U)* j2 `  @, T3 B6 c" A
  16. {$ N1 D; R& y$ g- A: A5 O0 A
  17. ep->dma_addr = (uint32_t)pBuf;
    4 Z" l  O8 W, w, L* v# l, e7 V
  18. }& \( t' _/ o3 R; ]# Y

  19. + P" [7 I# s& S$ B0 |
  20. __HAL_LOCK(hpcd);8 |1 W) ?: E. \' `$ s" m

  21. * I) f# o0 j: K$ c; r  G3 H
  22. if ((ep_addr & 0x7FU) == 0U)9 U6 X) x$ s7 J2 k
  23. {
    - r/ a6 z1 r( d3 D5 N: L& w2 k
  24. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);, s$ f0 S" P4 _& o* X$ n7 _1 B' B" V
  25. }2 V; ?8 s3 o) o( o9 H1 Z
  26. else% e% i7 Q# s" K, e0 G3 U) l
  27. {
    1 |: a' i: p+ f
  28. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    + R7 P- z' |* u( h  L9 |6 j6 i
  29. }. y3 M9 I; K/ a* ?7 h7 b4 R  @
  30. __HAL_UNLOCK(hpcd);
    # O' i  ~# h; w
  31. return HAL_OK;
    % E/ j% u5 n' e0 Z4 I3 s+ Z4 d
  32. }
复制代码
( ^7 I+ J# ^$ @# H6 a

6 ?  h+ h* {0 B0 L0 n7 m5 O之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:
$ _9 Q4 U) w+ y
  1. CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->' v- }, f  J5 s5 w2 Y
  2. HAL_PCD_EP_Transmit() :
    . A9 a9 @; h, g0 k0 A6 e
  3. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t- y1 J2 ~" @0 D6 Y6 T
  4. *pBuf, uint32_t len)+ c) F5 _) h' _$ X+ [' q
  5. {" ?" Y# @# a, b3 L; t" P
  6. USB_OTG_EPTypeDef *ep;$ M% i3 x( K7 w& ?+ h) c: c
  7. ( ?, [% |" w; K
  8. ep = &hpcd->IN_ep[ep_addr & 0x7FU];
    " A; b5 L. j2 o1 ]4 w7 N! C+ O4 w
  9. /*setup and start the Xfer */
    5 ~( J1 Y/ I; @8 {& M. R, U
  10. ep->xfer_buff = pBuf;
    6 }; S. Q$ e. @% x  x$ J
  11. ep->xfer_len = len;( X& P7 d3 e) J# y' h( T8 I
  12. ep->xfer_count = 0U;
    6 C- i* R4 f' y
  13. ep->is_in = 1U;
    0 Z3 b* @7 a  r
  14. ep->num = ep_addr & 0x7FU;- n. M6 k0 Y; K9 e7 W

  15. 1 }% P4 j$ a' c2 H( F4 q
  16. if (hpcd->Init.dma_enable == 1U)
    6 q" M; C2 M/ u# ~0 Z
  17. {
    6 S3 @: B& t( v0 ]$ Q
  18. ep->dma_addr = (uint32_t)pBuf;. R$ {  q  m, V+ U$ R& H% _
  19. }9 K! J* ~$ L/ I0 r3 y3 U

  20. ' k; w" ~1 L' U$ Q* ~
  21. __HAL_LOCK(hpcd);! [+ x: y! }8 g

  22. " R2 {# X8 ?% N) }1 E7 G0 q& ?
  23. if ((ep_addr & 0x7FU) == 0U)+ \  ~* n7 c- u9 @2 t, i
  24. {
    - Y1 q% X. W; m8 p6 z& V" r1 s
  25. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    # M" M2 N7 P( q
  26. }! ]) P3 s, T' g7 J- J/ Z$ v
  27. else6 R5 f% U7 m4 r! _$ P
  28. {
    " f( [2 `2 f4 D, A
  29. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    . Q$ ?3 l9 }( P# q5 g
  30. }7 |5 }0 R; S$ d6 Q( n5 M

  31. ( L+ p! ]% Z  w" P4 c* ~$ u8 l
  32. __HAL_UNLOCK(hpcd);
    8 d1 j7 T* \$ o6 `, _; W4 l( U
  33. return HAL_OK;1 ~- i% f' K% B% d
  34. }" x. @5 x$ V+ M( H  x+ {" a% Z
复制代码
' c- m( ~0 `; m) c3 e% u4 h& s
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;) p+ c$ m6 }! ?% \# ]
( ]; U  D; f# |9 Z5 J
我们进一步查看__HAL_LOCK()宏定义:5 n4 X' {2 R2 q4 P# g' j
  1. #define __HAL_LOCK(__HANDLE__)
    : `+ }" }+ k  D  T0 ^
  2. do{
    , Z( r, |3 ]  c' A$ O2 b& Z
  3. if((__HANDLE__)->Lock == HAL_LOCKED)   U! w% r& }/ t5 B' c
  4. { 7 X) f- W2 {! H+ k  o
  5. return HAL_BUSY; 3 l) e7 {- P8 Z- i9 @
  6. }
    # n2 z9 Y* I( R. h0 h+ y
  7. else 6 ^( \' j$ {+ U
  8. {
    , T7 m. W' {3 i4 q9 K! {
  9. (__HANDLE__)->Lock = HAL_LOCKED;
    + q4 p: e: ~: o* s6 X, s
  10. } , ~  Y! C$ I! ^) C
  11. }while (0U)
复制代码
" o0 x9 ]  X. ]- j3 T; `; b" Y! X
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:( U$ ^  y1 k  [0 W
  1. uint8_t Lock_Flag =0;3 R- Z$ m! x! j6 A0 f9 R. _* t3 ]
  2. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
      R. s1 o% V  x) }8 u
  3. *pBuf, uint32_t len)2 {- G- J/ i  s9 @
  4. {6 S  `0 C6 }* ~/ A0 g4 R
  5. USB_OTG_EPTypeDef *ep;+ e* L, {7 t0 C  A
  6. - Y) }5 c( ?9 x8 c  @. N
  7. ep = &hpcd->IN_ep[ep_addr & 0x7FU];$ m  H; @9 M5 k+ ~( i

  8. " q. n+ i6 A3 t" b/ X' i8 ?% R
  9. /*setup and start the Xfer */
    6 m6 Z2 |6 ^: R1 d: t# A& w, f  F1 u
  10. ep->xfer_buff = pBuf;& q* a3 z; j2 [& E
  11. ep->xfer_len = len;
    * m; U% ^/ n2 Y9 p8 L( J8 j. o' B
  12. ep->xfer_count = 0U;) Z5 r# n+ D- \. h1 _
  13. ep->is_in = 1U;$ X' P4 i" M1 T9 j  ]3 a: J$ o4 ^$ `
  14. ep->num = ep_addr & 0x7FU;
    * ^& [: [5 k0 f* Z! q* o6 T" n

  15. 4 ^. T  a5 M6 \& s& [
  16. if (hpcd->Init.dma_enable == 1U)5 A. E4 J3 {0 E+ v% q8 A
  17. {
    + u  h5 ^: j6 A- L
  18. ep->dma_addr = (uint32_t)pBuf;
    " A& g/ f" [. O" W* b) t
  19. }
    : f: {+ \6 e  s
  20. $ y+ f: Q5 z! Q0 j# m, g
  21. __HAL_LOCK(hpcd);0 `2 q- ^( Q+ z% q) ?" X" J
  22. Lock_Flag =1;( ?- r' L' `: ?- b% n
  23. 6 q% H" N1 P4 H0 X
  24. if ((ep_addr & 0x7FU) == 0U)5 v4 H0 G8 u6 r( g% @% Y4 d5 Y- Y
  25. {
    9 E% a( j, A. w$ R
  26. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    / a6 ^0 }/ @0 N, V. X/ n
  27. }
    ! s$ h, L% J3 U& }. A8 }- x# H
  28. else
    $ r( Y7 n5 \0 ^& c, J' g, m  R5 j
  29. {
    8 d* b  P- L! ]5 n" x( w/ G9 F9 M
  30. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);- W0 Z: k/ U6 h) l, s, l- G" K
  31. }( `. h' H1 C  _0 W; W8 h7 e

  32. # X; j0 T' f% E% r
  33. __HAL_UNLOCK(hpcd);
    # M4 i6 O) q* i; h; I
  34. Lock_Flag =0;
    , ^% `/ E# U, O
  35. return HAL_OK;6 ?- E2 J& i1 D! H
  36. }
复制代码

1 w, b3 ^9 t* |/ E$ {7 Z接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:
4 w9 M' {; e9 T' o
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    % i% X, D5 a4 |7 v* _* X
  2. *pBuf, uint32_t len)
    , {" [: E- P' i+ J1 K+ U& q1 T( Y
  3. {
    7 G$ Z, E8 Y$ a  ?4 v
  4. USB_OTG_EPTypeDef *ep;
    & L& v1 T! u4 S, ]+ @$ O+ X
  5. ! h" y9 q8 i6 ?( l
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];$ F$ O% l& M' S3 h, |/ J
  7. ! a  X2 X3 `# w; U+ ^8 ?
  8. /*setup and start the Xfer */
    ( I8 p, U" ^6 Y; X
  9. ep->xfer_buff = pBuf;
    - {0 t: j7 j. m$ W5 \# e( B8 ^2 y
  10. ep->xfer_len = len;
    $ J% h8 B3 l0 z" P1 m- E! a
  11. ep->xfer_count = 0U;
    * m# j# \2 ^5 o' j: d: T
  12. ep->is_in = 0U;
    1 |( L* S4 Q% S9 d3 s5 S1 v( V
  13. ep->num = ep_addr & 0x7FU;6 i: n5 l! u) |% @- _
  14. * ^& g+ W. a6 s3 F9 j5 A1 }! @
  15. if (hpcd->Init.dma_enable == 1U)' Z: h& b0 v* a5 f
  16. {
    ) g6 k- T9 w1 s$ Y7 G
  17. ep->dma_addr = (uint32_t)pBuf;
    ( j# v1 B: ^$ Q3 o
  18. }4 s6 ]) u* l/ u, c  j
  19. 3 ]$ H: O$ e6 {9 D% P. V
  20. if(Lock_Flag ==1)
    9 i3 Q/ W% R) j; n% g' ?& w+ B
  21. {, l+ q" i' x0 [! S0 y
  22. while(1);  `7 A1 A! }4 P& _/ A5 f4 s! {
  23. }1 y0 K' B3 v* }8 m
  24. __HAL_LOCK(hpcd);
    ( V8 m0 I0 v1 n4 P1 J4 n

  25. " x  [$ U8 a9 x
  26. if ((ep_addr & 0x7FU) == 0U)' B3 c5 ^- n8 \1 R- H  B
  27. {  k5 l- ~4 k- N* ]3 v
  28. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    * B! ^, T* U& d" B8 F' x' L
  29. }* g5 S, m; h( \* S. `8 o! O7 U1 M
  30. else2 C9 K4 Y  d( K" s) R5 o
  31. {, d; A6 m7 c, L6 @- ]
  32. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);) `# M8 r$ w3 p+ H; o
  33. }
    ) w+ f  Z( I( T' K, P
  34. __HAL_UNLOCK(hpcd);
    , j& j% m$ s; J# H. j

  35. $ C- u& y, d9 O, F5 e
  36. return HAL_OK;  i8 F0 o/ _$ z4 `2 l4 i4 e
  37. }
复制代码

4 f- X! E! X# w5 z. n通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
& P9 K1 E3 o1 D% o# |" c9 l1> 在 mian 函数中发送数据 CDC_Transmit_FS()3 t4 i' ~3 F6 `2 N+ r
2> USBD_CDC_TransmitPacket()1 v" P) V6 G4 f6 u
3> USBD_LL_Transmit(); p) h( Q: I2 w. |; F
4> HAL_PCD_EP_Transmit()) ^' V3 ~7 r! G  w+ ~
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁+ ?$ V# `4 g+ E5 G0 i5 i
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
& p" ]  ^5 f/ K' A+ @7> HAL_PCD_DataOutStageCallback()
; Z* }/ B8 M( s$ Q8> USBD_CDC_DataOut()
- Q( D  c) x* z: m% U; x  x9> CDC_Receive_FS()
  k/ x1 I( o3 Z10> USBD_CDC_ReceivePacket()& I& m6 ?; v- U9 g
11> USBD_LL_PrepareReceive(): K& a3 G3 t4 n3 O7 l8 i
12> HAL_PCD_EP_Receive()8 w% D' c% W" J- c: ~
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已% _1 M* Q# t9 p! m8 k# }! c# M. t; @# C
经提前结束,导致接收循环无以为继。4 ~( @2 r3 q- E+ `

0 T0 c0 y6 [5 X3 解决方案
. C/ z8 }! I+ g. m% z知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
( T& ^- ^$ H+ H$ N8 z2 d* z# i  C, G7 {0 S+ @: R$ I$ k
此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
; K" N6 R  [" G9 a' u& s5 e+ O
5 b4 H* D# T1 x0 K) q4 c
赞 收藏 评论0 发布时间:2022-2-15 19:59

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版