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

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

[复制链接]
STMCU小助手 发布时间:2022-2-15 19:59
1 前言
1 N( v& w! R( V# ]6 o) J* k, N在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。- I0 E4 n+ a9 c0 G) _/ l

6 o7 \0 ]/ O: ]* g8 M/ b1 b2 问题分析
. o  u( l& a0 j$ _' p. _) |! I" s. y这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
& r; _( l8 w- C8 P" M& ?这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。. t, m" q0 B- U
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):- j& b" M& D- z& T: w# E3 R5 B) l
1 b0 c, O) Y; M9 w3 G1 M
RA%Y@]HA1K@X1)`5GR3T`UB.png
: t6 E6 B! c1 c5 p! i2 o% \! e0 ]- F: z
展开 Data Out 数据:
6 h% ?3 l' m! w7 A, h. t9 N% ?& w+ I2 ?; i
Q~PHG_]1`BYIBE0$[(HE9.png
( R1 G6 o5 F* c( S4 W1 ?
) s- N' r1 b8 _% t& p- @% d+ Z# e分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?+ o1 j% K3 L2 z  M+ M
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:5 U% @1 p5 a7 M0 a
usbd_cdc_if.c:
( |/ y4 f9 b% ?
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)6 U  O7 G" E6 `+ x
  2. {& \* j' w$ e  f& q8 }& o! [5 X
  3. /* USER CODE BEGIN 6 */9 W9 x& X5 S; L
  4. //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
    : K5 I  v* k3 c' X  Q: D& T
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);" ~. V# |* w6 [+ ~2 C$ `8 v# k
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);- s& N; l0 g5 p3 w# z
  7. return (USBD_OK);
    0 Y0 W2 X! j) h7 y, [
  8. /* USER CODE END 6 */& m$ X0 ^$ s! P
  9. }
复制代码
6 E! H" A- @; z9 q! u8 G" B) L8 B
如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:( [3 N. O1 o, z6 E7 ]' [7 [% i$ r
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -2 e9 O' a  I5 |( R5 U. Z- _( X
> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
( l3 k/ P$ m5 h9 F- L: x> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:# d* e" i2 K/ n* x# p
  1. //…
    1 x2 O: e" k: P4 M6 ?) o
  2. else /* OUT endpoint */1 u; M7 O: ?2 q+ @
  3. {- X! u: j0 }. @( U' |
  4. /* Program the transfer size and packet count as follows:
    5 D3 i$ q. ?7 w) g& c0 |

  5. ! X/ j! u/ G3 B$ ?  H- c' ]- U6 p
  6. * pktcnt = N6 ]6 _4 y0 k/ ^* \1 y0 i
  7. * xfersize = N * maxpacket
    6 Q9 s* \: @$ D2 n( J- Z5 o0 L
  8.   */+ n" a7 c( [# B; M3 a  p; a* |
  9.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
    " _: [: \0 O/ H" |' q& l0 ?9 K) Q
  10.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);: F7 z$ ~" O7 |# l* u% Q" b
  11.   if (ep->xfer_len == 0U)$ i* R# ]/ M# O& \9 {! X8 e
  12.   {
    1 T# f* t2 e3 X% S, V: }
  13.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
    : I5 ~% b# X* a# g4 k
  14.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;1 _- S" g% _  ~! w6 N1 {4 N
  15.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket */ D4 [5 a+ w0 f& Z) ~
  16.   pktcnt));* v" G/ r  G" o6 v9 z- u/ ?4 l! Q+ k
  17.   }
    " n/ C" ^" \# u  ]% s1 V! W
  18.   if (dma == 1U)1 N! i: i3 J: L& {9 B
  19.   {% |& G  E' g+ N0 N- p$ Y; C8 I
  20.   USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;; k" a1 c- l5 O5 E' U& N3 S
  21.   }+ C# l0 j8 G2 _4 r# Q, x% f7 J" _2 D3 b& O
  22. 8 K9 _' E! L9 k' A4 l
  23. if (ep->type == EP_TYPE_ISOC)) O4 {) b+ c, T! ~
  24. {/ @. x/ L. p: O- g6 O0 a+ P: e
  25. if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;  l# ?8 G- u2 O5 J
  26. }
    $ w1 E2 l, T7 i9 O( ^
  27. else
    # F* Z% w+ z& M! c& w% H2 |
  28. {7 o' b# n( k* M0 S$ y7 T
  29. USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
    5 F/ G5 N! n9 F+ y4 ~$ J2 q
  30. }
    $ A5 L6 }. g. M
  31. }1 a# a: b: J+ F* V2 e1 {2 h+ ~
  32. /* EP enable */
    7 t- t* E1 k8 S1 ]
  33. USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
    % P" U7 S, e/ G6 {
  34. }
