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

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

[复制链接]
STMCU小助手 发布时间:2022-2-15 19:59
1 前言
, G5 V$ u* Z3 ?5 c# Q! m在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。
8 s. y1 r1 y( ]1 ~
* {- a$ N8 |: q$ W6 t; Y+ x3 Q" ]2 问题分析
8 ~6 m! F; I& n( l: ^2 S这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。3 d" X: h6 R( }7 d! @6 p
这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。
5 b# b/ H* r# A" g, I  o下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):
! ?" P- Z. H* ?- q( s; X( l3 O4 h- w& V* r0 C' Y; V
RA%Y@]HA1K@X1)`5GR3T`UB.png 7 m, y) M8 J7 A# F4 G

8 d& }6 ]  z0 _1 X$ i展开 Data Out 数据:, z! |2 D: @  B( ^

5 L/ `- K) U6 I" g- l5 a' V% r: y Q~PHG_]1`BYIBE0$[(HE9.png   y; q1 O% H6 o: s, g/ t+ o
6 E  p# h8 q, c
分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?
3 [, S$ G. r6 E% q% m通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:7 p, u2 _: q. N
usbd_cdc_if.c:; [7 s+ j7 q8 V0 {1 A
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
    : p8 _0 D9 C& O, y
  2. {" ~9 L7 m7 ]! d
  3. /* USER CODE BEGIN 6 */# N# ^! I- N! f2 h8 o- [$ `
  4. //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
    7 q. ~' C: c$ T0 X- s1 G) ~, }
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);0 @( F9 t) j5 x
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    . A6 ~% n, l( `1 T
  7. return (USBD_OK);$ J7 p7 A9 O  f2 m+ m* c, `
  8. /* USER CODE END 6 */
    $ ?1 h: w7 U+ c& T+ t6 m
  9. }
复制代码

' ~0 Q  S5 \6 V5 v. P如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:
2 |0 A; K/ F1 g% oCDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -
6 J+ M( W5 i1 {* S. ]+ B4 \$ p  M3 Q> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
( o, G  a- @. e  _& B> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
6 x: x( V2 R/ @4 l! P
  1. //…
    , D! g) E9 A6 A, C
  2. else /* OUT endpoint */
    / ~8 S+ h  m* t' L
  3. {* g7 B/ D, A% T8 j( [( P* K3 A. X/ \
  4. /* Program the transfer size and packet count as follows:
    1 P- r# ^6 l$ D! a, K8 [6 J" F

  5. 1 a" _6 o& G2 E
  6. * pktcnt = N
    & G! Y: z, J$ U& R+ v
  7. * xfersize = N * maxpacket
    * _( N! p8 X, o9 c0 ?! x* Q, S
  8.   */
    ! x; y, @: q% g) b$ c' a% a
  9.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);8 O! r% B. s* L0 l' c
  10.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);% }: P1 s$ [. Z# S% m# y
  11.   if (ep->xfer_len == 0U)
    6 R$ W5 a& k/ H# U" b  P) M( L
  12.   {: g, ]1 m+ B& j, ?
  13.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);( |2 [! q$ o, V/ l3 b) r+ z
  14.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;
    5 ~4 @$ F8 ~% F( X- A
  15.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *& S# M+ G2 f+ r0 x! @) z+ o% w% i
  16.   pktcnt));
    2 T) ?7 u4 o/ e) ?/ }
  17.   }9 h& {& s& X( ^/ G) [. E
  18.   if (dma == 1U): `( S$ }1 k& C
  19.   {
    7 A! u5 H; J% L  I8 U+ }% ?- S
  20.   USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
    & {2 Y' T. k# ]+ b$ i
  21.   }  k3 d  \# p0 O/ S
  22. 9 d+ S5 l* o; b6 ^( i6 v" S
  23. if (ep->type == EP_TYPE_ISOC)8 y- N" a* Z* u
  24. {
    # A  j% Y' k, T& {
  25. if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
    ' F( Y8 N0 c. @9 X6 y( ~- k
  26. }# |1 z! N; \" z: q# ^/ {5 h" K( s
  27. else" u% |2 x' e, q" `: h; W9 j
  28. {
    4 G: J9 L: |! z* E- i9 ^
  29. USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;) E# {, f7 ~1 d# W
  30. }9 ?& z' g. f( F  g  K- n# _
  31. }1 i! H( P1 _& J+ e/ n' v
  32. /* EP enable */  `6 F; `! d; n+ K2 H; o
  33. USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
    3 B  I: t! r7 {  H! w& k
  34. }
复制代码

3 b0 q% `: }3 ?% Z, I& A* f' t6 \也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
$ H7 f- r/ }( ?+ S3 r" K: v: a0 {- @; Q4 Y5 q+ w
DJ$@890HN0K]FT5U7JRG[6H.png 1 l2 l* O/ \& s6 ^8 x4 A( N
' `) b/ `4 B* Z# r9 [4 v
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
) G. Q: Q- h. p8 j& C7 f1> Host 端试图向一个端点发送 OUT token;% i- ^# P7 A: O, j% }( W# u
2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;
7 C7 Z( h) ?, G* N4 S$ B3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);+ n6 N! X6 L& h# T
4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;
6 d$ m( `1 |7 _0 H7 K1 x5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;9 Z3 r7 ]6 H4 N  B
6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);3 W0 x( ?1 I2 g+ B) O
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
- ~; V. J- {- d1 G/ v4 U0 d7 D6 n7 D; n3 E
从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
* `9 P: \) ~3 _& ~3 m* Q1 }) P
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    . ?- F( I% n! B2 c
  2. *pBuf, uint32_t len)+ ]2 O4 c) c, p" U4 r7 u1 d0 V
  3. {  ?+ U  l% j; G) h' z# o7 R+ {
  4. USB_OTG_EPTypeDef *ep;
    + T  T4 v1 M% O  G* J7 A( K

  5. 4 j9 L$ }4 Y7 t# D# r
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];  m4 s" n0 @1 n1 V- @" W/ Z

  7. ) M1 Z8 x# n$ q& c
  8. /*setup and start the Xfer */
    0 ]7 o2 h/ A, A5 `8 t
  9. ep->xfer_buff = pBuf;
    4 t* }" B. m0 b! I! {' Z6 n/ _) _
  10. ep->xfer_len = len;
    0 y: Q) J8 i0 v* w: |
  11. ep->xfer_count = 0U;) D7 d, A% D6 c) n1 O1 U
  12. ep->is_in = 0U;, F8 @+ y# K! X& V4 Q
  13. ep->num = ep_addr & 0x7FU;* [4 e* x/ A8 |
  14. * o' `( H( E" X5 j
  15. if (hpcd->Init.dma_enable == 1U)
    , q* W# W, N7 d/ P9 j! m' O6 `1 r
  16. {
    " {1 ~' G. |5 ^7 [$ Z$ {3 ~1 m
  17. ep->dma_addr = (uint32_t)pBuf;
    1 |) `# n6 y7 d% }
  18. }/ P6 {! j- y5 U$ v" P* c9 I
  19. : q9 z9 H- q. B' e9 B
  20. __HAL_LOCK(hpcd);( |& S9 t6 q  ~7 w( Y/ I0 K. I

  21. - M8 w2 L! P. \( [6 w: Z! x6 s
  22. if ((ep_addr & 0x7FU) == 0U)
    # X* Z6 a9 o% L! B
  23. {
    % b' L" j9 h& C. Y' C
  24. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    5 x- e2 c# d/ j
  25. }! |3 ?& h0 Y  E% X* X2 ]
  26. else' J$ K( T  _. l% M8 ~
  27. {# n6 r# Q4 k$ P9 P$ j. E6 h1 O1 e0 E
  28. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);6 ~0 i6 f+ p8 {
  29. }
    + y5 E" f& C  B& p) s- |" k+ R
  30. __HAL_UNLOCK(hpcd);
    * B$ l6 b) [. _3 \( p0 {5 ^
  31. return HAL_OK;
    & g1 `0 m; \$ z. }( z, s3 x
  32. }
