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

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

[复制链接]
STMCU小助手 发布时间:2022-2-15 19:59
1 前言* @7 Q% M! ?6 T. m/ I9 A
在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。
; X3 W/ y$ w" V# q- A* D5 T4 x' x' R3 h, T) V! A
2 问题分析
6 s3 j( }' q+ b" a$ P' `! B这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
3 l7 C, _/ ^  p这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。; Y! P! Q6 G) U3 j4 }
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):  c  Z5 a3 L9 ~( U$ T7 o+ m  D
: X% J7 N# N5 Y5 E4 @' H
RA%Y@]HA1K@X1)`5GR3T`UB.png 8 @/ Q3 ?# W+ L7 _) ?! a# ^

' l2 G2 [$ ^$ b) |0 m展开 Data Out 数据:
4 A7 @  q4 m9 t8 n
8 K3 C) a$ p6 V1 [8 f8 I% Q Q~PHG_]1`BYIBE0$[(HE9.png
9 m5 E1 ~& d) G1 o* p8 h# l! V% Z
% m, s+ |5 n7 U- u% V2 F分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?# s' ]6 x" k7 R' j. Q/ J
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:( i5 H7 @+ c% h% Z3 M& D" Y0 A
usbd_cdc_if.c:# \7 R. F4 r, v# Y4 v& A
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)  z* |, E. e) r! ^; u
  2. {
    2 m0 L4 F. T: ?6 p" M/ {
  3. /* USER CODE BEGIN 6 */
    ! x6 M5 i  ~1 ^
  4. //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
    / {$ ^+ n% B9 w, @2 Y3 o
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);, F/ R2 c* N. H( |
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    2 T7 v$ {" v& W5 O- T
  7. return (USBD_OK);0 {, N9 `: D0 Y
  8. /* USER CODE END 6 */
    , q$ c  G! O8 B7 N  N; C7 h9 Z; G
  9. }
复制代码

' R4 `7 D  \* K如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:
8 k" I* {: }$ a; y9 X! YCDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -" v* V2 ^2 M, ~8 L" ~8 g; U6 H! ^6 Z
> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
) x( E  ]# B( u" r9 P> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
) R2 ?' p. t2 v$ C
  1. //…
    " W* p& R/ z! e0 ^
  2. else /* OUT endpoint */& Z: \; S8 ?$ A4 \& U6 P4 P% Y
  3. {
    3 g: R3 g5 f3 b* ~' L; T
  4. /* Program the transfer size and packet count as follows:6 z5 v- s% q& G* Q2 E4 [
  5. ' ]. N6 [" a7 z9 a2 D* C
  6. * pktcnt = N9 {9 \- r4 V0 ?' E" M3 ^
  7. * xfersize = N * maxpacket
    5 v; a7 p: k6 N% N1 g6 K  L$ F- I
  8.   */
    ( N& e# C9 D9 j) X% q! N: U
  9.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
    $ i8 e! g5 {  d
  10.   USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);( @) x* [: h; U
  11.   if (ep->xfer_len == 0U)
    & h" i4 R5 X7 o& F* `
  12.   {/ W6 R( |" }$ ~! ?3 e
  13.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
    2 Q5 B- W1 H3 g  X" L/ h( g
  14.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;9 |9 L6 G" L7 E7 X: \2 O, q
  15.   USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *
    : L  W- C$ m% ?# E' ^5 g1 V
  16.   pktcnt));
    7 L9 N' V# n; O
  17.   }, `  @& H5 A9 M  |$ X& m3 D
  18.   if (dma == 1U)  D2 Y  v- z8 Z/ h" S& l
  19.   {) j0 ^; P; B( E+ r/ D. T
  20.   USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;4 g. M' H, m' R, Y3 k+ w' m
  21.   }2 i: N5 B. U9 R: S
  22. ) U) E- ]" M) M2 X0 K5 c3 a
  23. if (ep->type == EP_TYPE_ISOC)
    / x2 U, k% c' n7 ?% W( I, M: {
  24. {
    ; D' k8 Q& g* k( u; h- J
  25. if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
    ! y+ l+ D/ ?* x( M
  26. }! y$ m; i1 d1 q4 \2 D- A
  27. else
    * K) r- j& \1 ?( l
  28. {
    ) @. S; K+ t% I+ S$ K
  29. USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
      q$ w; F- ?# [& z
  30. }; s3 I1 N2 q" k6 F' {6 E, s- |8 [# W
  31. }
    8 _: @( ^" Z. _% M; G/ Q/ p
  32. /* EP enable */3 M2 i& [% Z1 k( r0 _/ g0 H
  33. USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);$ I+ [: b" ?7 i! g
  34. }
复制代码

2 a) o! q! A& N/ E也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
  i  \1 c, `9 [7 v7 _/ G" Q, S- K3 g$ C% N3 y: D
DJ$@890HN0K]FT5U7JRG[6H.png $ p  f9 W. j# N" m) m8 _- a
* U8 J  M, ~8 f5 v- w$ p
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:& p2 c) S" D2 i4 E( K
1> Host 端试图向一个端点发送 OUT token;
/ b9 a6 {& G1 M& ?2 n9 @9 b2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;4 Q7 p6 @0 M" U2 U! F$ q
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);
  K$ [* g9 e  `4 C% k0 H4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;* b. c" K5 E+ V0 L
5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
0 ?1 \  P% ]! q" u1 p! w' D% }6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);& T$ v: P  \: t" ^0 O: M  D
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;/ Y  K' r5 `8 t% Y2 K3 M' m

# T2 o: t0 k4 G+ y& T  R* {8 [从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
7 a4 o' [& O1 Z8 P* c
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t' U2 C9 A$ O* |% a
  2. *pBuf, uint32_t len). z/ J; T0 ]1 @  n/ s
  3. {) G3 |3 b# i, h: b
  4. USB_OTG_EPTypeDef *ep;
      ]& @" X9 V$ {4 F8 [2 ?' l6 w( x, c2 o
  5. / h( {. \! `7 i  Q4 z. Y: l
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
    7 A4 f# B5 c2 t, x& j* K$ N2 j

  7. - A! q1 t7 j/ g# x% M- d
  8. /*setup and start the Xfer */3 S' m4 J5 \+ O- K5 U6 ^
  9. ep->xfer_buff = pBuf;
    - I* ]2 C, K& h) [
  10. ep->xfer_len = len;
    ; ^8 j( B# F: Y" i. b- ~
  11. ep->xfer_count = 0U;
    1 T$ X4 C6 ?2 c' {2 w; k: Z' l
  12. ep->is_in = 0U;& _9 W: O4 k3 U2 n: n; w
  13. ep->num = ep_addr & 0x7FU;
    8 Z! i8 `; v; t& h# W

  14. 2 m! Q* h+ ^0 S+ z2 G7 D) o
  15. if (hpcd->Init.dma_enable == 1U)$ U! A1 M# p7 T, X4 o
  16. {1 J, x7 E# h; ^2 C' Z: a7 h& z
  17. ep->dma_addr = (uint32_t)pBuf;
    ) J0 A4 X% P. |
  18. }  ]  O. \8 [( ^- F) ~# H" ]
  19. % E$ |4 J! m% Z
  20. __HAL_LOCK(hpcd);) i- Y' b" v; {

  21. / |% p' [* m5 P% {  W: q
  22. if ((ep_addr & 0x7FU) == 0U)" y( W4 D4 T: w. W0 W6 d
  23. {: F# s' S$ U- B2 M" @  T% P( C
  24. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);0 k, q& f4 B& D/ x% J
  25. }  E5 K5 x' x! {' {3 f4 j; ^3 O
  26. else
    - q" U! t8 P( J9 l. a3 O" |
  27. {
    * _- t; B, i! K
  28. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);5 F4 D; y3 n; Y8 t- {
  29. }' K) u* M. E0 m
  30. __HAL_UNLOCK(hpcd); ( U0 K3 A; k. @; s
  31. return HAL_OK;
    1 T2 J& t3 o# i3 s) o
  32. }
复制代码
! u* e/ f) i0 a. r2 k
; [& r) g6 S( X) b) R7 i$ F
之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:
9 o1 w( W/ Y' m
  1. CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->
    9 L6 O# A& r0 q* B- N! a: K4 }
  2. HAL_PCD_EP_Transmit() :
    6 R$ _( h& x/ D
  3. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
      L( Y1 u! s! m7 c2 U
  4. *pBuf, uint32_t len)2 i) o0 I) ?. [( |$ y" }7 L
  5. {* h- n9 z. z, ^- e: s& j+ v
  6. USB_OTG_EPTypeDef *ep;
    ) ^" B$ A1 [; F" B
  7. : c' E$ O/ Z4 P/ O; R
  8. ep = &hpcd->IN_ep[ep_addr & 0x7FU];
    ; [7 ^4 f7 f" g  ]
  9. /*setup and start the Xfer */- M; {6 i/ x% b4 u
  10. ep->xfer_buff = pBuf;
    , a+ k8 q3 R5 \- C3 y+ E
  11. ep->xfer_len = len;
    1 `7 Y8 }' g$ f3 `
  12. ep->xfer_count = 0U;
    - N( m# p  y7 \. o# b
  13. ep->is_in = 1U;; O: i5 \, e- g1 j
  14. ep->num = ep_addr & 0x7FU;8 Y4 P8 I) I  l: F4 c5 U* h! T0 q
  15. ; Q2 A, B* I2 Y# m9 e
  16. if (hpcd->Init.dma_enable == 1U)
    + S+ C4 L; G0 k
  17. {
    $ j! ]  V4 Q' l, W' A
  18. ep->dma_addr = (uint32_t)pBuf;4 E1 W/ V' b% k" q5 e( H/ x4 F
  19. }2 G9 q% p# c1 Y, |! G& G

  20. 2 T5 d0 S# H. N8 B0 f; l$ i5 h
  21. __HAL_LOCK(hpcd);4 k1 |+ D3 i' {  |8 Y6 v# j/ K
  22. ! r# v* p& y0 T
  23. if ((ep_addr & 0x7FU) == 0U)
    6 e1 \  T5 v# v% x
  24. {
    4 n8 M- F/ ~% N! E
  25. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    7 m. Q3 t9 W1 D& w0 v. U  `
  26. }4 _- h4 U. \# t1 \) Y
  27. else- T+ p) J# d0 L' v% M$ l
  28. {7 z# @" [9 Q" O/ l/ I
  29. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    - J0 t/ M* M; W5 h" f# B$ d
  30. }
    6 C) X% X* a! c- v$ i2 @

  31. 3 U" s3 V1 {* Y9 N" q
  32. __HAL_UNLOCK(hpcd);) t9 n/ ]; J9 @. t2 d$ [
  33. return HAL_OK;
    ! P1 x& w$ K% h- l9 L9 u# K
  34. }3 i+ b) V/ U& H& b: r5 d
复制代码
; ?! d, t. t% `3 M0 G' R& I0 F
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;5 p5 ~" s- V* z" v0 P
/ h- F- t6 c, r: A0 l3 t& B
我们进一步查看__HAL_LOCK()宏定义:
: z: n0 M; F& f1 Z0 O3 K+ B
  1. #define __HAL_LOCK(__HANDLE__) & j6 s/ e4 R! b- m; m5 z3 M' V
  2. do{
    + Y) u* h' D+ y' E) p! V
  3. if((__HANDLE__)->Lock == HAL_LOCKED) ; B+ K; O. U2 e$ W
  4. {
    7 D+ N8 E1 u2 {' f* w
  5. return HAL_BUSY;
    & Z4 C8 a2 @: [
  6. }
    ( B4 C% I& ^, a; T9 k
  7. else 8 \) M% D( w% V
  8. {
      }, O1 R4 P& |# F! R7 D6 \
  9. (__HANDLE__)->Lock = HAL_LOCKED;
    / G" @/ ?; S4 T" ]3 w  }$ G8 g
  10. } ( c2 |1 Q1 D/ W, b
  11. }while (0U)
复制代码
+ O: |1 i7 F& _* {7 H( B& e
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:* @2 [3 s9 h% U: m) N& F
  1. uint8_t Lock_Flag =0;( W; s% d: C1 M7 \1 g
  2. HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t/ b+ \% }$ ?3 Z4 }
  3. *pBuf, uint32_t len)8 c' Z8 i* S$ v% `
  4. {
    / }. Z2 i" v' V8 a) `3 |& a
  5. USB_OTG_EPTypeDef *ep;! d: f7 i3 I1 m' A* J' Z, b4 i7 _' p$ \' U
  6. / k! e( k3 o% r( J* p- r
  7. ep = &hpcd->IN_ep[ep_addr & 0x7FU];, M$ \& K. k, t) h3 k

  8. 4 \0 i9 U" y, u4 R3 p0 Z7 X) l2 J
  9. /*setup and start the Xfer */9 w$ @; o8 Q) W( R
  10. ep->xfer_buff = pBuf;% G! \6 }5 Y4 ]; k
  11. ep->xfer_len = len;
    , n7 j, I$ d. I7 L% Q, f
  12. ep->xfer_count = 0U;4 y8 j  y7 w" K: B- }
  13. ep->is_in = 1U;8 M4 U* o: ~. o! @( E9 b+ ^
  14. ep->num = ep_addr & 0x7FU;/ ?0 N3 a5 |* _# E, F  g2 s
  15. $ ~7 E7 I( i9 H$ L
  16. if (hpcd->Init.dma_enable == 1U)
      w% F* n4 w' c# p7 _
  17. {0 z8 J; [* Y* f/ g3 l; M- z
  18. ep->dma_addr = (uint32_t)pBuf;
    - i' o2 ?4 [% B3 s- u, T# R
  19. }: {8 C2 i$ T; R9 H( N  H( P
  20. ' U7 F$ y( b5 d3 M9 F* ]
  21. __HAL_LOCK(hpcd);
    4 x8 }4 S( z' [# u1 C
  22. Lock_Flag =1;
    . n7 t' N2 B) G7 v6 t
  23. 6 u# d2 G; |: B! _( }$ G
  24. if ((ep_addr & 0x7FU) == 0U)) m  C  Z/ S0 O% N
  25. {0 e+ G$ K( A& O. c/ ~* u; W
  26. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    ) f0 A6 o5 f3 Q
  27. }
    % a( G2 T- O: `1 d. }8 d
  28. else
    7 F: r! w- T: d- P& E( s
  29. {- G7 E3 X6 W! C+ y; K0 j, E' Q( S- _
  30. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    6 S% [4 @0 L: t. s8 \
  31. }* O2 [5 z9 M& F  R3 b) ]$ W- T+ w

  32. ' r0 K2 E! W8 b, }* q8 f
  33. __HAL_UNLOCK(hpcd);
    . X: Z: t- V- z
  34. Lock_Flag =0;
    " B5 p- F; D% D3 I
  35. return HAL_OK;
    " D& g8 m( T7 J" C4 n
  36. }
复制代码

4 c$ J* m' k. `接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:1 U3 ?* @; V) t# }
  1. HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
    : {% K5 [0 Q4 D  r
  2. *pBuf, uint32_t len)
    9 I, P" _/ ^, }$ i0 a+ Q7 Z6 p2 v
  3. {! Y& X6 g; ]- h7 O! B1 l
  4. USB_OTG_EPTypeDef *ep;
    * S8 l* w1 v& l
  5. " r8 W2 {* v- s8 O
  6. ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
    6 b- J' }/ i) N) c; {
  7. 6 F0 I5 z- |7 ?( u+ x9 O0 Z4 y: @
  8. /*setup and start the Xfer */1 `* H& B- P9 |3 F! v
  9. ep->xfer_buff = pBuf;
    + `, [; T$ J. B( S
  10. ep->xfer_len = len;
    1 j, t! L' [! j4 w2 E
  11. ep->xfer_count = 0U;
    0 k; G) g3 C8 k7 ?" W, T
  12. ep->is_in = 0U;/ S7 ~6 u7 c% ]7 ?4 ]9 A/ ?
  13. ep->num = ep_addr & 0x7FU;
    1 o# `( w- j- [  G2 D; G
  14. 6 y1 s/ U6 k, _' B; U2 a/ G
  15. if (hpcd->Init.dma_enable == 1U)- y3 y- S* S5 u/ i) d& a2 O
  16. {: |5 L2 e6 [. a" G$ Z  D* X% Q- g
  17. ep->dma_addr = (uint32_t)pBuf;5 V) E2 V1 p; |% f* ?/ d9 B
  18. }( V8 w% d0 B8 T0 S* L  Q

  19. 4 I% H8 M# I: ~
  20. if(Lock_Flag ==1)
    0 s2 j0 E! ~3 b5 I* Q/ ?
  21. {) A% z: w# r4 L( E9 m# N7 \
  22. while(1);1 h/ [8 l8 W1 f, n5 @1 m
  23. }/ W! e& ?; O( ]$ {8 i
  24. __HAL_LOCK(hpcd);
    4 M4 v$ s3 r( d, }( n& C" M4 ^6 ^
  25. , g3 y/ p$ H( h6 p4 w* s
  26. if ((ep_addr & 0x7FU) == 0U)2 x/ M6 ^' d( ^% g/ M
  27. {% Y! w' T7 u+ E/ ]3 P3 I
  28. USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
    5 Y8 M; T; _& {( ]% s1 v7 H9 c
  29. }' h  g# V" a0 ^6 @
  30. else- H9 C4 \: H" L; _& O
  31. {
    - r$ H) {4 M8 H; n5 @: Z" g
  32. USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);6 k9 l: A1 R  u
  33. }
    ; `: `# E+ }# f- Y, V
  34. __HAL_UNLOCK(hpcd);
    6 ]0 S: F4 `5 u" B

  35. - J5 ~! `% h. ]
  36. return HAL_OK;
    " ?; O0 y/ o* W
  37. }
复制代码
# Y# e0 N' b5 l; P  I( H. A
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:4 ]4 Q4 }$ x$ Y5 i* `& }" _
1> 在 mian 函数中发送数据 CDC_Transmit_FS()
2 q; B1 K: `# s( `$ K2> USBD_CDC_TransmitPacket()6 L6 l6 T, n7 Q: p2 E
3> USBD_LL_Transmit()$ _2 a, v+ V' H; r$ ^- H
4> HAL_PCD_EP_Transmit()+ ^! F  \  ~1 O  x  h. G6 V
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁- C- }. x3 Z( h: T0 W' U
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
; f# L) f' Q# M- e/ x$ X  T7> HAL_PCD_DataOutStageCallback()
, n+ E3 q0 v7 z3 Y4 K$ C8> USBD_CDC_DataOut()6 t9 O$ a1 J# _; e# s7 q
9> CDC_Receive_FS(). R8 D) w( |" v# s" S* I
10> USBD_CDC_ReceivePacket()6 [8 |- b: X' [$ R3 I+ s/ O& s$ O
11> USBD_LL_PrepareReceive(): X0 e$ T! v" m1 Q* S
12> HAL_PCD_EP_Receive()
# a* `' k& M, y1 G& F13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已, }" f& o8 W% j9 B
经提前结束,导致接收循环无以为继。+ C8 t& u  y. L+ f5 L& W

7 Y* Y2 f2 l" a4 ?$ Q3 n3 解决方案. E1 R2 k; _1 d* e
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。& N# l: n, c8 _, {1 s3 E

" B( t' D* G4 I7 Q, [  \此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
' y5 o  ~$ \& b0 r' v. M) `# t; f
. U" I5 \) ^& x: S% J  o6 n3 |
收藏 评论0 发布时间:2022-2-15 19:59

举报

0个回答

所属标签

相似分享

官网相关资源

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