复制代码

( X5 @) _( G( i$ S也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
5 o" l3 Q' t; |) i0 D6 C: b7 Q: m+ |+ ~6 k% |/ K5 e) s. F4 [
DJ$@890HN0K]FT5U7JRG[6H.png
: k' @7 p+ C! l' V' J: j  d. ]7 x' _! c5 Z% q+ ?
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
2 i* ]" W6 }. J. B) ~* e' V: J1> Host 端试图向一个端点发送 OUT token;
; o5 P8 m6 H, J  w  _; v$ ^! c2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;9 W# ~/ Z4 H/ }/ l! w, W0 o/ P
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);
! J8 a; A, z- [8 D3 M5 n9 q- U) f4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;
7 e7 @8 w8 Y- G& `1 f5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
: O6 ~1 V" b0 c  o: G6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);" |% j* E1 |2 L3 h) h
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
9 L: a0 {' [: I! V/ |
! z  M9 }. v7 m; D- z& \1 w+ j从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:4 T, h5 j4 C/ g, S5 \
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t* G0 n3 S- c' u. S
  2. *pBuf, uint32_t len)
    3 p  [( @: w& p( W
  3. {$ `  w) }  A* w! E1 h  V
  4. USB_OTG_EPTypeDef *ep;+ m% S* m* z8 p% e6 Z' o7 D* Y

  5.   `4 v7 c1 r) Q5 |+ }
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];; ^- D" r4 U6 n6 \
  7. 2 r% W7 J# `, f# a/ g( Q! F, [
  8. /*setup and start the Xfer */
    & B, K. Y0 w8 J* |  s( U
  9. ep->xfer_buff = pBuf;. N7 c3 K" y1 h/ m! _" L1 Y
  10. ep->xfer_len = len;
    / p+ F3 j8 T$ d+ l+ e7 q
  11. ep->xfer_count = 0U;
    3 e: Y" j) W  J' q/ i' B
  12. ep->is_in = 0U;! O% b# F7 e6 ]/ T: K6 ~
  13. ep->num = ep_addr & 0x7FU;2 r6 Z  ^1 l& n' i3 F. H4 y
  14. : f  a. {  k+ I: A% u5 m
  15. if (hpcd->Init.dma_enable == 1U)/ D6 n+ p) `- U, V
  16. {
    ; Z; d! I7 ^) t: p
  17. ep->dma_addr = (uint32_t)pBuf;4 o: I) f# Y1 s2 d2 w9 D2 k! s
  18. }' |; V7 N8 M( Z5 q/ e# c
  19. 0 A' q) E" R# N' P- L7 g0 W
  20. __HAL_LOCK(hpcd);
    % T& W% ^# L* L7 i; `4 M
  21. ( }; |* T( a% C" i2 N* L6 r
  22. if ((ep_addr & 0x7FU) == 0U)
    / g3 h3 @; }( r& T% X1 f& \, }
  23. {
    1 `. }1 T4 d6 R. e* q
  24. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    6 b3 y  H7 y  B5 \
  25. }
    * b0 r. j& G! T. H$ d. w
  26. else
    ; v. I4 x& I7 f7 E. y* E
  27. {
    / x* f) G0 F' R9 J2 d2 w$ x5 ]
  28. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    " N0 l2 G- S" Y+ H' @* Z6 @# D
  29. }2 `2 S, y) Q& y, O/ p+ n' e- B
  30. __HAL_UNLOCK(hpcd); % x2 V2 `! s/ n3 J; U  A, o
  31. return HAL_OK;
    6 b$ E0 J+ d) }/ D! U2 }. e+ K
  32. }
复制代码

8 Y6 S7 z) j& K7 v
9 }8 Q: n- T5 D9 J之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:7 i4 ^8 ~% S' A) ]5 }/ |
  1. CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->+ e- |& ]* _) k# G
  2. HAL_PCD_EP_Transmit() :7 X! E: a$ G6 }
  3. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    4 \' Z0 k6 `$ p' D! L% d2 W, [
  4. *pBuf, uint32_t len)
    # }1 a* L5 E. g3 q3 p' s
  5. {
    7 S& C9 N( v) n% c
  6. USB_OTG_EPTypeDef *ep;
      _$ K. R- {! o* w

  7. 3 y6 g; r' i9 ]
  8. ep = &hpcd->IN_ep[ep_addr & 0x7FU];/ \  o' S: I9 u
  9. /*setup and start the Xfer */
    / Y2 [) b5 P1 E
  10. ep->xfer_buff = pBuf;, ^, d; K! r9 H+ ?' U! o/ m% L
  11. ep->xfer_len = len;
    8 z* f0 B  g( }0 o: e" y
  12. ep->xfer_count = 0U;
    * A) n, `( P" E& f
  13. ep->is_in = 1U;8 X& x9 V5 ^9 G& o: {
  14. ep->num = ep_addr & 0x7FU;
    * V, r) m. W$ }# U! B
  15. 3 Y, T  a. f9 Z6 S0 e5 E0 g
  16. if (hpcd->Init.dma_enable == 1U)* E% Z1 e; |! h& p  r* ~
  17. {* }* j3 Y( o3 ^
  18. ep->dma_addr = (uint32_t)pBuf;6 {; Y2 \# W; S3 _: i. K" J: E  g
  19. }
    - k/ M0 }5 {0 S! t3 l5 r5 N

  20. , l( w5 G1 X' q: a" `1 ?+ g" l
  21. __HAL_LOCK(hpcd);- P  e& S1 ~  P( V
  22. / ?( _) J$ e, J  t( m
  23. if ((ep_addr & 0x7FU) == 0U)- U% m- q8 `2 S/ V
  24. {% G3 V- o0 [8 k) l0 f) V" }' S4 k+ R
  25. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    3 n/ S$ l$ R+ q: l
  26. }
    ( D6 d) i+ L* p! `1 Z
  27. else0 G' ~& Y0 |# J& z1 @) p8 A" J
  28. {  a/ d0 d1 V& B; ~+ e  a
  29. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    ' h  x9 V; |# m/ ~& _: F( c
  30. }
    & N" Y: E5 A" Z9 ?
  31. 7 T5 _$ o( Q2 b
  32. __HAL_UNLOCK(hpcd);/ H3 s' e5 u4 _; x$ \( o+ u
  33. return HAL_OK;) @* r( g$ J! D. v" j; v- o' a
  34. }
    0 h" F, W( q$ w* [
复制代码
8 F7 R. @. i& E! V0 [) N
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;
3 @7 U3 W; C! w0 z4 H8 o5 G8 S
5 O% f: v7 [  m. A8 k我们进一步查看__HAL_LOCK()宏定义:6 U! A( {& D6 p8 J# h; Y" @
  1. #define __HAL_LOCK(__HANDLE__) ( |4 z7 d+ q( H' H4 \+ ~8 H
  2. do{ ( t& D# ?( [* e0 G
  3. if((__HANDLE__)->Lock == HAL_LOCKED) 2 S( V5 {4 t0 H5 x$ j
  4. { 5 }5 q+ P2 C/ p8 v
  5. return HAL_BUSY;
    ' [2 d1 {, i6 X/ ?0 @" _+ \
  6. }
    + A1 o4 i$ ?: T
  7. else ) c) r4 x% P) g4 G2 H% z3 j6 M
  8. {
    1 c6 G: x. {  p. k* p$ G
  9. (__HANDLE__)->Lock = HAL_LOCKED; " C; p+ }, v( L5 c$ Y/ T8 F
  10. }
    ! }4 L$ I9 V+ C7 o( O* Y2 q% z, q
  11. }while (0U)
复制代码
3 ?% O1 G+ U. N
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:
" Q: T8 K) [7 z4 _5 ^
  1. uint8_t Lock_Flag =0;! ^9 g( e! S. F5 o
  2. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t* Y& e8 D# [. G# }$ }
  3. *pBuf, uint32_t len)
    7 B, @& o: ]* g  S
  4. {
    ( i+ ^2 G5 M* q/ X; f7 m3 `
  5. USB_OTG_EPTypeDef *ep;6 E, g* k3 g6 G/ n. o7 N( {* \

  6. 0 o: ~: a# M" p( ?0 {2 h* T
  7. ep = &hpcd->IN_ep[ep_addr & 0x7FU];: `5 D4 I# ^: b# i6 Q; c$ f; \' n

  8. # ?% L0 ~, x4 e1 B
  9. /*setup and start the Xfer */7 Q* a' C0 C9 [
  10. ep->xfer_buff = pBuf;
    2 d6 t( \' C9 I/ Z; V
  11. ep->xfer_len = len;
    2 y' W. Q% l, u( Q- |
  12. ep->xfer_count = 0U;
    9 o8 J. E+ }2 j4 Z
  13. ep->is_in = 1U;: l' d; S7 k5 T) E. I7 q1 I; I/ [  I
  14. ep->num = ep_addr & 0x7FU;' s, P0 }4 F& K; A+ {) L! S
  15. $ g/ O8 x( u# {
  16. if (hpcd->Init.dma_enable == 1U)
    0 P! k. P' k1 v' f3 _
  17. {+ U2 E% [  [2 |/ i
  18. ep->dma_addr = (uint32_t)pBuf;
    1 E2 o- D) Z- V
  19. }
    4 t; P% Y, E# d% _& y

  20. 0 s6 ~4 x" j, s0 ]
  21. __HAL_LOCK(hpcd);
    9 v" G4 B; _% A% ]( H  s) \4 ]
  22. Lock_Flag =1;
    8 b. [& t6 V: |  K8 E* T

  23. 8 a2 H0 A2 z  M, F
  24. if ((ep_addr & 0x7FU) == 0U)9 C3 Z% {* O: J% W* g
  25. {
    7 q. i' E- q9 j
  26. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    5 B, ]* s% L2 e/ Y. l7 g! a
  27. }
    8 m" ]7 }6 A% b. Z4 ]8 i5 ^
  28. else
    & P# z9 T# ~) y& R- V% W7 n
  29. {
    ! ^' c; H8 V, R
  30. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);/ ^3 E6 `8 W1 a* q' D  u
  31. }
    - m5 o+ N3 r7 s9 e; [

  32. 1 y) W6 R8 q6 X, b+ c5 s
  33. __HAL_UNLOCK(hpcd);5 @. K* z$ ]' a" w. w  o$ H- m
  34. Lock_Flag =0;
    2 q5 O1 R. K. |. ~' t% X  ~$ ^
  35. return HAL_OK;& p. M) K/ m/ \3 ]2 h
  36. }
复制代码

3 `3 d2 v# V) M/ o: Z) S- o0 w接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:
# ?3 T: s* d( X) i; c
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    * k7 R0 K: G! h6 G/ E
  2. *pBuf, uint32_t len)* p3 i5 N3 I/ p9 W& v9 h
  3. {. g; D: u- L( ?8 l1 i
  4. USB_OTG_EPTypeDef *ep;
    ; y6 k( \1 w2 B+ e

  5. " ?. _% v/ b; y% i( Q; e
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
    6 R1 r2 P! c4 L2 T6 l  K
  7. / l% @, H1 K$ M/ H
  8. /*setup and start the Xfer */- {  s& J3 O0 I& ~/ P( h
  9. ep->xfer_buff = pBuf;
    & m$ E% f7 \5 d. t, i1 w2 ?
  10. ep->xfer_len = len;
    ' O4 \" I* P, h, E1 W$ `1 e
  11. ep->xfer_count = 0U;
    / I4 O0 V* t' |6 I6 a
  12. ep->is_in = 0U;8 }! o8 _( ^) \6 [9 [
  13. ep->num = ep_addr & 0x7FU;1 K! ^" z* V* R4 p$ n9 H
  14. , f; H4 O% e5 T) E! f' w, Q, |/ ]
  15. if (hpcd->Init.dma_enable == 1U)
    4 d# b. }  H* a  x4 }+ ?) F. m2 g
  16. {
    6 w6 }: p. N6 f  n3 K6 _0 _
  17. ep->dma_addr = (uint32_t)pBuf;
    # t  I- e1 T. {: n
  18. }7 d% A! ?2 a5 r) k
  19. 6 o6 `& _3 r( W# ]2 p" I2 C
  20. if(Lock_Flag ==1)
    " g  i: c7 M/ W$ j
  21. {- ^1 O0 \( }* Q/ m0 C+ K9 Q# _) t
  22. while(1);
    . r' ~4 {2 r! u' R1 u
  23. }
    : }0 @" X, P0 m" c. e  Q
  24. __HAL_LOCK(hpcd);- z1 {" m* `! H
  25. & ^" A7 Z, U: h" ~% l6 m3 K) \
  26. if ((ep_addr & 0x7FU) == 0U)  v1 g+ x- d# G1 W
  27. {
    3 J3 O4 M8 `$ R
  28. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);5 W4 d' T  v5 a. Q  J+ C
  29. }
    2 t9 I4 c. s& d+ b- x9 ]; t
  30. else1 I* |" Q' Q: O. R  _) I7 g+ [3 x4 b
  31. {
    / v( @" E. G  l$ i: A9 e
  32. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);. t8 i% L4 m7 L9 ?% e+ O: q7 k
  33. }
    " O4 e! `( j4 n6 x! N" I
  34. __HAL_UNLOCK(hpcd);
    / r5 Z  Z8 @4 D4 H. P
  35. 1 v+ I2 V* \) T) o  o. j
  36. return HAL_OK;
    ( g3 w- ?3 y8 s" k
  37. }
复制代码
' x' f! d% S: X2 p0 ?; {
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
8 E" |( c! E2 N+ I- `  j1> 在 mian 函数中发送数据 CDC_Transmit_FS()' R7 n' }- |6 k% l" t% g$ s
2> USBD_CDC_TransmitPacket()$ K! Q/ u; E/ F8 {  N
3> USBD_LL_Transmit()
. w; g/ Z& f- h" S1 W4 E  A5 ]4> HAL_PCD_EP_Transmit(), O! A0 K& @: J- a- c- ?& `
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁6 P% d+ D& S4 a3 c
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
' U7 E" Y) H7 g% c7> HAL_PCD_DataOutStageCallback()
7 \9 ]! w: J- ~7 W. |% @1 \8> USBD_CDC_DataOut()0 t; @# g. E8 X9 N# s# O0 P
9> CDC_Receive_FS()6 u* S8 l) {  E8 Y- F- }0 ~
10> USBD_CDC_ReceivePacket()
8 m* {4 H( S) N/ \5 t, e11> USBD_LL_PrepareReceive(), I  H; x7 j1 @) _
12> HAL_PCD_EP_Receive()' p/ i/ X( L+ {
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已# c1 q, D" J/ F, _, j& |5 O
经提前结束,导致接收循环无以为继。2 M' O1 n4 h8 S# R* m' d1 C6 y

+ i* g' w* A# o4 r  ?" c2 e) K& u" A3 解决方案
6 B7 ~- I8 d" ]; I! M+ [知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。9 k0 m  x, d  R
6 g! H* g) J' t4 y$ H4 v
此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
# g0 g0 J1 F& |5 U2 k" A( K: z$ a
. F4 O/ |% b, K8 m) W( }1 [1 e
收藏 评论0 发布时间:2022-2-15 19:59

举报

0个回答

所属标签

相似分享

官网相关资源

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