复制代码
, y' D/ z1 m; j

3 Z( X: [1 x9 K% {8 R之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:8 s" w, S. C! N9 j: U
  1. CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->5 D  R6 K' `$ {6 P8 t' F) x
  2. HAL_PCD_EP_Transmit() :+ U9 {- {2 K# C" I9 R& K
  3. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    : ?6 ?* {7 z- d  _9 d$ x- j: O# Z
  4. *pBuf, uint32_t len)( O% N7 y9 m4 l+ l
  5. {: C% ?6 Z& s, o: |% X4 i
  6. USB_OTG_EPTypeDef *ep;) t; m  c6 ?. c: ~. r+ r4 ?  t9 v

  7.   u- Y. I( {+ j5 @0 o, b9 d
  8. ep = &hpcd->IN_ep[ep_addr & 0x7FU];
    ( d" ^0 ^2 v/ L
  9. /*setup and start the Xfer */
    8 p2 B$ d$ u' E  n# h) D& U/ y
  10. ep->xfer_buff = pBuf;! z  m  q. [) ?9 Y8 u
  11. ep->xfer_len = len;. n  _& Q; R3 H+ s' P+ D+ E
  12. ep->xfer_count = 0U;. _1 D5 w5 x) v6 }) ?" r( q- W) k% U
  13. ep->is_in = 1U;7 c4 K3 H/ C) ^- I, K
  14. ep->num = ep_addr & 0x7FU;
    3 r  x8 d# H( j% y- t

  15. ; Q4 p: u" D" s. v! L8 n7 T; c
  16. if (hpcd->Init.dma_enable == 1U)
    / p5 ~) i6 B- C# p: j
  17. {
    2 m% P( K. Q4 z8 K
  18. ep->dma_addr = (uint32_t)pBuf;
    ' I% N5 k" L/ ^! w. _- B
  19. }! t! r3 a5 r9 N! _7 Q

  20. 0 t2 ]* X8 ]! I1 K1 t6 y& ?
  21. __HAL_LOCK(hpcd);% v8 F$ g+ S  G% G+ z
  22. , r2 S6 C, G4 l. R6 C& D& e( L
  23. if ((ep_addr & 0x7FU) == 0U)
    2 y. o3 C6 w- ^% D
  24. {2 c, V* |  f- s& A0 v" C5 {
  25. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    ' @; f/ U/ G  o' B3 L
  26. }3 ]" v/ J' E( ~; z% i9 z! M
  27. else
    3 ~% y( r' |: H% v; S
  28. {
    ) |& x4 ~. Q) w2 y
  29. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    $ L; @4 {  g# a/ ~1 Y
  30. }& M0 O/ l4 r0 U9 f

  31. : a0 F* o5 A0 q% q+ ^
  32. __HAL_UNLOCK(hpcd);
    ' H9 H3 q  Q& v0 ?
  33. return HAL_OK;
    " O. G% M( i5 C
  34. }
    0 I0 e7 V* c8 G$ W. O
复制代码

1 o0 y8 `, G8 L$ f6 z% i接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;/ G9 B: R2 V5 K9 j$ I- t: C" n
% }! Y# K( ]/ B1 C$ {
我们进一步查看__HAL_LOCK()宏定义:
) X; U% ^  G, Z1 C
  1. #define __HAL_LOCK(__HANDLE__) 9 M: Z. A# U6 P& u
  2. do{ 1 d; T0 C# E6 k+ @: K: S: e
  3. if((__HANDLE__)->Lock == HAL_LOCKED)
    6 c$ C2 r, H/ z
  4. { $ ]' E- o" p, X; ]2 h, i4 w
  5. return HAL_BUSY; ' w1 a) l( X( X: e$ M
  6. } 7 X% C7 i# E- T
  7. else
    . m% {7 v1 T4 z7 n  c
  8. {
    6 T' R, A# U$ x. v" E8 v- \. D
  9. (__HANDLE__)->Lock = HAL_LOCKED; : b- a5 ]2 Z. W! `# c! X5 \
  10. }   B5 E' n  y( V" g
  11. }while (0U)
复制代码
; d6 H5 w( z& S7 P+ _
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:* B$ V1 S: F+ {  y4 b( {
  1. uint8_t Lock_Flag =0;3 A  d. {8 D4 H; X. [; K
  2. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t9 l) ?. Q, b6 X
  3. *pBuf, uint32_t len)' r& W  c8 s; w
  4. {3 M7 B( e2 z8 b! M) {
  5. USB_OTG_EPTypeDef *ep;
    . J/ u6 l/ u! d! \

  6. ( X2 {/ E- T; ^5 t2 J2 c3 n" s3 o
  7. ep = &hpcd->IN_ep[ep_addr & 0x7FU];2 b( f+ f0 @8 q" T/ U: G  l) R
  8. 1 q" G0 F7 Q! M  P# y( ?
  9. /*setup and start the Xfer */
    " f. X) {- ]1 N3 p6 F4 x
  10. ep->xfer_buff = pBuf;
    9 [8 N( C" G  N+ ?. B
  11. ep->xfer_len = len;% Y$ ]. l8 W- ]* a7 H/ H; G/ @
  12. ep->xfer_count = 0U;  K# p; A# Q" ?( T3 w+ \
  13. ep->is_in = 1U;
    # H& C! ^3 z" o3 K$ e4 x0 U) @
  14. ep->num = ep_addr & 0x7FU;
    & S% l0 p  W6 B$ y% b
  15. 9 d. ^2 b, w8 D" ~
  16. if (hpcd->Init.dma_enable == 1U)' ?; q! B& x* E/ ~/ e4 s
  17. {+ A$ N! ?2 n7 u2 @! I
  18. ep->dma_addr = (uint32_t)pBuf;7 P, _) E; q$ i) z: h% ~
  19. }
    # g5 L% w* a3 s6 @% N* r

  20. : K5 U$ `3 E) R# ?2 W4 w
  21. __HAL_LOCK(hpcd);! _8 F" `8 z1 F* ~
  22. Lock_Flag =1;
    $ E+ f* q/ J% i8 H& p2 C' `) o6 ?& G

  23. + h2 i! \/ Q0 O/ }4 }! |" u
  24. if ((ep_addr & 0x7FU) == 0U)
    7 ^; i9 D0 P0 b2 s
  25. {) Q! g6 g. _; l* l9 x4 h% C* W
  26. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    . }% N5 Z8 l% N4 Y. y& Z
  27. }/ t4 n8 y6 w3 B" e
  28. else) W( L8 D- o5 a5 u6 w# G
  29. {' O8 e9 p+ ?2 w' g' F5 }
  30. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);, F. p$ m% ]9 Y! V) J# u4 E
  31. }+ J) x4 p/ I) m7 ]. F$ ]
  32. : ^/ e) N1 O$ `! A
  33. __HAL_UNLOCK(hpcd);4 P5 o- d' \9 ?/ u' j( ^# o
  34. Lock_Flag =0;
    5 ~) Q7 @: _4 r  d5 j2 \) m# L1 B9 M; B
  35. return HAL_OK;
    $ e* k2 L$ D5 @: G( C
  36. }
复制代码

2 n& k) G9 t6 W/ o' l9 s( l4 c接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:' j9 z5 [. @5 R, X& Y5 D% \
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t- u0 [$ i& c! V% R9 T
  2. *pBuf, uint32_t len)
    3 D9 s6 ?" }. ^' f! O8 S
  3. {
    ! h6 a+ L$ U- p$ `
  4. USB_OTG_EPTypeDef *ep;
    + O8 R6 b$ t! `( L, t% ~( I0 H- m

  5. $ Q" p% K" S( s7 |# s! G
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];  a) D# p  p5 X4 Z
  7. + P1 X$ B( Y1 g3 L2 B0 \
  8. /*setup and start the Xfer */" P* a, \- O9 p  P6 T7 h
  9. ep->xfer_buff = pBuf;4 Q: c5 z" j8 i: v" ^3 x  j& F
  10. ep->xfer_len = len;
    7 ]9 m/ q: k/ C3 F
  11. ep->xfer_count = 0U;
    4 k( R- W: S! O2 u4 _# e
  12. ep->is_in = 0U;
    & e; b6 O/ [: Z* [: }! Q1 y  r
  13. ep->num = ep_addr & 0x7FU;
    ' v4 i# u* j5 R# t

  14. 5 J; T( f$ [/ b; B
  15. if (hpcd->Init.dma_enable == 1U)
      Y# U0 I2 _. b+ K; m& c
  16. {' I6 M* \) l3 |) d+ I  C$ k" p+ T6 p
  17. ep->dma_addr = (uint32_t)pBuf;
    3 J+ E4 c) W2 L7 k# y% q
  18. }5 x9 F" j& H0 B/ |) I- ^& B  x8 k; Z

  19. 9 b4 P: w$ x# |' O0 i+ A
  20. if(Lock_Flag ==1)9 q( \/ r8 O8 V2 l6 Z. [% r
  21. {
    ( P/ F1 A1 y5 Z( i! U
  22. while(1);3 ?# @0 j" p$ f
  23. }$ O' h7 K; `, Z7 s9 e. p5 V
  24. __HAL_LOCK(hpcd);
    ) R! v! ^. M+ E7 r

  25. 7 C8 S8 J; p6 T" W
  26. if ((ep_addr & 0x7FU) == 0U)
    + b9 \$ c) M) ^. u, r) f( J0 W: b" a" A, z
  27. {5 J4 U1 ]% b, @9 c4 x, e
  28. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);( J0 A' N+ W+ Y1 M2 \1 b
  29. }6 B# ]# n, ]) ^% G3 n/ w  p
  30. else5 ]9 }; F7 j6 g  b0 z4 K6 e3 t
  31. {# _. ]2 H# z' v& W3 z; l' V
  32. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    ; \+ x7 ?# M& `( A5 U
  33. }& }) G7 R! }" H! X2 a
  34. __HAL_UNLOCK(hpcd);
    9 ~: [$ K6 f$ H: [( Q6 {/ D
  35. ) h/ y5 Z& J: h) f2 o1 z
  36. return HAL_OK;
    8 u3 `- C) w) _3 o0 E, }; P. ]
  37. }
复制代码
+ \, y8 D( X- _8 v
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:" s- f7 c! K2 l9 g' U
1> 在 mian 函数中发送数据 CDC_Transmit_FS()
: V2 V2 f# P+ n. l. e2 V; w* Z! r2> USBD_CDC_TransmitPacket()
& ]! p7 @4 D4 v4 [% a* \1 {8 u& N, @3> USBD_LL_Transmit()
7 Q. R* i2 F" l4> HAL_PCD_EP_Transmit()
/ j( R$ l, X5 F8 y3 r" M) L5> __HAL_LOCK(hpcd); 此时成功获取互斥锁7 m  w: k9 J/ l! ?  a4 t8 a- R
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
; [+ W% m: k5 u- K: A7> HAL_PCD_DataOutStageCallback()! c  [1 u2 L; G3 e2 O' e' i
8> USBD_CDC_DataOut()$ v  w, z- H3 ^2 u0 w- g: z$ {8 _
9> CDC_Receive_FS()
& j1 R. k) P! t: q0 q10> USBD_CDC_ReceivePacket()
2 ^5 E0 E1 s+ v1 }$ B. t11> USBD_LL_PrepareReceive()
0 ]) L: G( f1 G5 C) ^+ ?$ @% ~( n$ ?12> HAL_PCD_EP_Receive()1 e0 }) D+ b6 f: P
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已
1 Q1 g# T9 n8 B+ }- ^$ N4 L9 T经提前结束,导致接收循环无以为继。# ^/ ~& y: t* j# e3 Y( s# B

- t7 y; n& _' L$ e3 解决方案& ]3 i7 @# g. c9 p6 s$ @' [3 q
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
; m8 f$ `& h7 g2 n! B' G2 a% l
* Q! z% K+ Z8 D/ f  m此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
8 T- l: X  p/ T
. C9 x1 u( N2 d  s, Y
收藏 评论0 发布时间:2022-2-15 19:59

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版